@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
package/src/file-manager.ts
CHANGED
|
@@ -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;
|
|
@@ -185,6 +193,36 @@ export class PostHogFileManager {
|
|
|
185
193
|
}
|
|
186
194
|
}
|
|
187
195
|
|
|
196
|
+
async writeTodos(taskId: string, data: any): Promise<void> {
|
|
197
|
+
this.logger.debug('Writing todos', {
|
|
198
|
+
taskId,
|
|
199
|
+
total: data.metadata?.total ?? 0,
|
|
200
|
+
completed: data.metadata?.completed ?? 0,
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
await this.writeTaskFile(taskId, {
|
|
204
|
+
name: 'todos.json',
|
|
205
|
+
content: JSON.stringify(data, null, 2),
|
|
206
|
+
type: 'artifact'
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
this.logger.info('Todos file written', {
|
|
210
|
+
taskId,
|
|
211
|
+
total: data.metadata?.total ?? 0,
|
|
212
|
+
completed: data.metadata?.completed ?? 0,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
async readTodos(taskId: string): Promise<any | null> {
|
|
217
|
+
try {
|
|
218
|
+
const content = await this.readTaskFile(taskId, 'todos.json');
|
|
219
|
+
return content ? JSON.parse(content) : null;
|
|
220
|
+
} catch (error) {
|
|
221
|
+
this.logger.debug('Failed to parse todos.json', { error });
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
188
226
|
async getTaskFiles(taskId: string): Promise<SupportingFile[]> {
|
|
189
227
|
const fileNames = await this.listTaskFiles(taskId);
|
|
190
228
|
const files: SupportingFile[] = [];
|
|
@@ -193,11 +231,7 @@ export class PostHogFileManager {
|
|
|
193
231
|
const content = await this.readTaskFile(taskId, fileName);
|
|
194
232
|
if (content !== null) {
|
|
195
233
|
// Determine type based on file name
|
|
196
|
-
|
|
197
|
-
if (fileName === 'plan.md') type = 'plan';
|
|
198
|
-
else if (fileName === 'context.md') type = 'context';
|
|
199
|
-
else if (fileName === 'requirements.md') type = 'reference';
|
|
200
|
-
else if (fileName.startsWith('output_')) type = 'output';
|
|
234
|
+
const type = this.resolveFileType(fileName);
|
|
201
235
|
|
|
202
236
|
files.push({
|
|
203
237
|
name: fileName,
|
|
@@ -210,4 +244,53 @@ export class PostHogFileManager {
|
|
|
210
244
|
|
|
211
245
|
return files;
|
|
212
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
|
+
}
|
|
213
296
|
}
|
package/src/posthog-api.ts
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
import type {
|
|
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
|
package/src/prompt-builder.ts
CHANGED
|
@@ -386,12 +386,16 @@ export class PromptBuilder {
|
|
|
386
386
|
try {
|
|
387
387
|
const taskFiles = await this.getTaskFiles(task.id);
|
|
388
388
|
const hasPlan = taskFiles.some((f: any) => f.type === 'plan');
|
|
389
|
+
const todosFile = taskFiles.find((f: any) => f.name === 'todos.json');
|
|
389
390
|
|
|
390
391
|
if (taskFiles.length > 0) {
|
|
391
392
|
prompt += '\n<context>\n';
|
|
392
393
|
for (const file of taskFiles) {
|
|
393
394
|
if (file.type === 'plan') {
|
|
394
395
|
prompt += `<plan>\n${file.content}\n</plan>\n`;
|
|
396
|
+
} else if (file.name === 'todos.json') {
|
|
397
|
+
// skip - we do this below
|
|
398
|
+
continue;
|
|
395
399
|
} else {
|
|
396
400
|
prompt += `<file name="${file.name}" type="${file.type}">\n${file.content}\n</file>\n`;
|
|
397
401
|
}
|
|
@@ -399,6 +403,26 @@ export class PromptBuilder {
|
|
|
399
403
|
prompt += '</context>\n';
|
|
400
404
|
}
|
|
401
405
|
|
|
406
|
+
// Add todos context if resuming work
|
|
407
|
+
if (todosFile) {
|
|
408
|
+
try {
|
|
409
|
+
const todos = JSON.parse(todosFile.content);
|
|
410
|
+
if (todos.items && todos.items.length > 0) {
|
|
411
|
+
prompt += '\n<previous_todos>\n';
|
|
412
|
+
prompt += 'You previously created the following todo list for this task:\n\n';
|
|
413
|
+
for (const item of todos.items) {
|
|
414
|
+
const statusIcon = item.status === 'completed' ? '✓' : item.status === 'in_progress' ? '▶' : '○';
|
|
415
|
+
prompt += `${statusIcon} [${item.status}] ${item.content}\n`;
|
|
416
|
+
}
|
|
417
|
+
prompt += `\nProgress: ${todos.metadata.completed}/${todos.metadata.total} completed\n`;
|
|
418
|
+
prompt += '\nYou can reference this list when resuming work or create an updated list as needed.\n';
|
|
419
|
+
prompt += '</previous_todos>\n';
|
|
420
|
+
}
|
|
421
|
+
} catch (error) {
|
|
422
|
+
this.logger.debug('Failed to parse todos.json for context', { error });
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
402
426
|
prompt += '\n<instructions>\n';
|
|
403
427
|
if (hasPlan) {
|
|
404
428
|
prompt += 'Implement the changes described in the execution plan. Follow the plan step-by-step and make the necessary file modifications.\n';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Logger } from './utils/logger.js';
|
|
2
2
|
import type { PostHogAPIClient, TaskRunUpdate } from './posthog-api.js';
|
|
3
|
-
import type { AgentEvent, TaskRun } from './types.js';
|
|
3
|
+
import type { AgentEvent, TaskRun, LogEntry } from './types.js';
|
|
4
4
|
|
|
5
5
|
interface ProgressMetadata {
|
|
6
6
|
totalSteps?: number;
|
|
@@ -20,6 +20,14 @@ export class TaskProgressReporter {
|
|
|
20
20
|
private outputLog: string[] = [];
|
|
21
21
|
private totalSteps?: number;
|
|
22
22
|
private lastLogEntry?: string;
|
|
23
|
+
private tokenBuffer: string = '';
|
|
24
|
+
private tokenCount: number = 0;
|
|
25
|
+
private tokenFlushTimer?: NodeJS.Timeout;
|
|
26
|
+
private readonly TOKEN_BATCH_SIZE = 100;
|
|
27
|
+
private readonly TOKEN_FLUSH_INTERVAL_MS = 1000;
|
|
28
|
+
private logWriteQueue: Promise<void> = Promise.resolve();
|
|
29
|
+
private readonly LOG_APPEND_MAX_RETRIES = 3;
|
|
30
|
+
private readonly LOG_APPEND_RETRY_BASE_DELAY_MS = 200;
|
|
23
31
|
|
|
24
32
|
constructor(posthogAPI: PostHogAPIClient | undefined, logger: Logger) {
|
|
25
33
|
this.posthogAPI = posthogAPI;
|
|
@@ -51,6 +59,11 @@ export class TaskProgressReporter {
|
|
|
51
59
|
}
|
|
52
60
|
|
|
53
61
|
async complete(): Promise<void> {
|
|
62
|
+
await this.flushTokens(); // Flush any remaining tokens before completion
|
|
63
|
+
if (this.tokenFlushTimer) {
|
|
64
|
+
clearTimeout(this.tokenFlushTimer);
|
|
65
|
+
this.tokenFlushTimer = undefined;
|
|
66
|
+
}
|
|
54
67
|
await this.update({ status: 'completed' }, 'Task execution completed');
|
|
55
68
|
}
|
|
56
69
|
|
|
@@ -63,71 +76,319 @@ export class TaskProgressReporter {
|
|
|
63
76
|
await this.update({}, line);
|
|
64
77
|
}
|
|
65
78
|
|
|
79
|
+
private async flushTokens(): Promise<void> {
|
|
80
|
+
if (!this.tokenBuffer || this.tokenCount === 0) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const buffer = this.tokenBuffer;
|
|
85
|
+
this.tokenBuffer = '';
|
|
86
|
+
this.tokenCount = 0;
|
|
87
|
+
|
|
88
|
+
await this.appendLogEntry({
|
|
89
|
+
type: 'token',
|
|
90
|
+
message: buffer,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private scheduleTokenFlush(): void {
|
|
95
|
+
if (this.tokenFlushTimer) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
this.tokenFlushTimer = setTimeout(() => {
|
|
100
|
+
this.tokenFlushTimer = undefined;
|
|
101
|
+
this.flushTokens().catch((err) => {
|
|
102
|
+
this.logger.warn('Failed to flush tokens', { error: err });
|
|
103
|
+
});
|
|
104
|
+
}, this.TOKEN_FLUSH_INTERVAL_MS);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private appendLogEntry(entry: LogEntry): Promise<void> {
|
|
108
|
+
if (!this.posthogAPI || !this.runId || !this.taskId) {
|
|
109
|
+
return Promise.resolve();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const taskId = this.taskId;
|
|
113
|
+
const runId = this.runId;
|
|
114
|
+
|
|
115
|
+
this.logWriteQueue = this.logWriteQueue
|
|
116
|
+
.catch((error) => {
|
|
117
|
+
// Ensure previous failures don't block subsequent writes
|
|
118
|
+
this.logger.debug('Previous log append failed', {
|
|
119
|
+
taskId,
|
|
120
|
+
runId,
|
|
121
|
+
error: error instanceof Error ? error.message : String(error),
|
|
122
|
+
});
|
|
123
|
+
})
|
|
124
|
+
.then(() => this.writeLogEntry(taskId, runId, entry));
|
|
125
|
+
|
|
126
|
+
return this.logWriteQueue;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private async writeLogEntry(
|
|
130
|
+
taskId: string,
|
|
131
|
+
runId: string,
|
|
132
|
+
entry: LogEntry,
|
|
133
|
+
): Promise<void> {
|
|
134
|
+
if (!this.posthogAPI) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
for (let attempt = 1; attempt <= this.LOG_APPEND_MAX_RETRIES; attempt++) {
|
|
139
|
+
try {
|
|
140
|
+
await this.posthogAPI.appendTaskRunLog(taskId, runId, [entry]);
|
|
141
|
+
return;
|
|
142
|
+
} catch (error) {
|
|
143
|
+
this.logger.warn('Failed to append log entry', {
|
|
144
|
+
taskId,
|
|
145
|
+
runId,
|
|
146
|
+
attempt,
|
|
147
|
+
maxAttempts: this.LOG_APPEND_MAX_RETRIES,
|
|
148
|
+
error: (error as Error).message,
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
if (attempt === this.LOG_APPEND_MAX_RETRIES) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const delayMs =
|
|
156
|
+
this.LOG_APPEND_RETRY_BASE_DELAY_MS * Math.pow(2, attempt - 1);
|
|
157
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
66
162
|
async recordEvent(event: AgentEvent): Promise<void> {
|
|
67
163
|
if (!this.posthogAPI || !this.runId || !this.taskId) {
|
|
68
164
|
return;
|
|
69
165
|
}
|
|
70
166
|
|
|
71
167
|
switch (event.type) {
|
|
72
|
-
case 'token':
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
168
|
+
case 'token': {
|
|
169
|
+
// Batch tokens for efficiency
|
|
170
|
+
this.tokenBuffer += event.content;
|
|
171
|
+
this.tokenCount++;
|
|
172
|
+
|
|
173
|
+
if (this.tokenCount >= this.TOKEN_BATCH_SIZE) {
|
|
174
|
+
await this.flushTokens();
|
|
175
|
+
if (this.tokenFlushTimer) {
|
|
176
|
+
clearTimeout(this.tokenFlushTimer);
|
|
177
|
+
this.tokenFlushTimer = undefined;
|
|
178
|
+
}
|
|
179
|
+
} else {
|
|
180
|
+
this.scheduleTokenFlush();
|
|
181
|
+
}
|
|
83
182
|
return;
|
|
183
|
+
}
|
|
84
184
|
|
|
85
|
-
case '
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
185
|
+
case 'content_block_start': {
|
|
186
|
+
await this.appendLogEntry({
|
|
187
|
+
type: 'content_block_start',
|
|
188
|
+
message: JSON.stringify({
|
|
189
|
+
index: event.index,
|
|
190
|
+
contentType: event.contentType,
|
|
191
|
+
toolName: event.toolName,
|
|
192
|
+
toolId: event.toolId,
|
|
193
|
+
ts: event.ts,
|
|
194
|
+
}),
|
|
195
|
+
});
|
|
90
196
|
return;
|
|
91
197
|
}
|
|
92
198
|
|
|
93
|
-
case '
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
199
|
+
case 'content_block_stop': {
|
|
200
|
+
await this.appendLogEntry({
|
|
201
|
+
type: 'content_block_stop',
|
|
202
|
+
message: JSON.stringify({
|
|
203
|
+
index: event.index,
|
|
204
|
+
ts: event.ts,
|
|
205
|
+
}),
|
|
206
|
+
});
|
|
98
207
|
return;
|
|
99
208
|
}
|
|
100
209
|
|
|
101
|
-
case '
|
|
102
|
-
|
|
210
|
+
case 'message_start': {
|
|
211
|
+
await this.appendLogEntry({
|
|
212
|
+
type: 'message_start',
|
|
213
|
+
message: JSON.stringify({
|
|
214
|
+
messageId: event.messageId,
|
|
215
|
+
model: event.model,
|
|
216
|
+
ts: event.ts,
|
|
217
|
+
}),
|
|
218
|
+
});
|
|
103
219
|
return;
|
|
220
|
+
}
|
|
104
221
|
|
|
105
|
-
case '
|
|
106
|
-
await this.
|
|
222
|
+
case 'message_delta': {
|
|
223
|
+
await this.appendLogEntry({
|
|
224
|
+
type: 'message_delta',
|
|
225
|
+
message: JSON.stringify({
|
|
226
|
+
stopReason: event.stopReason,
|
|
227
|
+
stopSequence: event.stopSequence,
|
|
228
|
+
usage: event.usage,
|
|
229
|
+
ts: event.ts,
|
|
230
|
+
}),
|
|
231
|
+
});
|
|
107
232
|
return;
|
|
233
|
+
}
|
|
108
234
|
|
|
109
|
-
case '
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
);
|
|
235
|
+
case 'message_stop': {
|
|
236
|
+
await this.appendLogEntry({
|
|
237
|
+
type: 'message_stop',
|
|
238
|
+
message: JSON.stringify({ ts: event.ts }),
|
|
239
|
+
});
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
case 'status': {
|
|
244
|
+
await this.appendLogEntry({
|
|
245
|
+
type: 'status',
|
|
246
|
+
message: JSON.stringify({
|
|
247
|
+
phase: event.phase,
|
|
248
|
+
kind: event.kind,
|
|
249
|
+
branch: event.branch,
|
|
250
|
+
prUrl: event.prUrl,
|
|
251
|
+
taskId: event.taskId,
|
|
252
|
+
messageId: event.messageId,
|
|
253
|
+
model: event.model,
|
|
254
|
+
ts: event.ts,
|
|
255
|
+
}),
|
|
256
|
+
});
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
case 'artifact': {
|
|
261
|
+
await this.appendLogEntry({
|
|
262
|
+
type: 'artifact',
|
|
263
|
+
message: JSON.stringify({
|
|
264
|
+
kind: event.kind,
|
|
265
|
+
content: event.content,
|
|
266
|
+
ts: event.ts,
|
|
267
|
+
}),
|
|
268
|
+
});
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
case 'init': {
|
|
273
|
+
await this.appendLogEntry({
|
|
274
|
+
type: 'init',
|
|
275
|
+
message: JSON.stringify({
|
|
276
|
+
model: event.model,
|
|
277
|
+
tools: event.tools,
|
|
278
|
+
permissionMode: event.permissionMode,
|
|
279
|
+
cwd: event.cwd,
|
|
280
|
+
apiKeySource: event.apiKeySource,
|
|
281
|
+
ts: event.ts,
|
|
282
|
+
}),
|
|
283
|
+
});
|
|
114
284
|
return;
|
|
115
285
|
}
|
|
116
286
|
|
|
117
|
-
case '
|
|
118
|
-
|
|
287
|
+
case 'metric': {
|
|
288
|
+
await this.appendLogEntry({
|
|
289
|
+
type: 'metric',
|
|
290
|
+
message: JSON.stringify({
|
|
291
|
+
key: event.key,
|
|
292
|
+
value: event.value,
|
|
293
|
+
unit: event.unit,
|
|
294
|
+
ts: event.ts,
|
|
295
|
+
}),
|
|
296
|
+
});
|
|
119
297
|
return;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
case 'compact_boundary': {
|
|
301
|
+
await this.appendLogEntry({
|
|
302
|
+
type: 'compact_boundary',
|
|
303
|
+
message: JSON.stringify({
|
|
304
|
+
trigger: event.trigger,
|
|
305
|
+
preTokens: event.preTokens,
|
|
306
|
+
ts: event.ts,
|
|
307
|
+
}),
|
|
308
|
+
});
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
case 'tool_call': {
|
|
313
|
+
await this.appendLogEntry({
|
|
314
|
+
type: 'tool_call',
|
|
315
|
+
message: JSON.stringify({
|
|
316
|
+
toolName: event.toolName,
|
|
317
|
+
callId: event.callId,
|
|
318
|
+
args: event.args,
|
|
319
|
+
parentToolUseId: event.parentToolUseId,
|
|
320
|
+
ts: event.ts,
|
|
321
|
+
}),
|
|
322
|
+
});
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
case 'tool_result': {
|
|
327
|
+
await this.appendLogEntry({
|
|
328
|
+
type: 'tool_result',
|
|
329
|
+
message: JSON.stringify({
|
|
330
|
+
toolName: event.toolName,
|
|
331
|
+
callId: event.callId,
|
|
332
|
+
result: event.result,
|
|
333
|
+
isError: event.isError,
|
|
334
|
+
parentToolUseId: event.parentToolUseId,
|
|
335
|
+
ts: event.ts,
|
|
336
|
+
}),
|
|
337
|
+
});
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
case 'error': {
|
|
342
|
+
await this.appendLogEntry({
|
|
343
|
+
type: 'error',
|
|
344
|
+
message: JSON.stringify({
|
|
345
|
+
message: event.message,
|
|
346
|
+
errorType: event.errorType,
|
|
347
|
+
context: event.context,
|
|
348
|
+
ts: event.ts,
|
|
349
|
+
}),
|
|
350
|
+
});
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
case 'done': {
|
|
355
|
+
await this.appendLogEntry({
|
|
356
|
+
type: 'done',
|
|
357
|
+
message: JSON.stringify({
|
|
358
|
+
result: event.result,
|
|
359
|
+
durationMs: event.durationMs,
|
|
360
|
+
durationApiMs: event.durationApiMs,
|
|
361
|
+
numTurns: event.numTurns,
|
|
362
|
+
totalCostUsd: event.totalCostUsd,
|
|
363
|
+
usage: event.usage,
|
|
364
|
+
modelUsage: event.modelUsage,
|
|
365
|
+
permissionDenials: event.permissionDenials,
|
|
366
|
+
ts: event.ts,
|
|
367
|
+
}),
|
|
368
|
+
});
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
120
371
|
|
|
121
372
|
case 'user_message': {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
373
|
+
await this.appendLogEntry({
|
|
374
|
+
type: 'user_message',
|
|
375
|
+
message: JSON.stringify({
|
|
376
|
+
content: event.content,
|
|
377
|
+
isSynthetic: event.isSynthetic,
|
|
378
|
+
ts: event.ts,
|
|
379
|
+
}),
|
|
380
|
+
});
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
case 'raw_sdk_event': {
|
|
385
|
+
// Skip raw SDK events - too verbose for persistence
|
|
126
386
|
return;
|
|
127
387
|
}
|
|
128
388
|
|
|
129
389
|
default:
|
|
130
|
-
// For any unfamiliar event types,
|
|
390
|
+
// For any unfamiliar event types, log them as-is
|
|
391
|
+
this.logger.debug('Unknown event type', { type: (event as any).type });
|
|
131
392
|
return;
|
|
132
393
|
}
|
|
133
394
|
}
|
|
@@ -168,104 +429,4 @@ export class TaskProgressReporter {
|
|
|
168
429
|
}
|
|
169
430
|
}
|
|
170
431
|
|
|
171
|
-
private summarizeUserMessage(content?: string): string | null {
|
|
172
|
-
if (!content) {
|
|
173
|
-
return null;
|
|
174
|
-
}
|
|
175
|
-
const trimmed = content.trim();
|
|
176
|
-
if (!trimmed) {
|
|
177
|
-
return null;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
const fileUpdateMatch = trimmed.match(/The file\s+([^\s]+)\s+has been updated/i);
|
|
181
|
-
if (fileUpdateMatch) {
|
|
182
|
-
return `[user] file updated: ${fileUpdateMatch[1]}`;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
if (/Todos have been modified/i.test(trimmed)) {
|
|
186
|
-
return '[todo] list updated';
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
const diffMatch = trimmed.match(/diff --git a\/([^\s]+) b\/([^\s]+)/);
|
|
190
|
-
if (diffMatch) {
|
|
191
|
-
return `[diff] ${diffMatch[2] ?? diffMatch[1]}`;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
const gitStatusMatch = trimmed.match(/^On branch ([^\n]+)/);
|
|
195
|
-
if (gitStatusMatch) {
|
|
196
|
-
return `[git] status ${gitStatusMatch[1]}`;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
if (/This Bash command contains multiple operations/i.test(trimmed)) {
|
|
200
|
-
return '[approval] multi-step command pending';
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
if (/This command requires approval/i.test(trimmed)) {
|
|
204
|
-
return '[approval] command awaiting approval';
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
if (/^Exit plan mode\?/i.test(trimmed)) {
|
|
208
|
-
return null;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
if (trimmed.includes('node_modules')) {
|
|
212
|
-
return null;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
if (trimmed.includes('total ') && trimmed.includes('drwx')) {
|
|
216
|
-
return null;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
if (trimmed.includes('→')) {
|
|
220
|
-
return null;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
if (trimmed.split('\n').length > 2) {
|
|
224
|
-
return null;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
const normalized = trimmed.replace(/\s+/g, ' ');
|
|
228
|
-
const maxLen = 120;
|
|
229
|
-
if (!normalized) {
|
|
230
|
-
return null;
|
|
231
|
-
}
|
|
232
|
-
const preview = normalized.length > maxLen ? `${normalized.slice(0, maxLen)}…` : normalized;
|
|
233
|
-
return `[user] ${preview}`;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
private truncateMultiline(text: string, max = 160): string {
|
|
238
|
-
if (!text) {
|
|
239
|
-
return '';
|
|
240
|
-
}
|
|
241
|
-
const compact = text.replace(/\s+/g, ' ').trim();
|
|
242
|
-
return compact.length > max ? `${compact.slice(0, max)}…` : compact;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
private formatToolCallEvent(event: Extract<AgentEvent, { type: 'tool_call' }>): string | null {
|
|
246
|
-
// File operations to track
|
|
247
|
-
const fileOps = ['read_file', 'write', 'search_replace', 'delete_file', 'glob_file_search', 'file_search', 'list_dir', 'edit_notebook'];
|
|
248
|
-
// Terminal commands to track
|
|
249
|
-
const terminalOps = ['run_terminal_cmd', 'bash', 'shell'];
|
|
250
|
-
|
|
251
|
-
if (fileOps.includes(event.toolName)) {
|
|
252
|
-
// Extract file path from args
|
|
253
|
-
const path = event.args?.target_file || event.args?.file_path || event.args?.target_notebook || event.args?.target_directory || '';
|
|
254
|
-
return `[tool] ${event.toolName}${path ? `: ${path}` : ''}`;
|
|
255
|
-
} else if (terminalOps.includes(event.toolName)) {
|
|
256
|
-
// Extract command from args
|
|
257
|
-
const cmd = event.args?.command || '';
|
|
258
|
-
const truncated = cmd.length > 80 ? `${cmd.slice(0, 80)}…` : cmd;
|
|
259
|
-
return `[cmd] ${truncated}`;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
// Skip other tools from persistence
|
|
263
|
-
return null;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
private formatToolResultEvent(event: Extract<AgentEvent, { type: 'tool_result' }>): string | null {
|
|
267
|
-
// We don't need to log tool results separately - tool calls are sufficient
|
|
268
|
-
// This keeps the log concise
|
|
269
|
-
return null;
|
|
270
|
-
}
|
|
271
432
|
}
|