@posthog/agent 1.19.0 → 1.21.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/dist/claude-cli/cli.js +3197 -2675
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/src/adapters/claude/claude-adapter.d.ts +4 -3
- package/dist/src/adapters/claude/claude-adapter.d.ts.map +1 -1
- package/dist/src/adapters/claude/claude-adapter.js +149 -133
- package/dist/src/adapters/claude/claude-adapter.js.map +1 -1
- package/dist/src/adapters/types.d.ts +9 -4
- package/dist/src/adapters/types.d.ts.map +1 -1
- package/dist/src/agent.d.ts +1 -1
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +9 -8
- package/dist/src/agent.js.map +1 -1
- package/dist/src/agents/research.d.ts +1 -1
- package/dist/src/agents/research.d.ts.map +1 -1
- package/dist/src/agents/research.js +55 -5
- package/dist/src/agents/research.js.map +1 -1
- package/dist/src/file-manager.d.ts +12 -0
- package/dist/src/file-manager.d.ts.map +1 -1
- package/dist/src/file-manager.js +76 -10
- package/dist/src/file-manager.js.map +1 -1
- package/dist/src/posthog-api.d.ts +2 -1
- package/dist/src/posthog-api.d.ts.map +1 -1
- package/dist/src/posthog-api.js +11 -0
- package/dist/src/posthog-api.js.map +1 -1
- package/dist/src/prompt-builder.d.ts.map +1 -1
- package/dist/src/prompt-builder.js +25 -0
- package/dist/src/prompt-builder.js.map +1 -1
- package/dist/src/task-progress-reporter.d.ts +12 -4
- package/dist/src/task-progress-reporter.d.ts.map +1 -1
- package/dist/src/task-progress-reporter.js +271 -117
- package/dist/src/task-progress-reporter.js.map +1 -1
- package/dist/src/todo-manager.d.ts +29 -0
- package/dist/src/todo-manager.d.ts.map +1 -0
- package/dist/src/todo-manager.js +126 -0
- package/dist/src/todo-manager.js.map +1 -0
- package/dist/src/types.d.ts +17 -1
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/types.js.map +1 -1
- package/dist/src/workflow/config.d.ts.map +1 -1
- package/dist/src/workflow/config.js +11 -0
- package/dist/src/workflow/config.js.map +1 -1
- package/dist/src/workflow/steps/build.d.ts.map +1 -1
- package/dist/src/workflow/steps/build.js +10 -3
- package/dist/src/workflow/steps/build.js.map +1 -1
- package/dist/src/workflow/steps/finalize.d.ts +3 -0
- package/dist/src/workflow/steps/finalize.d.ts.map +1 -0
- package/dist/src/workflow/steps/finalize.js +173 -0
- package/dist/src/workflow/steps/finalize.js.map +1 -0
- package/dist/src/workflow/steps/plan.d.ts.map +1 -1
- package/dist/src/workflow/steps/plan.js +13 -6
- package/dist/src/workflow/steps/plan.js.map +1 -1
- package/dist/src/workflow/steps/research.js +3 -3
- package/dist/src/workflow/steps/research.js.map +1 -1
- package/package.json +1 -1
- package/src/adapters/claude/claude-adapter.ts +67 -48
- package/src/adapters/types.ts +10 -4
- package/src/agent.ts +17 -8
- package/src/agents/research.ts +55 -5
- package/src/file-manager.ts +89 -6
- package/src/posthog-api.ts +33 -1
- package/src/prompt-builder.ts +24 -0
- package/src/task-progress-reporter.ts +299 -138
- package/src/todo-manager.ts +169 -0
- package/src/types.ts +20 -1
- package/src/workflow/config.ts +11 -0
- package/src/workflow/steps/build.ts +12 -3
- package/src/workflow/steps/finalize.ts +207 -0
- package/src/workflow/steps/plan.ts +16 -6
- package/src/workflow/steps/research.ts +3 -3
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import type { PostHogFileManager } from './file-manager.js';
|
|
2
|
+
import { Logger } from './utils/logger.js';
|
|
3
|
+
|
|
4
|
+
export interface TodoItem {
|
|
5
|
+
content: string;
|
|
6
|
+
status: 'pending' | 'in_progress' | 'completed';
|
|
7
|
+
activeForm: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface TodoList {
|
|
11
|
+
items: TodoItem[];
|
|
12
|
+
metadata: {
|
|
13
|
+
total: number;
|
|
14
|
+
pending: number;
|
|
15
|
+
in_progress: number;
|
|
16
|
+
completed: number;
|
|
17
|
+
last_updated: string;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class TodoManager {
|
|
22
|
+
private fileManager: PostHogFileManager;
|
|
23
|
+
private logger: Logger;
|
|
24
|
+
|
|
25
|
+
constructor(fileManager: PostHogFileManager, logger?: Logger) {
|
|
26
|
+
this.fileManager = fileManager;
|
|
27
|
+
this.logger = logger || new Logger({ debug: false, prefix: '[TodoManager]' });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async readTodos(taskId: string): Promise<TodoList | null> {
|
|
31
|
+
try {
|
|
32
|
+
const content = await this.fileManager.readTaskFile(taskId, 'todos.json');
|
|
33
|
+
if (!content) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const parsed = JSON.parse(content) as TodoList;
|
|
38
|
+
this.logger.debug('Loaded todos', {
|
|
39
|
+
taskId,
|
|
40
|
+
total: parsed.metadata.total,
|
|
41
|
+
pending: parsed.metadata.pending,
|
|
42
|
+
in_progress: parsed.metadata.in_progress,
|
|
43
|
+
completed: parsed.metadata.completed,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
return parsed;
|
|
47
|
+
} catch (error) {
|
|
48
|
+
this.logger.debug('Failed to read todos.json', {
|
|
49
|
+
taskId,
|
|
50
|
+
error: error instanceof Error ? error.message : String(error),
|
|
51
|
+
});
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async writeTodos(taskId: string, todos: TodoList): Promise<void> {
|
|
57
|
+
this.logger.debug('Writing todos', {
|
|
58
|
+
taskId,
|
|
59
|
+
total: todos.metadata.total,
|
|
60
|
+
pending: todos.metadata.pending,
|
|
61
|
+
in_progress: todos.metadata.in_progress,
|
|
62
|
+
completed: todos.metadata.completed,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
await this.fileManager.writeTaskFile(taskId, {
|
|
66
|
+
name: 'todos.json',
|
|
67
|
+
content: JSON.stringify(todos, null, 2),
|
|
68
|
+
type: 'artifact',
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
this.logger.info('Todos saved', {
|
|
72
|
+
taskId,
|
|
73
|
+
total: todos.metadata.total,
|
|
74
|
+
completed: todos.metadata.completed,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
parseTodoWriteInput(toolInput: any): TodoList {
|
|
79
|
+
const items: TodoItem[] = [];
|
|
80
|
+
|
|
81
|
+
if (toolInput.todos && Array.isArray(toolInput.todos)) {
|
|
82
|
+
for (const todo of toolInput.todos) {
|
|
83
|
+
items.push({
|
|
84
|
+
content: todo.content || '',
|
|
85
|
+
status: todo.status || 'pending',
|
|
86
|
+
activeForm: todo.activeForm || todo.content || '',
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const metadata = this.calculateMetadata(items);
|
|
92
|
+
|
|
93
|
+
return { items, metadata };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private calculateMetadata(items: TodoItem[]): TodoList['metadata'] {
|
|
97
|
+
const total = items.length;
|
|
98
|
+
const pending = items.filter((t) => t.status === 'pending').length;
|
|
99
|
+
const in_progress = items.filter((t) => t.status === 'in_progress').length;
|
|
100
|
+
const completed = items.filter((t) => t.status === 'completed').length;
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
total,
|
|
104
|
+
pending,
|
|
105
|
+
in_progress,
|
|
106
|
+
completed,
|
|
107
|
+
last_updated: new Date().toISOString(),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async getTodoContext(taskId: string): Promise<string> {
|
|
112
|
+
const todos = await this.readTodos(taskId);
|
|
113
|
+
if (!todos || todos.items.length === 0) {
|
|
114
|
+
return '';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const lines: string[] = ['## Previous Todo List\n'];
|
|
118
|
+
lines.push('You previously created the following todo list:\n');
|
|
119
|
+
|
|
120
|
+
for (const item of todos.items) {
|
|
121
|
+
const statusIcon =
|
|
122
|
+
item.status === 'completed' ? '✓' : item.status === 'in_progress' ? '▶' : '○';
|
|
123
|
+
lines.push(`${statusIcon} [${item.status}] ${item.content}`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
lines.push(
|
|
127
|
+
`\nProgress: ${todos.metadata.completed}/${todos.metadata.total} completed\n`
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
return lines.join('\n');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// check for TodoWrite tool call and persist if found
|
|
134
|
+
async checkAndPersistFromMessage(
|
|
135
|
+
message: any,
|
|
136
|
+
taskId: string
|
|
137
|
+
): Promise<TodoList | null> {
|
|
138
|
+
if (message.type !== 'assistant' || !message.message?.content) {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
for (const block of message.message.content) {
|
|
143
|
+
if (block.type === 'tool_use' && block.name === 'TodoWrite') {
|
|
144
|
+
try {
|
|
145
|
+
this.logger.info('TodoWrite detected, persisting todos', { taskId });
|
|
146
|
+
|
|
147
|
+
const todoList = this.parseTodoWriteInput(block.input);
|
|
148
|
+
await this.writeTodos(taskId, todoList);
|
|
149
|
+
|
|
150
|
+
this.logger.info('Persisted todos successfully', {
|
|
151
|
+
taskId,
|
|
152
|
+
total: todoList.metadata.total,
|
|
153
|
+
completed: todoList.metadata.completed,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
return todoList;
|
|
157
|
+
} catch (error) {
|
|
158
|
+
this.logger.error('Failed to persist todos', {
|
|
159
|
+
taskId,
|
|
160
|
+
error: error instanceof Error ? error.message : String(error),
|
|
161
|
+
});
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -32,6 +32,17 @@ export interface LogEntry {
|
|
|
32
32
|
[key: string]: unknown; // Allow additional fields
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
export type ArtifactType = 'plan' | 'context' | 'reference' | 'output' | 'artifact';
|
|
36
|
+
|
|
37
|
+
export interface TaskRunArtifact {
|
|
38
|
+
name: string;
|
|
39
|
+
type: ArtifactType;
|
|
40
|
+
size?: number;
|
|
41
|
+
content_type?: string;
|
|
42
|
+
storage_path?: string;
|
|
43
|
+
uploaded_at?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
35
46
|
// TaskRun model - represents individual execution runs of tasks
|
|
36
47
|
export interface TaskRun {
|
|
37
48
|
id: string;
|
|
@@ -43,6 +54,7 @@ export interface TaskRun {
|
|
|
43
54
|
error_message: string | null;
|
|
44
55
|
output: Record<string, unknown> | null; // Structured output (PR URL, commit SHA, etc.)
|
|
45
56
|
state: Record<string, unknown>; // Intermediate run state (defaults to {}, never null)
|
|
57
|
+
artifacts?: TaskRunArtifact[];
|
|
46
58
|
created_at: string;
|
|
47
59
|
updated_at: string;
|
|
48
60
|
completed_at: string | null;
|
|
@@ -51,10 +63,17 @@ export interface TaskRun {
|
|
|
51
63
|
export interface SupportingFile {
|
|
52
64
|
name: string;
|
|
53
65
|
content: string;
|
|
54
|
-
type:
|
|
66
|
+
type: ArtifactType;
|
|
55
67
|
created_at: string;
|
|
56
68
|
}
|
|
57
69
|
|
|
70
|
+
export interface TaskArtifactUploadPayload {
|
|
71
|
+
name: string;
|
|
72
|
+
type: ArtifactType;
|
|
73
|
+
content: string;
|
|
74
|
+
content_type?: string;
|
|
75
|
+
}
|
|
76
|
+
|
|
58
77
|
export enum PermissionMode {
|
|
59
78
|
PLAN = "plan",
|
|
60
79
|
DEFAULT = "default",
|
package/src/workflow/config.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { WorkflowDefinition } from './types.js';
|
|
|
2
2
|
import { researchStep } from './steps/research.js';
|
|
3
3
|
import { planStep } from './steps/plan.js';
|
|
4
4
|
import { buildStep } from './steps/build.js';
|
|
5
|
+
import { finalizeStep } from './steps/finalize.js';
|
|
5
6
|
|
|
6
7
|
const MODELS = {
|
|
7
8
|
SONNET: "claude-sonnet-4-5",
|
|
@@ -39,4 +40,14 @@ export const TASK_WORKFLOW: WorkflowDefinition = [
|
|
|
39
40
|
push: true,
|
|
40
41
|
run: buildStep,
|
|
41
42
|
},
|
|
43
|
+
{
|
|
44
|
+
id: 'finalize',
|
|
45
|
+
name: 'Finalize',
|
|
46
|
+
agent: 'system', // not used
|
|
47
|
+
model: MODELS.HAIKU, // not used
|
|
48
|
+
permissionMode: 'plan', // not used
|
|
49
|
+
commit: true,
|
|
50
|
+
push: true,
|
|
51
|
+
run: finalizeStep,
|
|
52
|
+
},
|
|
42
53
|
];
|
|
@@ -3,6 +3,7 @@ import { EXECUTION_SYSTEM_PROMPT } from '../../agents/execution.js';
|
|
|
3
3
|
import { PermissionMode } from '../../types.js';
|
|
4
4
|
import type { WorkflowStepRunner } from '../types.js';
|
|
5
5
|
import { finalizeStepGitActions } from '../utils.js';
|
|
6
|
+
import { TodoManager } from '../../todo-manager.js';
|
|
6
7
|
|
|
7
8
|
export const buildStep: WorkflowStepRunner = async ({ step, context }) => {
|
|
8
9
|
const {
|
|
@@ -82,11 +83,19 @@ export const buildStep: WorkflowStepRunner = async ({ step, context }) => {
|
|
|
82
83
|
// Track commits made during Claude Code execution
|
|
83
84
|
const commitTracker = await gitManager.trackCommitsDuring();
|
|
84
85
|
|
|
86
|
+
// Track todos from TodoWrite tool calls
|
|
87
|
+
const todoManager = new TodoManager(context.fileManager, stepLogger);
|
|
88
|
+
|
|
85
89
|
for await (const message of response) {
|
|
86
90
|
emitEvent(adapter.createRawSDKEvent(message));
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
emitEvent(
|
|
91
|
+
const transformedEvents = adapter.transform(message);
|
|
92
|
+
for (const event of transformedEvents) {
|
|
93
|
+
emitEvent(event);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const todoList = await todoManager.checkAndPersistFromMessage(message, task.id);
|
|
97
|
+
if (todoList) {
|
|
98
|
+
emitEvent(adapter.createArtifactEvent('todos', todoList));
|
|
90
99
|
}
|
|
91
100
|
}
|
|
92
101
|
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import type { LocalArtifact } from '../../file-manager.js';
|
|
2
|
+
import type { Task, TaskRunArtifact } from '../../types.js';
|
|
3
|
+
import type { WorkflowStepRunner } from '../types.js';
|
|
4
|
+
|
|
5
|
+
const MAX_SNIPPET_LENGTH = 1200;
|
|
6
|
+
|
|
7
|
+
export const finalizeStep: WorkflowStepRunner = async ({ step, context }) => {
|
|
8
|
+
const {
|
|
9
|
+
task,
|
|
10
|
+
logger,
|
|
11
|
+
fileManager,
|
|
12
|
+
gitManager,
|
|
13
|
+
posthogAPI,
|
|
14
|
+
progressReporter,
|
|
15
|
+
} = context;
|
|
16
|
+
|
|
17
|
+
const stepLogger = logger.child('FinalizeStep');
|
|
18
|
+
const artifacts = await fileManager.collectTaskArtifacts(task.id);
|
|
19
|
+
let uploadedArtifacts: TaskRunArtifact[] | undefined;
|
|
20
|
+
|
|
21
|
+
if (artifacts.length && posthogAPI && progressReporter.runId) {
|
|
22
|
+
try {
|
|
23
|
+
const payload = artifacts.map((artifact) => ({
|
|
24
|
+
name: artifact.name,
|
|
25
|
+
type: artifact.type,
|
|
26
|
+
content: artifact.content,
|
|
27
|
+
content_type: artifact.contentType,
|
|
28
|
+
}));
|
|
29
|
+
uploadedArtifacts = await posthogAPI.uploadTaskArtifacts(task.id, progressReporter.runId, payload);
|
|
30
|
+
stepLogger.info('Uploaded task artifacts to PostHog', {
|
|
31
|
+
taskId: task.id,
|
|
32
|
+
uploadedCount: uploadedArtifacts.length,
|
|
33
|
+
});
|
|
34
|
+
} catch (error) {
|
|
35
|
+
stepLogger.warn('Failed to upload task artifacts', {
|
|
36
|
+
taskId: task.id,
|
|
37
|
+
error: error instanceof Error ? error.message : String(error),
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
} else {
|
|
41
|
+
stepLogger.debug('Skipping artifact upload', {
|
|
42
|
+
hasArtifacts: artifacts.length > 0,
|
|
43
|
+
hasPostHogApi: Boolean(posthogAPI),
|
|
44
|
+
runId: progressReporter.runId,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const prBody = buildPullRequestBody(task, artifacts, uploadedArtifacts);
|
|
49
|
+
await fileManager.cleanupTaskDirectory(task.id);
|
|
50
|
+
await gitManager.addAllPostHogFiles();
|
|
51
|
+
|
|
52
|
+
context.stepResults[step.id] = {
|
|
53
|
+
prBody,
|
|
54
|
+
uploadedArtifacts,
|
|
55
|
+
artifactCount: artifacts.length,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
return { status: 'completed' };
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
function buildPullRequestBody(task: Task, artifacts: LocalArtifact[], uploaded?: TaskRunArtifact[]): string {
|
|
62
|
+
const lines: string[] = [];
|
|
63
|
+
const taskSlug = (task as any).slug || task.id;
|
|
64
|
+
|
|
65
|
+
lines.push('## Task context');
|
|
66
|
+
lines.push(`- **Task**: ${taskSlug}`);
|
|
67
|
+
lines.push(`- **Title**: ${task.title}`);
|
|
68
|
+
lines.push(`- **Origin**: ${task.origin_product}`);
|
|
69
|
+
|
|
70
|
+
if (task.description) {
|
|
71
|
+
lines.push('');
|
|
72
|
+
lines.push('> ' + task.description.trim().split('\n').join('\n> '));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const usedFiles = new Set<string>();
|
|
76
|
+
|
|
77
|
+
const contextArtifact = artifacts.find((artifact) => artifact.name === 'context.md');
|
|
78
|
+
if (contextArtifact) {
|
|
79
|
+
lines.push('');
|
|
80
|
+
lines.push('### Task prompt');
|
|
81
|
+
lines.push(renderCodeFence(contextArtifact.content));
|
|
82
|
+
usedFiles.add(contextArtifact.name);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const researchArtifact = artifacts.find((artifact) => artifact.name === 'research.json');
|
|
86
|
+
if (researchArtifact) {
|
|
87
|
+
usedFiles.add(researchArtifact.name);
|
|
88
|
+
const researchSection = formatResearchSection(researchArtifact.content);
|
|
89
|
+
if (researchSection) {
|
|
90
|
+
lines.push('');
|
|
91
|
+
lines.push(researchSection);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const planArtifact = artifacts.find((artifact) => artifact.name === 'plan.md');
|
|
96
|
+
if (planArtifact) {
|
|
97
|
+
lines.push('');
|
|
98
|
+
lines.push('### Implementation plan');
|
|
99
|
+
lines.push(renderCodeFence(planArtifact.content));
|
|
100
|
+
usedFiles.add(planArtifact.name);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const todoArtifact = artifacts.find((artifact) => artifact.name === 'todos.json');
|
|
104
|
+
if (todoArtifact) {
|
|
105
|
+
const summary = summarizeTodos(todoArtifact.content);
|
|
106
|
+
if (summary) {
|
|
107
|
+
lines.push('');
|
|
108
|
+
lines.push('### Todo list');
|
|
109
|
+
lines.push(summary);
|
|
110
|
+
}
|
|
111
|
+
usedFiles.add(todoArtifact.name);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const remainingArtifacts = artifacts.filter((artifact) => !usedFiles.has(artifact.name));
|
|
115
|
+
if (remainingArtifacts.length) {
|
|
116
|
+
lines.push('');
|
|
117
|
+
lines.push('### Additional artifacts');
|
|
118
|
+
for (const artifact of remainingArtifacts) {
|
|
119
|
+
lines.push(`#### ${artifact.name}`);
|
|
120
|
+
lines.push(renderCodeFence(artifact.content));
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const artifactList = uploaded ?? artifacts.map((artifact) => ({
|
|
125
|
+
name: artifact.name,
|
|
126
|
+
type: artifact.type,
|
|
127
|
+
}));
|
|
128
|
+
|
|
129
|
+
if (artifactList.length) {
|
|
130
|
+
lines.push('');
|
|
131
|
+
lines.push('### Uploaded artifacts');
|
|
132
|
+
for (const artifact of artifactList) {
|
|
133
|
+
const storage = 'storage_path' in artifact && artifact.storage_path ? ` – \`${artifact.storage_path}\`` : '';
|
|
134
|
+
lines.push(`- ${artifact.name} (${artifact.type})${storage}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return lines.join('\n\n');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function renderCodeFence(content: string): string {
|
|
142
|
+
const snippet = truncate(content, MAX_SNIPPET_LENGTH);
|
|
143
|
+
return ['```', snippet, '```'].join('\n');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function truncate(value: string, maxLength: number): string {
|
|
147
|
+
if (value.length <= maxLength) {
|
|
148
|
+
return value;
|
|
149
|
+
}
|
|
150
|
+
return `${value.slice(0, maxLength)}\n…`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function formatResearchSection(content: string): string | null {
|
|
154
|
+
try {
|
|
155
|
+
const parsed = JSON.parse(content);
|
|
156
|
+
const sections: string[] = [];
|
|
157
|
+
|
|
158
|
+
if (parsed.context) {
|
|
159
|
+
sections.push('### Research summary');
|
|
160
|
+
sections.push(parsed.context);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (parsed.questions && parsed.questions.length) {
|
|
164
|
+
sections.push('');
|
|
165
|
+
sections.push('### Questions needing answers');
|
|
166
|
+
for (const question of parsed.questions) {
|
|
167
|
+
sections.push(`- ${question.question ?? question}`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (parsed.answers && parsed.answers.length) {
|
|
172
|
+
sections.push('');
|
|
173
|
+
sections.push('### Answers provided');
|
|
174
|
+
for (const answer of parsed.answers) {
|
|
175
|
+
const questionId = answer.questionId ? ` (Q: ${answer.questionId})` : '';
|
|
176
|
+
sections.push(`- ${answer.selectedOption || answer.customInput || 'answer'}${questionId}`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return sections.length ? sections.join('\n') : null;
|
|
181
|
+
} catch {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function summarizeTodos(content: string): string | null {
|
|
187
|
+
try {
|
|
188
|
+
const data = JSON.parse(content);
|
|
189
|
+
const total = data?.metadata?.total ?? data?.items?.length;
|
|
190
|
+
const completed = data?.metadata?.completed ?? data?.items?.filter((item: any) => item.status === 'completed').length;
|
|
191
|
+
|
|
192
|
+
const lines = [
|
|
193
|
+
`Progress: ${completed}/${total} completed`,
|
|
194
|
+
];
|
|
195
|
+
|
|
196
|
+
if (data?.items?.length) {
|
|
197
|
+
for (const item of data.items) {
|
|
198
|
+
lines.push(`- [${item.status}] ${item.content}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return lines.join('\n');
|
|
203
|
+
} catch {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
@@ -2,6 +2,7 @@ import { query } from '@anthropic-ai/claude-agent-sdk';
|
|
|
2
2
|
import { PLANNING_SYSTEM_PROMPT } from '../../agents/planning.js';
|
|
3
3
|
import type { WorkflowStepRunner } from '../types.js';
|
|
4
4
|
import { finalizeStepGitActions } from '../utils.js';
|
|
5
|
+
import { TodoManager } from '../../todo-manager.js';
|
|
5
6
|
|
|
6
7
|
export const planStep: WorkflowStepRunner = async ({ step, context }) => {
|
|
7
8
|
const {
|
|
@@ -96,17 +97,26 @@ export const planStep: WorkflowStepRunner = async ({ step, context }) => {
|
|
|
96
97
|
options: { ...baseOptions, ...(options.queryOverrides || {}) },
|
|
97
98
|
});
|
|
98
99
|
|
|
100
|
+
const todoManager = new TodoManager(fileManager, stepLogger);
|
|
101
|
+
|
|
99
102
|
let planContent = '';
|
|
100
103
|
for await (const message of response) {
|
|
101
104
|
emitEvent(adapter.createRawSDKEvent(message));
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
emitEvent(
|
|
105
|
+
const transformedEvents = adapter.transform(message);
|
|
106
|
+
for (const event of transformedEvents) {
|
|
107
|
+
emitEvent(event);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const todoList = await todoManager.checkAndPersistFromMessage(message, task.id);
|
|
111
|
+
if (todoList) {
|
|
112
|
+
emitEvent(adapter.createArtifactEvent('todos', todoList));
|
|
105
113
|
}
|
|
114
|
+
|
|
115
|
+
// Extract text content for plan
|
|
106
116
|
if (message.type === 'assistant' && message.message?.content) {
|
|
107
|
-
for (const
|
|
108
|
-
if (
|
|
109
|
-
planContent += `${
|
|
117
|
+
for (const block of message.message.content) {
|
|
118
|
+
if (block.type === 'text' && block.text) {
|
|
119
|
+
planContent += `${block.text}\n`;
|
|
110
120
|
}
|
|
111
121
|
}
|
|
112
122
|
}
|
|
@@ -83,9 +83,9 @@ export const researchStep: WorkflowStepRunner = async ({ step, context }) => {
|
|
|
83
83
|
let jsonContent = '';
|
|
84
84
|
for await (const message of response) {
|
|
85
85
|
emitEvent(adapter.createRawSDKEvent(message));
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
emitEvent(
|
|
86
|
+
const transformedEvents = adapter.transform(message);
|
|
87
|
+
for (const event of transformedEvents) {
|
|
88
|
+
emitEvent(event);
|
|
89
89
|
}
|
|
90
90
|
if (message.type === 'assistant' && message.message?.content) {
|
|
91
91
|
for (const c of message.message.content) {
|