@posthog/agent 1.1.0 → 1.3.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 +55 -14
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/src/agent.d.ts +5 -1
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +65 -11
- 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 +5 -2
- package/dist/src/stage-executor.d.ts.map +1 -1
- package/dist/src/stage-executor.js +20 -12
- 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/dist/src/types.d.ts +20 -0
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/types.js.map +1 -1
- package/package.json +2 -1
- package/src/agent.ts +77 -11
- package/src/event-transformer.ts +61 -7
- package/src/posthog-api.ts +79 -0
- package/src/stage-executor.ts +29 -15
- package/src/task-progress-reporter.ts +287 -0
- package/src/types.ts +30 -2
- package/dist/src/utils/mcp.d.ts +0 -10
- package/dist/src/utils/mcp.d.ts.map +0 -1
- package/dist/src/utils/mcp.js +0 -18
- package/dist/src/utils/mcp.js.map +0 -1
- package/src/utils/mcp.ts +0 -15
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import type { Logger } from './utils/logger.js';
|
|
2
|
+
import type { PostHogAPIClient, TaskProgressRecord, TaskProgressUpdate } from './posthog-api.js';
|
|
3
|
+
import type { AgentEvent } from './types.js';
|
|
4
|
+
|
|
5
|
+
interface ProgressMetadata {
|
|
6
|
+
workflowId?: string;
|
|
7
|
+
workflowRunId?: string;
|
|
8
|
+
activityId?: string;
|
|
9
|
+
totalSteps?: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Persists task execution progress to PostHog so clients can poll for updates.
|
|
14
|
+
*
|
|
15
|
+
* The reporter is intentionally best-effort – failures are logged but never
|
|
16
|
+
* allowed to break the agent execution flow.
|
|
17
|
+
*/
|
|
18
|
+
export class TaskProgressReporter {
|
|
19
|
+
private posthogAPI?: PostHogAPIClient;
|
|
20
|
+
private logger: Logger;
|
|
21
|
+
private progressRecord?: TaskProgressRecord;
|
|
22
|
+
private taskId?: string;
|
|
23
|
+
private outputLog: string[] = [];
|
|
24
|
+
private totalSteps?: number;
|
|
25
|
+
private lastLogEntry?: string;
|
|
26
|
+
|
|
27
|
+
constructor(posthogAPI: PostHogAPIClient | undefined, logger: Logger) {
|
|
28
|
+
this.posthogAPI = posthogAPI;
|
|
29
|
+
this.logger = logger.child('TaskProgressReporter');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
get progressId(): string | undefined {
|
|
33
|
+
return this.progressRecord?.id;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async start(taskId: string, metadata: ProgressMetadata = {}): Promise<void> {
|
|
37
|
+
if (!this.posthogAPI) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
this.taskId = taskId;
|
|
42
|
+
this.totalSteps = metadata.totalSteps;
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
const record = await this.posthogAPI.createTaskProgress(taskId, {
|
|
46
|
+
status: 'started',
|
|
47
|
+
current_step: 'initializing',
|
|
48
|
+
total_steps: metadata.totalSteps ?? 0,
|
|
49
|
+
completed_steps: 0,
|
|
50
|
+
workflow_id: metadata.workflowId,
|
|
51
|
+
workflow_run_id: metadata.workflowRunId,
|
|
52
|
+
activity_id: metadata.activityId,
|
|
53
|
+
output_log: '',
|
|
54
|
+
});
|
|
55
|
+
this.progressRecord = record;
|
|
56
|
+
this.outputLog = record.output_log ? record.output_log.split('\n') : [];
|
|
57
|
+
this.logger.debug('Created task progress record', { taskId, progressId: record.id });
|
|
58
|
+
} catch (error) {
|
|
59
|
+
this.logger.warn('Failed to create task progress record', { taskId, error: (error as Error).message });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async stageStarted(stageKey: string, stageIndex: number): Promise<void> {
|
|
64
|
+
await this.update({
|
|
65
|
+
status: 'in_progress',
|
|
66
|
+
current_step: stageKey,
|
|
67
|
+
completed_steps: Math.min(stageIndex, this.totalSteps ?? stageIndex),
|
|
68
|
+
}, `Stage started: ${stageKey}`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async stageCompleted(stageKey: string, completedStages: number): Promise<void> {
|
|
72
|
+
await this.update({
|
|
73
|
+
status: 'in_progress',
|
|
74
|
+
current_step: stageKey,
|
|
75
|
+
completed_steps: Math.min(completedStages, this.totalSteps ?? completedStages),
|
|
76
|
+
}, `Stage completed: ${stageKey}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async branchCreated(stageKey: string, branchName: string): Promise<void> {
|
|
80
|
+
await this.appendLog(`Branch created (${stageKey}): ${branchName}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async commitMade(stageKey: string, kind: 'plan' | 'implementation'): Promise<void> {
|
|
84
|
+
await this.appendLog(`Commit made (${stageKey}, ${kind})`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async pullRequestCreated(stageKey: string, prUrl: string): Promise<void> {
|
|
88
|
+
await this.appendLog(`Pull request created (${stageKey}): ${prUrl}`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async noNextStage(stageKey?: string): Promise<void> {
|
|
92
|
+
await this.appendLog(
|
|
93
|
+
stageKey
|
|
94
|
+
? `No next stage available after '${stageKey}'. Execution halted.`
|
|
95
|
+
: 'No next stage available. Execution halted.'
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async complete(): Promise<void> {
|
|
100
|
+
await this.update({ status: 'completed', completed_steps: this.totalSteps }, 'Workflow execution completed');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async fail(error: Error | string): Promise<void> {
|
|
104
|
+
const message = typeof error === 'string' ? error : error.message;
|
|
105
|
+
await this.update({ status: 'failed', error_message: message }, `Workflow execution failed: ${message}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async appendLog(line: string): Promise<void> {
|
|
109
|
+
await this.update({}, line);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async recordEvent(event: AgentEvent): Promise<void> {
|
|
113
|
+
if (!this.posthogAPI || !this.progressId || !this.taskId) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
switch (event.type) {
|
|
118
|
+
case 'token':
|
|
119
|
+
case 'message_delta':
|
|
120
|
+
case 'content_block_start':
|
|
121
|
+
case 'content_block_stop':
|
|
122
|
+
case 'compact_boundary':
|
|
123
|
+
case 'tool_call':
|
|
124
|
+
case 'tool_result':
|
|
125
|
+
case 'message_start':
|
|
126
|
+
case 'message_stop':
|
|
127
|
+
case 'metric':
|
|
128
|
+
case 'artifact':
|
|
129
|
+
// Skip verbose streaming artifacts from persistence
|
|
130
|
+
return;
|
|
131
|
+
|
|
132
|
+
case 'file_write':
|
|
133
|
+
await this.appendLog(this.formatFileWriteEvent(event));
|
|
134
|
+
return;
|
|
135
|
+
|
|
136
|
+
case 'diff':
|
|
137
|
+
await this.appendLog(this.formatDiffEvent(event));
|
|
138
|
+
return;
|
|
139
|
+
|
|
140
|
+
case 'status':
|
|
141
|
+
// Status events are covered by dedicated progress updates
|
|
142
|
+
return;
|
|
143
|
+
|
|
144
|
+
case 'error':
|
|
145
|
+
await this.appendLog(`[error] ${event.message}`);
|
|
146
|
+
return;
|
|
147
|
+
|
|
148
|
+
case 'done': {
|
|
149
|
+
const cost = event.totalCostUsd !== undefined ? ` cost=$${event.totalCostUsd.toFixed(2)}` : '';
|
|
150
|
+
await this.appendLog(
|
|
151
|
+
`[done] duration=${event.durationMs ?? 'unknown'}ms turns=${event.numTurns ?? 'unknown'}${cost}`
|
|
152
|
+
);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
case 'init':
|
|
157
|
+
// Omit verbose init messages from persisted log
|
|
158
|
+
return;
|
|
159
|
+
|
|
160
|
+
case 'user_message': {
|
|
161
|
+
const summary = this.summarizeUserMessage(event.content);
|
|
162
|
+
if (summary) {
|
|
163
|
+
await this.appendLog(summary);
|
|
164
|
+
}
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
default:
|
|
169
|
+
// For any unfamiliar event types, avoid spamming the log.
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private async update(update: TaskProgressUpdate, logLine?: string): Promise<void> {
|
|
175
|
+
if (!this.posthogAPI || !this.progressId || !this.taskId) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (logLine) {
|
|
180
|
+
if (logLine !== this.lastLogEntry) {
|
|
181
|
+
this.outputLog.push(logLine);
|
|
182
|
+
this.lastLogEntry = logLine;
|
|
183
|
+
}
|
|
184
|
+
update.output_log = this.outputLog.join('\n');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
const record = await this.posthogAPI.updateTaskProgress(this.taskId, this.progressId, update);
|
|
189
|
+
// Sync local cache with server response to avoid drift if server modifies values
|
|
190
|
+
this.progressRecord = record;
|
|
191
|
+
if (record.output_log !== undefined && record.output_log !== null) {
|
|
192
|
+
this.outputLog = record.output_log ? record.output_log.split('\n') : [];
|
|
193
|
+
}
|
|
194
|
+
} catch (error) {
|
|
195
|
+
this.logger.warn('Failed to update task progress record', {
|
|
196
|
+
taskId: this.taskId,
|
|
197
|
+
progressId: this.progressId,
|
|
198
|
+
error: (error as Error).message,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private summarizeUserMessage(content?: string): string | null {
|
|
204
|
+
if (!content) {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
const trimmed = content.trim();
|
|
208
|
+
if (!trimmed) {
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const fileUpdateMatch = trimmed.match(/The file\s+([^\s]+)\s+has been updated/i);
|
|
213
|
+
if (fileUpdateMatch) {
|
|
214
|
+
return `[user] file updated: ${fileUpdateMatch[1]}`;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (/Todos have been modified/i.test(trimmed)) {
|
|
218
|
+
return '[todo] list updated';
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const diffMatch = trimmed.match(/diff --git a\/([^\s]+) b\/([^\s]+)/);
|
|
222
|
+
if (diffMatch) {
|
|
223
|
+
return `[diff] ${diffMatch[2] ?? diffMatch[1]}`;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const gitStatusMatch = trimmed.match(/^On branch ([^\n]+)/);
|
|
227
|
+
if (gitStatusMatch) {
|
|
228
|
+
return `[git] status ${gitStatusMatch[1]}`;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (/This Bash command contains multiple operations/i.test(trimmed)) {
|
|
232
|
+
return '[approval] multi-step command pending';
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (/This command requires approval/i.test(trimmed)) {
|
|
236
|
+
return '[approval] command awaiting approval';
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (/^Exit plan mode\?/i.test(trimmed)) {
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (trimmed.includes('node_modules')) {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (trimmed.includes('total ') && trimmed.includes('drwx')) {
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (trimmed.includes('→')) {
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (trimmed.split('\n').length > 2) {
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const normalized = trimmed.replace(/\s+/g, ' ');
|
|
260
|
+
const maxLen = 120;
|
|
261
|
+
if (!normalized) {
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
const preview = normalized.length > maxLen ? `${normalized.slice(0, maxLen)}…` : normalized;
|
|
265
|
+
return `[user] ${preview}`;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
private formatFileWriteEvent(event: Extract<AgentEvent, { type: 'file_write' }>): string {
|
|
269
|
+
const size = event.bytes !== undefined ? ` (${event.bytes} bytes)` : '';
|
|
270
|
+
return `[file] wrote ${event.path}${size}`;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
private formatDiffEvent(event: Extract<AgentEvent, { type: 'diff' }>): string {
|
|
274
|
+
const summary = event.summary
|
|
275
|
+
? event.summary.trim()
|
|
276
|
+
: this.truncateMultiline(event.patch ?? '', 160);
|
|
277
|
+
return `[diff] ${event.file}${summary ? ` | ${summary}` : ''}`;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
private truncateMultiline(text: string, max = 160): string {
|
|
281
|
+
if (!text) {
|
|
282
|
+
return '';
|
|
283
|
+
}
|
|
284
|
+
const compact = text.replace(/\s+/g, ' ').trim();
|
|
285
|
+
return compact.length > max ? `${compact.slice(0, max)}…` : compact;
|
|
286
|
+
}
|
|
287
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -205,14 +205,42 @@ export interface TaskExecutionResult {
|
|
|
205
205
|
// Deprecated: mode removed in workflow-based execution
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
+
// MCP Server configuration types (re-exported from Claude SDK for convenience)
|
|
209
|
+
export type McpServerConfig = {
|
|
210
|
+
type?: 'stdio';
|
|
211
|
+
command: string;
|
|
212
|
+
args?: string[];
|
|
213
|
+
env?: Record<string, string>;
|
|
214
|
+
} | {
|
|
215
|
+
type: 'sse';
|
|
216
|
+
url: string;
|
|
217
|
+
headers?: Record<string, string>;
|
|
218
|
+
} | {
|
|
219
|
+
type: 'http';
|
|
220
|
+
url: string;
|
|
221
|
+
headers?: Record<string, string>;
|
|
222
|
+
} | {
|
|
223
|
+
type: 'sdk';
|
|
224
|
+
name: string;
|
|
225
|
+
instance?: any; // McpServer instance
|
|
226
|
+
};
|
|
227
|
+
|
|
208
228
|
export interface AgentConfig {
|
|
209
229
|
workingDirectory?: string;
|
|
210
230
|
onEvent?: (event: AgentEvent) => void;
|
|
211
|
-
|
|
231
|
+
|
|
212
232
|
// PostHog API configuration
|
|
213
233
|
posthogApiUrl?: string;
|
|
214
234
|
posthogApiKey?: string;
|
|
215
|
-
|
|
235
|
+
|
|
236
|
+
// PostHog MCP configuration
|
|
237
|
+
posthogMcpUrl?: string;
|
|
238
|
+
|
|
239
|
+
// MCP Server configuration
|
|
240
|
+
// Additional MCP servers (PostHog MCP is always included by default)
|
|
241
|
+
// You can override the PostHog MCP config by providing mcpServers.posthog
|
|
242
|
+
mcpServers?: Record<string, McpServerConfig>;
|
|
243
|
+
|
|
216
244
|
// Logging configuration
|
|
217
245
|
debug?: boolean;
|
|
218
246
|
}
|
package/dist/src/utils/mcp.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../../../src/utils/mcp.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,WAAW;;;;;;;;CAcvB,CAAC"}
|
package/dist/src/utils/mcp.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
const POSTHOG_MCP = {
|
|
2
|
-
posthog: {
|
|
3
|
-
command: "npx",
|
|
4
|
-
args: [
|
|
5
|
-
"-y",
|
|
6
|
-
"mcp-remote@latest",
|
|
7
|
-
"https://mcp.posthog.com/mcp",
|
|
8
|
-
"--header",
|
|
9
|
-
"Authorization:${POSTHOG_AUTH_HEADER}"
|
|
10
|
-
],
|
|
11
|
-
env: {
|
|
12
|
-
"POSTHOG_AUTH_HEADER": `Bearer ${process.env.POSTHOG_API_KEY}`
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export { POSTHOG_MCP };
|
|
18
|
-
//# sourceMappingURL=mcp.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"mcp.js","sources":["../../../src/utils/mcp.ts"],"sourcesContent":["export const POSTHOG_MCP = {\n posthog: {\n command: \"npx\",\n args: [\n \"-y\",\n \"mcp-remote@latest\",\n \"https://mcp.posthog.com/mcp\",\n \"--header\",\n \"Authorization:${POSTHOG_AUTH_HEADER}\"\n ],\n env: {\n \"POSTHOG_AUTH_HEADER\": `Bearer ${process.env.POSTHOG_API_KEY}`\n }\n }\n};"],"names":[],"mappings":"AAAO,MAAM,WAAW,GAAG;AACvB,IAAA,OAAO,EAAE;AACL,QAAA,OAAO,EAAE,KAAK;AACd,QAAA,IAAI,EAAE;YACF,IAAI;YACJ,mBAAmB;YACnB,6BAA6B;YAC7B,UAAU;YACV;AACH,SAAA;AACD,QAAA,GAAG,EAAE;AACD,YAAA,qBAAqB,EAAE,CAAA,OAAA,EAAU,OAAO,CAAC,GAAG,CAAC,eAAe,CAAA;AAC/D;AACJ;;;;;"}
|
package/src/utils/mcp.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
export const POSTHOG_MCP = {
|
|
2
|
-
posthog: {
|
|
3
|
-
command: "npx",
|
|
4
|
-
args: [
|
|
5
|
-
"-y",
|
|
6
|
-
"mcp-remote@latest",
|
|
7
|
-
"https://mcp.posthog.com/mcp",
|
|
8
|
-
"--header",
|
|
9
|
-
"Authorization:${POSTHOG_AUTH_HEADER}"
|
|
10
|
-
],
|
|
11
|
-
env: {
|
|
12
|
-
"POSTHOG_AUTH_HEADER": `Bearer ${process.env.POSTHOG_API_KEY}`
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
};
|