Something went wrong. Try again.
personal experiment with a claw
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179import { readFile, writeFile, readdir, mkdir, unlink } from "node:fs/promises";import { join } from "node:path";import { parseFrontmatter, type AgentSessionEvent,} from "@earendil-works/pi-coding-agent";
export const DISCORD_LIMIT = 1900;
export function chunkForDiscord(text: string, limit = DISCORD_LIMIT): string[] { if (!text) return []; if (text.length <= limit) return [text]; const out: string[] = []; let i = 0; while (i < text.length) { let end = Math.min(i + limit, text.length); if (end < text.length) { const nl = text.lastIndexOf("\n", end); if (nl > i + limit / 2) end = nl; } out.push(text.slice(i, end)); i = end; } return out;}
function assistantMessageSeparator(buffer: string): string { if (!buffer.trim()) return ""; if (buffer.endsWith("\n\n")) return ""; if (buffer.endsWith("\n")) return "\n"; return "\n\n";}
export function createAssistantTextCollector() { let buffer = ""; let assistantMessages = 0;
return { handleEvent(event: AgentSessionEvent) { if (event.type === "message_start" && event.message.role === "assistant") { if (assistantMessages > 0) { buffer += assistantMessageSeparator(buffer); } assistantMessages += 1; return; }
if ( event.type === "message_update" && event.assistantMessageEvent.type === "text_delta" ) { buffer += event.assistantMessageEvent.delta; } }, getOutput() { return buffer; }, };}
export interface ScheduleFrontmatter { name: string; cron: string; destination: string; /** Discord user ID for DM fallback (set when destination is a DM channel). */ user?: string; created: string; last_run: string | null;}
export interface ScheduleFile extends ScheduleFrontmatter { prompt: string; filePath: string;}
export function isValidScheduleName(name: string): boolean { return /^[a-z0-9][a-z0-9-]{0,62}$/.test(name);}
export async function readSchedule(filePath: string): Promise<ScheduleFile> { const raw = await readFile(filePath, "utf8"); const { frontmatter, body } = parseFrontmatter<Record<string, unknown>>(raw); return { name: String(frontmatter.name ?? ""), cron: String(frontmatter.cron ?? ""), destination: String(frontmatter.destination ?? ""), user: frontmatter.user == null ? undefined : String(frontmatter.user), created: String(frontmatter.created ?? ""), last_run: frontmatter.last_run == null ? null : String(frontmatter.last_run), prompt: body.trim(), filePath, };}
export async function listScheduleFiles(schedulesDir: string): Promise<ScheduleFile[]> { await mkdir(schedulesDir, { recursive: true }); const files = await readdir(schedulesDir); const out: ScheduleFile[] = []; for (const f of files) { if (!f.endsWith(".md")) continue; try { out.push(await readSchedule(join(schedulesDir, f))); } catch (err) { console.error(`scheduler: skipping malformed ${f}:`, err); } } return out;}
function formatScheduleFile(meta: ScheduleFrontmatter, prompt: string): string { // Quote snowflake IDs so the YAML parser keeps them as strings. // Discord IDs are 64-bit and can exceed Number.MAX_SAFE_INTEGER. const userLine = meta.user ? `user: "${meta.user}"\n` : ""; const lr = meta.last_run ? `"${meta.last_run}"` : "null"; return `---name: ${meta.name}cron: "${meta.cron}"destination: "${meta.destination}"${userLine}created: ${meta.created}last_run: ${lr}---${prompt}`;}
export async function writeScheduleFile( schedulesDir: string, meta: ScheduleFrontmatter, prompt: string): Promise<string> { await mkdir(schedulesDir, { recursive: true }); const filePath = join(schedulesDir, `${meta.name}.md`); await writeFile(filePath, formatScheduleFile(meta, prompt), "utf8"); return filePath;}
export async function deleteScheduleFile( schedulesDir: string, name: string): Promise<boolean> { try { await unlink(join(schedulesDir, `${name}.md`)); return true; } catch { return false; }}
export async function updateLastRun( filePath: string, when: Date = new Date()): Promise<void> { let file: ScheduleFile; try { file = await readSchedule(filePath); } catch (err: any) { if (err?.code === "ENOENT") { // Schedule file was deleted (e.g. via cancel_schedule). Nothing to update. return; } throw err; } await writeFile( filePath, formatScheduleFile( { name: file.name, cron: file.cron, destination: file.destination, user: file.user, created: file.created, last_run: when.toISOString(), }, file.prompt ), "utf8" );}