@posthog/agent 1.20.0 → 1.22.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 (57) hide show
  1. package/dist/claude-cli/cli.js +2251 -1888
  2. package/dist/src/adapters/claude/claude-adapter.d.ts +1 -1
  3. package/dist/src/adapters/claude/claude-adapter.d.ts.map +1 -1
  4. package/dist/src/adapters/claude/claude-adapter.js +141 -133
  5. package/dist/src/adapters/claude/claude-adapter.js.map +1 -1
  6. package/dist/src/adapters/types.d.ts +3 -3
  7. package/dist/src/adapters/types.d.ts.map +1 -1
  8. package/dist/src/agent.d.ts +1 -1
  9. package/dist/src/agent.d.ts.map +1 -1
  10. package/dist/src/agent.js +9 -8
  11. package/dist/src/agent.js.map +1 -1
  12. package/dist/src/file-manager.d.ts +10 -0
  13. package/dist/src/file-manager.d.ts.map +1 -1
  14. package/dist/src/file-manager.js +49 -10
  15. package/dist/src/file-manager.js.map +1 -1
  16. package/dist/src/git-manager.d.ts +1 -0
  17. package/dist/src/git-manager.d.ts.map +1 -1
  18. package/dist/src/git-manager.js +10 -3
  19. package/dist/src/git-manager.js.map +1 -1
  20. package/dist/src/posthog-api.d.ts +2 -1
  21. package/dist/src/posthog-api.d.ts.map +1 -1
  22. package/dist/src/posthog-api.js +11 -0
  23. package/dist/src/posthog-api.js.map +1 -1
  24. package/dist/src/task-progress-reporter.d.ts +12 -4
  25. package/dist/src/task-progress-reporter.d.ts.map +1 -1
  26. package/dist/src/task-progress-reporter.js +282 -117
  27. package/dist/src/task-progress-reporter.js.map +1 -1
  28. package/dist/src/types.d.ts +17 -1
  29. package/dist/src/types.d.ts.map +1 -1
  30. package/dist/src/types.js.map +1 -1
  31. package/dist/src/workflow/config.d.ts.map +1 -1
  32. package/dist/src/workflow/config.js +11 -0
  33. package/dist/src/workflow/config.js.map +1 -1
  34. package/dist/src/workflow/steps/build.js +3 -3
  35. package/dist/src/workflow/steps/build.js.map +1 -1
  36. package/dist/src/workflow/steps/finalize.d.ts +3 -0
  37. package/dist/src/workflow/steps/finalize.d.ts.map +1 -0
  38. package/dist/src/workflow/steps/finalize.js +182 -0
  39. package/dist/src/workflow/steps/finalize.js.map +1 -0
  40. package/dist/src/workflow/steps/plan.js +3 -3
  41. package/dist/src/workflow/steps/plan.js.map +1 -1
  42. package/dist/src/workflow/steps/research.js +3 -3
  43. package/dist/src/workflow/steps/research.js.map +1 -1
  44. package/package.json +2 -2
  45. package/src/adapters/claude/claude-adapter.ts +56 -46
  46. package/src/adapters/types.ts +3 -3
  47. package/src/agent.ts +17 -8
  48. package/src/file-manager.ts +59 -6
  49. package/src/git-manager.ts +11 -3
  50. package/src/posthog-api.ts +33 -1
  51. package/src/task-progress-reporter.ts +310 -138
  52. package/src/types.ts +20 -1
  53. package/src/workflow/config.ts +11 -0
  54. package/src/workflow/steps/build.ts +3 -3
  55. package/src/workflow/steps/finalize.ts +216 -0
  56. package/src/workflow/steps/plan.ts +3 -3
  57. package/src/workflow/steps/research.ts +3 -3
