- 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)
90 lines
3.2 KiB
TypeScript
90 lines
3.2 KiB
TypeScript
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"
|
|
);
|
|
}
|