@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,344 @@
|
|
|
1
|
+
import { exec } from 'child_process';
|
|
2
|
+
import { promisify } from 'util';
|
|
3
|
+
import { Logger } from './utils/logger';
|
|
4
|
+
|
|
5
|
+
const execAsync = promisify(exec);
|
|
6
|
+
|
|
7
|
+
export interface GitConfig {
|
|
8
|
+
repositoryPath: string;
|
|
9
|
+
authorName?: string;
|
|
10
|
+
authorEmail?: string;
|
|
11
|
+
logger?: Logger;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface BranchInfo {
|
|
15
|
+
name: string;
|
|
16
|
+
exists: boolean;
|
|
17
|
+
isCurrentBranch: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class GitManager {
|
|
21
|
+
private repositoryPath: string;
|
|
22
|
+
private authorName?: string;
|
|
23
|
+
private authorEmail?: string;
|
|
24
|
+
private logger: Logger;
|
|
25
|
+
|
|
26
|
+
constructor(config: GitConfig) {
|
|
27
|
+
this.repositoryPath = config.repositoryPath;
|
|
28
|
+
this.authorName = config.authorName;
|
|
29
|
+
this.authorEmail = config.authorEmail;
|
|
30
|
+
this.logger = config.logger || new Logger({ debug: false, prefix: '[GitManager]' });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private async runGitCommand(command: string): Promise<string> {
|
|
34
|
+
try {
|
|
35
|
+
const { stdout } = await execAsync(`cd "${this.repositoryPath}" && git ${command}`);
|
|
36
|
+
return stdout.trim();
|
|
37
|
+
} catch (error) {
|
|
38
|
+
throw new Error(`Git command failed: ${command}\n${error}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private async runCommand(command: string): Promise<string> {
|
|
43
|
+
try {
|
|
44
|
+
const { stdout } = await execAsync(`cd "${this.repositoryPath}" && ${command}`);
|
|
45
|
+
return stdout.trim();
|
|
46
|
+
} catch (error) {
|
|
47
|
+
throw new Error(`Command failed: ${command}\n${error}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async isGitRepository(): Promise<boolean> {
|
|
52
|
+
try {
|
|
53
|
+
await this.runGitCommand('rev-parse --git-dir');
|
|
54
|
+
return true;
|
|
55
|
+
} catch {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async getCurrentBranch(): Promise<string> {
|
|
61
|
+
return await this.runGitCommand('branch --show-current');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async getDefaultBranch(): Promise<string> {
|
|
65
|
+
try {
|
|
66
|
+
// Try to get the default branch from remote
|
|
67
|
+
const remoteBranch = await this.runGitCommand('symbolic-ref refs/remotes/origin/HEAD');
|
|
68
|
+
return remoteBranch.replace('refs/remotes/origin/', '');
|
|
69
|
+
} catch {
|
|
70
|
+
// Fallback: check if main exists, otherwise use master
|
|
71
|
+
if (await this.branchExists('main')) {
|
|
72
|
+
return 'main';
|
|
73
|
+
} else if (await this.branchExists('master')) {
|
|
74
|
+
return 'master';
|
|
75
|
+
} else {
|
|
76
|
+
throw new Error('Cannot determine default branch. No main or master branch found.');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async branchExists(branchName: string): Promise<boolean> {
|
|
82
|
+
try {
|
|
83
|
+
await this.runGitCommand(`rev-parse --verify ${branchName}`);
|
|
84
|
+
return true;
|
|
85
|
+
} catch {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async createBranch(branchName: string, baseBranch?: string): Promise<void> {
|
|
91
|
+
const base = baseBranch || await this.getCurrentBranch();
|
|
92
|
+
await this.runGitCommand(`checkout -b ${branchName} ${base}`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async switchToBranch(branchName: string): Promise<void> {
|
|
96
|
+
await this.runGitCommand(`checkout ${branchName}`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async createOrSwitchToBranch(branchName: string, baseBranch?: string): Promise<void> {
|
|
100
|
+
const exists = await this.branchExists(branchName);
|
|
101
|
+
if (exists) {
|
|
102
|
+
await this.switchToBranch(branchName);
|
|
103
|
+
} else {
|
|
104
|
+
await this.createBranch(branchName, baseBranch);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async addFiles(paths: string[]): Promise<void> {
|
|
109
|
+
const pathList = paths.map(p => `"${p}"`).join(' ');
|
|
110
|
+
await this.runGitCommand(`add ${pathList}`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async addAllPostHogFiles(): Promise<void> {
|
|
114
|
+
await this.runGitCommand('add .posthog/');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async commitChanges(message: string, options?: {
|
|
118
|
+
authorName?: string;
|
|
119
|
+
authorEmail?: string;
|
|
120
|
+
}): Promise<string> {
|
|
121
|
+
let command = 'commit -m "' + message.replace(/"/g, '\\"') + '"';
|
|
122
|
+
|
|
123
|
+
const authorName = options?.authorName || this.authorName;
|
|
124
|
+
const authorEmail = options?.authorEmail || this.authorEmail;
|
|
125
|
+
|
|
126
|
+
if (authorName && authorEmail) {
|
|
127
|
+
command += ` --author="${authorName} <${authorEmail}>"`;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return await this.runGitCommand(command);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async hasChanges(): Promise<boolean> {
|
|
134
|
+
try {
|
|
135
|
+
const status = await this.runGitCommand('status --porcelain');
|
|
136
|
+
return status.length > 0;
|
|
137
|
+
} catch {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async hasStagedChanges(): Promise<boolean> {
|
|
143
|
+
try {
|
|
144
|
+
const status = await this.runGitCommand('diff --cached --name-only');
|
|
145
|
+
return status.length > 0;
|
|
146
|
+
} catch {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async getRemoteUrl(): Promise<string | null> {
|
|
152
|
+
try {
|
|
153
|
+
return await this.runGitCommand('remote get-url origin');
|
|
154
|
+
} catch {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async pushBranch(branchName: string, force: boolean = false): Promise<void> {
|
|
160
|
+
const forceFlag = force ? '--force' : '';
|
|
161
|
+
await this.runGitCommand(`push ${forceFlag} -u origin ${branchName}`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Utility methods for PostHog task workflow
|
|
165
|
+
async createTaskPlanningBranch(taskId: string, baseBranch?: string): Promise<string> {
|
|
166
|
+
let branchName = `posthog/task-${taskId}-planning`;
|
|
167
|
+
let counter = 1;
|
|
168
|
+
|
|
169
|
+
// Find a unique branch name if the base name already exists
|
|
170
|
+
while (await this.branchExists(branchName)) {
|
|
171
|
+
branchName = `posthog/task-${taskId}-planning-${counter}`;
|
|
172
|
+
counter++;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
this.logger.debug('Creating unique planning branch', { branchName, taskId });
|
|
176
|
+
|
|
177
|
+
// If no base branch specified, ensure we're on main/master
|
|
178
|
+
if (!baseBranch) {
|
|
179
|
+
baseBranch = await this.getDefaultBranch();
|
|
180
|
+
await this.switchToBranch(baseBranch);
|
|
181
|
+
|
|
182
|
+
// Check for uncommitted changes
|
|
183
|
+
if (await this.hasChanges()) {
|
|
184
|
+
throw new Error(`Uncommitted changes detected. Please commit or stash changes before running tasks.`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
await this.createBranch(branchName, baseBranch); // Use createBranch instead of createOrSwitchToBranch for new branches
|
|
189
|
+
return branchName;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async createTaskImplementationBranch(taskId: string, planningBranchName?: string): Promise<string> {
|
|
193
|
+
let branchName = `posthog/task-${taskId}-implementation`;
|
|
194
|
+
let counter = 1;
|
|
195
|
+
|
|
196
|
+
// Find a unique branch name if the base name already exists
|
|
197
|
+
while (await this.branchExists(branchName)) {
|
|
198
|
+
branchName = `posthog/task-${taskId}-implementation-${counter}`;
|
|
199
|
+
counter++;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const currentBranchBefore = await this.getCurrentBranch();
|
|
203
|
+
this.logger.debug('Creating unique implementation branch', {
|
|
204
|
+
branchName,
|
|
205
|
+
taskId,
|
|
206
|
+
currentBranch: currentBranchBefore
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// Implementation branch should branch from the specific planning branch
|
|
210
|
+
let baseBranch = planningBranchName;
|
|
211
|
+
|
|
212
|
+
if (!baseBranch) {
|
|
213
|
+
// Try to find the corresponding planning branch
|
|
214
|
+
const currentBranch = await this.getCurrentBranch();
|
|
215
|
+
if (currentBranch.includes('-planning')) {
|
|
216
|
+
baseBranch = currentBranch; // Use current planning branch
|
|
217
|
+
this.logger.debug('Using current planning branch', { baseBranch });
|
|
218
|
+
} else {
|
|
219
|
+
// Fallback to default branch
|
|
220
|
+
baseBranch = await this.getDefaultBranch();
|
|
221
|
+
this.logger.debug('No planning branch found, using default', { baseBranch });
|
|
222
|
+
await this.switchToBranch(baseBranch);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
this.logger.debug('Creating implementation branch from base', { baseBranch, branchName });
|
|
227
|
+
await this.createBranch(branchName, baseBranch); // Create fresh branch from base
|
|
228
|
+
|
|
229
|
+
const currentBranchAfter = await this.getCurrentBranch();
|
|
230
|
+
this.logger.info('Implementation branch created', {
|
|
231
|
+
branchName,
|
|
232
|
+
currentBranch: currentBranchAfter
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
return branchName;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
async commitPlan(taskId: string, taskTitle: string): Promise<string> {
|
|
239
|
+
const currentBranch = await this.getCurrentBranch();
|
|
240
|
+
this.logger.debug('Committing plan', { taskId, currentBranch });
|
|
241
|
+
|
|
242
|
+
await this.addAllPostHogFiles();
|
|
243
|
+
|
|
244
|
+
const hasChanges = await this.hasStagedChanges();
|
|
245
|
+
this.logger.debug('Checking for staged changes', { hasChanges });
|
|
246
|
+
|
|
247
|
+
if (!hasChanges) {
|
|
248
|
+
this.logger.info('No plan changes to commit', { taskId });
|
|
249
|
+
return 'No changes to commit';
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const message = `📋 Add plan for task: ${taskTitle}
|
|
253
|
+
|
|
254
|
+
Task ID: ${taskId}
|
|
255
|
+
Generated by PostHog Agent
|
|
256
|
+
|
|
257
|
+
This commit contains the implementation plan and supporting documentation
|
|
258
|
+
for the task. Review the plan before proceeding with implementation.`;
|
|
259
|
+
|
|
260
|
+
const result = await this.commitChanges(message);
|
|
261
|
+
this.logger.info('Plan committed', { taskId, taskTitle });
|
|
262
|
+
return result;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async commitImplementation(taskId: string, taskTitle: string, planSummary?: string): Promise<string> {
|
|
266
|
+
await this.runGitCommand('add .');
|
|
267
|
+
|
|
268
|
+
const hasChanges = await this.hasStagedChanges();
|
|
269
|
+
if (!hasChanges) {
|
|
270
|
+
this.logger.warn('No implementation changes to commit', { taskId });
|
|
271
|
+
return 'No changes to commit';
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
let message = `✨ Implement task: ${taskTitle}
|
|
275
|
+
|
|
276
|
+
Task ID: ${taskId}
|
|
277
|
+
Generated by PostHog Agent`;
|
|
278
|
+
|
|
279
|
+
if (planSummary) {
|
|
280
|
+
message += `\n\nPlan Summary:\n${planSummary}`;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
message += `\n\nThis commit implements the changes described in the task plan.`;
|
|
284
|
+
|
|
285
|
+
const result = await this.commitChanges(message);
|
|
286
|
+
this.logger.info('Implementation committed', { taskId, taskTitle });
|
|
287
|
+
return result;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
async deleteBranch(branchName: string, force: boolean = false): Promise<void> {
|
|
291
|
+
const forceFlag = force ? '-D' : '-d';
|
|
292
|
+
await this.runGitCommand(`branch ${forceFlag} ${branchName}`);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async deleteRemoteBranch(branchName: string): Promise<void> {
|
|
296
|
+
await this.runGitCommand(`push origin --delete ${branchName}`);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
async getBranchInfo(branchName: string): Promise<BranchInfo> {
|
|
300
|
+
const exists = await this.branchExists(branchName);
|
|
301
|
+
const currentBranch = await this.getCurrentBranch();
|
|
302
|
+
|
|
303
|
+
return {
|
|
304
|
+
name: branchName,
|
|
305
|
+
exists,
|
|
306
|
+
isCurrentBranch: branchName === currentBranch
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
async getCommitSha(ref: string = 'HEAD'): Promise<string> {
|
|
311
|
+
return await this.runGitCommand(`rev-parse ${ref}`);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
async getCommitMessage(ref: string = 'HEAD'): Promise<string> {
|
|
315
|
+
return await this.runGitCommand(`log -1 --pretty=%B ${ref}`);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
async createPullRequest(
|
|
319
|
+
branchName: string,
|
|
320
|
+
title: string,
|
|
321
|
+
body: string,
|
|
322
|
+
baseBranch?: string
|
|
323
|
+
): Promise<string> {
|
|
324
|
+
const currentBranch = await this.getCurrentBranch();
|
|
325
|
+
if (currentBranch !== branchName) {
|
|
326
|
+
await this.switchToBranch(branchName);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
await this.pushBranch(branchName);
|
|
330
|
+
|
|
331
|
+
let command = `gh pr create --title "${title.replace(/"/g, '\\"')}" --body "${body.replace(/"/g, '\\"')}"`;
|
|
332
|
+
|
|
333
|
+
if (baseBranch) {
|
|
334
|
+
command += ` --base ${baseBranch}`;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
try {
|
|
338
|
+
const prUrl = await this.runCommand(command);
|
|
339
|
+
return prUrl.trim();
|
|
340
|
+
} catch (error) {
|
|
341
|
+
throw new Error(`Failed to create PR: ${error}`);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import type { Task, SupportingFile, PostHogAPIConfig } from './types';
|
|
2
|
+
import type { WorkflowDefinition, AgentDefinition } from './workflow-types';
|
|
3
|
+
|
|
4
|
+
interface PostHogApiResponse<T> {
|
|
5
|
+
results?: T[];
|
|
6
|
+
count?: number;
|
|
7
|
+
next?: string | null;
|
|
8
|
+
previous?: string | null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface TaskProgressResponse {
|
|
12
|
+
has_progress: boolean;
|
|
13
|
+
id?: string;
|
|
14
|
+
status?: "started" | "in_progress" | "completed" | "failed";
|
|
15
|
+
current_step?: string;
|
|
16
|
+
completed_steps?: number;
|
|
17
|
+
total_steps?: number;
|
|
18
|
+
progress_percentage?: number;
|
|
19
|
+
output_log?: string;
|
|
20
|
+
error_message?: string;
|
|
21
|
+
created_at?: string;
|
|
22
|
+
updated_at?: string;
|
|
23
|
+
completed_at?: string;
|
|
24
|
+
workflow_id?: string;
|
|
25
|
+
workflow_run_id?: string;
|
|
26
|
+
message?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class PostHogAPIClient {
|
|
30
|
+
private config: PostHogAPIConfig;
|
|
31
|
+
private _teamId: number | null = null;
|
|
32
|
+
|
|
33
|
+
constructor(config: PostHogAPIConfig) {
|
|
34
|
+
this.config = config;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private get baseUrl(): string {
|
|
38
|
+
const host = this.config.apiUrl.endsWith("/")
|
|
39
|
+
? this.config.apiUrl.slice(0, -1)
|
|
40
|
+
: this.config.apiUrl;
|
|
41
|
+
return host;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private get headers(): Record<string, string> {
|
|
45
|
+
return {
|
|
46
|
+
'Authorization': `Bearer ${this.config.apiKey}`,
|
|
47
|
+
'Content-Type': 'application/json',
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private async apiRequest<T>(
|
|
52
|
+
endpoint: string,
|
|
53
|
+
options: RequestInit = {}
|
|
54
|
+
): Promise<T> {
|
|
55
|
+
const url = `${this.baseUrl}${endpoint}`;
|
|
56
|
+
|
|
57
|
+
const response = await fetch(url, {
|
|
58
|
+
...options,
|
|
59
|
+
headers: {
|
|
60
|
+
...this.headers,
|
|
61
|
+
...options.headers,
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
if (!response.ok) {
|
|
66
|
+
let errorMessage: string;
|
|
67
|
+
try {
|
|
68
|
+
const errorResponse = await response.json();
|
|
69
|
+
errorMessage = `Failed request: [${response.status}] ${JSON.stringify(errorResponse)}`;
|
|
70
|
+
} catch {
|
|
71
|
+
errorMessage = `Failed request: [${response.status}] ${response.statusText}`;
|
|
72
|
+
}
|
|
73
|
+
throw new Error(errorMessage);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return response.json();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private async getTeamId(): Promise<number> {
|
|
80
|
+
if (this._teamId !== null) {
|
|
81
|
+
return this._teamId;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Fetch user info to get team ID (following Array's pattern)
|
|
85
|
+
const userResponse = await this.apiRequest<any>('/api/users/@me/');
|
|
86
|
+
|
|
87
|
+
if (!userResponse.team?.id) {
|
|
88
|
+
throw new Error('No team found for user');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const teamId = Number(userResponse.team.id);
|
|
92
|
+
this._teamId = teamId;
|
|
93
|
+
return teamId;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async fetchTask(taskId: string): Promise<Task> {
|
|
97
|
+
const teamId = await this.getTeamId();
|
|
98
|
+
return this.apiRequest<Task>(`/api/projects/${teamId}/tasks/${taskId}/`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async listTasks(filters?: {
|
|
102
|
+
repository?: string;
|
|
103
|
+
organization?: string;
|
|
104
|
+
origin_product?: string;
|
|
105
|
+
workflow?: string;
|
|
106
|
+
current_stage?: string;
|
|
107
|
+
}): Promise<Task[]> {
|
|
108
|
+
const teamId = await this.getTeamId();
|
|
109
|
+
const url = new URL(`${this.baseUrl}/api/projects/${teamId}/tasks/`);
|
|
110
|
+
|
|
111
|
+
if (filters) {
|
|
112
|
+
Object.entries(filters).forEach(([key, value]) => {
|
|
113
|
+
if (value) url.searchParams.append(key, value);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const response = await this.apiRequest<PostHogApiResponse<Task>>(
|
|
118
|
+
url.pathname + url.search
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
return response.results || [];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async updateTask(taskId: string, updates: Partial<Task>): Promise<Task> {
|
|
125
|
+
const teamId = await this.getTeamId();
|
|
126
|
+
return this.apiRequest<Task>(`/api/projects/${teamId}/tasks/${taskId}/`, {
|
|
127
|
+
method: 'PATCH',
|
|
128
|
+
body: JSON.stringify(updates),
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async updateTaskStage(taskId: string, stageId: string): Promise<Task> {
|
|
133
|
+
const teamId = await this.getTeamId();
|
|
134
|
+
return this.apiRequest<Task>(`/api/projects/${teamId}/tasks/${taskId}/update_stage/`, {
|
|
135
|
+
method: 'PATCH',
|
|
136
|
+
body: JSON.stringify({ current_stage: stageId }),
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async getTaskProgress(taskId: string): Promise<TaskProgressResponse> {
|
|
141
|
+
const teamId = await this.getTeamId();
|
|
142
|
+
return this.apiRequest<TaskProgressResponse>(`/api/projects/${teamId}/tasks/${taskId}/progress/`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Workflow endpoints
|
|
146
|
+
async fetchWorkflow(workflowId: string): Promise<WorkflowDefinition> {
|
|
147
|
+
const teamId = await this.getTeamId();
|
|
148
|
+
return this.apiRequest<WorkflowDefinition>(`/api/projects/${teamId}/task_workflows/${workflowId}/`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async listWorkflows(): Promise<WorkflowDefinition[]> {
|
|
152
|
+
const teamId = await this.getTeamId();
|
|
153
|
+
const response = await this.apiRequest<PostHogApiResponse<WorkflowDefinition>>(`/api/projects/${teamId}/task_workflows/`);
|
|
154
|
+
return response.results || [];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Agent catalog exposure
|
|
158
|
+
async listAgents(): Promise<AgentDefinition[]> {
|
|
159
|
+
return this.apiRequest<AgentDefinition[]>(`/api/agents/`);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async progressTask(taskId: string, options?: { next_stage_id?: string; auto?: boolean }): Promise<Task> {
|
|
163
|
+
const teamId = await this.getTeamId();
|
|
164
|
+
return this.apiRequest<Task>(`/api/projects/${teamId}/tasks/${taskId}/progress_task/`, {
|
|
165
|
+
method: 'POST',
|
|
166
|
+
body: JSON.stringify(options || {}),
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { Task } from './types';
|
|
2
|
+
import type { TemplateVariables } from './template-manager';
|
|
3
|
+
import { Logger } from './utils/logger';
|
|
4
|
+
|
|
5
|
+
export interface PromptBuilderDeps {
|
|
6
|
+
getTaskFiles: (taskId: string) => Promise<any[]>;
|
|
7
|
+
generatePlanTemplate: (vars: TemplateVariables) => Promise<string>;
|
|
8
|
+
logger?: Logger;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class PromptBuilder {
|
|
12
|
+
private getTaskFiles: PromptBuilderDeps['getTaskFiles'];
|
|
13
|
+
private generatePlanTemplate: PromptBuilderDeps['generatePlanTemplate'];
|
|
14
|
+
private logger: Logger;
|
|
15
|
+
|
|
16
|
+
constructor(deps: PromptBuilderDeps) {
|
|
17
|
+
this.getTaskFiles = deps.getTaskFiles;
|
|
18
|
+
this.generatePlanTemplate = deps.generatePlanTemplate;
|
|
19
|
+
this.logger = deps.logger || new Logger({ debug: false, prefix: '[PromptBuilder]' });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async buildPlanningPrompt(task: Task): Promise<string> {
|
|
23
|
+
let prompt = '';
|
|
24
|
+
prompt += `## Current Task\n\n**Task**: ${task.title}\n**Description**: ${task.description}`;
|
|
25
|
+
|
|
26
|
+
if ((task as any).primary_repository) {
|
|
27
|
+
prompt += `\n**Repository**: ${(task as any).primary_repository}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
const taskFiles = await this.getTaskFiles(task.id);
|
|
32
|
+
const contextFiles = taskFiles.filter((f: any) => f.type === 'context' || f.type === 'reference');
|
|
33
|
+
if (contextFiles.length > 0) {
|
|
34
|
+
prompt += `\n\n## Supporting Files`;
|
|
35
|
+
for (const file of contextFiles) {
|
|
36
|
+
prompt += `\n\n### ${file.name} (${file.type})\n${file.content}`;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
} catch (error) {
|
|
40
|
+
this.logger.debug('No existing task files found for planning', { taskId: task.id });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const templateVariables = {
|
|
44
|
+
task_id: task.id,
|
|
45
|
+
task_title: task.title,
|
|
46
|
+
task_description: task.description,
|
|
47
|
+
date: new Date().toISOString().split('T')[0],
|
|
48
|
+
repository: ((task as any).primary_repository || '') as string,
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const planTemplate = await this.generatePlanTemplate(templateVariables);
|
|
52
|
+
|
|
53
|
+
prompt += `\n\nPlease analyze the codebase and create a detailed implementation plan for this task. Use the following template structure for your plan:\n\n${planTemplate}\n\nFill in each section with specific, actionable information based on your analysis. Replace all placeholder content with actual details about this task.`;
|
|
54
|
+
|
|
55
|
+
return prompt;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async buildExecutionPrompt(task: Task): Promise<string> {
|
|
59
|
+
let prompt = '';
|
|
60
|
+
prompt += `## Current Task\n\n**Task**: ${task.title}\n**Description**: ${task.description}`;
|
|
61
|
+
|
|
62
|
+
if ((task as any).primary_repository) {
|
|
63
|
+
prompt += `\n**Repository**: ${(task as any).primary_repository}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const taskFiles = await this.getTaskFiles(task.id);
|
|
68
|
+
const hasPlan = taskFiles.some((f: any) => f.type === 'plan');
|
|
69
|
+
if (taskFiles.length > 0) {
|
|
70
|
+
prompt += `\n\n## Context and Supporting Information`;
|
|
71
|
+
for (const file of taskFiles) {
|
|
72
|
+
if (file.type === 'plan') {
|
|
73
|
+
prompt += `\n\n### Execution Plan\n${file.content}`;
|
|
74
|
+
} else {
|
|
75
|
+
prompt += `\n\n### ${file.name} (${file.type})\n${file.content}`;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (hasPlan) {
|
|
80
|
+
prompt += `\n\nPlease implement the changes described in the execution plan above. Follow the plan step-by-step and make the necessary file modifications. You must actually edit files and make changes - do not just analyze or review.`;
|
|
81
|
+
} else {
|
|
82
|
+
prompt += `\n\nPlease implement the changes described in the task above. You must actually edit files and make changes - do not just analyze or review.`;
|
|
83
|
+
}
|
|
84
|
+
} catch (error) {
|
|
85
|
+
this.logger.debug('No supporting files found for execution', { taskId: task.id });
|
|
86
|
+
prompt += `\n\nPlease implement the changes described in the task above.`;
|
|
87
|
+
}
|
|
88
|
+
return prompt;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
|