@posthog/agent 1.9.0 → 1.10.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/src/agent.ts CHANGED
@@ -158,13 +158,8 @@ export class Agent {
158
158
  await this.posthogAPI.updateTask(task.id, { workflow: workflowId } as any);
159
159
  (task as any).workflow = workflowId;
160
160
  }
161
- if (!(task as any).current_stage && workflow.stages.length > 0) {
162
- const firstStage = [...workflow.stages].sort((a, b) => a.position - b.position)[0];
163
- await this.posthogAPI.updateTaskStage(task.id, firstStage.id);
164
- (task as any).current_stage = firstStage.id;
165
- }
166
161
  } catch (e) {
167
- this.logger.warn('Failed to sync task workflow/stage before execution', { error: (e as Error).message });
162
+ this.logger.warn('Failed to sync task workflow before execution', { error: (e as Error).message });
168
163
  }
169
164
  }
170
165
 
@@ -172,11 +167,21 @@ export class Agent {
172
167
  this.logger.info('Starting workflow execution', { taskId: task.id, workflowId, executionId });
173
168
  this.taskManager.startExecution(task.id, 'plan_and_build', executionId);
174
169
  await this.progressReporter.start(task.id, {
175
- workflowId,
176
- workflowRunId: executionId,
177
170
  totalSteps: orderedStages.length,
178
171
  });
179
172
 
173
+ // Set initial stage on the newly created run
174
+ const firstStage = orderedStages[0];
175
+ if (this.posthogAPI && this.progressReporter.runId && firstStage) {
176
+ try {
177
+ await this.posthogAPI.updateTaskRun(task.id, this.progressReporter.runId, {
178
+ current_stage: firstStage.id
179
+ });
180
+ } catch (e) {
181
+ this.logger.warn('Failed to set initial stage on run', { error: (e as Error).message });
182
+ }
183
+ }
184
+
180
185
  try {
181
186
  let startIndex = 0;
182
187
  const currentStageId = (task as any).current_stage as string | undefined;
@@ -200,11 +205,17 @@ export class Agent {
200
205
  if (idx >= 0) startIndex = idx;
201
206
  }
202
207
 
203
- // Align server-side stage when restarting from the beginning
204
- if (this.posthogAPI) {
208
+ // Align server-side stage when restarting from a different stage
209
+ if (this.posthogAPI && this.progressReporter.runId) {
205
210
  const targetStage = orderedStages[startIndex];
206
211
  if (targetStage && targetStage.id !== currentStageId) {
207
- try { await this.posthogAPI.updateTaskStage(task.id, targetStage.id); (task as any).current_stage = targetStage.id; } catch {}
212
+ try {
213
+ await this.posthogAPI.updateTaskRun(task.id, this.progressReporter.runId, {
214
+ current_stage: targetStage.id
215
+ });
216
+ } catch (e) {
217
+ this.logger.warn('Failed to update run stage', { error: (e as Error).message });
218
+ }
208
219
  }
209
220
  }
210
221
 
@@ -318,13 +329,16 @@ export class Agent {
318
329
  }
319
330
 
320
331
  async progressToNextStage(taskId: string, currentStageKey?: string): Promise<void> {
321
- if (!this.posthogAPI) throw new Error('PostHog API not configured. Cannot progress stage.');
332
+ if (!this.posthogAPI || !this.progressReporter.runId) {
333
+ throw new Error('PostHog API not configured or no active run. Cannot progress stage.');
334
+ }
322
335
  try {
323
- await this.posthogAPI.progressTask(taskId, { auto: true });
336
+ await this.posthogAPI.progressTaskRun(taskId, this.progressReporter.runId);
324
337
  } catch (error) {
325
338
  if (error instanceof Error && error.message.includes('No next stage available')) {
326
- this.logger.warn('No next stage available when attempting to progress task', {
339
+ this.logger.warn('No next stage available when attempting to progress run', {
327
340
  taskId,
341
+ runId: this.progressReporter.runId,
328
342
  stage: currentStageKey,
329
343
  error: error.message,
330
344
  });
@@ -479,29 +493,36 @@ Generated by PostHog Agent`;
479
493
  }
480
494
 
481
495
  async attachPullRequestToTask(taskId: string, prUrl: string, branchName?: string): Promise<void> {
482
- this.logger.info('Attaching PR to task', { taskId, prUrl, branchName });
496
+ this.logger.info('Attaching PR to task run', { taskId, prUrl, branchName });
483
497
 
484
- if (!this.posthogAPI) {
485
- const error = new Error('PostHog API not configured. Cannot attach PR to task.');
498
+ if (!this.posthogAPI || !this.progressReporter.runId) {
499
+ const error = new Error('PostHog API not configured or no active run. Cannot attach PR to task.');
486
500
  this.logger.error('PostHog API not configured', error);
487
501
  throw error;
488
502
  }
489
503
 
490
- await this.posthogAPI.attachTaskPullRequest(taskId, prUrl, branchName);
491
- this.logger.debug('PR attached to task', { taskId, prUrl });
504
+ const updates: any = {
505
+ output: { pr_url: prUrl }
506
+ };
507
+ if (branchName) {
508
+ updates.branch = branchName;
509
+ }
510
+
511
+ await this.posthogAPI.updateTaskRun(taskId, this.progressReporter.runId, updates);
512
+ this.logger.debug('PR attached to task run', { taskId, runId: this.progressReporter.runId, prUrl });
492
513
  }
493
514
 
494
515
  async updateTaskBranch(taskId: string, branchName: string): Promise<void> {
495
- this.logger.info('Updating task branch', { taskId, branchName });
516
+ this.logger.info('Updating task run branch', { taskId, branchName });
496
517
 
497
- if (!this.posthogAPI) {
498
- const error = new Error('PostHog API not configured. Cannot update task branch.');
518
+ if (!this.posthogAPI || !this.progressReporter.runId) {
519
+ const error = new Error('PostHog API not configured or no active run. Cannot update branch.');
499
520
  this.logger.error('PostHog API not configured', error);
500
521
  throw error;
501
522
  }
502
523
 
503
- await this.posthogAPI.setTaskBranch(taskId, branchName);
504
- this.logger.debug('Task branch updated', { taskId, branchName });
524
+ await this.posthogAPI.updateTaskRun(taskId, this.progressReporter.runId, { branch: branchName });
525
+ this.logger.debug('Task run branch updated', { taskId, runId: this.progressReporter.runId, branchName });
505
526
  }
506
527
 
507
528
  // Execution management
@@ -1,4 +1,4 @@
1
- import type { Task, SupportingFile, PostHogAPIConfig, PostHogResource, ResourceType, UrlMention } from './types.js';
1
+ import type { Task, TaskRun, LogEntry, SupportingFile, PostHogAPIConfig, PostHogResource, ResourceType, UrlMention } from './types.js';
2
2
  import type { WorkflowDefinition, AgentDefinition } from './workflow-types.js';
3
3
 
4
4
  interface PostHogApiResponse<T> {
@@ -8,52 +8,14 @@ interface PostHogApiResponse<T> {
8
8
  previous?: string | null;
9
9
  }
10
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 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;
11
+ export interface TaskRunUpdate {
12
+ status?: TaskRun["status"];
13
+ branch?: string | null;
14
+ current_stage?: string | null;
15
+ log?: LogEntry[];
53
16
  error_message?: string | null;
54
- workflow_id?: string | null;
55
- workflow_run_id?: string | null;
56
- activity_id?: string | null;
17
+ output?: Record<string, unknown> | null;
18
+ state?: Record<string, unknown>;
57
19
  }
58
20
 
59
21
  export class PostHogAPIClient {
@@ -172,65 +134,76 @@ export class PostHogAPIClient {
172
134
  });
173
135
  }
174
136
 
175
- async updateTaskStage(taskId: string, stageId: string): Promise<Task> {
137
+ // TaskRun methods
138
+ async listTaskRuns(taskId: string): Promise<TaskRun[]> {
176
139
  const teamId = await this.getTeamId();
177
- return this.apiRequest<Task>(`/api/projects/${teamId}/tasks/${taskId}/update_stage/`, {
178
- method: 'PATCH',
179
- body: JSON.stringify({ current_stage: stageId }),
180
- });
140
+ const response = await this.apiRequest<PostHogApiResponse<TaskRun>>(
141
+ `/api/projects/${teamId}/tasks/${taskId}/runs/`
142
+ );
143
+ return response.results || [];
181
144
  }
182
145
 
183
- async setTaskBranch(taskId: string, branch: string): Promise<Task> {
146
+ async getTaskRun(taskId: string, runId: string): Promise<TaskRun> {
184
147
  const teamId = await this.getTeamId();
185
- return this.apiRequest<Task>(`/api/projects/${teamId}/tasks/${taskId}/set_branch/`, {
148
+ return this.apiRequest<TaskRun>(`/api/projects/${teamId}/tasks/${taskId}/runs/${runId}/`);
149
+ }
150
+
151
+ async createTaskRun(
152
+ taskId: string,
153
+ payload?: Partial<Omit<TaskRun, 'id' | 'task' | 'team' | 'created_at' | 'updated_at' | 'completed_at'>>
154
+ ): Promise<TaskRun> {
155
+ const teamId = await this.getTeamId();
156
+ return this.apiRequest<TaskRun>(`/api/projects/${teamId}/tasks/${taskId}/runs/`, {
186
157
  method: "POST",
187
- body: JSON.stringify({ branch }),
158
+ body: JSON.stringify(payload || {}),
188
159
  });
189
160
  }
190
161
 
191
- async attachTaskPullRequest(taskId: string, prUrl: string, branch?: string): Promise<Task> {
162
+ async updateTaskRun(
163
+ taskId: string,
164
+ runId: string,
165
+ payload: TaskRunUpdate
166
+ ): Promise<TaskRun> {
192
167
  const teamId = await this.getTeamId();
193
- const payload: Record<string, string> = { pr_url: prUrl };
194
- if (branch) {
195
- payload.branch = branch;
196
- }
197
- return this.apiRequest<Task>(`/api/projects/${teamId}/tasks/${taskId}/attach_pr/`, {
198
- method: "POST",
168
+ return this.apiRequest<TaskRun>(`/api/projects/${teamId}/tasks/${taskId}/runs/${runId}/`, {
169
+ method: "PATCH",
199
170
  body: JSON.stringify(payload),
200
171
  });
201
172
  }
202
173
 
203
- async getTaskProgress(taskId: string): Promise<TaskProgressResponse> {
174
+ async updateTaskRunStage(taskId: string, runId: string, stageId: string): Promise<TaskRun> {
204
175
  const teamId = await this.getTeamId();
205
- return this.apiRequest<TaskProgressResponse>(`/api/projects/${teamId}/tasks/${taskId}/progress/`);
176
+ return this.apiRequest<TaskRun>(`/api/projects/${teamId}/tasks/${taskId}/runs/${runId}/update_stage/`, {
177
+ method: 'PATCH',
178
+ body: JSON.stringify({ current_stage: stageId }),
179
+ });
206
180
  }
207
181
 
208
- async createTaskProgress(
209
- taskId: string,
210
- payload: TaskProgressUpdate & { status: TaskProgressRecord["status"] }
211
- ): Promise<TaskProgressRecord> {
182
+ async progressTaskRun(taskId: string, runId: string, nextStageId?: string): Promise<TaskRun> {
212
183
  const teamId = await this.getTeamId();
213
- return this.apiRequest<TaskProgressRecord>(`/api/projects/${teamId}/task_progress/`, {
214
- method: "POST",
215
- body: JSON.stringify({
216
- ...payload,
217
- task: taskId,
218
- }),
184
+ const payload: Record<string, string> = {};
185
+ if (nextStageId) {
186
+ payload.next_stage_id = nextStageId;
187
+ }
188
+ return this.apiRequest<TaskRun>(`/api/projects/${teamId}/tasks/${taskId}/runs/${runId}/progress_run/`, {
189
+ method: 'POST',
190
+ body: JSON.stringify(payload),
219
191
  });
220
192
  }
221
193
 
222
- async updateTaskProgress(
223
- taskId: string,
224
- progressId: string,
225
- payload: TaskProgressUpdate
226
- ): Promise<TaskProgressRecord> {
194
+ async setTaskRunOutput(taskId: string, runId: string, output: Record<string, unknown>): Promise<TaskRun> {
227
195
  const teamId = await this.getTeamId();
228
- return this.apiRequest<TaskProgressRecord>(`/api/projects/${teamId}/task_progress/${progressId}/`, {
229
- method: "PATCH",
230
- body: JSON.stringify({
231
- ...payload,
232
- task: taskId,
233
- }),
196
+ return this.apiRequest<TaskRun>(`/api/projects/${teamId}/tasks/${taskId}/runs/${runId}/set_output/`, {
197
+ method: 'PATCH',
198
+ body: JSON.stringify({ output }),
199
+ });
200
+ }
201
+
202
+ async appendTaskRunLog(taskId: string, runId: string, entries: LogEntry[]): Promise<TaskRun> {
203
+ const teamId = await this.getTeamId();
204
+ return this.apiRequest<TaskRun>(`/api/projects/${teamId}/tasks/${taskId}/runs/${runId}/append_log/`, {
205
+ method: 'POST',
206
+ body: JSON.stringify({ entries }),
234
207
  });
235
208
  }
236
209
 
@@ -251,14 +224,6 @@ export class PostHogAPIClient {
251
224
  return this.apiRequest<AgentDefinition[]>(`/api/agents/`);
252
225
  }
253
226
 
254
- async progressTask(taskId: string, options?: { next_stage_id?: string; auto?: boolean }): Promise<Task> {
255
- const teamId = await this.getTeamId();
256
- return this.apiRequest<Task>(`/api/projects/${teamId}/tasks/${taskId}/progress_task/`, {
257
- method: 'POST',
258
- body: JSON.stringify(options || {}),
259
- });
260
- }
261
-
262
227
  /**
263
228
  * Fetch error details from PostHog error tracking
264
229
  */
@@ -1,11 +1,8 @@
1
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';
2
+ import type { PostHogAPIClient, TaskRunUpdate } from './posthog-api.js';
3
+ import type { AgentEvent, TaskRun } from './types.js';
4
4
 
5
5
  interface ProgressMetadata {
6
- workflowId?: string;
7
- workflowRunId?: string;
8
- activityId?: string;
9
6
  totalSteps?: number;
10
7
  }
11
8
 
@@ -18,7 +15,7 @@ interface ProgressMetadata {
18
15
  export class TaskProgressReporter {
19
16
  private posthogAPI?: PostHogAPIClient;
20
17
  private logger: Logger;
21
- private progressRecord?: TaskProgressRecord;
18
+ private taskRun?: TaskRun;
22
19
  private taskId?: string;
23
20
  private outputLog: string[] = [];
24
21
  private totalSteps?: number;
@@ -29,8 +26,8 @@ export class TaskProgressReporter {
29
26
  this.logger = logger.child('TaskProgressReporter');
30
27
  }
31
28
 
32
- get progressId(): string | undefined {
33
- return this.progressRecord?.id;
29
+ get runId(): string | undefined {
30
+ return this.taskRun?.id;
34
31
  }
35
32
 
36
33
  async start(taskId: string, metadata: ProgressMetadata = {}): Promise<void> {
@@ -42,37 +39,27 @@ export class TaskProgressReporter {
42
39
  this.totalSteps = metadata.totalSteps;
43
40
 
44
41
  try {
45
- const record = await this.posthogAPI.createTaskProgress(taskId, {
42
+ const run = await this.posthogAPI.createTaskRun(taskId, {
46
43
  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: '',
44
+ log: [],
54
45
  });
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 });
46
+ this.taskRun = run;
47
+ this.outputLog = [];
48
+ this.logger.debug('Created task run', { taskId, runId: run.id });
58
49
  } catch (error) {
59
- this.logger.warn('Failed to create task progress record', { taskId, error: (error as Error).message });
50
+ this.logger.warn('Failed to create task run', { taskId, error: (error as Error).message });
60
51
  }
61
52
  }
62
53
 
63
54
  async stageStarted(stageKey: string, stageIndex: number): Promise<void> {
64
55
  await this.update({
65
56
  status: 'in_progress',
66
- current_step: stageKey,
67
- completed_steps: Math.min(stageIndex, this.totalSteps ?? stageIndex),
68
57
  }, `Stage started: ${stageKey}`);
69
58
  }
70
59
 
71
60
  async stageCompleted(stageKey: string, completedStages: number): Promise<void> {
72
61
  await this.update({
73
62
  status: 'in_progress',
74
- current_step: stageKey,
75
- completed_steps: Math.min(completedStages, this.totalSteps ?? completedStages),
76
63
  }, `Stage completed: ${stageKey}`);
77
64
  }
78
65
 
@@ -97,7 +84,7 @@ export class TaskProgressReporter {
97
84
  }
98
85
 
99
86
  async complete(): Promise<void> {
100
- await this.update({ status: 'completed', completed_steps: this.totalSteps }, 'Workflow execution completed');
87
+ await this.update({ status: 'completed' }, 'Workflow execution completed');
101
88
  }
102
89
 
103
90
  async fail(error: Error | string): Promise<void> {
@@ -110,7 +97,7 @@ export class TaskProgressReporter {
110
97
  }
111
98
 
112
99
  async recordEvent(event: AgentEvent): Promise<void> {
113
- if (!this.posthogAPI || !this.progressId || !this.taskId) {
100
+ if (!this.posthogAPI || !this.runId || !this.taskId) {
114
101
  return;
115
102
  }
116
103
 
@@ -178,32 +165,39 @@ export class TaskProgressReporter {
178
165
  }
179
166
  }
180
167
 
181
- private async update(update: TaskProgressUpdate, logLine?: string): Promise<void> {
182
- if (!this.posthogAPI || !this.progressId || !this.taskId) {
168
+ private async update(update: TaskRunUpdate, logLine?: string): Promise<void> {
169
+ if (!this.posthogAPI || !this.runId || !this.taskId) {
183
170
  return;
184
171
  }
185
172
 
186
- if (logLine) {
187
- if (logLine !== this.lastLogEntry) {
188
- this.outputLog.push(logLine);
173
+ // If there's a log line, append it separately using the append_log endpoint
174
+ if (logLine && logLine !== this.lastLogEntry) {
175
+ try {
176
+ await this.posthogAPI.appendTaskRunLog(this.taskId, this.runId, [
177
+ { type: 'info', message: logLine }
178
+ ]);
189
179
  this.lastLogEntry = logLine;
180
+ } catch (error) {
181
+ this.logger.warn('Failed to append log entry', {
182
+ taskId: this.taskId,
183
+ runId: this.runId,
184
+ error: (error as Error).message,
185
+ });
190
186
  }
191
- update.output_log = this.outputLog.join('\n');
192
187
  }
193
188
 
194
- try {
195
- const record = await this.posthogAPI.updateTaskProgress(this.taskId, this.progressId, update);
196
- // Sync local cache with server response to avoid drift if server modifies values
197
- this.progressRecord = record;
198
- if (record.output_log !== undefined && record.output_log !== null) {
199
- this.outputLog = record.output_log ? record.output_log.split('\n') : [];
189
+ // Update other fields if provided
190
+ if (Object.keys(update).length > 0) {
191
+ try {
192
+ const run = await this.posthogAPI.updateTaskRun(this.taskId, this.runId, update);
193
+ this.taskRun = run;
194
+ } catch (error) {
195
+ this.logger.warn('Failed to update task run', {
196
+ taskId: this.taskId,
197
+ runId: this.runId,
198
+ error: (error as Error).message,
199
+ });
200
200
  }
201
- } catch (error) {
202
- this.logger.warn('Failed to update task progress record', {
203
- taskId: this.taskId,
204
- progressId: this.progressId,
205
- error: (error as Error).message,
206
- });
207
201
  }
208
202
  }
209
203
 
package/src/types.ts CHANGED
@@ -6,15 +6,43 @@ export interface Task {
6
6
  origin_product: 'error_tracking' | 'eval_clusters' | 'user_created' | 'support_queue' | 'session_summaries';
7
7
  position?: number;
8
8
  workflow?: string | null;
9
- current_stage?: string | null;
10
9
  github_integration?: number | null;
11
10
  repository_config?: unknown; // JSONField
12
11
  repository_list: string;
13
12
  primary_repository: string;
14
- github_branch: string | null;
15
- github_pr_url: string | null;
16
13
  created_at: string;
17
14
  updated_at: string;
15
+
16
+ // DEPRECATED: These fields have been moved to TaskRun
17
+ // Use task.latest_run instead
18
+ current_stage?: string | null;
19
+ github_branch?: string | null;
20
+ github_pr_url?: string | null;
21
+ latest_run?: TaskRun;
22
+ }
23
+
24
+ // Log entry structure for TaskRun.log
25
+ export interface LogEntry {
26
+ type: string; // e.g., "info", "warning", "error", "success", "debug"
27
+ message: string;
28
+ [key: string]: unknown; // Allow additional fields
29
+ }
30
+
31
+ // TaskRun model - represents individual execution runs of tasks
32
+ export interface TaskRun {
33
+ id: string;
34
+ task: string; // Task ID
35
+ team: number;
36
+ branch: string | null;
37
+ current_stage: string | null; // WorkflowStage ID
38
+ status: 'started' | 'in_progress' | 'completed' | 'failed';
39
+ log: LogEntry[]; // Array of log entry objects
40
+ error_message: string | null;
41
+ output: Record<string, unknown> | null; // Structured output (PR URL, commit SHA, etc.)
42
+ state: Record<string, unknown>; // Intermediate run state (defaults to {}, never null)
43
+ created_at: string;
44
+ updated_at: string;
45
+ completed_at: string | null;
18
46
  }
19
47
 
20
48
  export interface SupportingFile {