@posthog/agent 1.10.0 → 1.11.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.
Files changed (49) hide show
  1. package/dist/index.d.ts +2 -0
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/src/agent-registry.d.ts.map +1 -1
  4. package/dist/src/agent-registry.js +6 -0
  5. package/dist/src/agent-registry.js.map +1 -1
  6. package/dist/src/agent.d.ts +5 -0
  7. package/dist/src/agent.d.ts.map +1 -1
  8. package/dist/src/agent.js +327 -2
  9. package/dist/src/agent.js.map +1 -1
  10. package/dist/src/agents/research.d.ts +2 -0
  11. package/dist/src/agents/research.d.ts.map +1 -0
  12. package/dist/src/agents/research.js +105 -0
  13. package/dist/src/agents/research.js.map +1 -0
  14. package/dist/src/file-manager.d.ts +19 -0
  15. package/dist/src/file-manager.d.ts.map +1 -1
  16. package/dist/src/file-manager.js +39 -0
  17. package/dist/src/file-manager.js.map +1 -1
  18. package/dist/src/git-manager.d.ts +4 -0
  19. package/dist/src/git-manager.d.ts.map +1 -1
  20. package/dist/src/git-manager.js +41 -0
  21. package/dist/src/git-manager.js.map +1 -1
  22. package/dist/src/prompt-builder.d.ts +1 -0
  23. package/dist/src/prompt-builder.d.ts.map +1 -1
  24. package/dist/src/prompt-builder.js +40 -0
  25. package/dist/src/prompt-builder.js.map +1 -1
  26. package/dist/src/stage-executor.d.ts +1 -0
  27. package/dist/src/stage-executor.d.ts.map +1 -1
  28. package/dist/src/stage-executor.js +43 -0
  29. package/dist/src/stage-executor.js.map +1 -1
  30. package/dist/src/structured-extraction.d.ts +22 -0
  31. package/dist/src/structured-extraction.d.ts.map +1 -0
  32. package/dist/src/structured-extraction.js +136 -0
  33. package/dist/src/structured-extraction.js.map +1 -0
  34. package/dist/src/types.d.ts +7 -0
  35. package/dist/src/types.d.ts.map +1 -1
  36. package/dist/src/types.js.map +1 -1
  37. package/dist/src/workflow-types.d.ts +1 -1
  38. package/dist/src/workflow-types.d.ts.map +1 -1
  39. package/package.json +4 -3
  40. package/src/agent-registry.ts +6 -0
  41. package/src/agent.ts +364 -2
  42. package/src/agents/research.ts +103 -0
  43. package/src/file-manager.ts +64 -0
  44. package/src/git-manager.ts +52 -0
  45. package/src/prompt-builder.ts +53 -0
  46. package/src/stage-executor.ts +50 -0
  47. package/src/structured-extraction.ts +167 -0
  48. package/src/types.ts +8 -0
  49. package/src/workflow-types.ts +1 -1
@@ -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(
@@ -4,6 +4,7 @@ import { ClaudeAdapter } from './adapters/claude/claude-adapter.js';
4
4
  import { AgentRegistry } from './agent-registry.js';
5
5
  import type { AgentEvent, Task, McpServerConfig } from './types.js';
6
6
  import type { WorkflowStage, WorkflowStageExecutionResult, WorkflowExecutionOptions } from './workflow-types.js';
7
+ import { RESEARCH_SYSTEM_PROMPT } from './agents/research.js';
7
8
  import { PLANNING_SYSTEM_PROMPT } from './agents/planning.js';
8
9
  import { EXECUTION_SYSTEM_PROMPT } from './agents/execution.js';
9
10
  import { PromptBuilder } from './prompt-builder.js';
@@ -57,6 +58,8 @@ export class StageExecutor {
57
58
  const cwd = options.repositoryPath || process.cwd();
58
59
 
59
60
  switch (agent.agent_type) {
61
+ case 'research':
62
+ return this.runResearch(task, cwd, options, stage.key);
60
63
  case 'planning':
61
64
  return this.runPlanning(task, cwd, options, stage.key);
62
65
  case 'execution':
@@ -70,6 +73,53 @@ export class StageExecutor {
70
73
  }
71
74
  }
72
75
 
76
+ private async runResearch(task: Task, cwd: string, options: WorkflowExecutionOptions, stageKey: string): Promise<WorkflowStageExecutionResult> {
77
+ const contextPrompt = await this.promptBuilder.buildResearchPrompt(task, cwd);
78
+ let prompt = RESEARCH_SYSTEM_PROMPT + '\n\n' + contextPrompt;
79
+
80
+ const stageOverrides = options.stageOverrides?.[stageKey] || options.stageOverrides?.['research'];
81
+ const mergedOverrides = {
82
+ ...(options.queryOverrides || {}),
83
+ ...(stageOverrides?.queryOverrides || {}),
84
+ } as Record<string, any>;
85
+
86
+ const baseOptions: Record<string, any> = {
87
+ model: 'claude-sonnet-4-5-20250929',
88
+ cwd,
89
+ permissionMode: 'plan',
90
+ settingSources: ['local'],
91
+ mcpServers: this.mcpServers
92
+ };
93
+
94
+ const response = query({
95
+ prompt,
96
+ options: { ...baseOptions, ...mergedOverrides },
97
+ });
98
+
99
+ let research = '';
100
+ for await (const message of response) {
101
+ // Emit raw SDK event first
102
+ this.eventHandler?.(this.adapter.createRawSDKEvent(message));
103
+
104
+ // Then emit transformed event
105
+ const transformed = this.adapter.transform(message);
106
+ if (transformed) {
107
+ if (transformed.type !== 'token') {
108
+ this.logger.debug('Research event', { type: transformed.type });
109
+ }
110
+ this.eventHandler?.(transformed);
111
+ }
112
+
113
+ if (message.type === 'assistant' && message.message?.content) {
114
+ for (const c of message.message.content) {
115
+ if (c.type === 'text' && c.text) research += c.text + '\n';
116
+ }
117
+ }
118
+ }
119
+
120
+ return { plan: research.trim() }; // Return as 'plan' field to match existing interface
121
+ }
122
+
73
123
  private async runPlanning(task: Task, cwd: string, options: WorkflowExecutionOptions, stageKey: string): Promise<WorkflowStageExecutionResult> {
74
124
  const contextPrompt = await this.promptBuilder.buildPlanningPrompt(task, cwd);
75
125
  let prompt = PLANNING_SYSTEM_PROMPT + '\n\n' + contextPrompt;
@@ -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
+ }
package/src/types.ts CHANGED
@@ -66,6 +66,14 @@ export interface ExecutionOptions {
66
66
  permissionMode?: PermissionMode;
67
67
  }
68
68
 
69
+ export interface TaskExecutionOptions {
70
+ repositoryPath?: string;
71
+ permissionMode?: PermissionMode;
72
+ isCloudMode?: boolean; // Determines local vs cloud behavior (local pauses after each phase)
73
+ autoProgress?: boolean;
74
+ queryOverrides?: Record<string, any>;
75
+ }
76
+
69
77
  // Base event with timestamp
70
78
  interface BaseEvent {
71
79
  ts: number;
@@ -1,6 +1,6 @@
1
1
  import type { PermissionMode, AgentEvent } from './types.js';
2
2
 
3
- export type AgentType = 'planning' | 'execution' | 'review' | 'testing';
3
+ export type AgentType = 'research' | 'planning' | 'execution' | 'review' | 'testing';
4
4
 
5
5
  export interface AgentDefinition {
6
6
  id: string;