@posthog/agent 1.5.0 → 1.7.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/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +1 -0
- package/dist/src/agent.js.map +1 -1
- package/dist/src/posthog-api.d.ts +13 -1
- package/dist/src/posthog-api.d.ts.map +1 -1
- package/dist/src/posthog-api.js +91 -0
- package/dist/src/posthog-api.js.map +1 -1
- package/dist/src/prompt-builder.d.ts +34 -3
- package/dist/src/prompt-builder.d.ts.map +1 -1
- package/dist/src/prompt-builder.js +197 -5
- package/dist/src/prompt-builder.js.map +1 -1
- package/dist/src/stage-executor.js +2 -2
- package/dist/src/stage-executor.js.map +1 -1
- package/dist/src/types.d.ts +15 -0
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/types.js.map +1 -1
- package/package.json +1 -1
- package/src/agent.ts +1 -0
- package/src/posthog-api.ts +108 -1
- package/src/prompt-builder.ts +247 -6
- package/src/stage-executor.ts +2 -2
- package/src/types.ts +19 -0
package/src/prompt-builder.ts
CHANGED
|
@@ -1,32 +1,246 @@
|
|
|
1
|
-
import type { Task } from './types.js';
|
|
1
|
+
import type { Task, UrlMention, PostHogResource } from './types.js';
|
|
2
2
|
import type { TemplateVariables } from './template-manager.js';
|
|
3
3
|
import { Logger } from './utils/logger.js';
|
|
4
|
+
import { promises as fs } from 'fs';
|
|
5
|
+
import { join } from 'path';
|
|
4
6
|
|
|
5
7
|
export interface PromptBuilderDeps {
|
|
6
8
|
getTaskFiles: (taskId: string) => Promise<any[]>;
|
|
7
9
|
generatePlanTemplate: (vars: TemplateVariables) => Promise<string>;
|
|
10
|
+
posthogClient?: { fetchResourceByUrl: (mention: UrlMention) => Promise<PostHogResource> };
|
|
8
11
|
logger?: Logger;
|
|
9
12
|
}
|
|
10
13
|
|
|
11
14
|
export class PromptBuilder {
|
|
12
15
|
private getTaskFiles: PromptBuilderDeps['getTaskFiles'];
|
|
13
16
|
private generatePlanTemplate: PromptBuilderDeps['generatePlanTemplate'];
|
|
17
|
+
private posthogClient?: PromptBuilderDeps['posthogClient'];
|
|
14
18
|
private logger: Logger;
|
|
15
19
|
|
|
16
20
|
constructor(deps: PromptBuilderDeps) {
|
|
17
21
|
this.getTaskFiles = deps.getTaskFiles;
|
|
18
22
|
this.generatePlanTemplate = deps.generatePlanTemplate;
|
|
23
|
+
this.posthogClient = deps.posthogClient;
|
|
19
24
|
this.logger = deps.logger || new Logger({ debug: false, prefix: '[PromptBuilder]' });
|
|
20
25
|
}
|
|
21
26
|
|
|
22
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Extract file paths from XML tags in description
|
|
29
|
+
* Format: <file path="relative/path.ts" />
|
|
30
|
+
*/
|
|
31
|
+
private extractFilePaths(description: string): string[] {
|
|
32
|
+
const fileTagRegex = /<file\s+path="([^"]+)"\s*\/>/g;
|
|
33
|
+
const paths: string[] = [];
|
|
34
|
+
let match: RegExpExecArray | null;
|
|
35
|
+
|
|
36
|
+
while ((match = fileTagRegex.exec(description)) !== null) {
|
|
37
|
+
paths.push(match[1]);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return paths;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Read file contents from repository
|
|
45
|
+
*/
|
|
46
|
+
private async readFileContent(repositoryPath: string, filePath: string): Promise<string | null> {
|
|
47
|
+
try {
|
|
48
|
+
const fullPath = join(repositoryPath, filePath);
|
|
49
|
+
const content = await fs.readFile(fullPath, 'utf8');
|
|
50
|
+
return content;
|
|
51
|
+
} catch (error) {
|
|
52
|
+
this.logger.warn(`Failed to read referenced file: ${filePath}`, { error });
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Extract URL mentions from XML tags in description
|
|
59
|
+
* Formats: <error id="..." />, <experiment id="..." />, <url href="..." />
|
|
60
|
+
*/
|
|
61
|
+
private extractUrlMentions(description: string): UrlMention[] {
|
|
62
|
+
const mentions: UrlMention[] = [];
|
|
63
|
+
|
|
64
|
+
// PostHog resource mentions: <error id="..." />, <experiment id="..." />, etc.
|
|
65
|
+
const resourceRegex = /<(error|experiment|insight|feature_flag)\s+id="([^"]+)"\s*\/>/g;
|
|
66
|
+
let match: RegExpExecArray | null;
|
|
67
|
+
|
|
68
|
+
while ((match = resourceRegex.exec(description)) !== null) {
|
|
69
|
+
const [, type, id] = match;
|
|
70
|
+
mentions.push({
|
|
71
|
+
url: '', // Will be reconstructed if needed
|
|
72
|
+
type: type as any,
|
|
73
|
+
id,
|
|
74
|
+
label: this.generateUrlLabel('', type as any),
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Generic URL mentions: <url href="..." />
|
|
79
|
+
const urlRegex = /<url\s+href="([^"]+)"\s*\/>/g;
|
|
80
|
+
while ((match = urlRegex.exec(description)) !== null) {
|
|
81
|
+
const [, url] = match;
|
|
82
|
+
mentions.push({
|
|
83
|
+
url,
|
|
84
|
+
type: 'generic',
|
|
85
|
+
label: this.generateUrlLabel(url, 'generic'),
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return mentions;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Generate a display label for a URL mention
|
|
94
|
+
*/
|
|
95
|
+
private generateUrlLabel(url: string, type: string): string {
|
|
96
|
+
try {
|
|
97
|
+
const urlObj = new URL(url);
|
|
98
|
+
switch (type) {
|
|
99
|
+
case 'error':
|
|
100
|
+
const errorMatch = url.match(/error_tracking\/([a-f0-9-]+)/);
|
|
101
|
+
return errorMatch ? `Error ${errorMatch[1].slice(0, 8)}...` : 'Error';
|
|
102
|
+
case 'experiment':
|
|
103
|
+
const expMatch = url.match(/experiments\/(\d+)/);
|
|
104
|
+
return expMatch ? `Experiment #${expMatch[1]}` : 'Experiment';
|
|
105
|
+
case 'insight':
|
|
106
|
+
return 'Insight';
|
|
107
|
+
case 'feature_flag':
|
|
108
|
+
return 'Feature Flag';
|
|
109
|
+
default:
|
|
110
|
+
return urlObj.hostname;
|
|
111
|
+
}
|
|
112
|
+
} catch {
|
|
113
|
+
return 'URL';
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Process URL references and fetch their content
|
|
119
|
+
*/
|
|
120
|
+
private async processUrlReferences(
|
|
121
|
+
description: string
|
|
122
|
+
): Promise<{ description: string; referencedResources: PostHogResource[] }> {
|
|
123
|
+
const urlMentions = this.extractUrlMentions(description);
|
|
124
|
+
const referencedResources: PostHogResource[] = [];
|
|
125
|
+
|
|
126
|
+
if (urlMentions.length === 0 || !this.posthogClient) {
|
|
127
|
+
return { description, referencedResources };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Fetch all referenced resources
|
|
131
|
+
for (const mention of urlMentions) {
|
|
132
|
+
try {
|
|
133
|
+
const resource = await this.posthogClient.fetchResourceByUrl(mention);
|
|
134
|
+
referencedResources.push(resource);
|
|
135
|
+
} catch (error) {
|
|
136
|
+
this.logger.warn(`Failed to fetch resource from URL: ${mention.url}`, { error });
|
|
137
|
+
// Add a placeholder resource for failed fetches
|
|
138
|
+
referencedResources.push({
|
|
139
|
+
type: mention.type,
|
|
140
|
+
id: mention.id || '',
|
|
141
|
+
url: mention.url,
|
|
142
|
+
title: mention.label || 'Unknown Resource',
|
|
143
|
+
content: `Failed to fetch resource from ${mention.url}: ${error}`,
|
|
144
|
+
metadata: {},
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Replace URL tags with just the label for readability
|
|
150
|
+
let processedDescription = description;
|
|
151
|
+
for (const mention of urlMentions) {
|
|
152
|
+
if (mention.type === 'generic') {
|
|
153
|
+
// Generic URLs: <url href="..." />
|
|
154
|
+
const escapedUrl = mention.url.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
155
|
+
processedDescription = processedDescription.replace(
|
|
156
|
+
new RegExp(`<url\\s+href="${escapedUrl}"\\s*/>`, 'g'),
|
|
157
|
+
`@${mention.label}`
|
|
158
|
+
);
|
|
159
|
+
} else {
|
|
160
|
+
// PostHog resources: <error id="..." />, <experiment id="..." />, etc.
|
|
161
|
+
const escapedType = mention.type.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
162
|
+
const escapedId = mention.id ? mention.id.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') : '';
|
|
163
|
+
processedDescription = processedDescription.replace(
|
|
164
|
+
new RegExp(`<${escapedType}\\s+id="${escapedId}"\\s*/>`, 'g'),
|
|
165
|
+
`@${mention.label}`
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return { description: processedDescription, referencedResources };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Process description to extract file tags and read contents
|
|
175
|
+
* Returns processed description and referenced file contents
|
|
176
|
+
*/
|
|
177
|
+
private async processFileReferences(
|
|
178
|
+
description: string,
|
|
179
|
+
repositoryPath?: string
|
|
180
|
+
): Promise<{ description: string; referencedFiles: Array<{ path: string; content: string }> }> {
|
|
181
|
+
const filePaths = this.extractFilePaths(description);
|
|
182
|
+
const referencedFiles: Array<{ path: string; content: string }> = [];
|
|
183
|
+
|
|
184
|
+
if (filePaths.length === 0 || !repositoryPath) {
|
|
185
|
+
return { description, referencedFiles };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Read all referenced files
|
|
189
|
+
for (const filePath of filePaths) {
|
|
190
|
+
const content = await this.readFileContent(repositoryPath, filePath);
|
|
191
|
+
if (content !== null) {
|
|
192
|
+
referencedFiles.push({ path: filePath, content });
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Replace file tags with just the filename for readability
|
|
197
|
+
let processedDescription = description;
|
|
198
|
+
for (const filePath of filePaths) {
|
|
199
|
+
const fileName = filePath.split('/').pop() || filePath;
|
|
200
|
+
processedDescription = processedDescription.replace(
|
|
201
|
+
new RegExp(`<file\\s+path="${filePath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}"\\s*/>`, 'g'),
|
|
202
|
+
`@${fileName}`
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return { description: processedDescription, referencedFiles };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async buildPlanningPrompt(task: Task, repositoryPath?: string): Promise<string> {
|
|
210
|
+
// Process file references in description
|
|
211
|
+
const { description: descriptionAfterFiles, referencedFiles } = await this.processFileReferences(
|
|
212
|
+
task.description,
|
|
213
|
+
repositoryPath
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
// Process URL references in description
|
|
217
|
+
const { description: processedDescription, referencedResources } = await this.processUrlReferences(
|
|
218
|
+
descriptionAfterFiles
|
|
219
|
+
);
|
|
220
|
+
|
|
23
221
|
let prompt = '';
|
|
24
|
-
prompt += `## Current Task\n\n**Task**: ${task.title}\n**Description**: ${
|
|
222
|
+
prompt += `## Current Task\n\n**Task**: ${task.title}\n**Description**: ${processedDescription}`;
|
|
25
223
|
|
|
26
224
|
if ((task as any).primary_repository) {
|
|
27
225
|
prompt += `\n**Repository**: ${(task as any).primary_repository}`;
|
|
28
226
|
}
|
|
29
227
|
|
|
228
|
+
// Add referenced files from @ mentions
|
|
229
|
+
if (referencedFiles.length > 0) {
|
|
230
|
+
prompt += `\n\n## Referenced Files\n\n`;
|
|
231
|
+
for (const file of referencedFiles) {
|
|
232
|
+
prompt += `### ${file.path}\n\`\`\`\n${file.content}\n\`\`\`\n\n`;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Add referenced resources from URL mentions
|
|
237
|
+
if (referencedResources.length > 0) {
|
|
238
|
+
prompt += `\n\n## Referenced Resources\n\n`;
|
|
239
|
+
for (const resource of referencedResources) {
|
|
240
|
+
prompt += `### ${resource.title} (${resource.type})\n**URL**: ${resource.url}\n\n${resource.content}\n\n`;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
30
244
|
try {
|
|
31
245
|
const taskFiles = await this.getTaskFiles(task.id);
|
|
32
246
|
const contextFiles = taskFiles.filter((f: any) => f.type === 'context' || f.type === 'reference');
|
|
@@ -43,7 +257,7 @@ export class PromptBuilder {
|
|
|
43
257
|
const templateVariables = {
|
|
44
258
|
task_id: task.id,
|
|
45
259
|
task_title: task.title,
|
|
46
|
-
task_description:
|
|
260
|
+
task_description: processedDescription,
|
|
47
261
|
date: new Date().toISOString().split('T')[0],
|
|
48
262
|
repository: ((task as any).primary_repository || '') as string,
|
|
49
263
|
};
|
|
@@ -55,14 +269,41 @@ export class PromptBuilder {
|
|
|
55
269
|
return prompt;
|
|
56
270
|
}
|
|
57
271
|
|
|
58
|
-
async buildExecutionPrompt(task: Task): Promise<string> {
|
|
272
|
+
async buildExecutionPrompt(task: Task, repositoryPath?: string): Promise<string> {
|
|
273
|
+
// Process file references in description
|
|
274
|
+
const { description: descriptionAfterFiles, referencedFiles } = await this.processFileReferences(
|
|
275
|
+
task.description,
|
|
276
|
+
repositoryPath
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
// Process URL references in description
|
|
280
|
+
const { description: processedDescription, referencedResources } = await this.processUrlReferences(
|
|
281
|
+
descriptionAfterFiles
|
|
282
|
+
);
|
|
283
|
+
|
|
59
284
|
let prompt = '';
|
|
60
|
-
prompt += `## Current Task\n\n**Task**: ${task.title}\n**Description**: ${
|
|
285
|
+
prompt += `## Current Task\n\n**Task**: ${task.title}\n**Description**: ${processedDescription}`;
|
|
61
286
|
|
|
62
287
|
if ((task as any).primary_repository) {
|
|
63
288
|
prompt += `\n**Repository**: ${(task as any).primary_repository}`;
|
|
64
289
|
}
|
|
65
290
|
|
|
291
|
+
// Add referenced files from @ mentions
|
|
292
|
+
if (referencedFiles.length > 0) {
|
|
293
|
+
prompt += `\n\n## Referenced Files\n\n`;
|
|
294
|
+
for (const file of referencedFiles) {
|
|
295
|
+
prompt += `### ${file.path}\n\`\`\`\n${file.content}\n\`\`\`\n\n`;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Add referenced resources from URL mentions
|
|
300
|
+
if (referencedResources.length > 0) {
|
|
301
|
+
prompt += `\n\n## Referenced Resources\n\n`;
|
|
302
|
+
for (const resource of referencedResources) {
|
|
303
|
+
prompt += `### ${resource.title} (${resource.type})\n**URL**: ${resource.url}\n\n${resource.content}\n\n`;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
66
307
|
try {
|
|
67
308
|
const taskFiles = await this.getTaskFiles(task.id);
|
|
68
309
|
const hasPlan = taskFiles.some((f: any) => f.type === 'plan');
|
package/src/stage-executor.ts
CHANGED
|
@@ -71,7 +71,7 @@ export class StageExecutor {
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
private async runPlanning(task: Task, cwd: string, options: WorkflowExecutionOptions, stageKey: string): Promise<WorkflowStageExecutionResult> {
|
|
74
|
-
const contextPrompt = await this.promptBuilder.buildPlanningPrompt(task);
|
|
74
|
+
const contextPrompt = await this.promptBuilder.buildPlanningPrompt(task, cwd);
|
|
75
75
|
let prompt = PLANNING_SYSTEM_PROMPT + '\n\n' + contextPrompt;
|
|
76
76
|
|
|
77
77
|
const stageOverrides = options.stageOverrides?.[stageKey] || options.stageOverrides?.['plan'];
|
|
@@ -118,7 +118,7 @@ export class StageExecutor {
|
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
private async runExecution(task: Task, cwd: string, permissionMode: WorkflowExecutionOptions['permissionMode'], options: WorkflowExecutionOptions, stageKey: string): Promise<WorkflowStageExecutionResult> {
|
|
121
|
-
const contextPrompt = await this.promptBuilder.buildExecutionPrompt(task);
|
|
121
|
+
const contextPrompt = await this.promptBuilder.buildExecutionPrompt(task, cwd);
|
|
122
122
|
let prompt = EXECUTION_SYSTEM_PROMPT + '\n\n' + contextPrompt;
|
|
123
123
|
|
|
124
124
|
const stageOverrides = options.stageOverrides?.[stageKey];
|
package/src/types.ts
CHANGED
|
@@ -256,4 +256,23 @@ export interface AgentConfig {
|
|
|
256
256
|
export interface PostHogAPIConfig {
|
|
257
257
|
apiUrl: string;
|
|
258
258
|
apiKey: string;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// URL mention types
|
|
262
|
+
export type ResourceType = 'error' | 'experiment' | 'insight' | 'feature_flag' | 'generic';
|
|
263
|
+
|
|
264
|
+
export interface PostHogResource {
|
|
265
|
+
type: ResourceType;
|
|
266
|
+
id: string;
|
|
267
|
+
url: string;
|
|
268
|
+
title?: string;
|
|
269
|
+
content: string;
|
|
270
|
+
metadata?: Record<string, any>;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export interface UrlMention {
|
|
274
|
+
url: string;
|
|
275
|
+
type: ResourceType;
|
|
276
|
+
id?: string;
|
|
277
|
+
label?: string;
|
|
259
278
|
}
|