@@ -58,9 +58,9 @@ const researchStep = async ({ step, context }) => {
58
58
  let jsonContent = '';
59
59
  for await (const message of response) {
60
60
  emitEvent(adapter.createRawSDKEvent(message));
61
- const transformed = adapter.transform(message);
62
- if (transformed) {
63
- emitEvent(transformed);
61
+ const transformedEvents = adapter.transform(message);
62
+ for (const event of transformedEvents) {
63
+ emitEvent(event);
64
64
  }
65
65
  if (message.type === 'assistant' && message.message?.content) {
66
66
  for (const c of message.message.content) {
@@ -1 +1 @@
1
- {"version":3,"file":"research.js","sources":["../../../../src/workflow/steps/research.ts"],"sourcesContent":["import { query } from '@anthropic-ai/claude-agent-sdk';\nimport { RESEARCH_SYSTEM_PROMPT } from '../../agents/research.js';\nimport type { WorkflowStepRunner } from '../types.js';\nimport type { ResearchEvaluation } from '../../types.js';\nimport { finalizeStepGitActions } from '../utils.js';\n\nexport const researchStep: WorkflowStepRunner = async ({ step, context }) => {\n const {\n task,\n cwd,\n isCloudMode,\n options,\n logger,\n fileManager,\n gitManager,\n promptBuilder,\n adapter,\n mcpServers,\n emitEvent,\n } = context;\n\n const stepLogger = logger.child('ResearchStep');\n\n const existingResearch = await fileManager.readResearch(task.id);\n if (existingResearch) {\n stepLogger.info('Research already exists', { taskId: task.id, hasQuestions: !!existingResearch.questions, answered: existingResearch.answered });\n \n // If there are unanswered questions, re-emit them so UI can prompt user\n if (existingResearch.questions && !existingResearch.answered) {\n stepLogger.info('Re-emitting unanswered research questions', { \n taskId: task.id,\n questionCount: existingResearch.questions.length \n });\n \n emitEvent({\n type: 'artifact',\n ts: Date.now(),\n kind: 'research_questions',\n content: existingResearch.questions,\n });\n \n // In local mode, halt to allow user to answer\n if (!isCloudMode) {\n emitEvent(adapter.createStatusEvent('phase_complete', { phase: 'research' }));\n return { status: 'skipped', halt: true };\n }\n }\n \n return { status: 'skipped' };\n }\n\n stepLogger.info('Starting research phase', { taskId: task.id });\n emitEvent(adapter.createStatusEvent('phase_start', { phase: 'research' }));\n\n const researchPrompt = await promptBuilder.buildResearchPrompt(task, cwd);\n const fullPrompt = `${RESEARCH_SYSTEM_PROMPT}\\n\\n${researchPrompt}`;\n\n const baseOptions: Record<string, any> = {\n model: step.model,\n cwd,\n permissionMode: 'plan',\n settingSources: ['local'],\n mcpServers,\n // Allow research tools: read-only operations, web search, and MCP resources\n allowedTools: [\n 'Read',\n 'Glob',\n 'Grep',\n 'WebFetch',\n 'WebSearch',\n 'ListMcpResources',\n 'ReadMcpResource',\n 'TodoWrite',\n 'BashOutput',\n ],\n };\n\n const response = query({\n prompt: fullPrompt,\n options: { ...baseOptions, ...(options.queryOverrides || {}) },\n });\n\n let jsonContent = '';\n for await (const message of response) {\n emitEvent(adapter.createRawSDKEvent(message));\n const transformed = adapter.transform(message);\n if (transformed) {\n emitEvent(transformed);\n }\n if (message.type === 'assistant' && message.message?.content) {\n for (const c of message.message.content) {\n if (c.type === 'text' && c.text) {\n jsonContent += c.text;\n }\n }\n }\n }\n\n if (!jsonContent.trim()) {\n stepLogger.error('No JSON output from research agent', { taskId: task.id });\n emitEvent({\n type: 'error',\n ts: Date.now(),\n message: 'Research agent returned no output',\n });\n return { status: 'completed', halt: true };\n }\n\n // Parse JSON response\n let evaluation: ResearchEvaluation;\n try {\n // Extract JSON from potential markdown code blocks or other wrapping\n const jsonMatch = jsonContent.match(/\\{[\\s\\S]*\\}/);\n if (!jsonMatch) {\n throw new Error('No JSON object found in response');\n }\n evaluation = JSON.parse(jsonMatch[0]);\n stepLogger.info('Parsed research evaluation', {\n taskId: task.id,\n score: evaluation.actionabilityScore,\n hasQuestions: !!evaluation.questions,\n });\n } catch (error) {\n stepLogger.error('Failed to parse research JSON', {\n taskId: task.id,\n error: error instanceof Error ? error.message : String(error),\n content: jsonContent.substring(0, 500),\n });\n emitEvent({\n type: 'error',\n ts: Date.now(),\n message: `Failed to parse research JSON: ${\n error instanceof Error ? error.message : String(error)\n }`,\n });\n return { status: 'completed', halt: true };\n }\n\n // Add answered/answers fields to evaluation\n if (evaluation.questions && evaluation.questions.length > 0) {\n evaluation.answered = false;\n evaluation.answers = undefined;\n }\n\n // Always write research.json\n await fileManager.writeResearch(task.id, evaluation);\n stepLogger.info('Research evaluation written', {\n taskId: task.id,\n score: evaluation.actionabilityScore,\n hasQuestions: !!evaluation.questions,\n });\n\n emitEvent({\n type: 'artifact',\n ts: Date.now(),\n kind: 'research_evaluation',\n content: evaluation,\n });\n\n await gitManager.addAllPostHogFiles();\n await finalizeStepGitActions(context, step, {\n commitMessage: `Research phase for ${task.title}`,\n });\n\n // Log whether questions need answering\n if (evaluation.actionabilityScore < 0.7 && evaluation.questions && evaluation.questions.length > 0) {\n stepLogger.info('Actionability score below threshold, questions needed', {\n taskId: task.id,\n score: evaluation.actionabilityScore,\n questionCount: evaluation.questions.length,\n });\n \n emitEvent({\n type: 'artifact',\n ts: Date.now(),\n kind: 'research_questions',\n content: evaluation.questions,\n });\n } else {\n stepLogger.info('Actionability score acceptable, proceeding to planning', {\n taskId: task.id,\n score: evaluation.actionabilityScore,\n });\n }\n\n // In local mode, always halt after research for user review\n if (!isCloudMode) {\n emitEvent(adapter.createStatusEvent('phase_complete', { phase: 'research' }));\n return { status: 'completed', halt: true };\n }\n\n // In cloud mode, check if questions need answering\n const researchData = await fileManager.readResearch(task.id);\n if (researchData?.questions && !researchData.answered) {\n // Questions need answering - halt for user input in cloud mode too\n emitEvent(adapter.createStatusEvent('phase_complete', { phase: 'research' }));\n return { status: 'completed', halt: true };\n }\n\n // No questions or questions already answered - proceed to planning\n emitEvent(adapter.createStatusEvent('phase_complete', { phase: 'research' }));\n return { status: 'completed' };\n};\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"research.js","sources":["../../../../src/workflow/steps/research.ts"],"sourcesContent":["import { query } from '@anthropic-ai/claude-agent-sdk';\nimport { RESEARCH_SYSTEM_PROMPT } from '../../agents/research.js';\nimport type { WorkflowStepRunner } from '../types.js';\nimport type { ResearchEvaluation } from '../../types.js';\nimport { finalizeStepGitActions } from '../utils.js';\n\nexport const researchStep: WorkflowStepRunner = async ({ step, context }) => {\n const {\n task,\n cwd,\n isCloudMode,\n options,\n logger,\n fileManager,\n gitManager,\n promptBuilder,\n adapter,\n mcpServers,\n emitEvent,\n } = context;\n\n const stepLogger = logger.child('ResearchStep');\n\n const existingResearch = await fileManager.readResearch(task.id);\n if (existingResearch) {\n stepLogger.info('Research already exists', { taskId: task.id, hasQuestions: !!existingResearch.questions, answered: existingResearch.answered });\n \n // If there are unanswered questions, re-emit them so UI can prompt user\n if (existingResearch.questions && !existingResearch.answered) {\n stepLogger.info('Re-emitting unanswered research questions', { \n taskId: task.id,\n questionCount: existingResearch.questions.length \n });\n \n emitEvent({\n type: 'artifact',\n ts: Date.now(),\n kind: 'research_questions',\n content: existingResearch.questions,\n });\n \n // In local mode, halt to allow user to answer\n if (!isCloudMode) {\n emitEvent(adapter.createStatusEvent('phase_complete', { phase: 'research' }));\n return { status: 'skipped', halt: true };\n }\n }\n \n return { status: 'skipped' };\n }\n\n stepLogger.info('Starting research phase', { taskId: task.id });\n emitEvent(adapter.createStatusEvent('phase_start', { phase: 'research' }));\n\n const researchPrompt = await promptBuilder.buildResearchPrompt(task, cwd);\n const fullPrompt = `${RESEARCH_SYSTEM_PROMPT}\\n\\n${researchPrompt}`;\n\n const baseOptions: Record<string, any> = {\n model: step.model,\n cwd,\n permissionMode: 'plan',\n settingSources: ['local'],\n mcpServers,\n // Allow research tools: read-only operations, web search, and MCP resources\n allowedTools: [\n 'Read',\n 'Glob',\n 'Grep',\n 'WebFetch',\n 'WebSearch',\n 'ListMcpResources',\n 'ReadMcpResource',\n 'TodoWrite',\n 'BashOutput',\n ],\n };\n\n const response = query({\n prompt: fullPrompt,\n options: { ...baseOptions, ...(options.queryOverrides || {}) },\n });\n\n let jsonContent = '';\n for await (const message of response) {\n emitEvent(adapter.createRawSDKEvent(message));\n const transformedEvents = adapter.transform(message);\n for (const event of transformedEvents) {\n emitEvent(event);\n }\n if (message.type === 'assistant' && message.message?.content) {\n for (const c of message.message.content) {\n if (c.type === 'text' && c.text) {\n jsonContent += c.text;\n }\n }\n }\n }\n\n if (!jsonContent.trim()) {\n stepLogger.error('No JSON output from research agent', { taskId: task.id });\n emitEvent({\n type: 'error',\n ts: Date.now(),\n message: 'Research agent returned no output',\n });\n return { status: 'completed', halt: true };\n }\n\n // Parse JSON response\n let evaluation: ResearchEvaluation;\n try {\n // Extract JSON from potential markdown code blocks or other wrapping\n const jsonMatch = jsonContent.match(/\\{[\\s\\S]*\\}/);\n if (!jsonMatch) {\n throw new Error('No JSON object found in response');\n }\n evaluation = JSON.parse(jsonMatch[0]);\n stepLogger.info('Parsed research evaluation', {\n taskId: task.id,\n score: evaluation.actionabilityScore,\n hasQuestions: !!evaluation.questions,\n });\n } catch (error) {\n stepLogger.error('Failed to parse research JSON', {\n taskId: task.id,\n error: error instanceof Error ? error.message : String(error),\n content: jsonContent.substring(0, 500),\n });\n emitEvent({\n type: 'error',\n ts: Date.now(),\n message: `Failed to parse research JSON: ${\n error instanceof Error ? error.message : String(error)\n }`,\n });\n return { status: 'completed', halt: true };\n }\n\n // Add answered/answers fields to evaluation\n if (evaluation.questions && evaluation.questions.length > 0) {\n evaluation.answered = false;\n evaluation.answers = undefined;\n }\n\n // Always write research.json\n await fileManager.writeResearch(task.id, evaluation);\n stepLogger.info('Research evaluation written', {\n taskId: task.id,\n score: evaluation.actionabilityScore,\n hasQuestions: !!evaluation.questions,\n });\n\n emitEvent({\n type: 'artifact',\n ts: Date.now(),\n kind: 'research_evaluation',\n content: evaluation,\n });\n\n await gitManager.addAllPostHogFiles();\n await finalizeStepGitActions(context, step, {\n commitMessage: `Research phase for ${task.title}`,\n });\n\n // Log whether questions need answering\n if (evaluation.actionabilityScore < 0.7 && evaluation.questions && evaluation.questions.length > 0) {\n stepLogger.info('Actionability score below threshold, questions needed', {\n taskId: task.id,\n score: evaluation.actionabilityScore,\n questionCount: evaluation.questions.length,\n });\n \n emitEvent({\n type: 'artifact',\n ts: Date.now(),\n kind: 'research_questions',\n content: evaluation.questions,\n });\n } else {\n stepLogger.info('Actionability score acceptable, proceeding to planning', {\n taskId: task.id,\n score: evaluation.actionabilityScore,\n });\n }\n\n // In local mode, always halt after research for user review\n if (!isCloudMode) {\n emitEvent(adapter.createStatusEvent('phase_complete', { phase: 'research' }));\n return { status: 'completed', halt: true };\n }\n\n // In cloud mode, check if questions need answering\n const researchData = await fileManager.readResearch(task.id);\n if (researchData?.questions && !researchData.answered) {\n // Questions need answering - halt for user input in cloud mode too\n emitEvent(adapter.createStatusEvent('phase_complete', { phase: 'research' }));\n return { status: 'completed', halt: true };\n }\n\n // No questions or questions already answered - proceed to planning\n emitEvent(adapter.createStatusEvent('phase_complete', { phase: 'research' }));\n return { status: 'completed' };\n};\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@posthog/agent",
3
- "version": "1.20.0",
3
+ "version": "1.22.0",
4
4
  "description": "TypeScript agent framework wrapping Claude Agent SDK with Git-based task execution for PostHog",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -49,7 +49,7 @@
49
49
  "typescript": "^5.5.0"
50
50
  },
51
51
  "dependencies": {
52
- "@anthropic-ai/claude-agent-sdk": "^0.1.30",
52
+ "@anthropic-ai/claude-agent-sdk": "^0.1.47",
53
53
  "dotenv": "^17.2.3",
54
54
  "yoga-wasm-web": "^0.3.3"
55
55
  },
@@ -18,72 +18,71 @@ export class ClaudeAdapter implements ProviderAdapter {
18
18
  };
19
19
  }
20
20
 
21
- transform(sdkMessage: SDKMessage): AgentEvent | null {
21
+ transform(sdkMessage: SDKMessage): AgentEvent[] {
22
22
  const baseEvent = { ts: Date.now() };
23
23
 
24
- // Handle stream events
25
24
  if (sdkMessage.type === 'stream_event') {
26
25
  const event = sdkMessage.event;
27
26
 
28
27
  switch (event.type) {
29
28
  case 'message_start':
30
- return {
29
+ return [{
31
30
  ...baseEvent,
32
31
  type: 'message_start',
33
32
  messageId: event.message?.id,
34
33
  model: event.message?.model
35
- };
34
+ }];
36
35
 
37
36
  case 'content_block_start':
38
37
  const contentBlock = event.content_block;
39
- if (!contentBlock) return null;
38
+ if (!contentBlock) return [];
40
39
 
41
- return {
40
+ return [{
42
41
  ...baseEvent,
43
42
  type: 'content_block_start',
44
43
  index: event.index,
45
44
  contentType: contentBlock.type as 'text' | 'tool_use' | 'thinking',
46
45
  toolName: contentBlock.type === 'tool_use' ? contentBlock.name : undefined,
47
46
  toolId: contentBlock.type === 'tool_use' ? contentBlock.id : undefined
48
- };
47
+ }];
49
48
 
50
49
  case 'content_block_delta':
51
50
  const delta = event.delta;
52
- if (!delta) return null;
51
+ if (!delta) return [];
53
52
 
54
53
  if (delta.type === 'text_delta') {
55
- return {
54
+ return [{
56
55
  ...baseEvent,
57
56
  type: 'token',
58
57
  content: delta.text,
59
58
  contentType: 'text'
60
- };
59
+ }];
61
60
  } else if (delta.type === 'input_json_delta') {
62
- return {
61
+ return [{
63
62
  ...baseEvent,
64
63
  type: 'token',
65
64
  content: delta.partial_json,
66
65
  contentType: 'tool_input'
67
- };
66
+ }];
68
67
  } else if (delta.type === 'thinking_delta') {
69
- return {
68
+ return [{
70
69
  ...baseEvent,
71
70
  type: 'token',
72
71
  content: delta.thinking,
73
72
  contentType: 'thinking'
74
- };
73
+ }];
75
74
  }
76
- return null;
75
+ return [];
77
76
 
78
77
  case 'content_block_stop':
79
- return {
78
+ return [{
80
79
  ...baseEvent,
81
80
  type: 'content_block_stop',
82
81
  index: event.index
83
- };
82
+ }];
84
83
 
85
84
  case 'message_delta':
86
- return {
85
+ return [{
87
86
  ...baseEvent,
88
87
  type: 'message_delta',
89
88
  stopReason: event.delta?.stop_reason,
@@ -91,20 +90,20 @@ export class ClaudeAdapter implements ProviderAdapter {
91
90
  usage: event.usage ? {
92
91
  outputTokens: event.usage.output_tokens
93
92
  } : undefined
94
- };
93
+ }];
95
94
 
96
95
  case 'message_stop':
97
- return {
96
+ return [{
98
97
  ...baseEvent,
99
98
  type: 'message_stop'
100
- };
99
+ }];
101
100
 
102
101
  case 'ping':
103
102
  // Ignore ping events
104
- return null;
103
+ return [];
105
104
 
106
105
  case 'error':
107
- return {
106
+ return [{
108
107
  ...baseEvent,
109
108
  type: 'error',
110
109
  message: event.error?.message || 'Unknown error',
@@ -115,18 +114,20 @@ export class ClaudeAdapter implements ProviderAdapter {
115
114
  code: event.error.code,
116
115
  } : undefined,
117
116
  sdkError: event.error
118
- };
117
+ }];
119
118
 
120
119
  default:
121
- return null;
120
+ return [];
122
121
  }
123
122
  }
124
123
 
125
124
  // Handle assistant messages (full message, not streaming)
126
125
  if (sdkMessage.type === 'assistant') {
127
126
  const message = sdkMessage.message;
127
+ const events: AgentEvent[] = [];
128
128
 
129
129
  // Extract tool calls from content blocks
130
+ // A single assistant message can contain multiple tool_use blocks
130
131
  if (message.content && Array.isArray(message.content)) {
131
132
  for (const block of message.content) {
132
133
  if (block.type === 'tool_use') {
@@ -139,31 +140,36 @@ export class ClaudeAdapter implements ProviderAdapter {
139
140
  args: block.input || {},
140
141
  parentToolUseId: sdkMessage.parent_tool_use_id
141
142
  };
142
- // Enrich with tool metadata
143
- return this.toolMapper.enrichToolCall(toolCallEvent);
143
+ // Enrich with tool metadata and add to events array
144
+ events.push(this.toolMapper.enrichToolCall(toolCallEvent));
144
145
  }
145
146
  }
146
147
  }
147
148
 
149
+ // If we found tool calls, return them
150
+ if (events.length > 0) {
151
+ return events;
152
+ }
153
+
148
154
  // If no tool calls, emit status event
149
- return {
155
+ return [{
150
156
  ...baseEvent,
151
157
  type: 'status',
152
158
  phase: 'assistant_message',
153
159
  messageId: message.id,
154
160
  model: message.model
155
- };
161
+ }];
156
162
  }
157
163
 
158
164
  // Handle user messages
159
165
  if (sdkMessage.type === 'user') {
160
166
  const message = sdkMessage.message;
167
+ const events: AgentEvent[] = [];
161
168
 
162
- // Check for tool results in content blocks
163
- if (message?.content && Array.isArray(message.content)) {
169
+ // Check for tool results in content blocks, A single user message can contain multiple tool_result blocks
170
+ if (message?.content && Array.isArray(message.content)) {
164
171
  for (const block of message.content) {
165
172
  if (block.type === 'tool_result') {
166
- // Create tool_result event and enrich with metadata
167
173
  const toolResultEvent = {
168
174
  ...baseEvent,
169
175
  type: 'tool_result' as const,
@@ -173,29 +179,33 @@ export class ClaudeAdapter implements ProviderAdapter {
173
179
  isError: block.is_error,
174
180
  parentToolUseId: sdkMessage.parent_tool_use_id
175
181
  };
176
- // Enrich with tool metadata
177
- return this.toolMapper.enrichToolResult(toolResultEvent);
182
+ events.push(this.toolMapper.enrichToolResult(toolResultEvent));
178
183
  }
179
184
  }
180
185
  }
181
186
 
187
+ // If we found tool results, return them
188
+ if (events.length > 0) {
189
+ return events;
190
+ }
191
+
182
192
  // Otherwise extract text content
183
193
  const textContent = this.extractUserContent(message?.content);
184
194
  if (!textContent) {
185
- return null;
195
+ return [];
186
196
  }
187
- return {
197
+ return [{
188
198
  ...baseEvent,
189
199
  type: 'user_message',
190
200
  content: textContent,
191
201
  isSynthetic: sdkMessage.isSynthetic
192
- };
202
+ }];
193
203
  }
194
204
 
195
205
  // Handle result messages
196
206
  if (sdkMessage.type === 'result') {
197
207
  if (sdkMessage.subtype === 'success') {
198
- return {
208
+ return [{
199
209
  ...baseEvent,
200
210
  type: 'done',
201
211
  result: sdkMessage.result,
@@ -206,9 +216,9 @@ export class ClaudeAdapter implements ProviderAdapter {
206
216
  usage: sdkMessage.usage,
207
217
  modelUsage: sdkMessage.modelUsage,
208
218
  permissionDenials: sdkMessage.permission_denials
209
- };
219
+ }];
210
220
  } else {
211
- return {
221
+ return [{
212
222
  ...baseEvent,
213
223
  type: 'error',
214
224
  message: `Execution failed: ${sdkMessage.subtype}`,
@@ -220,14 +230,14 @@ export class ClaudeAdapter implements ProviderAdapter {
220
230
  num_turns: sdkMessage.num_turns
221
231
  },
222
232
  sdkError: sdkMessage
223
- };
233
+ }];
224
234
  }
225
235
  }
226
236
 
227
237
  // Handle system messages
228
238
  if (sdkMessage.type === 'system') {
229
239
  if (sdkMessage.subtype === 'init') {
230
- return {
240
+ return [{
231
241
  ...baseEvent,
232
242
  type: 'init',
233
243
  model: sdkMessage.model,
@@ -239,18 +249,18 @@ export class ClaudeAdapter implements ProviderAdapter {
239
249
  slashCommands: sdkMessage.slash_commands,
240
250
  outputStyle: sdkMessage.output_style,
241
251
  mcpServers: sdkMessage.mcp_servers
242
- };
252
+ }];
243
253
  } else if (sdkMessage.subtype === 'compact_boundary') {
244
- return {
254
+ return [{
245
255
  ...baseEvent,
246
256
  type: 'compact_boundary',
247
257
  trigger: sdkMessage.compact_metadata.trigger,
248
258
  preTokens: sdkMessage.compact_metadata.pre_tokens
249
- };
259
+ }];
250
260
  }
251
261
  }
252
262
 
253
- return null;
263
+ return [];
254
264
  }
255
265
 
256
266
  createStatusEvent(phase: string, additionalData?: any): StatusEvent {
@@ -12,10 +12,10 @@ export interface ProviderAdapter {
12
12
  name: string;
13
13
 
14
14
  /**
15
- * Transform a provider-specific SDK message into an AgentEvent.
16
- * Returns null if the message should be ignored.
15
+ * Transform a provider-specific SDK message into one or more AgentEvents.
16
+ * Returns an array of events (can be empty if the message should be ignored).
17
17
  */
18
- transform(sdkMessage: unknown): AgentEvent | null;
18
+ transform(sdkMessage: unknown): AgentEvent[];
19
19
 
20
20
  /**
21
21
  * Create a standardized status event.
package/src/agent.ts CHANGED
@@ -214,10 +214,9 @@ export class Agent {
214
214
  this.logger.debug('Received message in direct run', message);
215
215
  // Emit raw SDK event
216
216
  this.emitEvent(this.adapter.createRawSDKEvent(message));
217
- // Emit transformed event
218
- const transformedEvent = this.adapter.transform(message);
219
- if (transformedEvent) {
220
- this.emitEvent(transformedEvent);
217
+ const transformedEvents = this.adapter.transform(message);
218
+ for (const event of transformedEvents) {
219
+ this.emitEvent(event);
221
220
  }
222
221
  results.push(message);
223
222
  }
@@ -308,11 +307,16 @@ export class Agent {
308
307
  return commitHash;
309
308
  }
310
309
 
311
- async createPullRequest(taskId: string, branchName: string, taskTitle: string, taskDescription: string): Promise<string> {
310
+ async createPullRequest(
311
+ taskId: string,
312
+ branchName: string,
313
+ taskTitle: string,
314
+ taskDescription: string,
315
+ customBody?: string
316
+ ): Promise<string> {
312
317
  this.logger.info('Creating pull request', { taskId, branchName, taskTitle });
313
318
 
314
- // Build PR body
315
- const prBody = `## Task Details
319
+ const defaultBody = `## Task Details
316
320
  **Task ID**: ${taskId}
317
321
  **Description**: ${taskDescription}
318
322
 
@@ -320,6 +324,7 @@ export class Agent {
320
324
  This PR implements the changes described in the task.
321
325
 
322
326
  Generated by PostHog Agent`;
327
+ const prBody = customBody || defaultBody;
323
328
 
324
329
  const prUrl = await this.gitManager.createPullRequest(
325
330
  branchName,
@@ -447,11 +452,15 @@ Generated by PostHog Agent`;
447
452
  }
448
453
 
449
454
  const branchName = await this.gitManager.getCurrentBranch();
455
+ const finalizeResult = stepResults['finalize'];
456
+ const prBody = finalizeResult?.prBody;
457
+
450
458
  const prUrl = await this.createPullRequest(
451
459
  task.id,
452
460
  branchName,
453
461
  task.title,
454
- task.description ?? ''
462
+ task.description ?? '',
463
+ prBody
455
464
  );
456
465
 
457
466
  this.emitEvent(this.adapter.createStatusEvent('pr_created', { prUrl }));
@@ -1,5 +1,5 @@
1
1
  import { promises as fs } from 'fs';
2
- import { join } from 'path';
2
+ import { join, extname } from 'path';
3
3
  import type { SupportingFile, ResearchEvaluation } from './types.js';
4
4
  import { Logger } from './utils/logger.js';
5
5
 
@@ -9,6 +9,14 @@ export interface TaskFile {
9
9
  type: 'plan' | 'context' | 'reference' | 'output' | 'artifact';
10
10
  }
11
11
 
12
+ export interface LocalArtifact {
13
+ name: string;
14
+ content: string;
15
+ type: TaskFile['type'];
16
+ contentType: string;
17
+ size: number;
18
+ }
19
+
12
20
  export class PostHogFileManager {
13
21
  private repositoryPath: string;
14
22
  private logger: Logger;
@@ -223,11 +231,7 @@ export class PostHogFileManager {
223
231
  const content = await this.readTaskFile(taskId, fileName);
224
232
  if (content !== null) {
225
233
  // Determine type based on file name
226
- let type: SupportingFile['type'] = 'reference';
227
- if (fileName === 'plan.md') type = 'plan';
228
- else if (fileName === 'context.md') type = 'context';
229
- else if (fileName === 'requirements.md') type = 'reference';
230
- else if (fileName.startsWith('output_')) type = 'output';
234
+ const type = this.resolveFileType(fileName);
231
235
 
232
236
  files.push({
233
237
  name: fileName,
@@ -240,4 +244,53 @@ export class PostHogFileManager {
240
244
 
241
245
  return files;
242
246
  }
247
+
248
+ async collectTaskArtifacts(taskId: string): Promise<LocalArtifact[]> {
249
+ const fileNames = await this.listTaskFiles(taskId);
250
+ const artifacts: LocalArtifact[] = [];
251
+
252
+ for (const fileName of fileNames) {
253
+ const content = await this.readTaskFile(taskId, fileName);
254
+ if (content === null) {
255
+ continue;
256
+ }
257
+
258
+ const type = this.resolveFileType(fileName);
259
+ const contentType = this.inferContentType(fileName);
260
+ const size = Buffer.byteLength(content, 'utf8');
261
+
262
+ artifacts.push({
263
+ name: fileName,
264
+ content,
265
+ type,
266
+ contentType,
267
+ size,
268
+ });
269
+ }
270
+
271
+ return artifacts;
272
+ }
273
+
274
+ private resolveFileType(fileName: string): TaskFile['type'] {
275
+ if (fileName === 'plan.md') return 'plan';
276
+ if (fileName === 'context.md') return 'context';
277
+ if (fileName === 'requirements.md') return 'reference';
278
+ if (fileName.startsWith('output_')) return 'output';
279
+ if (fileName.endsWith('.md')) return 'reference';
280
+ return 'artifact';
281
+ }
282
+
283
+ private inferContentType(fileName: string): string {
284
+ const extension = extname(fileName).toLowerCase();
285
+ switch (extension) {
286
+ case '.md':
287
+ return 'text/markdown';
288
+ case '.json':
289
+ return 'application/json';
290
+ case '.txt':
291
+ return 'text/plain';
292
+ default:
293
+ return 'text/plain';
294
+ }
295
+ }
243
296
  }
@@ -30,6 +30,14 @@ export class GitManager {
30
30
  this.logger = config.logger || new Logger({ debug: false, prefix: '[GitManager]' });
31
31
  }
32
32
 
33
+ private escapeShellArg(str: string): string {
34
+ return str
35
+ .replace(/\\/g, '\\\\')
36
+ .replace(/"/g, '\\"')
37
+ .replace(/`/g, '\\`')
38
+ .replace(/\$/g, '\\$');
39
+ }
40
+
33
41
  private async runGitCommand(command: string): Promise<string> {
34
42
  try {
35
43
  const { stdout } = await execAsync(`cd "${this.repositoryPath}" && git ${command}`);
@@ -130,7 +138,7 @@ export class GitManager {
130
138
  }
131
139
 
132
140
  async addFiles(paths: string[]): Promise<void> {
133
- const pathList = paths.map(p => `"${p}"`).join(' ');
141
+ const pathList = paths.map(p => `"${this.escapeShellArg(p)}"`).join(' ');
134
142
  await this.runGitCommand(`add ${pathList}`);
135
143
  }
136
144
 
@@ -204,7 +212,7 @@ export class GitManager {
204
212
  }
205
213
 
206
214
  private buildCommitCommand(message: string, options?: { allowEmpty?: boolean; authorName?: string; authorEmail?: string }): string {
207
- let command = `commit -m "${message.replace(/"/g, '\\"')}"`;
215
+ let command = `commit -m "${this.escapeShellArg(message)}"`;
208
216
 
209
217
  if (options?.allowEmpty) {
210
218
  command += ' --allow-empty';
@@ -446,7 +454,7 @@ Generated by PostHog Agent`;
446
454
 
447
455
  await this.pushBranch(branchName);
448
456
 
449
- let command = `gh pr create --title "${title.replace(/"/g, '\\"')}" --body "${body.replace(/"/g, '\\"')}"`;
457
+ let command = `gh pr create --title "${this.escapeShellArg(title)}" --body "${this.escapeShellArg(body)}"`;
450
458
 
451
459
  if (baseBranch) {
452
460
  command += ` --base ${baseBranch}`;
@@ -1,4 +1,15 @@
1
- import type { Task, TaskRun, LogEntry, SupportingFile, PostHogAPIConfig, PostHogResource, ResourceType, UrlMention } from './types.js';
1
+ import type {
2
+ Task,
3
+ TaskRun,
4
+ LogEntry,
5
+ SupportingFile,
6
+ PostHogAPIConfig,
7
+ PostHogResource,
8
+ ResourceType,
9
+ UrlMention,
10
+ TaskRunArtifact,
11
+ TaskArtifactUploadPayload,
12
+ } from './types.js';
2
13
 
3
14
  interface PostHogApiResponse<T> {
4
15
  results?: T[];
@@ -170,6 +181,27 @@ export class PostHogAPIClient {
170
181
  });
171
182
  }
172
183
 
184
+ async uploadTaskArtifacts(
185
+ taskId: string,
186
+ runId: string,
187
+ artifacts: TaskArtifactUploadPayload[]
188
+ ): Promise<TaskRunArtifact[]> {
189
+ if (!artifacts.length) {
190
+ return [];
191
+ }
192
+
193
+ const teamId = this.getTeamId();
194
+ const response = await this.apiRequest<{ artifacts: TaskRunArtifact[] }>(
195
+ `/api/projects/${teamId}/tasks/${taskId}/runs/${runId}/artifacts/`,
196
+ {
197
+ method: 'POST',
198
+ body: JSON.stringify({ artifacts }),
199
+ }
200
+ );
201
+
202
+ return response.artifacts ?? [];
203
+ }
204
+
173
205
  /**
174
206
  * Fetch logs from S3 using presigned URL from TaskRun
175
207
  * @param taskRun - The task run containing the log_url