@posthog/agent 1.1.0 → 1.2.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 +68 -35
- package/README.md +46 -14
- package/dist/src/agent.d.ts +4 -1
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +38 -8
- package/dist/src/agent.js.map +1 -1
- package/dist/src/event-transformer.d.ts +2 -0
- package/dist/src/event-transformer.d.ts.map +1 -1
- package/dist/src/event-transformer.js +53 -5
- package/dist/src/event-transformer.js.map +1 -1
- package/dist/src/posthog-api.d.ts +34 -0
- package/dist/src/posthog-api.d.ts.map +1 -1
- package/dist/src/posthog-api.js +38 -0
- package/dist/src/posthog-api.js.map +1 -1
- package/dist/src/stage-executor.d.ts +4 -2
- package/dist/src/stage-executor.d.ts.map +1 -1
- package/dist/src/stage-executor.js +16 -5
- package/dist/src/stage-executor.js.map +1 -1
- package/dist/src/task-progress-reporter.d.ts +44 -0
- package/dist/src/task-progress-reporter.d.ts.map +1 -0
- package/dist/src/task-progress-reporter.js +234 -0
- package/dist/src/task-progress-reporter.js.map +1 -0
- package/package.json +1 -1
- package/src/agent.ts +41 -8
- package/src/event-transformer.ts +61 -7
- package/src/posthog-api.ts +79 -0
- package/src/stage-executor.ts +24 -8
- package/src/task-progress-reporter.ts +287 -0
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persists task execution progress to PostHog so clients can poll for updates.
|
|
3
|
+
*
|
|
4
|
+
* The reporter is intentionally best-effort – failures are logged but never
|
|
5
|
+
* allowed to break the agent execution flow.
|
|
6
|
+
*/
|
|
7
|
+
class TaskProgressReporter {
|
|
8
|
+
posthogAPI;
|
|
9
|
+
logger;
|
|
10
|
+
progressRecord;
|
|
11
|
+
taskId;
|
|
12
|
+
outputLog = [];
|
|
13
|
+
totalSteps;
|
|
14
|
+
lastLogEntry;
|
|
15
|
+
constructor(posthogAPI, logger) {
|
|
16
|
+
this.posthogAPI = posthogAPI;
|
|
17
|
+
this.logger = logger.child('TaskProgressReporter');
|
|
18
|
+
}
|
|
19
|
+
get progressId() {
|
|
20
|
+
return this.progressRecord?.id;
|
|
21
|
+
}
|
|
22
|
+
async start(taskId, metadata = {}) {
|
|
23
|
+
if (!this.posthogAPI) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
this.taskId = taskId;
|
|
27
|
+
this.totalSteps = metadata.totalSteps;
|
|
28
|
+
try {
|
|
29
|
+
const record = await this.posthogAPI.createTaskProgress(taskId, {
|
|
30
|
+
status: 'started',
|
|
31
|
+
current_step: 'initializing',
|
|
32
|
+
total_steps: metadata.totalSteps ?? 0,
|
|
33
|
+
completed_steps: 0,
|
|
34
|
+
workflow_id: metadata.workflowId,
|
|
35
|
+
workflow_run_id: metadata.workflowRunId,
|
|
36
|
+
activity_id: metadata.activityId,
|
|
37
|
+
output_log: '',
|
|
38
|
+
});
|
|
39
|
+
this.progressRecord = record;
|
|
40
|
+
this.outputLog = record.output_log ? record.output_log.split('\n') : [];
|
|
41
|
+
this.logger.debug('Created task progress record', { taskId, progressId: record.id });
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
this.logger.warn('Failed to create task progress record', { taskId, error: error.message });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async stageStarted(stageKey, stageIndex) {
|
|
48
|
+
await this.update({
|
|
49
|
+
status: 'in_progress',
|
|
50
|
+
current_step: stageKey,
|
|
51
|
+
completed_steps: Math.min(stageIndex, this.totalSteps ?? stageIndex),
|
|
52
|
+
}, `Stage started: ${stageKey}`);
|
|
53
|
+
}
|
|
54
|
+
async stageCompleted(stageKey, completedStages) {
|
|
55
|
+
await this.update({
|
|
56
|
+
status: 'in_progress',
|
|
57
|
+
current_step: stageKey,
|
|
58
|
+
completed_steps: Math.min(completedStages, this.totalSteps ?? completedStages),
|
|
59
|
+
}, `Stage completed: ${stageKey}`);
|
|
60
|
+
}
|
|
61
|
+
async branchCreated(stageKey, branchName) {
|
|
62
|
+
await this.appendLog(`Branch created (${stageKey}): ${branchName}`);
|
|
63
|
+
}
|
|
64
|
+
async commitMade(stageKey, kind) {
|
|
65
|
+
await this.appendLog(`Commit made (${stageKey}, ${kind})`);
|
|
66
|
+
}
|
|
67
|
+
async pullRequestCreated(stageKey, prUrl) {
|
|
68
|
+
await this.appendLog(`Pull request created (${stageKey}): ${prUrl}`);
|
|
69
|
+
}
|
|
70
|
+
async noNextStage(stageKey) {
|
|
71
|
+
await this.appendLog(stageKey
|
|
72
|
+
? `No next stage available after '${stageKey}'. Execution halted.`
|
|
73
|
+
: 'No next stage available. Execution halted.');
|
|
74
|
+
}
|
|
75
|
+
async complete() {
|
|
76
|
+
await this.update({ status: 'completed', completed_steps: this.totalSteps }, 'Workflow execution completed');
|
|
77
|
+
}
|
|
78
|
+
async fail(error) {
|
|
79
|
+
const message = typeof error === 'string' ? error : error.message;
|
|
80
|
+
await this.update({ status: 'failed', error_message: message }, `Workflow execution failed: ${message}`);
|
|
81
|
+
}
|
|
82
|
+
async appendLog(line) {
|
|
83
|
+
await this.update({}, line);
|
|
84
|
+
}
|
|
85
|
+
async recordEvent(event) {
|
|
86
|
+
if (!this.posthogAPI || !this.progressId || !this.taskId) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
switch (event.type) {
|
|
90
|
+
case 'token':
|
|
91
|
+
case 'message_delta':
|
|
92
|
+
case 'content_block_start':
|
|
93
|
+
case 'content_block_stop':
|
|
94
|
+
case 'compact_boundary':
|
|
95
|
+
case 'tool_call':
|
|
96
|
+
case 'tool_result':
|
|
97
|
+
case 'message_start':
|
|
98
|
+
case 'message_stop':
|
|
99
|
+
case 'metric':
|
|
100
|
+
case 'artifact':
|
|
101
|
+
// Skip verbose streaming artifacts from persistence
|
|
102
|
+
return;
|
|
103
|
+
case 'file_write':
|
|
104
|
+
await this.appendLog(this.formatFileWriteEvent(event));
|
|
105
|
+
return;
|
|
106
|
+
case 'diff':
|
|
107
|
+
await this.appendLog(this.formatDiffEvent(event));
|
|
108
|
+
return;
|
|
109
|
+
case 'status':
|
|
110
|
+
// Status events are covered by dedicated progress updates
|
|
111
|
+
return;
|
|
112
|
+
case 'error':
|
|
113
|
+
await this.appendLog(`[error] ${event.message}`);
|
|
114
|
+
return;
|
|
115
|
+
case 'done': {
|
|
116
|
+
const cost = event.totalCostUsd !== undefined ? ` cost=$${event.totalCostUsd.toFixed(2)}` : '';
|
|
117
|
+
await this.appendLog(`[done] duration=${event.durationMs ?? 'unknown'}ms turns=${event.numTurns ?? 'unknown'}${cost}`);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
case 'init':
|
|
121
|
+
// Omit verbose init messages from persisted log
|
|
122
|
+
return;
|
|
123
|
+
case 'user_message': {
|
|
124
|
+
const summary = this.summarizeUserMessage(event.content);
|
|
125
|
+
if (summary) {
|
|
126
|
+
await this.appendLog(summary);
|
|
127
|
+
}
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
default:
|
|
131
|
+
// For any unfamiliar event types, avoid spamming the log.
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
async update(update, logLine) {
|
|
136
|
+
if (!this.posthogAPI || !this.progressId || !this.taskId) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (logLine) {
|
|
140
|
+
if (logLine !== this.lastLogEntry) {
|
|
141
|
+
this.outputLog.push(logLine);
|
|
142
|
+
this.lastLogEntry = logLine;
|
|
143
|
+
}
|
|
144
|
+
update.output_log = this.outputLog.join('\n');
|
|
145
|
+
}
|
|
146
|
+
try {
|
|
147
|
+
const record = await this.posthogAPI.updateTaskProgress(this.taskId, this.progressId, update);
|
|
148
|
+
// Sync local cache with server response to avoid drift if server modifies values
|
|
149
|
+
this.progressRecord = record;
|
|
150
|
+
if (record.output_log !== undefined && record.output_log !== null) {
|
|
151
|
+
this.outputLog = record.output_log ? record.output_log.split('\n') : [];
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
this.logger.warn('Failed to update task progress record', {
|
|
156
|
+
taskId: this.taskId,
|
|
157
|
+
progressId: this.progressId,
|
|
158
|
+
error: error.message,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
summarizeUserMessage(content) {
|
|
163
|
+
if (!content) {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
const trimmed = content.trim();
|
|
167
|
+
if (!trimmed) {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
const fileUpdateMatch = trimmed.match(/The file\s+([^\s]+)\s+has been updated/i);
|
|
171
|
+
if (fileUpdateMatch) {
|
|
172
|
+
return `[user] file updated: ${fileUpdateMatch[1]}`;
|
|
173
|
+
}
|
|
174
|
+
if (/Todos have been modified/i.test(trimmed)) {
|
|
175
|
+
return '[todo] list updated';
|
|
176
|
+
}
|
|
177
|
+
const diffMatch = trimmed.match(/diff --git a\/([^\s]+) b\/([^\s]+)/);
|
|
178
|
+
if (diffMatch) {
|
|
179
|
+
return `[diff] ${diffMatch[2] ?? diffMatch[1]}`;
|
|
180
|
+
}
|
|
181
|
+
const gitStatusMatch = trimmed.match(/^On branch ([^\n]+)/);
|
|
182
|
+
if (gitStatusMatch) {
|
|
183
|
+
return `[git] status ${gitStatusMatch[1]}`;
|
|
184
|
+
}
|
|
185
|
+
if (/This Bash command contains multiple operations/i.test(trimmed)) {
|
|
186
|
+
return '[approval] multi-step command pending';
|
|
187
|
+
}
|
|
188
|
+
if (/This command requires approval/i.test(trimmed)) {
|
|
189
|
+
return '[approval] command awaiting approval';
|
|
190
|
+
}
|
|
191
|
+
if (/^Exit plan mode\?/i.test(trimmed)) {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
if (trimmed.includes('node_modules')) {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
if (trimmed.includes('total ') && trimmed.includes('drwx')) {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
if (trimmed.includes('→')) {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
if (trimmed.split('\n').length > 2) {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
const normalized = trimmed.replace(/\s+/g, ' ');
|
|
207
|
+
const maxLen = 120;
|
|
208
|
+
if (!normalized) {
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
const preview = normalized.length > maxLen ? `${normalized.slice(0, maxLen)}…` : normalized;
|
|
212
|
+
return `[user] ${preview}`;
|
|
213
|
+
}
|
|
214
|
+
formatFileWriteEvent(event) {
|
|
215
|
+
const size = event.bytes !== undefined ? ` (${event.bytes} bytes)` : '';
|
|
216
|
+
return `[file] wrote ${event.path}${size}`;
|
|
217
|
+
}
|
|
218
|
+
formatDiffEvent(event) {
|
|
219
|
+
const summary = event.summary
|
|
220
|
+
? event.summary.trim()
|
|
221
|
+
: this.truncateMultiline(event.patch ?? '', 160);
|
|
222
|
+
return `[diff] ${event.file}${summary ? ` | ${summary}` : ''}`;
|
|
223
|
+
}
|
|
224
|
+
truncateMultiline(text, max = 160) {
|
|
225
|
+
if (!text) {
|
|
226
|
+
return '';
|
|
227
|
+
}
|
|
228
|
+
const compact = text.replace(/\s+/g, ' ').trim();
|
|
229
|
+
return compact.length > max ? `${compact.slice(0, max)}…` : compact;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export { TaskProgressReporter };
|
|
234
|
+
//# sourceMappingURL=task-progress-reporter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"task-progress-reporter.js","sources":["../../src/task-progress-reporter.ts"],"sourcesContent":["import type { Logger } from './utils/logger.js';\nimport type { PostHogAPIClient, TaskProgressRecord, TaskProgressUpdate } from './posthog-api.js';\nimport type { AgentEvent } from './types.js';\n\ninterface ProgressMetadata {\n workflowId?: string;\n workflowRunId?: string;\n activityId?: string;\n totalSteps?: number;\n}\n\n/**\n * Persists task execution progress to PostHog so clients can poll for updates.\n *\n * The reporter is intentionally best-effort – failures are logged but never\n * allowed to break the agent execution flow.\n */\nexport class TaskProgressReporter {\n private posthogAPI?: PostHogAPIClient;\n private logger: Logger;\n private progressRecord?: TaskProgressRecord;\n private taskId?: string;\n private outputLog: string[] = [];\n private totalSteps?: number;\n private lastLogEntry?: string;\n\n constructor(posthogAPI: PostHogAPIClient | undefined, logger: Logger) {\n this.posthogAPI = posthogAPI;\n this.logger = logger.child('TaskProgressReporter');\n }\n\n get progressId(): string | undefined {\n return this.progressRecord?.id;\n }\n\n async start(taskId: string, metadata: ProgressMetadata = {}): Promise<void> {\n if (!this.posthogAPI) {\n return;\n }\n\n this.taskId = taskId;\n this.totalSteps = metadata.totalSteps;\n\n try {\n const record = await this.posthogAPI.createTaskProgress(taskId, {\n status: 'started',\n current_step: 'initializing',\n total_steps: metadata.totalSteps ?? 0,\n completed_steps: 0,\n workflow_id: metadata.workflowId,\n workflow_run_id: metadata.workflowRunId,\n activity_id: metadata.activityId,\n output_log: '',\n });\n this.progressRecord = record;\n this.outputLog = record.output_log ? record.output_log.split('\\n') : [];\n this.logger.debug('Created task progress record', { taskId, progressId: record.id });\n } catch (error) {\n this.logger.warn('Failed to create task progress record', { taskId, error: (error as Error).message });\n }\n }\n\n async stageStarted(stageKey: string, stageIndex: number): Promise<void> {\n await this.update({\n status: 'in_progress',\n current_step: stageKey,\n completed_steps: Math.min(stageIndex, this.totalSteps ?? stageIndex),\n }, `Stage started: ${stageKey}`);\n }\n\n async stageCompleted(stageKey: string, completedStages: number): Promise<void> {\n await this.update({\n status: 'in_progress',\n current_step: stageKey,\n completed_steps: Math.min(completedStages, this.totalSteps ?? completedStages),\n }, `Stage completed: ${stageKey}`);\n }\n\n async branchCreated(stageKey: string, branchName: string): Promise<void> {\n await this.appendLog(`Branch created (${stageKey}): ${branchName}`);\n }\n\n async commitMade(stageKey: string, kind: 'plan' | 'implementation'): Promise<void> {\n await this.appendLog(`Commit made (${stageKey}, ${kind})`);\n }\n\n async pullRequestCreated(stageKey: string, prUrl: string): Promise<void> {\n await this.appendLog(`Pull request created (${stageKey}): ${prUrl}`);\n }\n\n async noNextStage(stageKey?: string): Promise<void> {\n await this.appendLog(\n stageKey\n ? `No next stage available after '${stageKey}'. Execution halted.`\n : 'No next stage available. Execution halted.'\n );\n }\n\n async complete(): Promise<void> {\n await this.update({ status: 'completed', completed_steps: this.totalSteps }, 'Workflow execution completed');\n }\n\n async fail(error: Error | string): Promise<void> {\n const message = typeof error === 'string' ? error : error.message;\n await this.update({ status: 'failed', error_message: message }, `Workflow execution failed: ${message}`);\n }\n\n async appendLog(line: string): Promise<void> {\n await this.update({}, line);\n }\n\n async recordEvent(event: AgentEvent): Promise<void> {\n if (!this.posthogAPI || !this.progressId || !this.taskId) {\n return;\n }\n\n switch (event.type) {\n case 'token':\n case 'message_delta':\n case 'content_block_start':\n case 'content_block_stop':\n case 'compact_boundary':\n case 'tool_call':\n case 'tool_result':\n case 'message_start':\n case 'message_stop':\n case 'metric':\n case 'artifact':\n // Skip verbose streaming artifacts from persistence\n return;\n\n case 'file_write':\n await this.appendLog(this.formatFileWriteEvent(event));\n return;\n\n case 'diff':\n await this.appendLog(this.formatDiffEvent(event));\n return;\n\n case 'status':\n // Status events are covered by dedicated progress updates\n return;\n\n case 'error':\n await this.appendLog(`[error] ${event.message}`);\n return;\n\n case 'done': {\n const cost = event.totalCostUsd !== undefined ? ` cost=$${event.totalCostUsd.toFixed(2)}` : '';\n await this.appendLog(\n `[done] duration=${event.durationMs ?? 'unknown'}ms turns=${event.numTurns ?? 'unknown'}${cost}`\n );\n return;\n }\n\n case 'init':\n // Omit verbose init messages from persisted log\n return;\n\n case 'user_message': {\n const summary = this.summarizeUserMessage(event.content);\n if (summary) {\n await this.appendLog(summary);\n }\n return;\n }\n\n default:\n // For any unfamiliar event types, avoid spamming the log.\n return;\n }\n }\n\n private async update(update: TaskProgressUpdate, logLine?: string): Promise<void> {\n if (!this.posthogAPI || !this.progressId || !this.taskId) {\n return;\n }\n\n if (logLine) {\n if (logLine !== this.lastLogEntry) {\n this.outputLog.push(logLine);\n this.lastLogEntry = logLine;\n }\n update.output_log = this.outputLog.join('\\n');\n }\n\n try {\n const record = await this.posthogAPI.updateTaskProgress(this.taskId, this.progressId, update);\n // Sync local cache with server response to avoid drift if server modifies values\n this.progressRecord = record;\n if (record.output_log !== undefined && record.output_log !== null) {\n this.outputLog = record.output_log ? record.output_log.split('\\n') : [];\n }\n } catch (error) {\n this.logger.warn('Failed to update task progress record', {\n taskId: this.taskId,\n progressId: this.progressId,\n error: (error as Error).message,\n });\n }\n }\n\n private summarizeUserMessage(content?: string): string | null {\n if (!content) {\n return null;\n }\n const trimmed = content.trim();\n if (!trimmed) {\n return null;\n }\n\n const fileUpdateMatch = trimmed.match(/The file\\s+([^\\s]+)\\s+has been updated/i);\n if (fileUpdateMatch) {\n return `[user] file updated: ${fileUpdateMatch[1]}`;\n }\n\n if (/Todos have been modified/i.test(trimmed)) {\n return '[todo] list updated';\n }\n\n const diffMatch = trimmed.match(/diff --git a\\/([^\\s]+) b\\/([^\\s]+)/);\n if (diffMatch) {\n return `[diff] ${diffMatch[2] ?? diffMatch[1]}`;\n }\n\n const gitStatusMatch = trimmed.match(/^On branch ([^\\n]+)/);\n if (gitStatusMatch) {\n return `[git] status ${gitStatusMatch[1]}`;\n }\n\n if (/This Bash command contains multiple operations/i.test(trimmed)) {\n return '[approval] multi-step command pending';\n }\n\n if (/This command requires approval/i.test(trimmed)) {\n return '[approval] command awaiting approval';\n }\n\n if (/^Exit plan mode\\?/i.test(trimmed)) {\n return null;\n }\n\n if (trimmed.includes('node_modules')) {\n return null;\n }\n\n if (trimmed.includes('total ') && trimmed.includes('drwx')) {\n return null;\n }\n\n if (trimmed.includes('→')) {\n return null;\n }\n\n if (trimmed.split('\\n').length > 2) {\n return null;\n }\n\n const normalized = trimmed.replace(/\\s+/g, ' ');\n const maxLen = 120;\n if (!normalized) {\n return null;\n }\n const preview = normalized.length > maxLen ? `${normalized.slice(0, maxLen)}…` : normalized;\n return `[user] ${preview}`;\n }\n\n private formatFileWriteEvent(event: Extract<AgentEvent, { type: 'file_write' }>): string {\n const size = event.bytes !== undefined ? ` (${event.bytes} bytes)` : '';\n return `[file] wrote ${event.path}${size}`;\n }\n\n private formatDiffEvent(event: Extract<AgentEvent, { type: 'diff' }>): string {\n const summary = event.summary\n ? event.summary.trim()\n : this.truncateMultiline(event.patch ?? '', 160);\n return `[diff] ${event.file}${summary ? ` | ${summary}` : ''}`;\n }\n\n private truncateMultiline(text: string, max = 160): string {\n if (!text) {\n return '';\n }\n const compact = text.replace(/\\s+/g, ' ').trim();\n return compact.length > max ? `${compact.slice(0, max)}…` : compact;\n }\n}\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/agent.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { AgentRegistry } from './agent-registry.js';
|
|
|
14
14
|
import { WorkflowRegistry } from './workflow-registry.js';
|
|
15
15
|
import { StageExecutor } from './stage-executor.js';
|
|
16
16
|
import { PromptBuilder } from './prompt-builder.js';
|
|
17
|
+
import { TaskProgressReporter } from './task-progress-reporter.js';
|
|
17
18
|
|
|
18
19
|
export class Agent {
|
|
19
20
|
private workingDirectory: string;
|
|
@@ -28,6 +29,7 @@ export class Agent {
|
|
|
28
29
|
private agentRegistry: AgentRegistry;
|
|
29
30
|
private workflowRegistry: WorkflowRegistry;
|
|
30
31
|
private stageExecutor: StageExecutor;
|
|
32
|
+
private progressReporter: TaskProgressReporter;
|
|
31
33
|
public debug: boolean;
|
|
32
34
|
|
|
33
35
|
constructor(config: AgentConfig = {}) {
|
|
@@ -64,6 +66,8 @@ export class Agent {
|
|
|
64
66
|
logger: this.logger.child('PromptBuilder')
|
|
65
67
|
});
|
|
66
68
|
this.stageExecutor = new StageExecutor(this.agentRegistry, this.logger, promptBuilder);
|
|
69
|
+
this.stageExecutor.setEventHandler((event) => this.emitEvent(event));
|
|
70
|
+
this.progressReporter = new TaskProgressReporter(this.posthogAPI, this.logger);
|
|
67
71
|
}
|
|
68
72
|
|
|
69
73
|
/**
|
|
@@ -82,6 +86,7 @@ export class Agent {
|
|
|
82
86
|
if (!workflow) {
|
|
83
87
|
throw new Error(`Workflow ${workflowId} not found`);
|
|
84
88
|
}
|
|
89
|
+
const orderedStages = [...workflow.stages].sort((a, b) => a.position - b.position);
|
|
85
90
|
|
|
86
91
|
// Ensure task is assigned to workflow and positioned at first stage
|
|
87
92
|
if (this.posthogAPI) {
|
|
@@ -103,9 +108,13 @@ export class Agent {
|
|
|
103
108
|
const executionId = this.taskManager.generateExecutionId();
|
|
104
109
|
this.logger.info('Starting workflow execution', { taskId: task.id, workflowId, executionId });
|
|
105
110
|
this.taskManager.startExecution(task.id, 'plan_and_build', executionId);
|
|
111
|
+
await this.progressReporter.start(task.id, {
|
|
112
|
+
workflowId,
|
|
113
|
+
workflowRunId: executionId,
|
|
114
|
+
totalSteps: orderedStages.length,
|
|
115
|
+
});
|
|
106
116
|
|
|
107
117
|
try {
|
|
108
|
-
const orderedStages = [...workflow.stages].sort((a, b) => a.position - b.position);
|
|
109
118
|
let startIndex = 0;
|
|
110
119
|
const currentStageId = (task as any).current_stage as string | undefined;
|
|
111
120
|
|
|
@@ -114,7 +123,10 @@ export class Agent {
|
|
|
114
123
|
const currIdx = orderedStages.findIndex(s => s.id === currentStageId);
|
|
115
124
|
const atLastStage = currIdx >= 0 && currIdx === orderedStages.length - 1;
|
|
116
125
|
if (atLastStage) {
|
|
117
|
-
|
|
126
|
+
const finalStageKey = orderedStages[currIdx]?.key;
|
|
127
|
+
this.emitEvent(this.eventTransformer.createStatusEvent('no_next_stage', { stage: finalStageKey }));
|
|
128
|
+
await this.progressReporter.noNextStage(finalStageKey);
|
|
129
|
+
await this.progressReporter.complete();
|
|
118
130
|
this.taskManager.completeExecution(executionId, { task, workflow });
|
|
119
131
|
return { task, workflow };
|
|
120
132
|
}
|
|
@@ -135,7 +147,9 @@ export class Agent {
|
|
|
135
147
|
|
|
136
148
|
for (let i = startIndex; i < orderedStages.length; i++) {
|
|
137
149
|
const stage = orderedStages[i];
|
|
150
|
+
await this.progressReporter.stageStarted(stage.key, i);
|
|
138
151
|
await this.executeStage(task, stage, options);
|
|
152
|
+
await this.progressReporter.stageCompleted(stage.key, i + 1);
|
|
139
153
|
if (options.autoProgress) {
|
|
140
154
|
const hasNext = i < orderedStages.length - 1;
|
|
141
155
|
if (hasNext) {
|
|
@@ -143,9 +157,11 @@ export class Agent {
|
|
|
143
157
|
}
|
|
144
158
|
}
|
|
145
159
|
}
|
|
160
|
+
await this.progressReporter.complete();
|
|
146
161
|
this.taskManager.completeExecution(executionId, { task, workflow });
|
|
147
162
|
return { task, workflow };
|
|
148
163
|
} catch (error) {
|
|
164
|
+
await this.progressReporter.fail(error as Error);
|
|
149
165
|
this.taskManager.failExecution(executionId, error as Error);
|
|
150
166
|
throw error;
|
|
151
167
|
}
|
|
@@ -167,10 +183,12 @@ export class Agent {
|
|
|
167
183
|
const planningBranch = await this.createPlanningBranch(task.id);
|
|
168
184
|
await this.updateTaskBranch(task.id, planningBranch);
|
|
169
185
|
this.emitEvent(this.eventTransformer.createStatusEvent('branch_created', { stage: stage.key, branch: planningBranch }));
|
|
186
|
+
await this.progressReporter.branchCreated(stage.key, planningBranch);
|
|
170
187
|
} else if (!isPlanning && !isManual && shouldCreateImplBranch) {
|
|
171
188
|
const implBranch = await this.createImplementationBranch(task.id);
|
|
172
189
|
await this.updateTaskBranch(task.id, implBranch);
|
|
173
190
|
this.emitEvent(this.eventTransformer.createStatusEvent('branch_created', { stage: stage.key, branch: implBranch }));
|
|
191
|
+
await this.progressReporter.branchCreated(stage.key, implBranch);
|
|
174
192
|
}
|
|
175
193
|
|
|
176
194
|
const result = await this.stageExecutor.execute(task, stage, options);
|
|
@@ -179,6 +197,7 @@ export class Agent {
|
|
|
179
197
|
await this.writePlan(task.id, result.plan);
|
|
180
198
|
await this.commitPlan(task.id, task.title);
|
|
181
199
|
this.emitEvent(this.eventTransformer.createStatusEvent('commit_made', { stage: stage.key, kind: 'plan' }));
|
|
200
|
+
await this.progressReporter.commitMade(stage.key, 'plan');
|
|
182
201
|
}
|
|
183
202
|
|
|
184
203
|
if (isManual) {
|
|
@@ -193,12 +212,14 @@ export class Agent {
|
|
|
193
212
|
await this.updateTaskBranch(task.id, implBranch);
|
|
194
213
|
branchName = implBranch;
|
|
195
214
|
this.emitEvent(this.eventTransformer.createStatusEvent('branch_created', { stage: stage.key, branch: implBranch }));
|
|
215
|
+
await this.progressReporter.branchCreated(stage.key, implBranch);
|
|
196
216
|
}
|
|
197
217
|
try {
|
|
198
218
|
const prUrl = await this.createPullRequest(task.id, branchName, task.title, task.description);
|
|
199
219
|
await this.updateTaskBranch(task.id, branchName);
|
|
200
|
-
await this.attachPullRequestToTask(task.id, prUrl);
|
|
220
|
+
await this.attachPullRequestToTask(task.id, prUrl, branchName);
|
|
201
221
|
this.emitEvent(this.eventTransformer.createStatusEvent('pr_created', { stage: stage.key, prUrl }));
|
|
222
|
+
await this.progressReporter.pullRequestCreated(stage.key, prUrl);
|
|
202
223
|
} catch {}
|
|
203
224
|
}
|
|
204
225
|
// Do not auto-progress on manual stages
|
|
@@ -211,6 +232,7 @@ export class Agent {
|
|
|
211
232
|
const planSummary = existingPlan ? existingPlan.split('\n')[0] : undefined;
|
|
212
233
|
await this.commitImplementation(task.id, task.title, planSummary);
|
|
213
234
|
this.emitEvent(this.eventTransformer.createStatusEvent('commit_made', { stage: stage.key, kind: 'implementation' }));
|
|
235
|
+
await this.progressReporter.commitMade(stage.key, 'implementation');
|
|
214
236
|
}
|
|
215
237
|
|
|
216
238
|
// PR creation on complete stage (or if explicitly requested), regardless of whether edits occurred
|
|
@@ -222,8 +244,9 @@ export class Agent {
|
|
|
222
244
|
try {
|
|
223
245
|
const prUrl = await this.createPullRequest(task.id, branchName, task.title, task.description);
|
|
224
246
|
await this.updateTaskBranch(task.id, branchName);
|
|
225
|
-
await this.attachPullRequestToTask(task.id, prUrl);
|
|
247
|
+
await this.attachPullRequestToTask(task.id, prUrl, branchName);
|
|
226
248
|
this.emitEvent(this.eventTransformer.createStatusEvent('pr_created', { stage: stage.key, prUrl }));
|
|
249
|
+
await this.progressReporter.pullRequestCreated(stage.key, prUrl);
|
|
227
250
|
} catch {}
|
|
228
251
|
}
|
|
229
252
|
}
|
|
@@ -271,6 +294,10 @@ export class Agent {
|
|
|
271
294
|
}
|
|
272
295
|
return this.posthogAPI.fetchTask(taskId);
|
|
273
296
|
}
|
|
297
|
+
|
|
298
|
+
getPostHogClient(): PostHogAPIClient | undefined {
|
|
299
|
+
return this.posthogAPI;
|
|
300
|
+
}
|
|
274
301
|
|
|
275
302
|
async listTasks(filters?: {
|
|
276
303
|
repository?: string;
|
|
@@ -367,8 +394,8 @@ Generated by PostHog Agent`;
|
|
|
367
394
|
return prUrl;
|
|
368
395
|
}
|
|
369
396
|
|
|
370
|
-
async attachPullRequestToTask(taskId: string, prUrl: string): Promise<void> {
|
|
371
|
-
this.logger.info('Attaching PR to task', { taskId, prUrl });
|
|
397
|
+
async attachPullRequestToTask(taskId: string, prUrl: string, branchName?: string): Promise<void> {
|
|
398
|
+
this.logger.info('Attaching PR to task', { taskId, prUrl, branchName });
|
|
372
399
|
|
|
373
400
|
if (!this.posthogAPI) {
|
|
374
401
|
const error = new Error('PostHog API not configured. Cannot attach PR to task.');
|
|
@@ -376,7 +403,7 @@ Generated by PostHog Agent`;
|
|
|
376
403
|
throw error;
|
|
377
404
|
}
|
|
378
405
|
|
|
379
|
-
await this.posthogAPI.
|
|
406
|
+
await this.posthogAPI.attachTaskPullRequest(taskId, prUrl, branchName);
|
|
380
407
|
this.logger.debug('PR attached to task', { taskId, prUrl });
|
|
381
408
|
}
|
|
382
409
|
|
|
@@ -389,7 +416,7 @@ Generated by PostHog Agent`;
|
|
|
389
416
|
throw error;
|
|
390
417
|
}
|
|
391
418
|
|
|
392
|
-
await this.posthogAPI.
|
|
419
|
+
await this.posthogAPI.setTaskBranch(taskId, branchName);
|
|
393
420
|
this.logger.debug('Task branch updated', { taskId, branchName });
|
|
394
421
|
}
|
|
395
422
|
|
|
@@ -419,6 +446,12 @@ Generated by PostHog Agent`;
|
|
|
419
446
|
// Log all events except tokens (too verbose)
|
|
420
447
|
this.logger.debug('Emitting event', { type: event.type, ts: event.ts });
|
|
421
448
|
}
|
|
449
|
+
const persistPromise = this.progressReporter.recordEvent(event);
|
|
450
|
+
if (persistPromise && typeof persistPromise.then === 'function') {
|
|
451
|
+
persistPromise.catch((error: Error) =>
|
|
452
|
+
this.logger.debug('Failed to persist agent event', { message: error.message })
|
|
453
|
+
);
|
|
454
|
+
}
|
|
422
455
|
this.onEvent?.(event);
|
|
423
456
|
}
|
|
424
457
|
}
|
package/src/event-transformer.ts
CHANGED
|
@@ -118,15 +118,14 @@ export class EventTransformer {
|
|
|
118
118
|
|
|
119
119
|
// Handle user messages
|
|
120
120
|
if (sdkMessage.type === 'user') {
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
121
|
+
const textContent = this.extractUserContent(sdkMessage.message?.content);
|
|
122
|
+
if (!textContent) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
126
125
|
return {
|
|
127
126
|
...baseEvent,
|
|
128
127
|
type: 'user_message',
|
|
129
|
-
content: textContent
|
|
128
|
+
content: textContent,
|
|
130
129
|
isSynthetic: sdkMessage.isSynthetic
|
|
131
130
|
};
|
|
132
131
|
}
|
|
@@ -186,4 +185,59 @@ export class EventTransformer {
|
|
|
186
185
|
...additionalData
|
|
187
186
|
};
|
|
188
187
|
}
|
|
189
|
-
|
|
188
|
+
|
|
189
|
+
private extractUserContent(content: unknown): string | null {
|
|
190
|
+
if (!content) {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (typeof content === 'string') {
|
|
195
|
+
const trimmed = content.trim();
|
|
196
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (Array.isArray(content)) {
|
|
200
|
+
const parts: string[] = [];
|
|
201
|
+
for (const block of content) {
|
|
202
|
+
const extracted = this.extractUserContent(block);
|
|
203
|
+
if (extracted) {
|
|
204
|
+
parts.push(extracted);
|
|
205
|
+
} else if (block && typeof block === 'object') {
|
|
206
|
+
const candidate = this.extractFromObject(block as Record<string, unknown>);
|
|
207
|
+
if (candidate) {
|
|
208
|
+
parts.push(candidate);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
const text = parts.join('\n').trim();
|
|
213
|
+
return text.length > 0 ? text : null;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (typeof content === 'object') {
|
|
217
|
+
return this.extractFromObject(content as Record<string, unknown>);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
private extractFromObject(value: Record<string, unknown>): string | null {
|
|
224
|
+
const preferredKeys = ['text', 'input_text', 'input', 'markdown', 'content', 'message'];
|
|
225
|
+
for (const key of preferredKeys) {
|
|
226
|
+
if (typeof value[key] === 'string') {
|
|
227
|
+
const trimmed = (value[key] as string).trim();
|
|
228
|
+
if (trimmed.length > 0) {
|
|
229
|
+
return trimmed;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
for (const entry of Object.values(value)) {
|
|
235
|
+
const extracted = this.extractUserContent(entry);
|
|
236
|
+
if (extracted) {
|
|
237
|
+
return extracted;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
}
|
package/src/posthog-api.ts
CHANGED
|
@@ -26,6 +26,36 @@ interface TaskProgressResponse {
|
|
|
26
26
|
message?: string;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
export interface TaskProgressRecord {
|
|
30
|
+
id: string;
|
|
31
|
+
task: string;
|
|
32
|
+
status: "started" | "in_progress" | "completed" | "failed";
|
|
33
|
+
current_step?: string | null;
|
|
34
|
+
completed_steps?: number | null;
|
|
35
|
+
total_steps?: number | null;
|
|
36
|
+
progress_percentage?: number | null;
|
|
37
|
+
output_log?: string | null;
|
|
38
|
+
error_message?: string | null;
|
|
39
|
+
workflow_id?: string | null;
|
|
40
|
+
workflow_run_id?: string | null;
|
|
41
|
+
activity_id?: string | null;
|
|
42
|
+
created_at: string;
|
|
43
|
+
updated_at: string;
|
|
44
|
+
completed_at?: string | null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface TaskProgressUpdate {
|
|
48
|
+
status?: TaskProgressRecord["status"];
|
|
49
|
+
current_step?: string | null;
|
|
50
|
+
completed_steps?: number | null;
|
|
51
|
+
total_steps?: number | null;
|
|
52
|
+
output_log?: string | null;
|
|
53
|
+
error_message?: string | null;
|
|
54
|
+
workflow_id?: string | null;
|
|
55
|
+
workflow_run_id?: string | null;
|
|
56
|
+
activity_id?: string | null;
|
|
57
|
+
}
|
|
58
|
+
|
|
29
59
|
export class PostHogAPIClient {
|
|
30
60
|
private config: PostHogAPIConfig;
|
|
31
61
|
private _teamId: number | null = null;
|
|
@@ -137,11 +167,60 @@ export class PostHogAPIClient {
|
|
|
137
167
|
});
|
|
138
168
|
}
|
|
139
169
|
|
|
170
|
+
async setTaskBranch(taskId: string, branch: string): Promise<Task> {
|
|
171
|
+
const teamId = await this.getTeamId();
|
|
172
|
+
return this.apiRequest<Task>(`/api/projects/${teamId}/tasks/${taskId}/set_branch/`, {
|
|
173
|
+
method: "POST",
|
|
174
|
+
body: JSON.stringify({ branch }),
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async attachTaskPullRequest(taskId: string, prUrl: string, branch?: string): Promise<Task> {
|
|
179
|
+
const teamId = await this.getTeamId();
|
|
180
|
+
const payload: Record<string, string> = { pr_url: prUrl };
|
|
181
|
+
if (branch) {
|
|
182
|
+
payload.branch = branch;
|
|
183
|
+
}
|
|
184
|
+
return this.apiRequest<Task>(`/api/projects/${teamId}/tasks/${taskId}/attach_pr/`, {
|
|
185
|
+
method: "POST",
|
|
186
|
+
body: JSON.stringify(payload),
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
140
190
|
async getTaskProgress(taskId: string): Promise<TaskProgressResponse> {
|
|
141
191
|
const teamId = await this.getTeamId();
|
|
142
192
|
return this.apiRequest<TaskProgressResponse>(`/api/projects/${teamId}/tasks/${taskId}/progress/`);
|
|
143
193
|
}
|
|
144
194
|
|
|
195
|
+
async createTaskProgress(
|
|
196
|
+
taskId: string,
|
|
197
|
+
payload: TaskProgressUpdate & { status: TaskProgressRecord["status"] }
|
|
198
|
+
): Promise<TaskProgressRecord> {
|
|
199
|
+
const teamId = await this.getTeamId();
|
|
200
|
+
return this.apiRequest<TaskProgressRecord>(`/api/projects/${teamId}/task_progress/`, {
|
|
201
|
+
method: "POST",
|
|
202
|
+
body: JSON.stringify({
|
|
203
|
+
...payload,
|
|
204
|
+
task: taskId,
|
|
205
|
+
}),
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async updateTaskProgress(
|
|
210
|
+
taskId: string,
|
|
211
|
+
progressId: string,
|
|
212
|
+
payload: TaskProgressUpdate
|
|
213
|
+
): Promise<TaskProgressRecord> {
|
|
214
|
+
const teamId = await this.getTeamId();
|
|
215
|
+
return this.apiRequest<TaskProgressRecord>(`/api/projects/${teamId}/task_progress/${progressId}/`, {
|
|
216
|
+
method: "PATCH",
|
|
217
|
+
body: JSON.stringify({
|
|
218
|
+
...payload,
|
|
219
|
+
task: taskId,
|
|
220
|
+
}),
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
145
224
|
// Workflow endpoints
|
|
146
225
|
async fetchWorkflow(workflowId: string): Promise<WorkflowDefinition> {
|
|
147
226
|
const teamId = await this.getTeamId();
|