@posthog/agent
Version:
TypeScript agent framework wrapping Claude Agent SDK with Git-based task execution for PostHog
181 lines (153 loc) • 4.95 kB
text/typescript
import type { PostHogFileManager } from "./file-manager.js";
import { Logger } from "./utils/logger.js";
export interface TodoItem {
content: string;
status: "pending" | "in_progress" | "completed";
activeForm: string;
}
export interface TodoList {
items: TodoItem[];
metadata: {
total: number;
pending: number;
in_progress: number;
completed: number;
last_updated: string;
};
}
export class TodoManager {
private fileManager: PostHogFileManager;
private logger: Logger;
constructor(fileManager: PostHogFileManager, logger?: Logger) {
this.fileManager = fileManager;
this.logger =
logger || new Logger({ debug: false, prefix: "[TodoManager]" });
}
async readTodos(taskId: string): Promise<TodoList | null> {
try {
const content = await this.fileManager.readTaskFile(taskId, "todos.json");
if (!content) {
return null;
}
const parsed = JSON.parse(content) as TodoList;
this.logger.debug("Loaded todos", {
taskId,
total: parsed.metadata.total,
pending: parsed.metadata.pending,
in_progress: parsed.metadata.in_progress,
completed: parsed.metadata.completed,
});
return parsed;
} catch (error) {
this.logger.debug("Failed to read todos.json", {
taskId,
error: error instanceof Error ? error.message : String(error),
});
return null;
}
}
async writeTodos(taskId: string, todos: TodoList): Promise<void> {
this.logger.debug("Writing todos", {
taskId,
total: todos.metadata.total,
pending: todos.metadata.pending,
in_progress: todos.metadata.in_progress,
completed: todos.metadata.completed,
});
await this.fileManager.writeTaskFile(taskId, {
name: "todos.json",
content: JSON.stringify(todos, null, 2),
type: "artifact",
});
this.logger.info("Todos saved", {
taskId,
total: todos.metadata.total,
completed: todos.metadata.completed,
});
}
parseTodoWriteInput(toolInput: Record<string, unknown>): TodoList {
const items: TodoItem[] = [];
if (toolInput.todos && Array.isArray(toolInput.todos)) {
for (const todo of toolInput.todos) {
items.push({
content: todo.content || "",
status: todo.status || "pending",
activeForm: todo.activeForm || todo.content || "",
});
}
}
const metadata = this.calculateMetadata(items);
return { items, metadata };
}
private calculateMetadata(items: TodoItem[]): TodoList["metadata"] {
const total = items.length;
const pending = items.filter((t) => t.status === "pending").length;
const in_progress = items.filter((t) => t.status === "in_progress").length;
const completed = items.filter((t) => t.status === "completed").length;
return {
total,
pending,
in_progress,
completed,
last_updated: new Date().toISOString(),
};
}
async getTodoContext(taskId: string): Promise<string> {
const todos = await this.readTodos(taskId);
if (!todos || todos.items.length === 0) {
return "";
}
const lines: string[] = ["## Previous Todo List\n"];
lines.push("You previously created the following todo list:\n");
for (const item of todos.items) {
const statusIcon =
item.status === "completed"
? "✓"
: item.status === "in_progress"
? "▶"
: "○";
lines.push(`${statusIcon} [${item.status}] ${item.content}`);
}
lines.push(
`\nProgress: ${todos.metadata.completed}/${todos.metadata.total} completed\n`,
);
return lines.join("\n");
}
// check for TodoWrite tool call and persist if found
async checkAndPersistFromMessage(
message: Record<string, unknown>,
taskId: string,
): Promise<TodoList | null> {
if (
message.type !== "assistant" ||
typeof message.message !== "object" ||
!message.message ||
!("content" in message.message) ||
!Array.isArray(message.message.content)
) {
return null;
}
for (const block of message.message.content) {
if (block.type === "tool_use" && block.name === "TodoWrite") {
try {
this.logger.info("TodoWrite detected, persisting todos", { taskId });
const todoList = this.parseTodoWriteInput(block.input);
await this.writeTodos(taskId, todoList);
this.logger.info("Persisted todos successfully", {
taskId,
total: todoList.metadata.total,
completed: todoList.metadata.completed,
});
return todoList;
} catch (error) {
this.logger.error("Failed to persist todos", {
taskId,
error: error instanceof Error ? error.message : String(error),
});
return null;
}
}
}
return null;
}
}