@posthog/agent 1.28.0 → 1.30.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/dist/index.d.ts +22 -6
- package/dist/index.js +66 -36
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/acp-extensions.ts +14 -1
- package/src/adapters/claude/claude.ts +32 -3
- package/src/agent.ts +20 -8
- package/src/git-manager.ts +2 -20
- package/src/posthog-api.ts +4 -4
- package/src/prompt-builder.ts +5 -3
- package/src/template-manager.ts +2 -6
- package/src/types.ts +2 -2
- package/src/utils/gateway.ts +15 -0
- package/src/worktree-manager.ts +4 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@posthog/agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.30.0",
|
|
4
4
|
"repository": "https://github.com/PostHog/array",
|
|
5
5
|
"description": "TypeScript agent framework wrapping Claude Agent SDK with Git-based task execution for PostHog",
|
|
6
6
|
"main": "./dist/index.js",
|
package/src/acp-extensions.ts
CHANGED
|
@@ -33,6 +33,8 @@ export const POSTHOG_NOTIFICATIONS = {
|
|
|
33
33
|
CONSOLE: "_posthog/console",
|
|
34
34
|
/** SDK session ID notification (for resumption) */
|
|
35
35
|
SDK_SESSION: "_posthog/sdk_session",
|
|
36
|
+
/** Sandbox execution output (stdout/stderr from Modal or Docker) */
|
|
37
|
+
SANDBOX_OUTPUT: "_posthog/sandbox_output",
|
|
36
38
|
} as const;
|
|
37
39
|
|
|
38
40
|
export type PostHogNotificationType =
|
|
@@ -105,6 +107,16 @@ export interface SdkSessionPayload {
|
|
|
105
107
|
sdkSessionId: string;
|
|
106
108
|
}
|
|
107
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Sandbox execution output
|
|
112
|
+
*/
|
|
113
|
+
export interface SandboxOutputPayload {
|
|
114
|
+
sessionId: string;
|
|
115
|
+
stdout: string;
|
|
116
|
+
stderr: string;
|
|
117
|
+
exitCode: number;
|
|
118
|
+
}
|
|
119
|
+
|
|
108
120
|
export type PostHogNotificationPayload =
|
|
109
121
|
| ArtifactNotificationPayload
|
|
110
122
|
| PhaseNotificationPayload
|
|
@@ -114,4 +126,5 @@ export type PostHogNotificationPayload =
|
|
|
114
126
|
| TaskCompletePayload
|
|
115
127
|
| ErrorNotificationPayload
|
|
116
128
|
| ConsoleNotificationPayload
|
|
117
|
-
| SdkSessionPayload
|
|
129
|
+
| SdkSessionPayload
|
|
130
|
+
| SandboxOutputPayload;
|
|
@@ -135,6 +135,8 @@ export type NewSessionMeta = {
|
|
|
135
135
|
*/
|
|
136
136
|
options?: Options;
|
|
137
137
|
};
|
|
138
|
+
/** Initial model to use for the session (e.g., 'claude-opus-4-5', 'gpt-5.1') */
|
|
139
|
+
model?: string;
|
|
138
140
|
};
|
|
139
141
|
|
|
140
142
|
/**
|
|
@@ -443,6 +445,20 @@ export class ClaudeAcpAgent implements Agent {
|
|
|
443
445
|
const availableCommands = await getAvailableSlashCommands(q);
|
|
444
446
|
const models = await getAvailableModels(q);
|
|
445
447
|
|
|
448
|
+
// Set initial model if provided via _meta (must be after getAvailableModels which resets to default)
|
|
449
|
+
const requestedModel = (params._meta as NewSessionMeta | undefined)?.model;
|
|
450
|
+
if (requestedModel) {
|
|
451
|
+
try {
|
|
452
|
+
await q.setModel(requestedModel);
|
|
453
|
+
this.logger.info("Set initial model", { model: requestedModel });
|
|
454
|
+
} catch (err) {
|
|
455
|
+
this.logger.warn("Failed to set initial model, using default", {
|
|
456
|
+
requestedModel,
|
|
457
|
+
error: err,
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
446
462
|
// Needs to happen after we return the session
|
|
447
463
|
setTimeout(() => {
|
|
448
464
|
this.client.sessionUpdate({
|
|
@@ -657,12 +673,16 @@ export class ClaudeAcpAgent implements Agent {
|
|
|
657
673
|
throw RequestError.authRequired();
|
|
658
674
|
}
|
|
659
675
|
|
|
660
|
-
//
|
|
661
|
-
// But some gateways (like LiteLLM) don't stream, so we process all content.
|
|
676
|
+
// Text/thinking is streamed via stream_event, so skip them here to avoid duplication.
|
|
662
677
|
const content = message.message.content;
|
|
678
|
+
const contentToProcess = Array.isArray(content)
|
|
679
|
+
? content.filter(
|
|
680
|
+
(block) => block.type !== "text" && block.type !== "thinking",
|
|
681
|
+
)
|
|
682
|
+
: content;
|
|
663
683
|
|
|
664
684
|
for (const notification of toAcpNotifications(
|
|
665
|
-
content,
|
|
685
|
+
contentToProcess as typeof content,
|
|
666
686
|
message.message.role,
|
|
667
687
|
params.sessionId,
|
|
668
688
|
this.toolUseCache,
|
|
@@ -922,6 +942,15 @@ export class ClaudeAcpAgent implements Agent {
|
|
|
922
942
|
return {};
|
|
923
943
|
}
|
|
924
944
|
|
|
945
|
+
if (method === "session/setModel") {
|
|
946
|
+
const { sessionId, modelId } = params as {
|
|
947
|
+
sessionId: string;
|
|
948
|
+
modelId: string;
|
|
949
|
+
};
|
|
950
|
+
await this.setSessionModel({ sessionId, modelId });
|
|
951
|
+
return {};
|
|
952
|
+
}
|
|
953
|
+
|
|
925
954
|
throw RequestError.methodNotFound(method);
|
|
926
955
|
}
|
|
927
956
|
|
package/src/agent.ts
CHANGED
|
@@ -57,8 +57,8 @@ export class Agent {
|
|
|
57
57
|
|
|
58
58
|
// Add auth if API key provided
|
|
59
59
|
const headers: Record<string, string> = {};
|
|
60
|
-
if (config.
|
|
61
|
-
headers.Authorization = `Bearer ${config.
|
|
60
|
+
if (config.getPosthogApiKey) {
|
|
61
|
+
headers.Authorization = `Bearer ${config.getPosthogApiKey()}`;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
const defaultMcpServers = {
|
|
@@ -93,12 +93,12 @@ export class Agent {
|
|
|
93
93
|
|
|
94
94
|
if (
|
|
95
95
|
config.posthogApiUrl &&
|
|
96
|
-
config.
|
|
96
|
+
config.getPosthogApiKey &&
|
|
97
97
|
config.posthogProjectId
|
|
98
98
|
) {
|
|
99
99
|
this.posthogAPI = new PostHogAPIClient({
|
|
100
100
|
apiUrl: config.posthogApiUrl,
|
|
101
|
-
|
|
101
|
+
getApiKey: config.getPosthogApiKey,
|
|
102
102
|
projectId: config.posthogProjectId,
|
|
103
103
|
});
|
|
104
104
|
|
|
@@ -126,7 +126,7 @@ export class Agent {
|
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
/**
|
|
129
|
-
* Configure LLM gateway environment variables for Claude Code CLI
|
|
129
|
+
* Configure LLM gateway environment variables for Claude Code CLI.
|
|
130
130
|
*/
|
|
131
131
|
private async _configureLlmGateway(): Promise<void> {
|
|
132
132
|
if (!this.posthogAPI) {
|
|
@@ -139,6 +139,7 @@ export class Agent {
|
|
|
139
139
|
process.env.ANTHROPIC_BASE_URL = gatewayUrl;
|
|
140
140
|
process.env.ANTHROPIC_AUTH_TOKEN = apiKey;
|
|
141
141
|
this.ensureOpenAIGatewayEnv(gatewayUrl, apiKey);
|
|
142
|
+
this.ensureGeminiGatewayEnv(gatewayUrl, apiKey);
|
|
142
143
|
} catch (error) {
|
|
143
144
|
this.logger.error("Failed to configure LLM gateway", error);
|
|
144
145
|
throw error;
|
|
@@ -373,9 +374,7 @@ export class Agent {
|
|
|
373
374
|
**Description**: ${taskDescription}
|
|
374
375
|
|
|
375
376
|
## Changes
|
|
376
|
-
This PR implements the changes described in the task
|
|
377
|
-
|
|
378
|
-
Generated by PostHog Agent`;
|
|
377
|
+
This PR implements the changes described in the task.`;
|
|
379
378
|
const prBody = customBody || defaultBody;
|
|
380
379
|
|
|
381
380
|
const prUrl = await this.gitManager.createPullRequest(
|
|
@@ -529,6 +528,19 @@ Generated by PostHog Agent`;
|
|
|
529
528
|
}
|
|
530
529
|
}
|
|
531
530
|
|
|
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
|
+
|
|
532
544
|
async runTaskCloud(
|
|
533
545
|
taskId: string,
|
|
534
546
|
taskRunId: string,
|
package/src/git-manager.ts
CHANGED
|
@@ -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
|
-
|
|
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}`;
|
package/src/posthog-api.ts
CHANGED
|
@@ -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.
|
|
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.
|
|
88
|
+
return this.config.getApiKey();
|
|
88
89
|
}
|
|
89
90
|
|
|
90
91
|
getLlmGatewayUrl(): string {
|
|
91
|
-
|
|
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> {
|
package/src/prompt-builder.ts
CHANGED
|
@@ -215,17 +215,19 @@ export class PromptBuilder {
|
|
|
215
215
|
return { description, referencedFiles };
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
-
// Read all referenced files
|
|
218
|
+
// Read all referenced files, tracking which ones succeed
|
|
219
|
+
const successfulPaths = new Set<string>();
|
|
219
220
|
for (const filePath of filePaths) {
|
|
220
221
|
const content = await this.readFileContent(repositoryPath, filePath);
|
|
221
222
|
if (content !== null) {
|
|
222
223
|
referencedFiles.push({ path: filePath, content });
|
|
224
|
+
successfulPaths.add(filePath);
|
|
223
225
|
}
|
|
224
226
|
}
|
|
225
227
|
|
|
226
|
-
//
|
|
228
|
+
// Only replace tags for files that were successfully read
|
|
227
229
|
let processedDescription = description;
|
|
228
|
-
for (const filePath of
|
|
230
|
+
for (const filePath of successfulPaths) {
|
|
229
231
|
const fileName = filePath.split("/").pop() || filePath;
|
|
230
232
|
processedDescription = processedDescription.replace(
|
|
231
233
|
new RegExp(
|
package/src/template-manager.ts
CHANGED
|
@@ -205,7 +205,7 @@ export class TemplateManager {
|
|
|
205
205
|
generatePostHogReadme(): string {
|
|
206
206
|
return `# PostHog Task Files
|
|
207
207
|
|
|
208
|
-
This directory contains task-related files
|
|
208
|
+
This directory contains task-related files.
|
|
209
209
|
|
|
210
210
|
## Structure
|
|
211
211
|
|
|
@@ -221,7 +221,7 @@ Each task has its own subdirectory: \`.posthog/{task-id}/\`
|
|
|
221
221
|
|
|
222
222
|
These files are:
|
|
223
223
|
- Version controlled alongside your code
|
|
224
|
-
- Used
|
|
224
|
+
- Used for task context and planning
|
|
225
225
|
- Available for review in pull requests
|
|
226
226
|
- Organized by task ID for easy reference
|
|
227
227
|
|
|
@@ -231,10 +231,6 @@ Customize \`.posthog/.gitignore\` to control which files are committed:
|
|
|
231
231
|
- Include plans and documentation by default
|
|
232
232
|
- Exclude temporary files and sensitive data
|
|
233
233
|
- Customize based on your team's needs
|
|
234
|
-
|
|
235
|
-
---
|
|
236
|
-
|
|
237
|
-
*Generated by PostHog Agent*
|
|
238
234
|
`;
|
|
239
235
|
}
|
|
240
236
|
}
|
package/src/types.ts
CHANGED
|
@@ -197,7 +197,7 @@ export interface AgentConfig {
|
|
|
197
197
|
|
|
198
198
|
// PostHog API configuration (optional - enables PostHog integration when provided)
|
|
199
199
|
posthogApiUrl?: string;
|
|
200
|
-
|
|
200
|
+
getPosthogApiKey?: () => string;
|
|
201
201
|
posthogProjectId?: number;
|
|
202
202
|
|
|
203
203
|
// PostHog MCP configuration
|
|
@@ -219,7 +219,7 @@ export interface AgentConfig {
|
|
|
219
219
|
|
|
220
220
|
export interface PostHogAPIConfig {
|
|
221
221
|
apiUrl: string;
|
|
222
|
-
|
|
222
|
+
getApiKey: () => string;
|
|
223
223
|
projectId: number;
|
|
224
224
|
}
|
|
225
225
|
|
|
@@ -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
|
+
}
|
package/src/worktree-manager.ts
CHANGED
|
@@ -628,7 +628,9 @@ export class WorktreeManager {
|
|
|
628
628
|
}
|
|
629
629
|
}
|
|
630
630
|
|
|
631
|
-
async createWorktree(
|
|
631
|
+
async createWorktree(options?: {
|
|
632
|
+
baseBranch?: string;
|
|
633
|
+
}): Promise<WorktreeInfo> {
|
|
632
634
|
// Only modify .git/info/exclude when using in-repo storage
|
|
633
635
|
if (!this.usesExternalPath()) {
|
|
634
636
|
await this.ensureArrayDirIgnored();
|
|
@@ -644,7 +646,7 @@ export class WorktreeManager {
|
|
|
644
646
|
const worktreeName = await this.generateUniqueWorktreeName();
|
|
645
647
|
const worktreePath = this.getWorktreePath(worktreeName);
|
|
646
648
|
const branchName = `array/${worktreeName}`;
|
|
647
|
-
const baseBranch = await this.getDefaultBranch();
|
|
649
|
+
const baseBranch = options?.baseBranch ?? (await this.getDefaultBranch());
|
|
648
650
|
|
|
649
651
|
this.logger.info("Creating worktree", {
|
|
650
652
|
worktreeName,
|