@posthog/agent 1.10.0 → 1.12.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/README.md +26 -65
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/src/adapters/types.d.ts +1 -1
- package/dist/src/agent.d.ts +5 -13
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +300 -187
- package/dist/src/agent.js.map +1 -1
- package/dist/src/agents/execution.d.ts +1 -1
- package/dist/src/agents/execution.js +2 -2
- package/dist/src/agents/execution.js.map +1 -1
- package/dist/src/agents/research.d.ts +2 -0
- package/dist/src/agents/research.d.ts.map +1 -0
- package/dist/src/agents/research.js +105 -0
- package/dist/src/agents/research.js.map +1 -0
- package/dist/src/file-manager.d.ts +19 -0
- package/dist/src/file-manager.d.ts.map +1 -1
- package/dist/src/file-manager.js +39 -0
- package/dist/src/file-manager.js.map +1 -1
- package/dist/src/git-manager.d.ts +4 -0
- package/dist/src/git-manager.d.ts.map +1 -1
- package/dist/src/git-manager.js +42 -1
- package/dist/src/git-manager.js.map +1 -1
- package/dist/src/posthog-api.d.ts +0 -8
- package/dist/src/posthog-api.d.ts.map +1 -1
- package/dist/src/posthog-api.js +0 -32
- package/dist/src/posthog-api.js.map +1 -1
- package/dist/src/prompt-builder.d.ts +1 -0
- package/dist/src/prompt-builder.d.ts.map +1 -1
- package/dist/src/prompt-builder.js +40 -0
- package/dist/src/prompt-builder.js.map +1 -1
- package/dist/src/structured-extraction.d.ts +22 -0
- package/dist/src/structured-extraction.d.ts.map +1 -0
- package/dist/src/structured-extraction.js +136 -0
- package/dist/src/structured-extraction.js.map +1 -0
- package/dist/src/task-progress-reporter.d.ts +0 -6
- package/dist/src/task-progress-reporter.d.ts.map +1 -1
- package/dist/src/task-progress-reporter.js +2 -26
- package/dist/src/task-progress-reporter.js.map +1 -1
- package/dist/src/template-manager.d.ts.map +1 -1
- package/dist/src/template-manager.js +26 -4
- package/dist/src/template-manager.js.map +1 -1
- package/dist/src/types.d.ts +7 -4
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/types.js +0 -1
- package/dist/src/types.js.map +1 -1
- package/package.json +5 -5
- package/src/adapters/types.ts +1 -1
- package/src/agent.ts +326 -195
- package/src/agents/execution.ts +2 -2
- package/src/agents/research.ts +103 -0
- package/src/file-manager.ts +64 -0
- package/src/git-manager.ts +53 -1
- package/src/posthog-api.ts +0 -40
- package/src/prompt-builder.ts +53 -0
- package/src/structured-extraction.ts +167 -0
- package/src/task-progress-reporter.ts +2 -34
- package/src/template-manager.ts +35 -5
- package/src/types.ts +8 -7
- package/dist/src/agent-registry.d.ts +0 -16
- package/dist/src/agent-registry.d.ts.map +0 -1
- package/dist/src/agent-registry.js +0 -56
- package/dist/src/agent-registry.js.map +0 -1
- package/dist/src/stage-executor.d.ts +0 -19
- package/dist/src/stage-executor.d.ts.map +0 -1
- package/dist/src/stage-executor.js +0 -135
- package/dist/src/stage-executor.js.map +0 -1
- package/dist/src/workflow-registry.d.ts +0 -11
- package/dist/src/workflow-registry.d.ts.map +0 -1
- package/dist/src/workflow-registry.js +0 -27
- package/dist/src/workflow-registry.js.map +0 -1
- package/dist/src/workflow-types.d.ts +0 -45
- package/dist/src/workflow-types.d.ts.map +0 -1
- package/src/agent-registry.ts +0 -59
- package/src/stage-executor.ts +0 -160
- package/src/workflow-registry.ts +0 -30
- package/src/workflow-types.ts +0 -52
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
export const RESEARCH_SYSTEM_PROMPT = `# PostHog AI Coding Agent - Research Mode
|
|
2
|
+
|
|
3
|
+
You are a PostHog AI Coding Agent operating in RESEARCH mode.
|
|
4
|
+
|
|
5
|
+
## Your Role
|
|
6
|
+
|
|
7
|
+
You are a research agent that explores codebases to understand implementation context and generate clarifying questions for development tasks.
|
|
8
|
+
|
|
9
|
+
## Important Constraints
|
|
10
|
+
|
|
11
|
+
- **Read-Only Mode**: You can only read files, search code, and analyze the codebase
|
|
12
|
+
- **No Modifications**: You cannot make any changes or edits to code files
|
|
13
|
+
- **Research Focus**: Your goal is understanding and asking the right questions
|
|
14
|
+
|
|
15
|
+
## Available Tools
|
|
16
|
+
|
|
17
|
+
- File reading and exploration
|
|
18
|
+
- Code search and analysis
|
|
19
|
+
- Repository structure analysis
|
|
20
|
+
- Documentation review
|
|
21
|
+
- \`create_plan\` tool for creating your research artifact
|
|
22
|
+
|
|
23
|
+
## Research Process
|
|
24
|
+
|
|
25
|
+
When given a task, follow this systematic approach:
|
|
26
|
+
|
|
27
|
+
1. **Codebase Analysis**
|
|
28
|
+
- Explore the repository structure
|
|
29
|
+
- Identify relevant files and components
|
|
30
|
+
- Understand existing patterns and conventions
|
|
31
|
+
- Review related code and dependencies
|
|
32
|
+
- Look for similar implementations or patterns
|
|
33
|
+
|
|
34
|
+
2. **Decision Point Identification**
|
|
35
|
+
- Identify areas where implementation decisions need to be made
|
|
36
|
+
- Find multiple viable approaches in the codebase
|
|
37
|
+
- Note where user preferences would affect the implementation
|
|
38
|
+
- Consider architectural or design pattern choices
|
|
39
|
+
|
|
40
|
+
3. **Question Generation**
|
|
41
|
+
- Generate 3-5 clarifying questions
|
|
42
|
+
- Each question should offer 2-3 concrete options based on codebase analysis
|
|
43
|
+
- Options should reference actual patterns/approaches found in the code
|
|
44
|
+
- Always include option c) as "Something else (please specify)" for flexibility
|
|
45
|
+
- Focus on high-impact decisions that affect the implementation approach
|
|
46
|
+
|
|
47
|
+
## Output Format
|
|
48
|
+
|
|
49
|
+
After completing your research, you MUST use the \`create_plan\` tool to create a research.md artifact with your questions.
|
|
50
|
+
|
|
51
|
+
The artifact MUST follow this EXACT markdown format (this is critical for parsing):
|
|
52
|
+
|
|
53
|
+
\`\`\`markdown
|
|
54
|
+
# Research Questions
|
|
55
|
+
|
|
56
|
+
Based on my analysis of the codebase, here are the key questions to guide implementation:
|
|
57
|
+
|
|
58
|
+
## Question 1: [Question text - be specific and clear]
|
|
59
|
+
|
|
60
|
+
**Options:**
|
|
61
|
+
- a) [Concrete option based on existing pattern - reference specific files/components]
|
|
62
|
+
- b) [Alternative approach based on another pattern - reference specific files/components]
|
|
63
|
+
- c) Something else (please specify)
|
|
64
|
+
|
|
65
|
+
## Question 2: [Next question - be specific and clear]
|
|
66
|
+
|
|
67
|
+
**Options:**
|
|
68
|
+
- a) [Option with specific code references]
|
|
69
|
+
- b) [Alternative with specific code references]
|
|
70
|
+
- c) Something else (please specify)
|
|
71
|
+
|
|
72
|
+
## Question 3: [Continue with 3-5 questions total]
|
|
73
|
+
|
|
74
|
+
**Options:**
|
|
75
|
+
- a) [Option]
|
|
76
|
+
- b) [Alternative]
|
|
77
|
+
- c) Something else (please specify)
|
|
78
|
+
\`\`\`
|
|
79
|
+
|
|
80
|
+
## CRITICAL FORMAT REQUIREMENTS
|
|
81
|
+
|
|
82
|
+
- Use EXACTLY "## Question N:" format for question headers (h2 level, not h3)
|
|
83
|
+
- Each question MUST be followed by "**Options:**" on its own line
|
|
84
|
+
- Each option MUST start with "- a)", "- b)", "- c)", etc.
|
|
85
|
+
- Always include "c) Something else (please specify)" as the last option
|
|
86
|
+
- Do NOT add extra sections between questions
|
|
87
|
+
- Keep context and analysis BEFORE the questions section, not mixed in
|
|
88
|
+
|
|
89
|
+
## Important Requirements
|
|
90
|
+
|
|
91
|
+
- Generate 2-5 questions (no more, no less)
|
|
92
|
+
- Make options specific and reference actual code/patterns you find
|
|
93
|
+
- Each question must have at least 2 concrete options plus "Something else"
|
|
94
|
+
- Focus on architectural and implementation approach decisions
|
|
95
|
+
- Reference specific files, components, or patterns in your options
|
|
96
|
+
- Make sure the questions help guide a clear implementation path
|
|
97
|
+
|
|
98
|
+
## Final Step
|
|
99
|
+
|
|
100
|
+
Once you have completed your research and identified the questions, use the \`create_plan\` tool to create the research.md artifact with the markdown content above. Do NOT use any other tools after creating the artifact.
|
|
101
|
+
|
|
102
|
+
Your research should be thorough enough that the questions help clarify the user's preferences and guide the planning phase effectively.`;
|
|
103
|
+
|
package/src/file-manager.ts
CHANGED
|
@@ -9,6 +9,24 @@ export interface TaskFile {
|
|
|
9
9
|
type: 'plan' | 'context' | 'reference' | 'output' | 'artifact';
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
export interface QuestionData {
|
|
13
|
+
id: string;
|
|
14
|
+
question: string;
|
|
15
|
+
options: string[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface AnswerData {
|
|
19
|
+
questionId: string;
|
|
20
|
+
selectedOption: string;
|
|
21
|
+
customInput?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface QuestionsFile {
|
|
25
|
+
questions: QuestionData[];
|
|
26
|
+
answered: boolean;
|
|
27
|
+
answers: AnswerData[] | null;
|
|
28
|
+
}
|
|
29
|
+
|
|
12
30
|
export class PostHogFileManager {
|
|
13
31
|
private repositoryPath: string;
|
|
14
32
|
private logger: Logger;
|
|
@@ -152,6 +170,52 @@ export class PostHogFileManager {
|
|
|
152
170
|
return await this.readTaskFile(taskId, 'requirements.md');
|
|
153
171
|
}
|
|
154
172
|
|
|
173
|
+
async writeResearch(taskId: string, content: string): Promise<void> {
|
|
174
|
+
this.logger.debug('Writing research', {
|
|
175
|
+
taskId,
|
|
176
|
+
contentLength: content.length,
|
|
177
|
+
contentPreview: content.substring(0, 200)
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
await this.writeTaskFile(taskId, {
|
|
181
|
+
name: 'research.md',
|
|
182
|
+
content: content,
|
|
183
|
+
type: 'artifact'
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
this.logger.info('Research file written', { taskId });
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async readResearch(taskId: string): Promise<string | null> {
|
|
190
|
+
return await this.readTaskFile(taskId, 'research.md');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async writeQuestions(taskId: string, data: QuestionsFile): Promise<void> {
|
|
194
|
+
this.logger.debug('Writing questions', {
|
|
195
|
+
taskId,
|
|
196
|
+
questionCount: data.questions.length,
|
|
197
|
+
answered: data.answered,
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
await this.writeTaskFile(taskId, {
|
|
201
|
+
name: 'questions.json',
|
|
202
|
+
content: JSON.stringify(data, null, 2),
|
|
203
|
+
type: 'artifact'
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
this.logger.info('Questions file written', { taskId });
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async readQuestions(taskId: string): Promise<QuestionsFile | null> {
|
|
210
|
+
try {
|
|
211
|
+
const content = await this.readTaskFile(taskId, 'questions.json');
|
|
212
|
+
return content ? JSON.parse(content) as QuestionsFile : null;
|
|
213
|
+
} catch (error) {
|
|
214
|
+
this.logger.debug('Failed to parse questions.json', { error });
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
155
219
|
async getTaskFiles(taskId: string): Promise<SupportingFile[]> {
|
|
156
220
|
const fileNames = await this.listTaskFiles(taskId);
|
|
157
221
|
const files: SupportingFile[] = [];
|
package/src/git-manager.ts
CHANGED
|
@@ -161,7 +161,7 @@ export class GitManager {
|
|
|
161
161
|
await this.runGitCommand(`push ${forceFlag} -u origin ${branchName}`);
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
-
// Utility methods for PostHog task
|
|
164
|
+
// Utility methods for PostHog task execution
|
|
165
165
|
async createTaskPlanningBranch(taskId: string, baseBranch?: string): Promise<string> {
|
|
166
166
|
let branchName = `posthog/task-${taskId}-planning`;
|
|
167
167
|
let counter = 1;
|
|
@@ -341,4 +341,56 @@ Generated by PostHog Agent`;
|
|
|
341
341
|
throw new Error(`Failed to create PR: ${error}`);
|
|
342
342
|
}
|
|
343
343
|
}
|
|
344
|
+
|
|
345
|
+
async getTaskBranch(taskSlug: string): Promise<string | null> {
|
|
346
|
+
try {
|
|
347
|
+
// Get all branches matching the task slug pattern
|
|
348
|
+
const branches = await this.runGitCommand('branch --list --all');
|
|
349
|
+
const branchPattern = `posthog/task-${taskSlug}`;
|
|
350
|
+
|
|
351
|
+
// Look for exact match or with counter suffix
|
|
352
|
+
const lines = branches.split('\n').map(l => l.trim().replace(/^\*\s+/, ''));
|
|
353
|
+
for (const line of lines) {
|
|
354
|
+
const cleanBranch = line.replace('remotes/origin/', '');
|
|
355
|
+
if (cleanBranch.startsWith(branchPattern)) {
|
|
356
|
+
return cleanBranch;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return null;
|
|
361
|
+
} catch (error) {
|
|
362
|
+
this.logger.debug('Failed to get task branch', { taskSlug, error });
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
async commitAndPush(message: string, options?: { allowEmpty?: boolean }): Promise<void> {
|
|
368
|
+
const hasChanges = await this.hasStagedChanges();
|
|
369
|
+
|
|
370
|
+
if (!hasChanges && !options?.allowEmpty) {
|
|
371
|
+
this.logger.debug('No changes to commit, skipping');
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
let command = `commit -m "${message.replace(/"/g, '\\"')}"`;
|
|
376
|
+
|
|
377
|
+
if (options?.allowEmpty) {
|
|
378
|
+
command += ' --allow-empty';
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const authorName = this.authorName;
|
|
382
|
+
const authorEmail = this.authorEmail;
|
|
383
|
+
|
|
384
|
+
if (authorName && authorEmail) {
|
|
385
|
+
command += ` --author="${authorName} <${authorEmail}>"`;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
await this.runGitCommand(command);
|
|
389
|
+
|
|
390
|
+
// Push to origin
|
|
391
|
+
const currentBranch = await this.getCurrentBranch();
|
|
392
|
+
await this.pushBranch(currentBranch);
|
|
393
|
+
|
|
394
|
+
this.logger.info('Committed and pushed changes', { branch: currentBranch, message });
|
|
395
|
+
}
|
|
344
396
|
}
|
package/src/posthog-api.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { Task, TaskRun, LogEntry, SupportingFile, PostHogAPIConfig, PostHogResource, ResourceType, UrlMention } from './types.js';
|
|
2
|
-
import type { WorkflowDefinition, AgentDefinition } from './workflow-types.js';
|
|
3
2
|
|
|
4
3
|
interface PostHogApiResponse<T> {
|
|
5
4
|
results?: T[];
|
|
@@ -107,8 +106,6 @@ export class PostHogAPIClient {
|
|
|
107
106
|
repository?: string;
|
|
108
107
|
organization?: string;
|
|
109
108
|
origin_product?: string;
|
|
110
|
-
workflow?: string;
|
|
111
|
-
current_stage?: string;
|
|
112
109
|
}): Promise<Task[]> {
|
|
113
110
|
const teamId = await this.getTeamId();
|
|
114
111
|
const url = new URL(`${this.baseUrl}/api/projects/${teamId}/tasks/`);
|
|
@@ -171,26 +168,6 @@ export class PostHogAPIClient {
|
|
|
171
168
|
});
|
|
172
169
|
}
|
|
173
170
|
|
|
174
|
-
async updateTaskRunStage(taskId: string, runId: string, stageId: string): Promise<TaskRun> {
|
|
175
|
-
const teamId = await this.getTeamId();
|
|
176
|
-
return this.apiRequest<TaskRun>(`/api/projects/${teamId}/tasks/${taskId}/runs/${runId}/update_stage/`, {
|
|
177
|
-
method: 'PATCH',
|
|
178
|
-
body: JSON.stringify({ current_stage: stageId }),
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
async progressTaskRun(taskId: string, runId: string, nextStageId?: string): Promise<TaskRun> {
|
|
183
|
-
const teamId = await this.getTeamId();
|
|
184
|
-
const payload: Record<string, string> = {};
|
|
185
|
-
if (nextStageId) {
|
|
186
|
-
payload.next_stage_id = nextStageId;
|
|
187
|
-
}
|
|
188
|
-
return this.apiRequest<TaskRun>(`/api/projects/${teamId}/tasks/${taskId}/runs/${runId}/progress_run/`, {
|
|
189
|
-
method: 'POST',
|
|
190
|
-
body: JSON.stringify(payload),
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
|
|
194
171
|
async setTaskRunOutput(taskId: string, runId: string, output: Record<string, unknown>): Promise<TaskRun> {
|
|
195
172
|
const teamId = await this.getTeamId();
|
|
196
173
|
return this.apiRequest<TaskRun>(`/api/projects/${teamId}/tasks/${taskId}/runs/${runId}/set_output/`, {
|
|
@@ -207,23 +184,6 @@ export class PostHogAPIClient {
|
|
|
207
184
|
});
|
|
208
185
|
}
|
|
209
186
|
|
|
210
|
-
// Workflow endpoints
|
|
211
|
-
async fetchWorkflow(workflowId: string): Promise<WorkflowDefinition> {
|
|
212
|
-
const teamId = await this.getTeamId();
|
|
213
|
-
return this.apiRequest<WorkflowDefinition>(`/api/projects/${teamId}/workflows/${workflowId}/`);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
async listWorkflows(): Promise<WorkflowDefinition[]> {
|
|
217
|
-
const teamId = await this.getTeamId();
|
|
218
|
-
const response = await this.apiRequest<PostHogApiResponse<WorkflowDefinition>>(`/api/projects/${teamId}/workflows/`);
|
|
219
|
-
return response.results || [];
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// Agent catalog exposure
|
|
223
|
-
async listAgents(): Promise<AgentDefinition[]> {
|
|
224
|
-
return this.apiRequest<AgentDefinition[]>(`/api/agents/`);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
187
|
/**
|
|
228
188
|
* Fetch error details from PostHog error tracking
|
|
229
189
|
*/
|
package/src/prompt-builder.ts
CHANGED
|
@@ -206,6 +206,59 @@ export class PromptBuilder {
|
|
|
206
206
|
return { description: processedDescription, referencedFiles };
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
+
async buildResearchPrompt(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
|
+
|
|
221
|
+
let prompt = '';
|
|
222
|
+
prompt += `## Current Task\n\n**Task**: ${task.title}\n**Description**: ${processedDescription}`;
|
|
223
|
+
|
|
224
|
+
if ((task as any).primary_repository) {
|
|
225
|
+
prompt += `\n**Repository**: ${(task as any).primary_repository}`;
|
|
226
|
+
}
|
|
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
|
+
|
|
244
|
+
try {
|
|
245
|
+
const taskFiles = await this.getTaskFiles(task.id);
|
|
246
|
+
const contextFiles = taskFiles.filter((f: any) => f.type === 'context' || f.type === 'reference');
|
|
247
|
+
if (contextFiles.length > 0) {
|
|
248
|
+
prompt += `\n\n## Supporting Files`;
|
|
249
|
+
for (const file of contextFiles) {
|
|
250
|
+
prompt += `\n\n### ${file.name} (${file.type})\n${file.content}`;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
} catch (error) {
|
|
254
|
+
this.logger.debug('No existing task files found for research', { taskId: task.id });
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
prompt += `\n\nPlease explore the codebase thoroughly and generate 3-5 clarifying questions that will help guide the implementation of this task. Use the \`create_plan\` tool to create a research.md artifact with your questions in the markdown format specified in your system prompt.`;
|
|
258
|
+
|
|
259
|
+
return prompt;
|
|
260
|
+
}
|
|
261
|
+
|
|
209
262
|
async buildPlanningPrompt(task: Task, repositoryPath?: string): Promise<string> {
|
|
210
263
|
// Process file references in description
|
|
211
264
|
const { description: descriptionAfterFiles, referencedFiles } = await this.processFileReferences(
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import OpenAI from 'openai';
|
|
2
|
+
import { Logger } from './utils/logger.js';
|
|
3
|
+
|
|
4
|
+
export interface ExtractedQuestion {
|
|
5
|
+
id: string;
|
|
6
|
+
question: string;
|
|
7
|
+
options: string[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface ExtractedQuestionWithAnswer extends ExtractedQuestion {
|
|
11
|
+
recommendedAnswer: string;
|
|
12
|
+
justification: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const questionsOnlySchema = {
|
|
16
|
+
type: 'object',
|
|
17
|
+
properties: {
|
|
18
|
+
questions: {
|
|
19
|
+
type: 'array',
|
|
20
|
+
items: {
|
|
21
|
+
type: 'object',
|
|
22
|
+
properties: {
|
|
23
|
+
id: { type: 'string' },
|
|
24
|
+
question: { type: 'string' },
|
|
25
|
+
options: {
|
|
26
|
+
type: 'array',
|
|
27
|
+
items: { type: 'string' }
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
required: ['id', 'question', 'options'],
|
|
31
|
+
additionalProperties: false
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
required: ['questions'],
|
|
36
|
+
additionalProperties: false
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const questionsWithAnswersSchema = {
|
|
40
|
+
type: 'object',
|
|
41
|
+
properties: {
|
|
42
|
+
questions: {
|
|
43
|
+
type: 'array',
|
|
44
|
+
items: {
|
|
45
|
+
type: 'object',
|
|
46
|
+
properties: {
|
|
47
|
+
id: { type: 'string' },
|
|
48
|
+
question: { type: 'string' },
|
|
49
|
+
options: {
|
|
50
|
+
type: 'array',
|
|
51
|
+
items: { type: 'string' }
|
|
52
|
+
},
|
|
53
|
+
recommendedAnswer: { type: 'string' },
|
|
54
|
+
justification: { type: 'string' }
|
|
55
|
+
},
|
|
56
|
+
required: ['id', 'question', 'options', 'recommendedAnswer', 'justification'],
|
|
57
|
+
additionalProperties: false
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
required: ['questions'],
|
|
62
|
+
additionalProperties: false
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export interface StructuredExtractor {
|
|
66
|
+
extractQuestions(researchContent: string): Promise<ExtractedQuestion[]>;
|
|
67
|
+
extractQuestionsWithAnswers(researchContent: string): Promise<ExtractedQuestionWithAnswer[]>;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export class OpenAIExtractor implements StructuredExtractor {
|
|
71
|
+
private client: OpenAI;
|
|
72
|
+
private logger: Logger;
|
|
73
|
+
|
|
74
|
+
constructor(logger?: Logger) {
|
|
75
|
+
const apiKey = process.env.OPENAI_API_KEY;
|
|
76
|
+
if (!apiKey) {
|
|
77
|
+
throw new Error('OPENAI_API_KEY environment variable is required for structured extraction');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
this.client = new OpenAI({ apiKey });
|
|
81
|
+
this.logger = logger || new Logger({ debug: false, prefix: '[OpenAIExtractor]' });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async extractQuestions(researchContent: string): Promise<ExtractedQuestion[]> {
|
|
85
|
+
this.logger.debug('Extracting questions from research content', {
|
|
86
|
+
contentLength: researchContent.length,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const completion = await this.client.chat.completions.create({
|
|
90
|
+
model: 'gpt-4o-mini',
|
|
91
|
+
messages: [
|
|
92
|
+
{
|
|
93
|
+
role: 'system',
|
|
94
|
+
content: 'Extract the research questions from the provided markdown. Return a JSON object matching the schema.',
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
role: 'user',
|
|
98
|
+
content: researchContent,
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
response_format: {
|
|
102
|
+
type: 'json_schema',
|
|
103
|
+
json_schema: {
|
|
104
|
+
name: 'questions',
|
|
105
|
+
strict: true,
|
|
106
|
+
schema: questionsOnlySchema,
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const content = completion.choices[0].message.content;
|
|
112
|
+
if (!content) {
|
|
113
|
+
throw new Error('No content in OpenAI response');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const parsed = JSON.parse(content) as { questions: ExtractedQuestion[] };
|
|
117
|
+
|
|
118
|
+
this.logger.info('Successfully extracted questions', {
|
|
119
|
+
questionCount: parsed.questions.length,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
return parsed.questions;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async extractQuestionsWithAnswers(
|
|
126
|
+
researchContent: string,
|
|
127
|
+
): Promise<ExtractedQuestionWithAnswer[]> {
|
|
128
|
+
this.logger.debug('Extracting questions with recommended answers', {
|
|
129
|
+
contentLength: researchContent.length,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const completion = await this.client.chat.completions.create({
|
|
133
|
+
model: 'gpt-4o-mini',
|
|
134
|
+
messages: [
|
|
135
|
+
{
|
|
136
|
+
role: 'system',
|
|
137
|
+
content: 'Extract the research questions from the markdown and provide recommended answers based on the analysis. For each question, include a recommendedAnswer (the letter: a, b, c, etc.) and a brief justification. Return a JSON object matching the schema.',
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
role: 'user',
|
|
141
|
+
content: researchContent,
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
response_format: {
|
|
145
|
+
type: 'json_schema',
|
|
146
|
+
json_schema: {
|
|
147
|
+
name: 'questions_with_answers',
|
|
148
|
+
strict: true,
|
|
149
|
+
schema: questionsWithAnswersSchema,
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const content = completion.choices[0].message.content;
|
|
155
|
+
if (!content) {
|
|
156
|
+
throw new Error('No content in OpenAI response');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const parsed = JSON.parse(content) as { questions: ExtractedQuestionWithAnswer[] };
|
|
160
|
+
|
|
161
|
+
this.logger.info('Successfully extracted questions with answers', {
|
|
162
|
+
questionCount: parsed.questions.length,
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
return parsed.questions;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
@@ -51,45 +51,13 @@ export class TaskProgressReporter {
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
async stageStarted(stageKey: string, stageIndex: number): Promise<void> {
|
|
55
|
-
await this.update({
|
|
56
|
-
status: 'in_progress',
|
|
57
|
-
}, `Stage started: ${stageKey}`);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
async stageCompleted(stageKey: string, completedStages: number): Promise<void> {
|
|
61
|
-
await this.update({
|
|
62
|
-
status: 'in_progress',
|
|
63
|
-
}, `Stage completed: ${stageKey}`);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
async branchCreated(stageKey: string, branchName: string): Promise<void> {
|
|
67
|
-
await this.appendLog(`Branch created (${stageKey}): ${branchName}`);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
async commitMade(stageKey: string, kind: 'plan' | 'implementation'): Promise<void> {
|
|
71
|
-
await this.appendLog(`Commit made (${stageKey}, ${kind})`);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
async pullRequestCreated(stageKey: string, prUrl: string): Promise<void> {
|
|
75
|
-
await this.appendLog(`Pull request created (${stageKey}): ${prUrl}`);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
async noNextStage(stageKey?: string): Promise<void> {
|
|
79
|
-
await this.appendLog(
|
|
80
|
-
stageKey
|
|
81
|
-
? `No next stage available after '${stageKey}'. Execution halted.`
|
|
82
|
-
: 'No next stage available. Execution halted.'
|
|
83
|
-
);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
54
|
async complete(): Promise<void> {
|
|
87
|
-
await this.update({ status: 'completed' }, '
|
|
55
|
+
await this.update({ status: 'completed' }, 'Task execution completed');
|
|
88
56
|
}
|
|
89
57
|
|
|
90
58
|
async fail(error: Error | string): Promise<void> {
|
|
91
59
|
const message = typeof error === 'string' ? error : error.message;
|
|
92
|
-
await this.update({ status: 'failed', error_message: message }, `
|
|
60
|
+
await this.update({ status: 'failed', error_message: message }, `Task execution failed: ${message}`);
|
|
93
61
|
}
|
|
94
62
|
|
|
95
63
|
async appendLog(line: string): Promise<void> {
|
package/src/template-manager.ts
CHANGED
|
@@ -17,14 +17,45 @@ export class TemplateManager {
|
|
|
17
17
|
constructor() {
|
|
18
18
|
const __filename = fileURLToPath(import.meta.url);
|
|
19
19
|
const __dirname = dirname(__filename);
|
|
20
|
+
|
|
21
|
+
// Exhaustive list of possible template locations
|
|
20
22
|
const candidateDirs = [
|
|
21
|
-
|
|
23
|
+
// Standard build output (dist/src/template-manager.js -> dist/templates)
|
|
22
24
|
join(__dirname, '..', 'templates'),
|
|
25
|
+
|
|
26
|
+
// If preserveModules creates nested structure (dist/src/template-manager.js -> dist/src/templates)
|
|
27
|
+
join(__dirname, 'templates'),
|
|
28
|
+
|
|
29
|
+
// Development scenarios (src/template-manager.ts -> src/templates)
|
|
30
|
+
join(__dirname, '..', '..', 'src', 'templates'),
|
|
31
|
+
|
|
32
|
+
// Package root templates directory
|
|
23
33
|
join(__dirname, '..', '..', 'templates'),
|
|
24
|
-
|
|
34
|
+
|
|
35
|
+
// When node_modules symlink or installed (node_modules/@posthog/agent/dist/src/... -> node_modules/@posthog/agent/dist/templates)
|
|
36
|
+
join(__dirname, '..', '..', 'dist', 'templates'),
|
|
37
|
+
|
|
38
|
+
// When consumed from node_modules deep in tree
|
|
39
|
+
join(__dirname, '..', '..', '..', 'templates'),
|
|
40
|
+
join(__dirname, '..', '..', '..', 'dist', 'templates'),
|
|
41
|
+
join(__dirname, '..', '..', '..', 'src', 'templates'),
|
|
42
|
+
|
|
43
|
+
// When bundled by Vite/Webpack (e.g., .vite/build/index.js -> node_modules/@posthog/agent/dist/templates)
|
|
44
|
+
// Try to find node_modules from current location
|
|
45
|
+
join(__dirname, '..', 'node_modules', '@posthog', 'agent', 'dist', 'templates'),
|
|
46
|
+
join(__dirname, '..', '..', 'node_modules', '@posthog', 'agent', 'dist', 'templates'),
|
|
47
|
+
join(__dirname, '..', '..', '..', 'node_modules', '@posthog', 'agent', 'dist', 'templates'),
|
|
25
48
|
];
|
|
26
49
|
|
|
27
50
|
const resolvedDir = candidateDirs.find((dir) => existsSync(dir));
|
|
51
|
+
|
|
52
|
+
if (!resolvedDir) {
|
|
53
|
+
console.error('[TemplateManager] Could not find templates directory.');
|
|
54
|
+
console.error('[TemplateManager] Current file:', __filename);
|
|
55
|
+
console.error('[TemplateManager] Current dir:', __dirname);
|
|
56
|
+
console.error('[TemplateManager] Tried:', candidateDirs.map(d => `\n - ${d} (exists: ${existsSync(d)})`).join(''));
|
|
57
|
+
}
|
|
58
|
+
|
|
28
59
|
this.templatesDir = resolvedDir ?? candidateDirs[0];
|
|
29
60
|
}
|
|
30
61
|
|
|
@@ -33,7 +64,7 @@ export class TemplateManager {
|
|
|
33
64
|
const templatePath = join(this.templatesDir, templateName);
|
|
34
65
|
return await fs.readFile(templatePath, 'utf8');
|
|
35
66
|
} catch (error) {
|
|
36
|
-
throw new Error(`Failed to load template ${templateName}: ${error}`);
|
|
67
|
+
throw new Error(`Failed to load template ${templateName} from ${this.templatesDir}: ${error}`);
|
|
37
68
|
}
|
|
38
69
|
}
|
|
39
70
|
|
|
@@ -60,7 +91,6 @@ export class TemplateManager {
|
|
|
60
91
|
});
|
|
61
92
|
}
|
|
62
93
|
|
|
63
|
-
|
|
64
94
|
async generateCustomFile(templateName: string, variables: TemplateVariables): Promise<string> {
|
|
65
95
|
const template = await this.loadTemplate(templateName);
|
|
66
96
|
return this.substituteVariables(template, {
|
|
@@ -147,7 +177,7 @@ These files are:
|
|
|
147
177
|
Customize \`.posthog/.gitignore\` to control which files are committed:
|
|
148
178
|
- Include plans and documentation by default
|
|
149
179
|
- Exclude temporary files and sensitive data
|
|
150
|
-
- Customize based on your team's
|
|
180
|
+
- Customize based on your team's needs
|
|
151
181
|
|
|
152
182
|
---
|
|
153
183
|
|