@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.
Files changed (39) hide show
  1. package/CLAUDE.md +68 -35
  2. package/README.md +55 -14
  3. package/dist/index.d.ts +1 -1
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/src/agent.d.ts +5 -1
  6. package/dist/src/agent.d.ts.map +1 -1
  7. package/dist/src/agent.js +65 -11
  8. package/dist/src/agent.js.map +1 -1
  9. package/dist/src/event-transformer.d.ts +2 -0
  10. package/dist/src/event-transformer.d.ts.map +1 -1
  11. package/dist/src/event-transformer.js +53 -5
  12. package/dist/src/event-transformer.js.map +1 -1
  13. package/dist/src/posthog-api.d.ts +34 -0
  14. package/dist/src/posthog-api.d.ts.map +1 -1
  15. package/dist/src/posthog-api.js +38 -0
  16. package/dist/src/posthog-api.js.map +1 -1
  17. package/dist/src/stage-executor.d.ts +5 -2
  18. package/dist/src/stage-executor.d.ts.map +1 -1
  19. package/dist/src/stage-executor.js +20 -12
  20. package/dist/src/stage-executor.js.map +1 -1
  21. package/dist/src/task-progress-reporter.d.ts +44 -0
  22. package/dist/src/task-progress-reporter.d.ts.map +1 -0
  23. package/dist/src/task-progress-reporter.js +234 -0
  24. package/dist/src/task-progress-reporter.js.map +1 -0
  25. package/dist/src/types.d.ts +20 -0
  26. package/dist/src/types.d.ts.map +1 -1
  27. package/dist/src/types.js.map +1 -1
  28. package/package.json +2 -1
  29. package/src/agent.ts +77 -11
  30. package/src/event-transformer.ts +61 -7
  31. package/src/posthog-api.ts +79 -0
  32. package/src/stage-executor.ts +29 -15
  33. package/src/task-progress-reporter.ts +287 -0
  34. package/src/types.ts +30 -2
  35. package/dist/src/utils/mcp.d.ts +0 -10
  36. package/dist/src/utils/mcp.d.ts.map +0 -1
  37. package/dist/src/utils/mcp.js +0 -18
  38. package/dist/src/utils/mcp.js.map +0 -1
  39. 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
  }
@@ -1,10 +0,0 @@
1
- export declare const POSTHOG_MCP: {
2
- posthog: {
3
- command: string;
4
- args: string[];
5
- env: {
6
- POSTHOG_AUTH_HEADER: string;
7
- };
8
- };
9
- };
10
- //# sourceMappingURL=mcp.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../../../src/utils/mcp.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,WAAW;;;;;;;;CAcvB,CAAC"}
@@ -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
- };