@posthog/agent 1.29.0 → 2.0.0

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/src/agent.ts CHANGED
@@ -9,14 +9,12 @@ import { POSTHOG_NOTIFICATIONS } from "./acp-extensions.js";
9
9
  import {
10
10
  createAcpConnection,
11
11
  type InProcessAcpConnection,
12
- } from "./adapters/claude/claude.js";
12
+ } from "./adapters/connection.js";
13
13
  import { PostHogFileManager } from "./file-manager.js";
14
14
  import { GitManager } from "./git-manager.js";
15
15
  import { PostHogAPIClient } from "./posthog-api.js";
16
- import { PromptBuilder } from "./prompt-builder.js";
17
16
  import { SessionStore } from "./session-store.js";
18
17
  import { TaskManager } from "./task-manager.js";
19
- import { TemplateManager } from "./template-manager.js";
20
18
  import type {
21
19
  AgentConfig,
22
20
  CanUseTool,
@@ -25,8 +23,14 @@ import type {
25
23
  TaskExecutionOptions,
26
24
  } from "./types.js";
27
25
  import { Logger } from "./utils/logger.js";
28
- import { TASK_WORKFLOW } from "./workflow/config.js";
29
- import type { SendNotification, WorkflowRuntime } from "./workflow/types.js";
26
+
27
+ /**
28
+ * Type for sending ACP notifications
29
+ */
30
+ type SendNotification = (
31
+ method: string,
32
+ params: Record<string, unknown>,
33
+ ) => Promise<void>;
30
34
 
31
35
  export class Agent {
32
36
  private workingDirectory: string;
@@ -34,10 +38,8 @@ export class Agent {
34
38
  private posthogAPI?: PostHogAPIClient;
35
39
  private fileManager: PostHogFileManager;
36
40
  private gitManager: GitManager;
37
- private templateManager: TemplateManager;
38
41
  private logger: Logger;
39
42
  private acpConnection?: InProcessAcpConnection;
40
- private promptBuilder: PromptBuilder;
41
43
  private mcpServers?: Record<string, any>;
42
44
  private canUseTool?: CanUseTool;
43
45
  private currentRunId?: string;
@@ -57,8 +59,8 @@ export class Agent {
57
59
 
58
60
  // Add auth if API key provided
59
61
  const headers: Record<string, string> = {};
60
- if (config.posthogApiKey) {
61
- headers.Authorization = `Bearer ${config.posthogApiKey}`;
62
+ if (config.getPosthogApiKey) {
63
+ headers.Authorization = `Bearer ${config.getPosthogApiKey()}`;
62
64
  }
63
65
 
64
66
  const defaultMcpServers = {
@@ -89,16 +91,15 @@ export class Agent {
89
91
  repositoryPath: this.workingDirectory,
90
92
  logger: this.logger.child("GitManager"),
91
93
  });
92
- this.templateManager = new TemplateManager();
93
94
 
94
95
  if (
95
96
  config.posthogApiUrl &&
96
- config.posthogApiKey &&
97
+ config.getPosthogApiKey &&
97
98
  config.posthogProjectId
98
99
  ) {
99
100
  this.posthogAPI = new PostHogAPIClient({
100
101
  apiUrl: config.posthogApiUrl,
101
- apiKey: config.posthogApiKey,
102
+ getApiKey: config.getPosthogApiKey,
102
103
  projectId: config.posthogProjectId,
103
104
  });
104
105
 
@@ -108,13 +109,6 @@ export class Agent {
108
109
  this.logger.child("SessionStore"),
109
110
  );
110
111
  }
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
112
  }
119
113
 
120
114
  /**
@@ -126,7 +120,7 @@ export class Agent {
126
120
  }
127
121
 
128
122
  /**
129
- * Configure LLM gateway environment variables for Claude Code CLI
123
+ * Configure LLM gateway environment variables for Claude Code CLI.
130
124
  */
131
125
  private async _configureLlmGateway(): Promise<void> {
132
126
  if (!this.posthogAPI) {
@@ -139,113 +133,25 @@ export class Agent {
139
133
  process.env.ANTHROPIC_BASE_URL = gatewayUrl;
140
134
  process.env.ANTHROPIC_AUTH_TOKEN = apiKey;
141
135
  this.ensureOpenAIGatewayEnv(gatewayUrl, apiKey);
136
+ this.ensureGeminiGatewayEnv(gatewayUrl, apiKey);
142
137
  } catch (error) {
143
138
  this.logger.error("Failed to configure LLM gateway", error);
144
139
  throw error;
145
140
  }
146
141
  }
147
142
 
148
- private getOrCreateConnection(): InProcessAcpConnection {
149
- if (!this.acpConnection) {
150
- this.acpConnection = createAcpConnection({
151
- sessionStore: this.sessionStore,
152
- });
153
- }
154
- return this.acpConnection;
155
- }
156
-
157
- // Adaptive task execution orchestrated via workflow steps
143
+ /**
144
+ * @deprecated Use runTaskV2() for local execution or runTaskCloud() for cloud execution.
145
+ * This method used the old workflow system which has been removed.
146
+ */
158
147
  async runTask(
159
- taskId: string,
160
- taskRunId: string,
161
- options: import("./types.js").TaskExecutionOptions = {},
148
+ _taskId: string,
149
+ _taskRunId: string,
150
+ _options: import("./types.js").TaskExecutionOptions = {},
162
151
  ): Promise<void> {
163
- // await this._configureLlmGateway();
164
-
165
- const task = await this.fetchTask(taskId);
166
- const cwd = options.repositoryPath || this.workingDirectory;
167
- const isCloudMode = options.isCloudMode ?? false;
168
- const taskSlug = (task as any).slug || task.id;
169
-
170
- // Use taskRunId as sessionId - they are the same identifier
171
- this.currentRunId = taskRunId;
172
-
173
- this.logger.info("Starting adaptive task execution", {
174
- taskId: task.id,
175
- taskSlug,
176
- taskRunId,
177
- isCloudMode,
178
- });
179
-
180
- const connection = this.getOrCreateConnection();
181
-
182
- // Create sendNotification using ACP connection's extNotification
183
- const sendNotification: SendNotification = async (method, params) => {
184
- this.logger.debug(`Notification: ${method}`, params);
185
- await connection.agentConnection.extNotification?.(method, params);
186
- };
187
-
188
- await sendNotification(POSTHOG_NOTIFICATIONS.RUN_STARTED, {
189
- sessionId: taskRunId,
190
- runId: taskRunId,
191
- });
192
-
193
- await this.prepareTaskBranch(taskSlug, isCloudMode, sendNotification);
194
-
195
- let taskError: Error | undefined;
196
- try {
197
- const workflowContext: WorkflowRuntime = {
198
- task,
199
- taskSlug,
200
- runId: taskRunId,
201
- cwd,
202
- isCloudMode,
203
- options,
204
- logger: this.logger,
205
- fileManager: this.fileManager,
206
- gitManager: this.gitManager,
207
- promptBuilder: this.promptBuilder,
208
- connection: connection.agentConnection,
209
- sessionId: taskRunId,
210
- sendNotification,
211
- mcpServers: this.mcpServers,
212
- posthogAPI: this.posthogAPI,
213
- stepResults: {},
214
- };
215
-
216
- for (const step of TASK_WORKFLOW) {
217
- const result = await step.run({ step, context: workflowContext });
218
- if (result.halt) {
219
- return;
220
- }
221
- }
222
-
223
- const shouldCreatePR = options.createPR ?? isCloudMode;
224
- if (shouldCreatePR) {
225
- await this.ensurePullRequest(
226
- task,
227
- workflowContext.stepResults,
228
- sendNotification,
229
- );
230
- }
231
-
232
- this.logger.info("Task execution complete", { taskId: task.id });
233
- await sendNotification(POSTHOG_NOTIFICATIONS.TASK_COMPLETE, {
234
- sessionId: taskRunId,
235
- taskId: task.id,
236
- });
237
- } catch (error) {
238
- taskError = error instanceof Error ? error : new Error(String(error));
239
- this.logger.error("Task execution failed", {
240
- taskId: task.id,
241
- error: taskError.message,
242
- });
243
- await sendNotification(POSTHOG_NOTIFICATIONS.ERROR, {
244
- sessionId: taskRunId,
245
- message: taskError.message,
246
- });
247
- throw taskError;
248
- }
152
+ throw new Error(
153
+ "runTask() is deprecated. Use runTaskV2() for local execution or runTaskCloud() for cloud execution.",
154
+ );
249
155
  }
250
156
 
251
157
  /**
@@ -262,8 +168,6 @@ export class Agent {
262
168
  ): Promise<InProcessAcpConnection> {
263
169
  await this._configureLlmGateway();
264
170
 
265
- const task = await this.fetchTask(taskId);
266
- const taskSlug = (task as any).slug || task.id;
267
171
  const isCloudMode = options.isCloudMode ?? false;
268
172
  const _cwd = options.repositoryPath || this.workingDirectory;
269
173
 
@@ -271,9 +175,10 @@ export class Agent {
271
175
  this.currentRunId = taskRunId;
272
176
 
273
177
  this.acpConnection = createAcpConnection({
178
+ framework: options.framework,
274
179
  sessionStore: this.sessionStore,
275
180
  sessionId: taskRunId,
276
- taskId: task.id,
181
+ taskId,
277
182
  });
278
183
 
279
184
  const sendNotification: SendNotification = async (method, params) => {
@@ -284,12 +189,17 @@ export class Agent {
284
189
  );
285
190
  };
286
191
 
287
- await sendNotification(POSTHOG_NOTIFICATIONS.RUN_STARTED, {
288
- sessionId: taskRunId,
289
- runId: taskRunId,
290
- });
192
+ if (!options.isReconnect) {
193
+ await sendNotification(POSTHOG_NOTIFICATIONS.RUN_STARTED, {
194
+ sessionId: taskRunId,
195
+ runId: taskRunId,
196
+ });
197
+ }
291
198
 
199
+ // Only fetch task when we need the slug for git branch creation
292
200
  if (!options.skipGitBranch) {
201
+ const task = options.task ?? (await this.fetchTask(taskId));
202
+ const taskSlug = (task as any).slug || task.id;
293
203
  try {
294
204
  await this.prepareTaskBranch(taskSlug, isCloudMode, sendNotification);
295
205
  } catch (error) {
@@ -373,9 +283,7 @@ export class Agent {
373
283
  **Description**: ${taskDescription}
374
284
 
375
285
  ## Changes
376
- This PR implements the changes described in the task.
377
-
378
- Generated by PostHog Agent`;
286
+ This PR implements the changes described in the task.`;
379
287
  const prBody = customBody || defaultBody;
380
288
 
381
289
  const prUrl = await this.gitManager.createPullRequest(
@@ -529,6 +437,19 @@ Generated by PostHog Agent`;
529
437
  }
530
438
  }
531
439
 
440
+ private ensureGeminiGatewayEnv(gatewayUrl?: string, token?: string): void {
441
+ const resolvedGatewayUrl = gatewayUrl || process.env.ANTHROPIC_BASE_URL;
442
+ const resolvedToken = token || process.env.ANTHROPIC_AUTH_TOKEN;
443
+
444
+ if (resolvedGatewayUrl) {
445
+ process.env.GEMINI_BASE_URL = resolvedGatewayUrl;
446
+ }
447
+
448
+ if (resolvedToken) {
449
+ process.env.GEMINI_API_KEY = resolvedToken;
450
+ }
451
+ }
452
+
532
453
  async runTaskCloud(
533
454
  taskId: string,
534
455
  taskRunId: string,
@@ -741,61 +662,6 @@ Generated by PostHog Agent`;
741
662
  throw error;
742
663
  }
743
664
  }
744
-
745
- private async ensurePullRequest(
746
- task: Task,
747
- stepResults: Record<string, any>,
748
- sendNotification: SendNotification,
749
- ): Promise<void> {
750
- const latestRun = task.latest_run;
751
- const existingPr =
752
- latestRun?.output && typeof latestRun.output === "object"
753
- ? (latestRun.output as any).pr_url
754
- : null;
755
-
756
- if (existingPr) {
757
- this.logger.info("PR already exists, skipping creation", {
758
- taskId: task.id,
759
- prUrl: existingPr,
760
- });
761
- return;
762
- }
763
-
764
- const buildResult = stepResults.build;
765
- if (!buildResult?.commitCreated) {
766
- this.logger.warn(
767
- "Build step did not produce a commit; skipping PR creation",
768
- { taskId: task.id },
769
- );
770
- return;
771
- }
772
-
773
- const branchName = await this.gitManager.getCurrentBranch();
774
- const finalizeResult = stepResults.finalize;
775
- const prBody = finalizeResult?.prBody;
776
-
777
- const prUrl = await this.createPullRequest(
778
- task.id,
779
- branchName,
780
- task.title,
781
- task.description ?? "",
782
- prBody,
783
- );
784
-
785
- await sendNotification(POSTHOG_NOTIFICATIONS.PR_CREATED, { prUrl });
786
-
787
- try {
788
- await this.attachPullRequestToTask(task.id, prUrl, branchName);
789
- this.logger.info("PR attached to task successfully", {
790
- taskId: task.id,
791
- prUrl,
792
- });
793
- } catch (error) {
794
- this.logger.warn("Could not attach PR to task", {
795
- error: error instanceof Error ? error.message : String(error),
796
- });
797
- }
798
- }
799
665
  }
800
666
 
801
667
  export type {
@@ -1,7 +1,7 @@
1
1
  import { promises as fs } from "node:fs";
2
2
  import { extname, join } from "node:path";
3
3
  import z from "zod";
4
- import type { ResearchEvaluation, SupportingFile } from "./types.js";
4
+ import type { SupportingFile } from "./types.js";
5
5
  import { Logger } from "./utils/logger.js";
6
6
 
7
7
  export interface TaskFile {
@@ -162,39 +162,6 @@ export class PostHogFileManager {
162
162
  return await this.readTaskFile(taskId, "requirements.md");
163
163
  }
164
164
 
165
- async writeResearch(taskId: string, data: ResearchEvaluation): Promise<void> {
166
- this.logger.debug("Writing research", {
167
- taskId,
168
- score: data.actionabilityScore,
169
- hasQuestions: !!data.questions,
170
- questionCount: data.questions?.length ?? 0,
171
- answered: data.answered ?? false,
172
- });
173
-
174
- await this.writeTaskFile(taskId, {
175
- name: "research.json",
176
- content: JSON.stringify(data, null, 2),
177
- type: "artifact",
178
- });
179
-
180
- this.logger.info("Research file written", {
181
- taskId,
182
- score: data.actionabilityScore,
183
- hasQuestions: !!data.questions,
184
- answered: data.answered ?? false,
185
- });
186
- }
187
-
188
- async readResearch(taskId: string): Promise<ResearchEvaluation | null> {
189
- try {
190
- const content = await this.readTaskFile(taskId, "research.json");
191
- return content ? (JSON.parse(content) as ResearchEvaluation) : null;
192
- } catch (error) {
193
- this.logger.debug("Failed to parse research.json", { error });
194
- return null;
195
- }
196
- }
197
-
198
165
  async writeTodos(taskId: string, data: unknown): Promise<void> {
199
166
  const todos = z.object({
200
167
  metadata: z.object({
@@ -6,8 +6,6 @@ const execAsync = promisify(exec);
6
6
 
7
7
  export interface GitConfig {
8
8
  repositoryPath: string;
9
- authorName?: string;
10
- authorEmail?: string;
11
9
  logger?: Logger;
12
10
  }
13
11
 
@@ -19,14 +17,10 @@ export interface BranchInfo {
19
17
 
20
18
  export class GitManager {
21
19
  private repositoryPath: string;
22
- private authorName?: string;
23
- private authorEmail?: string;
24
20
  private logger: Logger;
25
21
 
26
22
  constructor(config: GitConfig) {
27
23
  this.repositoryPath = config.repositoryPath;
28
- this.authorName = config.authorName;
29
- this.authorEmail = config.authorEmail;
30
24
  this.logger =
31
25
  config.logger || new Logger({ debug: false, prefix: "[GitManager]" });
32
26
  }
@@ -170,8 +164,7 @@ export class GitManager {
170
164
  async commitChanges(
171
165
  message: string,
172
166
  options?: {
173
- authorName?: string;
174
- authorEmail?: string;
167
+ allowEmpty?: boolean;
175
168
  },
176
169
  ): Promise<string> {
177
170
  const command = this.buildCommitCommand(message, options);
@@ -244,8 +237,6 @@ export class GitManager {
244
237
  message: string,
245
238
  options?: {
246
239
  allowEmpty?: boolean;
247
- authorName?: string;
248
- authorEmail?: string;
249
240
  },
250
241
  ): string {
251
242
  let command = `commit -m "${this.escapeShellArg(message)}"`;
@@ -254,13 +245,6 @@ export class GitManager {
254
245
  command += " --allow-empty";
255
246
  }
256
247
 
257
- const authorName = options?.authorName || this.authorName;
258
- const authorEmail = options?.authorEmail || this.authorEmail;
259
-
260
- if (authorName && authorEmail) {
261
- command += ` --author="${authorName} <${authorEmail}>"`;
262
- }
263
-
264
248
  return command;
265
249
  }
266
250
 
@@ -427,7 +411,6 @@ export class GitManager {
427
411
  const message = `📋 Add plan for task: ${taskTitle}
428
412
 
429
413
  Task ID: ${taskId}
430
- Generated by PostHog Agent
431
414
 
432
415
  This commit contains the implementation plan and supporting documentation
433
416
  for the task. Review the plan before proceeding with implementation.`;
@@ -452,8 +435,7 @@ for the task. Review the plan before proceeding with implementation.`;
452
435
 
453
436
  let message = `✨ Implement task: ${taskTitle}
454
437
 
455
- Task ID: ${taskId}
456
- Generated by PostHog Agent`;
438
+ Task ID: ${taskId}`;
457
439
 
458
440
  if (planSummary) {
459
441
  message += `\n\nPlan Summary:\n${planSummary}`;
@@ -8,6 +8,7 @@ import type {
8
8
  TaskRunArtifact,
9
9
  UrlMention,
10
10
  } from "./types.js";
11
+ import { getLlmGatewayUrl } from "./utils/gateway.js";
11
12
 
12
13
  interface PostHogApiResponse<T> {
13
14
  results?: T[];
@@ -42,7 +43,7 @@ export class PostHogAPIClient {
42
43
 
43
44
  private get headers(): Record<string, string> {
44
45
  return {
45
- Authorization: `Bearer ${this.config.apiKey}`,
46
+ Authorization: `Bearer ${this.config.getApiKey()}`,
46
47
  "Content-Type": "application/json",
47
48
  };
48
49
  }
@@ -84,12 +85,11 @@ export class PostHogAPIClient {
84
85
  }
85
86
 
86
87
  getApiKey(): string {
87
- return this.config.apiKey;
88
+ return this.config.getApiKey();
88
89
  }
89
90
 
90
91
  getLlmGatewayUrl(): string {
91
- const teamId = this.getTeamId();
92
- return `${this.baseUrl}/api/projects/${teamId}/llm_gateway`;
92
+ return getLlmGatewayUrl(this.baseUrl);
93
93
  }
94
94
 
95
95
  async fetchTask(taskId: string): Promise<Task> {
@@ -84,6 +84,11 @@ const TOOL_DEFINITIONS: Record<string, Tool> = {
84
84
  category: "assistant",
85
85
  description: "Exit plan mode and present plan to user",
86
86
  },
87
+ AskUserQuestion: {
88
+ name: "AskUserQuestion",
89
+ category: "assistant",
90
+ description: "Ask the user a clarifying question with options",
91
+ },
87
92
  SlashCommand: {
88
93
  name: "SlashCommand",
89
94
  category: "assistant",
@@ -100,6 +100,11 @@ export interface ExitPlanModeTool extends Tool {
100
100
  category: "assistant";
101
101
  }
102
102
 
103
+ export interface AskUserQuestionTool extends Tool {
104
+ name: "AskUserQuestion";
105
+ category: "assistant";
106
+ }
107
+
103
108
  export interface SlashCommandTool extends Tool {
104
109
  name: "SlashCommand";
105
110
  category: "assistant";
@@ -124,4 +129,5 @@ export type KnownTool =
124
129
  | TaskTool
125
130
  | TodoWriteTool
126
131
  | ExitPlanModeTool
132
+ | AskUserQuestionTool
127
133
  | SlashCommandTool;
package/src/types.ts CHANGED
@@ -141,6 +141,9 @@ export interface TaskExecutionOptions {
141
141
  // See: https://docs.claude.com/en/api/agent-sdk/permissions
142
142
  canUseTool?: CanUseTool;
143
143
  skipGitBranch?: boolean; // Skip creating a task-specific git branch
144
+ framework?: "claude"; // Agent framework to use (defaults to "claude")
145
+ task?: Task; // Pre-fetched task to avoid redundant API call
146
+ isReconnect?: boolean; // Session recreation - skip RUN_STARTED notification
144
147
  }
145
148
 
146
149
  export interface ExecutionResult {
@@ -197,7 +200,7 @@ export interface AgentConfig {
197
200
 
198
201
  // PostHog API configuration (optional - enables PostHog integration when provided)
199
202
  posthogApiUrl?: string;
200
- posthogApiKey?: string;
203
+ getPosthogApiKey?: () => string;
201
204
  posthogProjectId?: number;
202
205
 
203
206
  // PostHog MCP configuration
@@ -219,7 +222,7 @@ export interface AgentConfig {
219
222
 
220
223
  export interface PostHogAPIConfig {
221
224
  apiUrl: string;
222
- apiKey: string;
225
+ getApiKey: () => string;
223
226
  projectId: number;
224
227
  }
225
228
 
@@ -248,29 +251,6 @@ export interface UrlMention {
248
251
  label?: string;
249
252
  }
250
253
 
251
- // Research evaluation types
252
- export interface ResearchQuestion {
253
- id: string;
254
- question: string;
255
- options: string[];
256
- }
257
-
258
- export interface ResearchAnswer {
259
- questionId: string;
260
- selectedOption: string;
261
- customInput?: string;
262
- }
263
-
264
- export interface ResearchEvaluation {
265
- actionabilityScore: number; // 0-1 confidence score
266
- context: string; // brief summary for planning
267
- keyFiles: string[]; // files needing modification
268
- blockers?: string[]; // what's preventing full confidence
269
- questions?: ResearchQuestion[]; // only if score < 0.7
270
- answered?: boolean; // whether questions have been answered
271
- answers?: ResearchAnswer[]; // user's answers to questions
272
- }
273
-
274
254
  // Worktree types for parallel task development
275
255
  export interface WorktreeInfo {
276
256
  worktreePath: string;
@@ -0,0 +1,15 @@
1
+ export function getLlmGatewayUrl(posthogHost: string): string {
2
+ const url = new URL(posthogHost);
3
+ const hostname = url.hostname;
4
+
5
+ if (hostname === "localhost" || hostname === "127.0.0.1") {
6
+ return `${url.protocol}//localhost:3308`;
7
+ }
8
+
9
+ // Extract region from hostname (us.posthog.com, eu.posthog.com)
10
+ // app.posthog.com is legacy US
11
+ const regionMatch = hostname.match(/^(us|eu)\.posthog\.com$/);
12
+ const region = regionMatch ? regionMatch[1] : "us";
13
+
14
+ return `https://gateway.${region}.posthog.com`;
15
+ }