@posthog/agent 1.0.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/CLAUDE.md +296 -0
- package/README.md +142 -0
- package/dist/example.d.ts +3 -0
- package/dist/example.d.ts.map +1 -0
- package/dist/example.js +49 -0
- package/dist/example.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/src/agent-registry.d.ts +16 -0
- package/dist/src/agent-registry.d.ts.map +1 -0
- package/dist/src/agent-registry.js +54 -0
- package/dist/src/agent-registry.js.map +1 -0
- package/dist/src/agent.d.ts +60 -0
- package/dist/src/agent.d.ts.map +1 -0
- package/dist/src/agent.js +371 -0
- package/dist/src/agent.js.map +1 -0
- package/dist/src/agents/execution.d.ts +2 -0
- package/dist/src/agents/execution.d.ts.map +1 -0
- package/dist/src/agents/execution.js +54 -0
- package/dist/src/agents/execution.js.map +1 -0
- package/dist/src/agents/planning.d.ts +2 -0
- package/dist/src/agents/planning.d.ts.map +1 -0
- package/dist/src/agents/planning.js +68 -0
- package/dist/src/agents/planning.js.map +1 -0
- package/dist/src/event-transformer.d.ts +7 -0
- package/dist/src/event-transformer.d.ts.map +1 -0
- package/dist/src/event-transformer.js +174 -0
- package/dist/src/event-transformer.js.map +1 -0
- package/dist/src/file-manager.d.ts +30 -0
- package/dist/src/file-manager.d.ts.map +1 -0
- package/dist/src/file-manager.js +181 -0
- package/dist/src/file-manager.js.map +1 -0
- package/dist/src/git-manager.d.ts +49 -0
- package/dist/src/git-manager.d.ts.map +1 -0
- package/dist/src/git-manager.js +278 -0
- package/dist/src/git-manager.js.map +1 -0
- package/dist/src/posthog-api.d.ts +48 -0
- package/dist/src/posthog-api.d.ts.map +1 -0
- package/dist/src/posthog-api.js +110 -0
- package/dist/src/posthog-api.js.map +1 -0
- package/dist/src/prompt-builder.d.ts +17 -0
- package/dist/src/prompt-builder.d.ts.map +1 -0
- package/dist/src/prompt-builder.js +75 -0
- package/dist/src/prompt-builder.js.map +1 -0
- package/dist/src/stage-executor.d.ts +16 -0
- package/dist/src/stage-executor.d.ts.map +1 -0
- package/dist/src/stage-executor.js +119 -0
- package/dist/src/stage-executor.js.map +1 -0
- package/dist/src/task-manager.d.ts +25 -0
- package/dist/src/task-manager.d.ts.map +1 -0
- package/dist/src/task-manager.js +119 -0
- package/dist/src/task-manager.js.map +1 -0
- package/dist/src/template-manager.d.ts +30 -0
- package/dist/src/template-manager.d.ts.map +1 -0
- package/dist/src/template-manager.js +118 -0
- package/dist/src/template-manager.js.map +1 -0
- package/dist/src/types.d.ts +163 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +9 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/utils/logger.d.ts +29 -0
- package/dist/src/utils/logger.d.ts.map +1 -0
- package/dist/src/utils/logger.js +66 -0
- package/dist/src/utils/logger.js.map +1 -0
- package/dist/src/utils/mcp.d.ts +10 -0
- package/dist/src/utils/mcp.d.ts.map +1 -0
- package/dist/src/utils/mcp.js +16 -0
- package/dist/src/utils/mcp.js.map +1 -0
- package/dist/src/workflow-registry.d.ts +11 -0
- package/dist/src/workflow-registry.d.ts.map +1 -0
- package/dist/src/workflow-registry.js +26 -0
- package/dist/src/workflow-registry.js.map +1 -0
- package/dist/src/workflow-types.d.ts +45 -0
- package/dist/src/workflow-types.d.ts.map +1 -0
- package/dist/src/workflow-types.js +2 -0
- package/dist/src/workflow-types.js.map +1 -0
- package/package.json +61 -0
- package/src/agent-registry.ts +60 -0
- package/src/agent.ts +428 -0
- package/src/agents/execution.ts +53 -0
- package/src/agents/planning.ts +67 -0
- package/src/event-transformer.ts +189 -0
- package/src/file-manager.ts +204 -0
- package/src/git-manager.ts +344 -0
- package/src/posthog-api.ts +169 -0
- package/src/prompt-builder.ts +93 -0
- package/src/stage-executor.ts +137 -0
- package/src/task-manager.ts +155 -0
- package/src/template-manager.ts +149 -0
- package/src/templates/plan-template.md +45 -0
- package/src/types.ts +223 -0
- package/src/utils/logger.ts +79 -0
- package/src/utils/mcp.ts +15 -0
- package/src/workflow-registry.ts +31 -0
- package/src/workflow-types.ts +53 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { query } from '@anthropic-ai/claude-agent-sdk';
|
|
2
|
+
import { Logger } from './utils/logger';
|
|
3
|
+
import { EventTransformer } from './event-transformer';
|
|
4
|
+
import { AgentRegistry } from './agent-registry';
|
|
5
|
+
import type { Task } from './types';
|
|
6
|
+
import type { WorkflowStage, WorkflowStageExecutionResult, WorkflowExecutionOptions } from './workflow-types';
|
|
7
|
+
import { PLANNING_SYSTEM_PROMPT } from './agents/planning';
|
|
8
|
+
import { EXECUTION_SYSTEM_PROMPT } from './agents/execution';
|
|
9
|
+
import { PromptBuilder } from './prompt-builder';
|
|
10
|
+
import { POSTHOG_MCP } from './utils/mcp';
|
|
11
|
+
|
|
12
|
+
export class StageExecutor {
|
|
13
|
+
private registry: AgentRegistry;
|
|
14
|
+
private logger: Logger;
|
|
15
|
+
private eventTransformer: EventTransformer;
|
|
16
|
+
private promptBuilder: PromptBuilder;
|
|
17
|
+
|
|
18
|
+
constructor(registry: AgentRegistry, logger: Logger, promptBuilder?: PromptBuilder) {
|
|
19
|
+
this.registry = registry;
|
|
20
|
+
this.logger = logger.child('StageExecutor');
|
|
21
|
+
this.eventTransformer = new EventTransformer();
|
|
22
|
+
this.promptBuilder = promptBuilder || new PromptBuilder({
|
|
23
|
+
getTaskFiles: async () => [],
|
|
24
|
+
generatePlanTemplate: async () => '',
|
|
25
|
+
logger,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async execute(task: Task, stage: WorkflowStage, options: WorkflowExecutionOptions): Promise<WorkflowStageExecutionResult> {
|
|
30
|
+
const isManual = stage.is_manual_only === true;
|
|
31
|
+
if (isManual) {
|
|
32
|
+
this.logger.info('Manual stage detected; skipping agent execution', { stage: stage.key });
|
|
33
|
+
return { results: [] };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const inferredAgent = stage.key.toLowerCase().includes('plan') ? 'planning_basic' : 'code_generation';
|
|
37
|
+
const agentName = stage.agent_name || inferredAgent;
|
|
38
|
+
const agent = this.registry.getAgent(agentName);
|
|
39
|
+
if (!agent) {
|
|
40
|
+
throw new Error(`Unknown agent '${agentName}' for stage '${stage.key}'`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const permissionMode = (options.permissionMode as any) || 'acceptEdits';
|
|
44
|
+
const cwd = options.repositoryPath || process.cwd();
|
|
45
|
+
|
|
46
|
+
switch (agent.agent_type) {
|
|
47
|
+
case 'planning':
|
|
48
|
+
return this.runPlanning(task, cwd, options, stage.key);
|
|
49
|
+
case 'execution':
|
|
50
|
+
return this.runExecution(task, cwd, permissionMode, options, stage.key);
|
|
51
|
+
case 'review': // TODO: Implement review
|
|
52
|
+
case 'testing': // TODO: Implement testing
|
|
53
|
+
default:
|
|
54
|
+
// throw new Error(`Unsupported agent type: ${agent.agent_type}`);
|
|
55
|
+
console.warn(`Unsupported agent type: ${agent.agent_type}`);
|
|
56
|
+
return { results: [] };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private async runPlanning(task: Task, cwd: string, options: WorkflowExecutionOptions, stageKey: string): Promise<WorkflowStageExecutionResult> {
|
|
61
|
+
const contextPrompt = await this.promptBuilder.buildPlanningPrompt(task);
|
|
62
|
+
let prompt = PLANNING_SYSTEM_PROMPT + '\n\n' + contextPrompt;
|
|
63
|
+
|
|
64
|
+
const stageOverrides = options.stageOverrides?.[stageKey] || options.stageOverrides?.['plan'];
|
|
65
|
+
const mergedOverrides = {
|
|
66
|
+
...(options.queryOverrides || {}),
|
|
67
|
+
...(stageOverrides?.queryOverrides || {}),
|
|
68
|
+
} as Record<string, any>;
|
|
69
|
+
|
|
70
|
+
const baseOptions: Record<string, any> = {
|
|
71
|
+
model: 'claude-sonnet-4-5-20250929',
|
|
72
|
+
cwd,
|
|
73
|
+
permissionMode: 'plan',
|
|
74
|
+
settingSources: ['local'],
|
|
75
|
+
mcpServers: {
|
|
76
|
+
...POSTHOG_MCP
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const response = query({
|
|
81
|
+
prompt,
|
|
82
|
+
options: { ...baseOptions, ...mergedOverrides },
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
let plan = '';
|
|
86
|
+
for await (const message of response) {
|
|
87
|
+
const transformed = this.eventTransformer.transform(message);
|
|
88
|
+
if (transformed && transformed.type !== 'token') {
|
|
89
|
+
this.logger.debug('Planning event', { type: transformed.type });
|
|
90
|
+
}
|
|
91
|
+
if (message.type === 'assistant' && message.message?.content) {
|
|
92
|
+
for (const c of message.message.content) {
|
|
93
|
+
if (c.type === 'text' && c.text) plan += c.text + '\n';
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return { plan: plan.trim() };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private async runExecution(task: Task, cwd: string, permissionMode: WorkflowExecutionOptions['permissionMode'], options: WorkflowExecutionOptions, stageKey: string): Promise<WorkflowStageExecutionResult> {
|
|
102
|
+
const contextPrompt = await this.promptBuilder.buildExecutionPrompt(task);
|
|
103
|
+
let prompt = EXECUTION_SYSTEM_PROMPT + '\n\n' + contextPrompt;
|
|
104
|
+
|
|
105
|
+
const stageOverrides = options.stageOverrides?.[stageKey];
|
|
106
|
+
const mergedOverrides = {
|
|
107
|
+
...(options.queryOverrides || {}),
|
|
108
|
+
...(stageOverrides?.queryOverrides || {}),
|
|
109
|
+
} as Record<string, any>;
|
|
110
|
+
|
|
111
|
+
const baseOptions: Record<string, any> = {
|
|
112
|
+
model: 'claude-sonnet-4-5-20250929',
|
|
113
|
+
cwd,
|
|
114
|
+
permissionMode,
|
|
115
|
+
settingSources: ['local'],
|
|
116
|
+
mcpServers: {
|
|
117
|
+
...POSTHOG_MCP
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const response = query({
|
|
122
|
+
prompt,
|
|
123
|
+
options: { ...baseOptions, ...mergedOverrides },
|
|
124
|
+
});
|
|
125
|
+
const results: any[] = [];
|
|
126
|
+
for await (const message of response) {
|
|
127
|
+
const transformed = this.eventTransformer.transform(message);
|
|
128
|
+
if (transformed && transformed.type !== 'token') {
|
|
129
|
+
this.logger.debug('Execution event', { type: transformed.type });
|
|
130
|
+
}
|
|
131
|
+
results.push(message);
|
|
132
|
+
}
|
|
133
|
+
return { results };
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { randomBytes } from 'crypto';
|
|
2
|
+
|
|
3
|
+
export interface TaskExecutionState {
|
|
4
|
+
taskId: string;
|
|
5
|
+
status: 'running' | 'completed' | 'failed' | 'canceled' | 'timeout';
|
|
6
|
+
mode: 'plan_only' | 'plan_and_build' | 'build_only';
|
|
7
|
+
result?: any;
|
|
8
|
+
startedAt: number;
|
|
9
|
+
completedAt?: number;
|
|
10
|
+
abortController?: AbortController;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class TaskManager {
|
|
14
|
+
private executionStates = new Map<string, TaskExecutionState>();
|
|
15
|
+
private defaultTimeout = 10 * 60 * 1000; // 10 minutes
|
|
16
|
+
|
|
17
|
+
generateExecutionId(): string {
|
|
18
|
+
return randomBytes(16).toString('hex');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
startExecution(
|
|
22
|
+
taskId: string,
|
|
23
|
+
mode: 'plan_only' | 'plan_and_build' | 'build_only',
|
|
24
|
+
executionId: string = this.generateExecutionId()
|
|
25
|
+
): TaskExecutionState {
|
|
26
|
+
const executionState: TaskExecutionState = {
|
|
27
|
+
taskId,
|
|
28
|
+
status: 'running',
|
|
29
|
+
mode,
|
|
30
|
+
startedAt: Date.now(),
|
|
31
|
+
abortController: new AbortController(),
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
this.executionStates.set(executionId, executionState);
|
|
35
|
+
this.scheduleTimeout(executionId);
|
|
36
|
+
|
|
37
|
+
return executionState;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async waitForCompletion(executionId: string): Promise<any> {
|
|
41
|
+
const execution = this.executionStates.get(executionId);
|
|
42
|
+
if (!execution) {
|
|
43
|
+
throw new Error(`Execution ${executionId} not found`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (execution.result && execution.status === 'completed') {
|
|
47
|
+
return execution.result;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return new Promise((resolve, reject) => {
|
|
51
|
+
const checkInterval = setInterval(() => {
|
|
52
|
+
const currentExecution = this.executionStates.get(executionId);
|
|
53
|
+
if (!currentExecution) {
|
|
54
|
+
clearInterval(checkInterval);
|
|
55
|
+
reject(new Error(`Execution ${executionId} disappeared`));
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (currentExecution.status === 'completed' && currentExecution.result) {
|
|
60
|
+
clearInterval(checkInterval);
|
|
61
|
+
resolve(currentExecution.result);
|
|
62
|
+
} else if (
|
|
63
|
+
currentExecution.status === 'failed' ||
|
|
64
|
+
currentExecution.status === 'canceled' ||
|
|
65
|
+
currentExecution.status === 'timeout'
|
|
66
|
+
) {
|
|
67
|
+
clearInterval(checkInterval);
|
|
68
|
+
reject(new Error(`Execution ${executionId} ${currentExecution.status}`));
|
|
69
|
+
}
|
|
70
|
+
}, 100);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
completeExecution(executionId: string, result: any): void {
|
|
75
|
+
const execution = this.executionStates.get(executionId);
|
|
76
|
+
if (!execution) {
|
|
77
|
+
throw new Error(`Execution ${executionId} not found`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
execution.status = 'completed';
|
|
81
|
+
execution.result = result;
|
|
82
|
+
execution.completedAt = Date.now();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
failExecution(executionId: string, error: Error): void {
|
|
86
|
+
const execution = this.executionStates.get(executionId);
|
|
87
|
+
if (!execution) {
|
|
88
|
+
throw new Error(`Execution ${executionId} not found`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
execution.status = 'failed';
|
|
92
|
+
execution.completedAt = Date.now();
|
|
93
|
+
execution.result = {
|
|
94
|
+
error: error.message,
|
|
95
|
+
status: 'failed',
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
cancelExecution(executionId: string): void {
|
|
100
|
+
const execution = this.executionStates.get(executionId);
|
|
101
|
+
if (!execution) {
|
|
102
|
+
throw new Error(`Execution ${executionId} not found`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
execution.status = 'canceled';
|
|
106
|
+
execution.completedAt = Date.now();
|
|
107
|
+
execution.abortController?.abort();
|
|
108
|
+
|
|
109
|
+
if (!execution.result) {
|
|
110
|
+
execution.result = {
|
|
111
|
+
status: 'canceled',
|
|
112
|
+
message: 'Execution was canceled',
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
getExecution(executionId: string): TaskExecutionState | undefined {
|
|
118
|
+
return this.executionStates.get(executionId);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
getAbortSignal(executionId: string): AbortSignal | undefined {
|
|
122
|
+
return this.executionStates.get(executionId)?.abortController?.signal;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
getAbortController(executionId: string): AbortController | undefined {
|
|
126
|
+
return this.executionStates.get(executionId)?.abortController;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private scheduleTimeout(executionId: string, timeout: number = this.defaultTimeout): void {
|
|
130
|
+
setTimeout(() => {
|
|
131
|
+
const execution = this.executionStates.get(executionId);
|
|
132
|
+
if (execution && execution.status === 'running') {
|
|
133
|
+
execution.status = 'timeout';
|
|
134
|
+
execution.completedAt = Date.now();
|
|
135
|
+
execution.abortController?.abort();
|
|
136
|
+
|
|
137
|
+
if (!execution.result) {
|
|
138
|
+
execution.result = {
|
|
139
|
+
status: 'timeout',
|
|
140
|
+
message: 'Execution timed out',
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}, timeout);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
cleanup(olderThan: number = 60 * 60 * 1000): void {
|
|
148
|
+
const cutoff = Date.now() - olderThan;
|
|
149
|
+
for (const [executionId, execution] of this.executionStates) {
|
|
150
|
+
if (execution.completedAt && execution.completedAt < cutoff) {
|
|
151
|
+
this.executionStates.delete(executionId);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import { join, dirname } from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
|
|
5
|
+
export interface TemplateVariables {
|
|
6
|
+
task_id: string;
|
|
7
|
+
task_title: string;
|
|
8
|
+
task_description?: string;
|
|
9
|
+
date: string;
|
|
10
|
+
repository?: string;
|
|
11
|
+
[key: string]: string | undefined;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class TemplateManager {
|
|
15
|
+
private templatesDir: string;
|
|
16
|
+
|
|
17
|
+
constructor() {
|
|
18
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
19
|
+
const __dirname = dirname(__filename);
|
|
20
|
+
this.templatesDir = join(__dirname, 'templates');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
private async loadTemplate(templateName: string): Promise<string> {
|
|
24
|
+
try {
|
|
25
|
+
const templatePath = join(this.templatesDir, templateName);
|
|
26
|
+
return await fs.readFile(templatePath, 'utf8');
|
|
27
|
+
} catch (error) {
|
|
28
|
+
throw new Error(`Failed to load template ${templateName}: ${error}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
private substituteVariables(template: string, variables: TemplateVariables): string {
|
|
33
|
+
let result = template;
|
|
34
|
+
|
|
35
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
36
|
+
if (value !== undefined) {
|
|
37
|
+
const placeholder = new RegExp(`{{${key}}}`, 'g');
|
|
38
|
+
result = result.replace(placeholder, value);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
result = result.replace(/{{[^}]+}}/g, '[PLACEHOLDER]');
|
|
43
|
+
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async generatePlan(variables: TemplateVariables): Promise<string> {
|
|
48
|
+
const template = await this.loadTemplate('plan-template.md');
|
|
49
|
+
return this.substituteVariables(template, {
|
|
50
|
+
...variables,
|
|
51
|
+
date: variables.date || new Date().toISOString().split('T')[0]
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
async generateCustomFile(templateName: string, variables: TemplateVariables): Promise<string> {
|
|
57
|
+
const template = await this.loadTemplate(templateName);
|
|
58
|
+
return this.substituteVariables(template, {
|
|
59
|
+
...variables,
|
|
60
|
+
date: variables.date || new Date().toISOString().split('T')[0]
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async createTaskStructure(taskId: string, taskTitle: string, options?: {
|
|
65
|
+
includePlan?: boolean;
|
|
66
|
+
additionalFiles?: Array<{
|
|
67
|
+
name: string;
|
|
68
|
+
template?: string;
|
|
69
|
+
content?: string;
|
|
70
|
+
}>;
|
|
71
|
+
}): Promise<Array<{ name: string; content: string; type: 'plan' | 'context' | 'reference' | 'output' }>> {
|
|
72
|
+
const files: Array<{ name: string; content: string; type: 'plan' | 'context' | 'reference' | 'output' }> = [];
|
|
73
|
+
|
|
74
|
+
const variables: TemplateVariables = {
|
|
75
|
+
task_id: taskId,
|
|
76
|
+
task_title: taskTitle,
|
|
77
|
+
date: new Date().toISOString().split('T')[0]
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// Generate plan file if requested
|
|
81
|
+
if (options?.includePlan !== false) {
|
|
82
|
+
const planContent = await this.generatePlan(variables);
|
|
83
|
+
files.push({
|
|
84
|
+
name: 'plan.md',
|
|
85
|
+
content: planContent,
|
|
86
|
+
type: 'plan'
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
if (options?.additionalFiles) {
|
|
92
|
+
for (const file of options.additionalFiles) {
|
|
93
|
+
let content: string;
|
|
94
|
+
|
|
95
|
+
if (file.template) {
|
|
96
|
+
content = await this.generateCustomFile(file.template, variables);
|
|
97
|
+
} else if (file.content) {
|
|
98
|
+
content = this.substituteVariables(file.content, variables);
|
|
99
|
+
} else {
|
|
100
|
+
content = `# ${file.name}\n\nPlaceholder content for ${file.name}`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
files.push({
|
|
104
|
+
name: file.name,
|
|
105
|
+
content,
|
|
106
|
+
type: file.name.includes('context') ? 'context' : 'reference'
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return files;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
generatePostHogReadme(): string {
|
|
115
|
+
return `# PostHog Task Files
|
|
116
|
+
|
|
117
|
+
This directory contains task-related files generated by the PostHog Agent.
|
|
118
|
+
|
|
119
|
+
## Structure
|
|
120
|
+
|
|
121
|
+
Each task has its own subdirectory: \`.posthog/{task-id}/\`
|
|
122
|
+
|
|
123
|
+
### Common Files
|
|
124
|
+
|
|
125
|
+
- **plan.md** - Implementation plan generated during planning phase
|
|
126
|
+
- **Supporting files** - Any additional files added for task context
|
|
127
|
+
- **artifacts/** - Generated files, outputs, and temporary artifacts
|
|
128
|
+
|
|
129
|
+
### Usage
|
|
130
|
+
|
|
131
|
+
These files are:
|
|
132
|
+
- Version controlled alongside your code
|
|
133
|
+
- Used by the PostHog Agent for context
|
|
134
|
+
- Available for review in pull requests
|
|
135
|
+
- Organized by task ID for easy reference
|
|
136
|
+
|
|
137
|
+
### Gitignore
|
|
138
|
+
|
|
139
|
+
Customize \`.posthog/.gitignore\` to control which files are committed:
|
|
140
|
+
- Include plans and documentation by default
|
|
141
|
+
- Exclude temporary files and sensitive data
|
|
142
|
+
- Customize based on your team's workflow
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
*Generated by PostHog Agent*
|
|
147
|
+
`;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Implementation Plan: {{task_title}}
|
|
2
|
+
|
|
3
|
+
**Task ID:** {{task_id}}
|
|
4
|
+
**Generated:** {{date}}
|
|
5
|
+
|
|
6
|
+
## Summary
|
|
7
|
+
|
|
8
|
+
Brief description of what will be implemented and the overall approach.
|
|
9
|
+
|
|
10
|
+
## Implementation Steps
|
|
11
|
+
|
|
12
|
+
### 1. Analysis
|
|
13
|
+
- [ ] Identify relevant files and components
|
|
14
|
+
- [ ] Review existing patterns and constraints
|
|
15
|
+
|
|
16
|
+
### 2. Changes Required
|
|
17
|
+
- [ ] Files to create/modify
|
|
18
|
+
- [ ] Dependencies to add/update
|
|
19
|
+
|
|
20
|
+
### 3. Implementation
|
|
21
|
+
- [ ] Core functionality changes
|
|
22
|
+
- [ ] Tests and validation
|
|
23
|
+
- [ ] Documentation updates
|
|
24
|
+
|
|
25
|
+
## File Changes
|
|
26
|
+
|
|
27
|
+
### New Files
|
|
28
|
+
```
|
|
29
|
+
path/to/new/file.ts - Purpose
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Modified Files
|
|
33
|
+
```
|
|
34
|
+
path/to/existing/file.ts - Changes needed
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Considerations
|
|
38
|
+
|
|
39
|
+
- Key architectural decisions
|
|
40
|
+
- Potential risks and mitigation
|
|
41
|
+
- Testing approach
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
*Generated by PostHog Agent*
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
// PostHog Task model (matches Array's OpenAPI schema)
|
|
2
|
+
export interface Task {
|
|
3
|
+
id: string;
|
|
4
|
+
title: string;
|
|
5
|
+
description: string;
|
|
6
|
+
origin_product: 'error_tracking' | 'eval_clusters' | 'user_created' | 'support_queue' | 'session_summaries';
|
|
7
|
+
position?: number;
|
|
8
|
+
workflow?: string | null;
|
|
9
|
+
current_stage?: string | null;
|
|
10
|
+
github_integration?: number | null;
|
|
11
|
+
repository_config?: unknown; // JSONField
|
|
12
|
+
repository_list: string;
|
|
13
|
+
primary_repository: string;
|
|
14
|
+
github_branch: string | null;
|
|
15
|
+
github_pr_url: string | null;
|
|
16
|
+
created_at: string;
|
|
17
|
+
updated_at: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface SupportingFile {
|
|
21
|
+
name: string;
|
|
22
|
+
content: string;
|
|
23
|
+
type: 'plan' | 'context' | 'reference' | 'output';
|
|
24
|
+
created_at: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Removed legacy ExecutionMode in favor of configurable workflows
|
|
28
|
+
|
|
29
|
+
export enum PermissionMode {
|
|
30
|
+
PLAN = "plan",
|
|
31
|
+
DEFAULT = "default",
|
|
32
|
+
ACCEPT_EDITS = "acceptEdits",
|
|
33
|
+
BYPASS = "bypassPermissions"
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface ExecutionOptions {
|
|
37
|
+
repositoryPath?: string;
|
|
38
|
+
permissionMode?: PermissionMode;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Base event with timestamp
|
|
42
|
+
interface BaseEvent {
|
|
43
|
+
ts: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Streaming content events
|
|
47
|
+
export interface TokenEvent extends BaseEvent {
|
|
48
|
+
type: 'token';
|
|
49
|
+
content: string;
|
|
50
|
+
contentType?: 'text' | 'thinking' | 'tool_input';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface ContentBlockStartEvent extends BaseEvent {
|
|
54
|
+
type: 'content_block_start';
|
|
55
|
+
index: number;
|
|
56
|
+
contentType: 'text' | 'tool_use' | 'thinking';
|
|
57
|
+
toolName?: string;
|
|
58
|
+
toolId?: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface ContentBlockStopEvent extends BaseEvent {
|
|
62
|
+
type: 'content_block_stop';
|
|
63
|
+
index: number;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Tool events
|
|
67
|
+
export interface ToolCallEvent extends BaseEvent {
|
|
68
|
+
type: 'tool_call';
|
|
69
|
+
toolName: string;
|
|
70
|
+
callId: string;
|
|
71
|
+
args: Record<string, any>;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface ToolResultEvent extends BaseEvent {
|
|
75
|
+
type: 'tool_result';
|
|
76
|
+
toolName: string;
|
|
77
|
+
callId: string;
|
|
78
|
+
result: any;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Message lifecycle events
|
|
82
|
+
export interface MessageStartEvent extends BaseEvent {
|
|
83
|
+
type: 'message_start';
|
|
84
|
+
messageId?: string;
|
|
85
|
+
model?: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface MessageDeltaEvent extends BaseEvent {
|
|
89
|
+
type: 'message_delta';
|
|
90
|
+
stopReason?: string;
|
|
91
|
+
stopSequence?: string;
|
|
92
|
+
usage?: {
|
|
93
|
+
outputTokens: number;
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface MessageStopEvent extends BaseEvent {
|
|
98
|
+
type: 'message_stop';
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// User message events
|
|
102
|
+
export interface UserMessageEvent extends BaseEvent {
|
|
103
|
+
type: 'user_message';
|
|
104
|
+
content: string;
|
|
105
|
+
isSynthetic?: boolean;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// System events
|
|
109
|
+
export interface StatusEvent extends BaseEvent {
|
|
110
|
+
type: 'status';
|
|
111
|
+
phase: string;
|
|
112
|
+
[key: string]: any;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface InitEvent extends BaseEvent {
|
|
116
|
+
type: 'init';
|
|
117
|
+
model: string;
|
|
118
|
+
tools: string[];
|
|
119
|
+
permissionMode: string;
|
|
120
|
+
cwd: string;
|
|
121
|
+
apiKeySource: string;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export interface CompactBoundaryEvent extends BaseEvent {
|
|
125
|
+
type: 'compact_boundary';
|
|
126
|
+
trigger: 'manual' | 'auto';
|
|
127
|
+
preTokens: number;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Result events
|
|
131
|
+
export interface DoneEvent extends BaseEvent {
|
|
132
|
+
type: 'done';
|
|
133
|
+
durationMs?: number;
|
|
134
|
+
numTurns?: number;
|
|
135
|
+
totalCostUsd?: number;
|
|
136
|
+
usage?: any;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export interface ErrorEvent extends BaseEvent {
|
|
140
|
+
type: 'error';
|
|
141
|
+
message: string;
|
|
142
|
+
error?: any;
|
|
143
|
+
errorType?: string;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Legacy events (keeping for backwards compatibility)
|
|
147
|
+
export interface DiffEvent extends BaseEvent {
|
|
148
|
+
type: 'diff';
|
|
149
|
+
file: string;
|
|
150
|
+
patch: string;
|
|
151
|
+
summary?: string;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export interface FileWriteEvent extends BaseEvent {
|
|
155
|
+
type: 'file_write';
|
|
156
|
+
path: string;
|
|
157
|
+
bytes: number;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export interface MetricEvent extends BaseEvent {
|
|
161
|
+
type: 'metric';
|
|
162
|
+
key: string;
|
|
163
|
+
value: number;
|
|
164
|
+
unit?: string;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export interface ArtifactEvent extends BaseEvent {
|
|
168
|
+
type: 'artifact';
|
|
169
|
+
kind: string;
|
|
170
|
+
content: any;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export type AgentEvent =
|
|
174
|
+
| TokenEvent
|
|
175
|
+
| ContentBlockStartEvent
|
|
176
|
+
| ContentBlockStopEvent
|
|
177
|
+
| ToolCallEvent
|
|
178
|
+
| ToolResultEvent
|
|
179
|
+
| MessageStartEvent
|
|
180
|
+
| MessageDeltaEvent
|
|
181
|
+
| MessageStopEvent
|
|
182
|
+
| UserMessageEvent
|
|
183
|
+
| StatusEvent
|
|
184
|
+
| InitEvent
|
|
185
|
+
| CompactBoundaryEvent
|
|
186
|
+
| DoneEvent
|
|
187
|
+
| ErrorEvent
|
|
188
|
+
| DiffEvent
|
|
189
|
+
| FileWriteEvent
|
|
190
|
+
| MetricEvent
|
|
191
|
+
| ArtifactEvent;
|
|
192
|
+
|
|
193
|
+
export interface ExecutionResult {
|
|
194
|
+
results: any[];
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export interface PlanResult {
|
|
198
|
+
plan: string;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export interface TaskExecutionResult {
|
|
202
|
+
task: Task;
|
|
203
|
+
plan?: string;
|
|
204
|
+
executionResult?: ExecutionResult;
|
|
205
|
+
// Deprecated: mode removed in workflow-based execution
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export interface AgentConfig {
|
|
209
|
+
workingDirectory?: string;
|
|
210
|
+
onEvent?: (event: AgentEvent) => void;
|
|
211
|
+
|
|
212
|
+
// PostHog API configuration
|
|
213
|
+
posthogApiUrl?: string;
|
|
214
|
+
posthogApiKey?: string;
|
|
215
|
+
|
|
216
|
+
// Logging configuration
|
|
217
|
+
debug?: boolean;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export interface PostHogAPIConfig {
|
|
221
|
+
apiUrl: string;
|
|
222
|
+
apiKey: string;
|
|
223
|
+
}
|