feat: add task management CLI and improve activity curation
- Add task store with dependency tracking and CLI (packages/server/src/tasks/) - Extract principal param logic to shared util for server/app reuse - Improve activity curator: collapse timeline, dedupe tool calls, simpler output - Add MessageInput ref for focus control after agent cancel - Include recent activity in wait timeout messages 🤖 Generated with [Claude Code](https://claude.com/claude-code)
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
import { z } from "zod";
|
||||
|
||||
// ---- Principal Parameter Extraction ----
|
||||
|
||||
// Schema for file entries in arrays (e.g., apply_patch files)
|
||||
const FileEntrySchema = z.object({ path: z.string() });
|
||||
|
||||
// Schema for TodoWrite todos array
|
||||
const TodoEntrySchema = z.object({
|
||||
content: z.string(),
|
||||
status: z.enum(["pending", "in_progress", "completed"]),
|
||||
activeForm: z.string().optional(),
|
||||
});
|
||||
|
||||
const PrincipalParamSchema = z.union([
|
||||
// Direct path keys
|
||||
z.object({ file_path: z.string() }).transform((d) => ({ type: "path" as const, value: d.file_path })),
|
||||
z.object({ filePath: z.string() }).transform((d) => ({ type: "path" as const, value: d.filePath })),
|
||||
z.object({ path: z.string() }).transform((d) => ({ type: "path" as const, value: d.path })),
|
||||
// Command as string
|
||||
z.object({ command: z.string() }).transform((d) => ({ type: "text" as const, value: d.command })),
|
||||
// Command as array (Codex sends this)
|
||||
z.object({ command: z.array(z.string()).nonempty() }).transform((d) => ({ type: "text" as const, value: d.command.join(" ") })),
|
||||
// Other text params
|
||||
z.object({ pattern: z.string() }).transform((d) => ({ type: "text" as const, value: d.pattern })),
|
||||
z.object({ query: z.string() }).transform((d) => ({ type: "text" as const, value: d.query })),
|
||||
z.object({ url: z.string() }).transform((d) => ({ type: "text" as const, value: d.url })),
|
||||
// Files array (Codex apply_patch)
|
||||
z.object({ files: z.array(FileEntrySchema).nonempty() }).transform((d) => ({ type: "path" as const, value: d.files[0].path })),
|
||||
// TodoWrite - show in_progress item or count
|
||||
z.object({ todos: z.array(TodoEntrySchema).nonempty() }).transform((d) => {
|
||||
const inProgress = d.todos.find((t) => t.status === "in_progress");
|
||||
if (inProgress) {
|
||||
return { type: "text" as const, value: inProgress.activeForm ?? inProgress.content };
|
||||
}
|
||||
return { type: "text" as const, value: `${d.todos.length} tasks` };
|
||||
}),
|
||||
]);
|
||||
|
||||
export function stripCwdPrefix(filePath: string, cwd?: string): string {
|
||||
if (!cwd || !filePath) return filePath;
|
||||
|
||||
const normalizedCwd = cwd.replace(/\\/g, "/").replace(/\/+$/, "");
|
||||
const normalizedPath = filePath.replace(/\\/g, "/");
|
||||
|
||||
const prefix = `${normalizedCwd}/`;
|
||||
if (normalizedPath.startsWith(prefix)) {
|
||||
return normalizedPath.slice(prefix.length);
|
||||
}
|
||||
if (normalizedPath === normalizedCwd) {
|
||||
return ".";
|
||||
}
|
||||
return filePath;
|
||||
}
|
||||
|
||||
export function extractPrincipalParam(args: unknown, cwd?: string): string | undefined {
|
||||
const parsed = PrincipalParamSchema.safeParse(args);
|
||||
if (!parsed.success) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const { type, value } = parsed.data;
|
||||
return type === "path" ? stripCwdPrefix(value, cwd) : value;
|
||||
}
|
||||
|
||||
// ---- TodoWrite Extraction ----
|
||||
|
||||
export interface TodoItem {
|
||||
content: string;
|
||||
status: "pending" | "in_progress" | "completed";
|
||||
activeForm?: string;
|
||||
}
|
||||
|
||||
export function extractTodos(value: unknown): TodoItem[] {
|
||||
if (typeof value !== "object" || value === null) {
|
||||
return [];
|
||||
}
|
||||
const obj = value as Record<string, unknown>;
|
||||
if (!Array.isArray(obj.todos)) {
|
||||
return [];
|
||||
}
|
||||
return obj.todos.filter(
|
||||
(t): t is TodoItem =>
|
||||
typeof t === "object" &&
|
||||
t !== null &&
|
||||
typeof (t as Record<string, unknown>).content === "string" &&
|
||||
typeof (t as Record<string, unknown>).status === "string"
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user