@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
package/src/agent.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
2
2
|
import type { Task, ExecutionResult, PlanResult, AgentConfig } from './types.js';
|
|
3
|
-
import type { WorkflowDefinition, WorkflowStage, WorkflowExecutionOptions } from './workflow-types.js';
|
|
4
3
|
import { TaskManager } from './task-manager.js';
|
|
5
4
|
import { PostHogAPIClient } from './posthog-api.js';
|
|
6
5
|
import { PostHogFileManager } from './file-manager.js';
|
|
@@ -11,11 +10,9 @@ import type { ProviderAdapter } from './adapters/types.js';
|
|
|
11
10
|
import { PLANNING_SYSTEM_PROMPT } from './agents/planning.js';
|
|
12
11
|
import { EXECUTION_SYSTEM_PROMPT } from './agents/execution.js';
|
|
13
12
|
import { Logger } from './utils/logger.js';
|
|
14
|
-
import { AgentRegistry } from './agent-registry.js';
|
|
15
|
-
import { WorkflowRegistry } from './workflow-registry.js';
|
|
16
|
-
import { StageExecutor } from './stage-executor.js';
|
|
17
13
|
import { PromptBuilder } from './prompt-builder.js';
|
|
18
14
|
import { TaskProgressReporter } from './task-progress-reporter.js';
|
|
15
|
+
import { OpenAIExtractor, type ExtractedQuestion, type ExtractedQuestionWithAnswer } from './structured-extraction.js';
|
|
19
16
|
|
|
20
17
|
export class Agent {
|
|
21
18
|
private workingDirectory: string;
|
|
@@ -27,10 +24,9 @@ export class Agent {
|
|
|
27
24
|
private templateManager: TemplateManager;
|
|
28
25
|
private adapter: ProviderAdapter;
|
|
29
26
|
private logger: Logger;
|
|
30
|
-
private agentRegistry: AgentRegistry;
|
|
31
|
-
private workflowRegistry: WorkflowRegistry;
|
|
32
|
-
private stageExecutor: StageExecutor;
|
|
33
27
|
private progressReporter: TaskProgressReporter;
|
|
28
|
+
private promptBuilder: PromptBuilder;
|
|
29
|
+
private extractor?: OpenAIExtractor;
|
|
34
30
|
private mcpServers?: Record<string, any>;
|
|
35
31
|
public debug: boolean;
|
|
36
32
|
|
|
@@ -78,7 +74,6 @@ export class Agent {
|
|
|
78
74
|
// TODO: Add author config from environment or config
|
|
79
75
|
});
|
|
80
76
|
this.templateManager = new TemplateManager();
|
|
81
|
-
this.agentRegistry = new AgentRegistry();
|
|
82
77
|
|
|
83
78
|
if (config.posthogApiUrl && config.posthogApiKey) {
|
|
84
79
|
this.posthogAPI = new PostHogAPIClient({
|
|
@@ -87,22 +82,18 @@ export class Agent {
|
|
|
87
82
|
});
|
|
88
83
|
}
|
|
89
84
|
|
|
90
|
-
this.
|
|
91
|
-
const promptBuilder = new PromptBuilder({
|
|
85
|
+
this.promptBuilder = new PromptBuilder({
|
|
92
86
|
getTaskFiles: (taskId: string) => this.getTaskFiles(taskId),
|
|
93
87
|
generatePlanTemplate: (vars) => this.templateManager.generatePlan(vars),
|
|
94
88
|
posthogClient: this.posthogAPI,
|
|
95
89
|
logger: this.logger.child('PromptBuilder')
|
|
96
90
|
});
|
|
97
|
-
this.stageExecutor = new StageExecutor(
|
|
98
|
-
this.agentRegistry,
|
|
99
|
-
this.logger,
|
|
100
|
-
promptBuilder,
|
|
101
|
-
undefined, // eventHandler set via setEventHandler below
|
|
102
|
-
this.mcpServers
|
|
103
|
-
);
|
|
104
|
-
this.stageExecutor.setEventHandler((event) => this.emitEvent(event));
|
|
105
91
|
this.progressReporter = new TaskProgressReporter(this.posthogAPI, this.logger);
|
|
92
|
+
|
|
93
|
+
// Initialize OpenAI extractor if API key is available
|
|
94
|
+
if (process.env.OPENAI_API_KEY) {
|
|
95
|
+
this.extractor = new OpenAIExtractor(this.logger.child('OpenAIExtractor'));
|
|
96
|
+
}
|
|
106
97
|
}
|
|
107
98
|
|
|
108
99
|
/**
|
|
@@ -139,215 +130,339 @@ export class Agent {
|
|
|
139
130
|
}
|
|
140
131
|
}
|
|
141
132
|
|
|
142
|
-
//
|
|
143
|
-
async
|
|
133
|
+
// Adaptive task execution - 3 phases (research → plan → build)
|
|
134
|
+
async runTask(taskOrId: Task | string, options: import('./types.js').TaskExecutionOptions = {}): Promise<void> {
|
|
144
135
|
await this._configureLlmGateway();
|
|
145
136
|
|
|
146
137
|
const task = typeof taskOrId === 'string' ? await this.fetchTask(taskOrId) : taskOrId;
|
|
147
|
-
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
138
|
+
const cwd = options.repositoryPath || this.workingDirectory;
|
|
139
|
+
const isCloudMode = options.isCloudMode ?? false;
|
|
140
|
+
const taskSlug = (task as any).slug || task.id;
|
|
141
|
+
|
|
142
|
+
this.logger.info('Starting adaptive task execution', { taskId: task.id, taskSlug, isCloudMode });
|
|
143
|
+
|
|
144
|
+
// Initialize progress reporter for task run tracking (needed for PR attachment)
|
|
145
|
+
await this.progressReporter.start(task.id, { totalSteps: 3 }); // 3 phases: research, plan, build
|
|
146
|
+
this.emitEvent(this.adapter.createStatusEvent('run_started', { runId: this.progressReporter.runId }));
|
|
147
|
+
|
|
148
|
+
// Phase 1: Branch check
|
|
149
|
+
const existingBranch = await this.gitManager.getTaskBranch(taskSlug);
|
|
150
|
+
if (!existingBranch) {
|
|
151
|
+
this.logger.info('Creating task branch', { taskSlug });
|
|
152
|
+
const branchName = `posthog/task-${taskSlug}`;
|
|
153
|
+
await this.gitManager.createOrSwitchToBranch(branchName);
|
|
154
|
+
this.emitEvent(this.adapter.createStatusEvent('branch_created', { branch: branchName }));
|
|
155
|
+
|
|
156
|
+
// Initial commit
|
|
157
|
+
await this.fileManager.ensureGitignore();
|
|
158
|
+
await this.gitManager.addAllPostHogFiles();
|
|
159
|
+
if (isCloudMode) {
|
|
160
|
+
await this.gitManager.commitAndPush(`Initialize task ${taskSlug}`, { allowEmpty: true });
|
|
161
|
+
} else {
|
|
162
|
+
await this.gitManager.commitChanges(`Initialize task ${taskSlug}`);
|
|
163
|
+
}
|
|
164
|
+
} else {
|
|
165
|
+
this.logger.info('Switching to existing task branch', { branch: existingBranch });
|
|
166
|
+
await this.gitManager.switchToBranch(existingBranch);
|
|
151
167
|
}
|
|
152
|
-
const orderedStages = [...workflow.stages].sort((a, b) => a.position - b.position);
|
|
153
168
|
|
|
154
|
-
//
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
169
|
+
// Phase 2: Research
|
|
170
|
+
const researchExists = await this.fileManager.readResearch(task.id);
|
|
171
|
+
if (!researchExists) {
|
|
172
|
+
this.logger.info('Starting research phase', { taskId: task.id });
|
|
173
|
+
this.emitEvent(this.adapter.createStatusEvent('phase_start', { phase: 'research' }));
|
|
174
|
+
|
|
175
|
+
// Run research agent
|
|
176
|
+
const researchPrompt = await this.promptBuilder.buildResearchPrompt(task, cwd);
|
|
177
|
+
const { RESEARCH_SYSTEM_PROMPT } = await import('./agents/research.js');
|
|
178
|
+
const fullPrompt = RESEARCH_SYSTEM_PROMPT + '\n\n' + researchPrompt;
|
|
179
|
+
|
|
180
|
+
const baseOptions: Record<string, any> = {
|
|
181
|
+
model: 'claude-sonnet-4-5-20250929',
|
|
182
|
+
cwd,
|
|
183
|
+
permissionMode: 'plan',
|
|
184
|
+
settingSources: ['local'],
|
|
185
|
+
mcpServers: this.mcpServers,
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const response = query({
|
|
189
|
+
prompt: fullPrompt,
|
|
190
|
+
options: { ...baseOptions, ...(options.queryOverrides || {}) },
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
let researchContent = '';
|
|
194
|
+
for await (const message of response) {
|
|
195
|
+
this.emitEvent(this.adapter.createRawSDKEvent(message));
|
|
196
|
+
const transformed = this.adapter.transform(message);
|
|
197
|
+
if (transformed) {
|
|
198
|
+
this.emitEvent(transformed);
|
|
199
|
+
}
|
|
200
|
+
if (message.type === 'assistant' && message.message?.content) {
|
|
201
|
+
for (const c of message.message.content) {
|
|
202
|
+
if (c.type === 'text' && c.text) researchContent += c.text + '\n';
|
|
203
|
+
}
|
|
160
204
|
}
|
|
161
|
-
} catch (e) {
|
|
162
|
-
this.logger.warn('Failed to sync task workflow before execution', { error: (e as Error).message });
|
|
163
205
|
}
|
|
164
|
-
}
|
|
165
206
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
});
|
|
207
|
+
// Write research.md
|
|
208
|
+
if (researchContent.trim()) {
|
|
209
|
+
await this.fileManager.writeResearch(task.id, researchContent.trim());
|
|
210
|
+
this.logger.info('Research completed', { taskId: task.id });
|
|
211
|
+
}
|
|
172
212
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
213
|
+
// Commit research
|
|
214
|
+
await this.gitManager.addAllPostHogFiles();
|
|
215
|
+
|
|
216
|
+
// Extract questions using structured output and save to questions.json
|
|
217
|
+
if (this.extractor) {
|
|
218
|
+
try {
|
|
219
|
+
this.logger.info('Extracting questions from research.md', { taskId: task.id });
|
|
220
|
+
const questions = await this.extractQuestionsFromResearch(task.id, false);
|
|
221
|
+
|
|
222
|
+
this.logger.info('Questions extracted successfully', { taskId: task.id, count: questions.length });
|
|
223
|
+
|
|
224
|
+
// Save questions.json
|
|
225
|
+
await this.fileManager.writeQuestions(task.id, {
|
|
226
|
+
questions,
|
|
227
|
+
answered: false,
|
|
228
|
+
answers: null,
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
this.logger.info('Questions saved to questions.json', { taskId: task.id });
|
|
232
|
+
|
|
233
|
+
// Emit event for Array to pick up (local mode)
|
|
234
|
+
if (!isCloudMode) {
|
|
235
|
+
this.emitEvent({
|
|
236
|
+
type: 'artifact',
|
|
237
|
+
ts: Date.now(),
|
|
238
|
+
kind: 'research_questions',
|
|
239
|
+
content: questions,
|
|
240
|
+
});
|
|
241
|
+
this.logger.info('Emitted research_questions artifact event', { taskId: task.id });
|
|
242
|
+
}
|
|
243
|
+
} catch (error) {
|
|
244
|
+
this.logger.error('Failed to extract questions', { error: error instanceof Error ? error.message : String(error) });
|
|
245
|
+
this.emitEvent({
|
|
246
|
+
type: 'error',
|
|
247
|
+
ts: Date.now(),
|
|
248
|
+
message: `Failed to extract questions: ${error instanceof Error ? error.message : String(error)}`,
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
} else {
|
|
252
|
+
this.logger.warn('OpenAI extractor not available (OPENAI_API_KEY not set), skipping question extraction');
|
|
253
|
+
this.emitEvent({
|
|
254
|
+
type: 'status',
|
|
255
|
+
ts: Date.now(),
|
|
256
|
+
phase: 'extraction_skipped',
|
|
257
|
+
message: 'Question extraction skipped - OPENAI_API_KEY not configured',
|
|
179
258
|
});
|
|
180
|
-
}
|
|
181
|
-
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (isCloudMode) {
|
|
262
|
+
await this.gitManager.commitAndPush(`Research phase for ${task.title}`);
|
|
263
|
+
} else {
|
|
264
|
+
await this.gitManager.commitChanges(`Research phase for ${task.title}`);
|
|
265
|
+
this.emitEvent(this.adapter.createStatusEvent('phase_complete', { phase: 'research' }));
|
|
266
|
+
return; // Local mode: return to user
|
|
182
267
|
}
|
|
183
268
|
}
|
|
184
269
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
await this.
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
270
|
+
// Phase 3: Auto-answer questions (cloud mode only)
|
|
271
|
+
if (isCloudMode) {
|
|
272
|
+
const questionsData = await this.fileManager.readQuestions(task.id);
|
|
273
|
+
if (questionsData && !questionsData.answered) {
|
|
274
|
+
this.logger.info('Auto-answering research questions', { taskId: task.id });
|
|
275
|
+
|
|
276
|
+
// Extract questions with recommended answers using structured output
|
|
277
|
+
if (this.extractor) {
|
|
278
|
+
const questionsWithAnswers = await this.extractQuestionsFromResearch(task.id, true) as ExtractedQuestionWithAnswer[];
|
|
279
|
+
|
|
280
|
+
// Save answers to questions.json
|
|
281
|
+
await this.fileManager.writeQuestions(task.id, {
|
|
282
|
+
questions: questionsWithAnswers.map(qa => ({
|
|
283
|
+
id: qa.id,
|
|
284
|
+
question: qa.question,
|
|
285
|
+
options: qa.options,
|
|
286
|
+
})),
|
|
287
|
+
answered: true,
|
|
288
|
+
answers: questionsWithAnswers.map(qa => ({
|
|
289
|
+
questionId: qa.id,
|
|
290
|
+
selectedOption: qa.recommendedAnswer,
|
|
291
|
+
customInput: qa.justification,
|
|
292
|
+
})),
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
this.logger.info('Auto-answers saved to questions.json', { taskId: task.id });
|
|
296
|
+
await this.gitManager.addAllPostHogFiles();
|
|
297
|
+
await this.gitManager.commitAndPush(`Answer research questions for ${task.title}`);
|
|
298
|
+
} else {
|
|
299
|
+
this.logger.warn('OpenAI extractor not available, skipping auto-answer');
|
|
200
300
|
}
|
|
201
301
|
}
|
|
302
|
+
}
|
|
202
303
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
304
|
+
// Phase 4: Plan
|
|
305
|
+
const planExists = await this.readPlan(task.id);
|
|
306
|
+
if (!planExists) {
|
|
307
|
+
// Check if questions have been answered
|
|
308
|
+
const questionsData = await this.fileManager.readQuestions(task.id);
|
|
309
|
+
if (!questionsData || !questionsData.answered) {
|
|
310
|
+
this.logger.info('Waiting for user answers to research questions');
|
|
311
|
+
this.emitEvent(this.adapter.createStatusEvent('phase_complete', { phase: 'research_questions' }));
|
|
312
|
+
return; // Wait for user to answer questions
|
|
206
313
|
}
|
|
207
314
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
315
|
+
this.logger.info('Starting planning phase', { taskId: task.id });
|
|
316
|
+
this.emitEvent(this.adapter.createStatusEvent('phase_start', { phase: 'planning' }));
|
|
317
|
+
|
|
318
|
+
// Build context with research + questions + answers
|
|
319
|
+
const research = await this.fileManager.readResearch(task.id);
|
|
320
|
+
let researchContext = '';
|
|
321
|
+
if (research) {
|
|
322
|
+
researchContext += `## Research Analysis\n\n${research}\n\n`;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Add questions and answers
|
|
326
|
+
researchContext += `## Implementation Decisions\n\n`;
|
|
327
|
+
const answers = questionsData.answers || [];
|
|
328
|
+
for (const question of questionsData.questions) {
|
|
329
|
+
const answer = answers.find((a: any) => a.questionId === question.id);
|
|
330
|
+
|
|
331
|
+
researchContext += `### ${question.question}\n\n`;
|
|
332
|
+
if (answer) {
|
|
333
|
+
researchContext += `**Selected:** ${answer.selectedOption}\n`;
|
|
334
|
+
if (answer.customInput) {
|
|
335
|
+
researchContext += `**Details:** ${answer.customInput}\n`;
|
|
218
336
|
}
|
|
337
|
+
} else {
|
|
338
|
+
this.logger.warn('No answer found for question', { questionId: question.id });
|
|
339
|
+
researchContext += `**Selected:** Not answered\n`;
|
|
219
340
|
}
|
|
341
|
+
researchContext += '\n';
|
|
220
342
|
}
|
|
343
|
+
|
|
344
|
+
// Run planning agent with full context
|
|
345
|
+
const planningPrompt = await this.promptBuilder.buildPlanningPrompt(task, cwd);
|
|
346
|
+
const { PLANNING_SYSTEM_PROMPT } = await import('./agents/planning.js');
|
|
347
|
+
const fullPrompt = PLANNING_SYSTEM_PROMPT + '\n\n' + planningPrompt + '\n\n' + researchContext;
|
|
348
|
+
|
|
349
|
+
const baseOptions: Record<string, any> = {
|
|
350
|
+
model: 'claude-sonnet-4-5-20250929',
|
|
351
|
+
cwd,
|
|
352
|
+
permissionMode: 'plan',
|
|
353
|
+
settingSources: ['local'],
|
|
354
|
+
mcpServers: this.mcpServers,
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
const response = query({
|
|
358
|
+
prompt: fullPrompt,
|
|
359
|
+
options: { ...baseOptions, ...(options.queryOverrides || {}) },
|
|
360
|
+
});
|
|
221
361
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
362
|
+
let planContent = '';
|
|
363
|
+
for await (const message of response) {
|
|
364
|
+
this.emitEvent(this.adapter.createRawSDKEvent(message));
|
|
365
|
+
const transformed = this.adapter.transform(message);
|
|
366
|
+
if (transformed) {
|
|
367
|
+
this.emitEvent(transformed);
|
|
368
|
+
}
|
|
369
|
+
if (message.type === 'assistant' && message.message?.content) {
|
|
370
|
+
for (const c of message.message.content) {
|
|
371
|
+
if (c.type === 'text' && c.text) planContent += c.text + '\n';
|
|
231
372
|
}
|
|
232
373
|
}
|
|
233
374
|
}
|
|
234
|
-
await this.progressReporter.complete();
|
|
235
|
-
this.taskManager.completeExecution(executionId, { task, workflow });
|
|
236
|
-
return { task, workflow };
|
|
237
|
-
} catch (error) {
|
|
238
|
-
await this.progressReporter.fail(error as Error);
|
|
239
|
-
this.taskManager.failExecution(executionId, error as Error);
|
|
240
|
-
throw error;
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
375
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
const isManual = stage.is_manual_only === true;
|
|
250
|
-
const stageKeyLower = (stage.key || '').toLowerCase().trim();
|
|
251
|
-
const isPlanningByKey = stageKeyLower === 'plan' || stageKeyLower.includes('plan');
|
|
252
|
-
const isPlanning = !isManual && ((agentDef?.agent_type === 'planning') || isPlanningByKey);
|
|
253
|
-
const shouldCreatePlanningBranch = overrides?.createPlanningBranch !== false; // default true
|
|
254
|
-
const shouldCreateImplBranch = overrides?.createImplementationBranch !== false; // default true
|
|
255
|
-
|
|
256
|
-
if (isPlanning && shouldCreatePlanningBranch) {
|
|
257
|
-
const planningBranch = await this.createPlanningBranch(task.id);
|
|
258
|
-
await this.updateTaskBranch(task.id, planningBranch);
|
|
259
|
-
this.emitEvent(this.adapter.createStatusEvent('branch_created', { stage: stage.key, branch: planningBranch }));
|
|
260
|
-
await this.progressReporter.branchCreated(stage.key, planningBranch);
|
|
261
|
-
} else if (!isPlanning && !isManual && shouldCreateImplBranch) {
|
|
262
|
-
const implBranch = await this.createImplementationBranch(task.id);
|
|
263
|
-
await this.updateTaskBranch(task.id, implBranch);
|
|
264
|
-
this.emitEvent(this.adapter.createStatusEvent('branch_created', { stage: stage.key, branch: implBranch }));
|
|
265
|
-
await this.progressReporter.branchCreated(stage.key, implBranch);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
const result = await this.stageExecutor.execute(task, stage, options);
|
|
376
|
+
// Write plan.md
|
|
377
|
+
if (planContent.trim()) {
|
|
378
|
+
await this.writePlan(task.id, planContent.trim());
|
|
379
|
+
this.logger.info('Plan completed', { taskId: task.id });
|
|
380
|
+
}
|
|
269
381
|
|
|
270
|
-
|
|
271
|
-
await this.
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
382
|
+
// Commit plan
|
|
383
|
+
await this.gitManager.addAllPostHogFiles();
|
|
384
|
+
if (isCloudMode) {
|
|
385
|
+
await this.gitManager.commitAndPush(`Planning phase for ${task.title}`);
|
|
386
|
+
} else {
|
|
387
|
+
await this.gitManager.commitChanges(`Planning phase for ${task.title}`);
|
|
388
|
+
this.emitEvent(this.adapter.createStatusEvent('phase_complete', { phase: 'planning' }));
|
|
389
|
+
return; // Local mode: return to user
|
|
390
|
+
}
|
|
275
391
|
}
|
|
276
392
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
393
|
+
// Phase 5: Build
|
|
394
|
+
const latestRun = task.latest_run;
|
|
395
|
+
const prExists = latestRun?.output && (latestRun.output as any).pr_url;
|
|
396
|
+
|
|
397
|
+
if (!prExists) {
|
|
398
|
+
this.logger.info('Starting build phase', { taskId: task.id });
|
|
399
|
+
this.emitEvent(this.adapter.createStatusEvent('phase_start', { phase: 'build' }));
|
|
400
|
+
|
|
401
|
+
// Run execution agent
|
|
402
|
+
const executionPrompt = await this.promptBuilder.buildExecutionPrompt(task, cwd);
|
|
403
|
+
const { EXECUTION_SYSTEM_PROMPT } = await import('./agents/execution.js');
|
|
404
|
+
const fullPrompt = EXECUTION_SYSTEM_PROMPT + '\n\n' + executionPrompt;
|
|
405
|
+
|
|
406
|
+
const { PermissionMode } = await import('./types.js');
|
|
407
|
+
const permissionMode = options.permissionMode || PermissionMode.ACCEPT_EDITS;
|
|
408
|
+
const baseOptions: Record<string, any> = {
|
|
409
|
+
model: 'claude-sonnet-4-5-20250929',
|
|
410
|
+
cwd,
|
|
411
|
+
permissionMode,
|
|
412
|
+
settingSources: ['local'],
|
|
413
|
+
mcpServers: this.mcpServers,
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
const response = query({
|
|
417
|
+
prompt: fullPrompt,
|
|
418
|
+
options: { ...baseOptions, ...(options.queryOverrides || {}) },
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
for await (const message of response) {
|
|
422
|
+
this.emitEvent(this.adapter.createRawSDKEvent(message));
|
|
423
|
+
const transformed = this.adapter.transform(message);
|
|
424
|
+
if (transformed) {
|
|
425
|
+
this.emitEvent(transformed);
|
|
290
426
|
}
|
|
291
|
-
try {
|
|
292
|
-
const prUrl = await this.createPullRequest(task.id, branchName, task.title, task.description);
|
|
293
|
-
await this.updateTaskBranch(task.id, branchName);
|
|
294
|
-
await this.attachPullRequestToTask(task.id, prUrl, branchName);
|
|
295
|
-
this.emitEvent(this.adapter.createStatusEvent('pr_created', { stage: stage.key, prUrl }));
|
|
296
|
-
await this.progressReporter.pullRequestCreated(stage.key, prUrl);
|
|
297
|
-
} catch {}
|
|
298
427
|
}
|
|
299
|
-
// Do not auto-progress on manual stages
|
|
300
|
-
this.emitEvent(this.adapter.createStatusEvent('stage_complete', { stage: stage.key }));
|
|
301
|
-
return;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
if (result.results) {
|
|
305
|
-
const existingPlan = await this.readPlan(task.id);
|
|
306
|
-
const planSummary = existingPlan ? existingPlan.split('\n')[0] : undefined;
|
|
307
|
-
await this.commitImplementation(task.id, task.title, planSummary);
|
|
308
|
-
this.emitEvent(this.adapter.createStatusEvent('commit_made', { stage: stage.key, kind: 'implementation' }));
|
|
309
|
-
await this.progressReporter.commitMade(stage.key, 'implementation');
|
|
310
|
-
}
|
|
311
428
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
const
|
|
315
|
-
|
|
316
|
-
|
|
429
|
+
// Commit and push implementation
|
|
430
|
+
// Stage ALL changes (not just .posthog/)
|
|
431
|
+
const hasChanges = await this.gitManager.hasChanges();
|
|
432
|
+
if (hasChanges) {
|
|
433
|
+
await this.gitManager.addFiles(['.']); // Stage all changes
|
|
434
|
+
await this.gitManager.commitChanges(`Implementation for ${task.title}`);
|
|
435
|
+
|
|
436
|
+
// Push to origin
|
|
317
437
|
const branchName = await this.gitManager.getCurrentBranch();
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
await this.progressReporter.pullRequestCreated(stage.key, prUrl);
|
|
324
|
-
} catch {}
|
|
438
|
+
await this.gitManager.pushBranch(branchName);
|
|
439
|
+
|
|
440
|
+
this.logger.info('Implementation committed and pushed', { taskId: task.id });
|
|
441
|
+
} else {
|
|
442
|
+
this.logger.warn('No changes to commit in build phase', { taskId: task.id });
|
|
325
443
|
}
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
this.emitEvent(this.adapter.createStatusEvent('stage_complete', { stage: stage.key }));
|
|
329
|
-
}
|
|
330
444
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
this.
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
error: error.message,
|
|
344
|
-
});
|
|
345
|
-
this.emitEvent(this.adapter.createStatusEvent('no_next_stage', { stage: currentStageKey }));
|
|
346
|
-
await this.progressReporter.noNextStage(currentStageKey);
|
|
347
|
-
return;
|
|
445
|
+
// Create PR
|
|
446
|
+
const branchName = await this.gitManager.getCurrentBranch();
|
|
447
|
+
const prUrl = await this.createPullRequest(task.id, branchName, task.title, task.description);
|
|
448
|
+
this.logger.info('Pull request created', { taskId: task.id, prUrl });
|
|
449
|
+
this.emitEvent(this.adapter.createStatusEvent('pr_created', { prUrl }));
|
|
450
|
+
|
|
451
|
+
// Attach PR to task run
|
|
452
|
+
try {
|
|
453
|
+
await this.attachPullRequestToTask(task.id, prUrl, branchName);
|
|
454
|
+
this.logger.info('PR attached to task successfully', { taskId: task.id, prUrl });
|
|
455
|
+
} catch (error) {
|
|
456
|
+
this.logger.warn('Could not attach PR to task', { error: error instanceof Error ? error.message : String(error) });
|
|
348
457
|
}
|
|
349
|
-
|
|
458
|
+
} else {
|
|
459
|
+
this.logger.info('PR already exists, skipping build phase', { taskId: task.id });
|
|
350
460
|
}
|
|
461
|
+
|
|
462
|
+
// Phase 6: Complete
|
|
463
|
+
await this.progressReporter.complete();
|
|
464
|
+
this.logger.info('Task execution complete', { taskId: task.id });
|
|
465
|
+
this.emitEvent(this.adapter.createStatusEvent('task_complete', { taskId: task.id }));
|
|
351
466
|
}
|
|
352
467
|
|
|
353
468
|
// Direct prompt execution - still supported for low-level usage
|
|
@@ -401,8 +516,6 @@ export class Agent {
|
|
|
401
516
|
repository?: string;
|
|
402
517
|
organization?: string;
|
|
403
518
|
origin_product?: string;
|
|
404
|
-
workflow?: string;
|
|
405
|
-
current_stage?: string;
|
|
406
519
|
}): Promise<Task[]> {
|
|
407
520
|
if (!this.posthogAPI) {
|
|
408
521
|
throw new Error('PostHog API not configured. Provide posthogApiUrl and posthogApiKey in constructor.');
|
|
@@ -437,8 +550,27 @@ export class Agent {
|
|
|
437
550
|
this.logger.debug('Reading plan', { taskId });
|
|
438
551
|
return await this.fileManager.readPlan(taskId);
|
|
439
552
|
}
|
|
440
|
-
|
|
441
|
-
|
|
553
|
+
|
|
554
|
+
async extractQuestionsFromResearch(taskId: string, includeAnswers: boolean = false): Promise<ExtractedQuestion[] | ExtractedQuestionWithAnswer[]> {
|
|
555
|
+
this.logger.info('Extracting questions from research.md', { taskId, includeAnswers });
|
|
556
|
+
|
|
557
|
+
if (!this.extractor) {
|
|
558
|
+
throw new Error('OpenAI extractor not initialized. Set OPENAI_API_KEY environment variable.');
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
const researchContent = await this.fileManager.readResearch(taskId);
|
|
562
|
+
if (!researchContent) {
|
|
563
|
+
throw new Error('research.md not found for task ' + taskId);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
if (includeAnswers) {
|
|
567
|
+
return await this.extractor.extractQuestionsWithAnswers(researchContent);
|
|
568
|
+
} else {
|
|
569
|
+
return await this.extractor.extractQuestions(researchContent);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// Git operations for task execution
|
|
442
574
|
async createPlanningBranch(taskId: string): Promise<string> {
|
|
443
575
|
this.logger.info('Creating planning branch', { taskId });
|
|
444
576
|
const branchName = await this.gitManager.createTaskPlanningBranch(taskId);
|
|
@@ -563,4 +695,3 @@ Generated by PostHog Agent`;
|
|
|
563
695
|
|
|
564
696
|
export { PermissionMode } from './types.js';
|
|
565
697
|
export type { Task, SupportingFile, ExecutionResult, AgentConfig } from './types.js';
|
|
566
|
-
export type { WorkflowDefinition, WorkflowStage, WorkflowExecutionOptions } from './workflow-types.js';
|
package/src/agents/execution.ts
CHANGED
|
@@ -34,11 +34,11 @@ export const EXECUTION_SYSTEM_PROMPT = `<context>
|
|
|
34
34
|
- Verified no build artifacts or dependencies are being committed
|
|
35
35
|
</checklist>
|
|
36
36
|
|
|
37
|
-
<
|
|
37
|
+
<approach>
|
|
38
38
|
- first make a plan and create a todo list
|
|
39
39
|
- execute the todo list one by one
|
|
40
40
|
- test the changes
|
|
41
|
-
</
|
|
41
|
+
</approach>
|
|
42
42
|
|
|
43
43
|
<output_format>
|
|
44
44
|
Once finished respond with a summary of changes made
|