/// import { action, computed, type Default, handler, NAME, pattern, safeDateNow, type Stream, UI, type VNode, wish, Writable, } from "commonfabric"; import type { AgentPiece, AgentStatus } from "./schemas.tsx"; export type { AgentPiece }; // ===== Types ===== interface AgentInput { agentName?: Writable>; directive?: Writable>; enabled?: Writable>; learned?: Writable>; status?: Writable>; lastRun?: Writable>; lastRunSummary?: Writable>; isAgent?: boolean | Default; } /** An #agent piece — autonomous worker with directive, learned state, and status. */ export interface AgentOutput extends AgentPiece { [NAME]: string; [UI]: VNode; agentName: string; directive: string; enabled: boolean; learned: string; status: AgentStatus; lastRun: string; lastRunSummary: string; isAgent: boolean; summary: string; // Handlers setDirective: Stream<{ value: string }>; setLearned: Stream<{ value: string }>; appendLearned: Stream<{ entry: string }>; toggleEnabled: Stream; markRunning: Stream; markIdle: Stream<{ summary: string; learned?: string }>; markError: Stream<{ summary: string }>; } // ===== Activity Log Discovery Type ===== interface ActivityLogPiece { logEvent: Stream<{ agent: string; action: string; note?: string }>; } // ===== Module-scope Handlers ===== const setDirectiveHandler = handler< { value: string }, { directive: Writable } >((args, { directive }) => { directive.set(args.value); }); const setLearnedHandler = handler< { value: string }, { learned: Writable } >((args, { learned }) => { learned.set(args.value); }); const appendLearnedHandler = handler< { entry: string }, { learned: Writable } >((args, { learned }) => { const current = learned.get() || ""; const separator = current && !current.endsWith("\n") ? "\n" : ""; learned.set(`${current}${separator}${args.entry}`); }); // ===== The Pattern ===== export default pattern( ({ agentName, directive, enabled, learned, status, lastRun, lastRunSummary, isAgent, }) => { // Discover activity-log (optional — null-checked before use) const activityLogWish = wish({ query: "#activityLog", headless: true, }); const activityLog = activityLogWish.result; // Bind module-scope handlers const setDirective = setDirectiveHandler({ directive }); const setLearned = setLearnedHandler({ learned }); const appendLearned = appendLearnedHandler({ learned }); // Pattern-body actions const toggleEnabled = action(() => { enabled.set(!enabled.get()); }); const markRunning = action(() => { status.set("running"); if (activityLog) { activityLog.logEvent.send({ agent: agentName.get(), action: "started", }); } }); const markIdle = action( ({ summary, learned: learnedEntry }: { summary: string; learned?: string; }) => { const nowIso = new Date(safeDateNow()).toISOString(); status.set("idle"); lastRun.set(nowIso); lastRunSummary.set(summary); if (learnedEntry) { const current = learned.get() || ""; const separator = current && !current.endsWith("\n") ? "\n" : ""; learned.set(`${current}${separator}${learnedEntry}`); } if (activityLog) { activityLog.logEvent.send({ agent: agentName.get(), action: "completed", note: summary, }); } }, ); const markError = action(({ summary }: { summary: string }) => { const nowIso = new Date(safeDateNow()).toISOString(); status.set("error"); lastRun.set(nowIso); lastRunSummary.set(`ERROR: ${summary}`); if (activityLog) { activityLog.logEvent.send({ agent: agentName.get(), action: "errored", note: summary, }); } }); // UI state const isEditingName = new Writable(false); const startEditingName = action(() => isEditingName.set(true)); const stopEditingName = action(() => isEditingName.set(false)); const handleNameKeydown = action((event: { key?: string }) => { if (event?.key === "Enter") isEditingName.set(false); }); const learnedExpanded = new Writable(false); const toggleLearned = action(() => learnedExpanded.set(!learnedExpanded.get()) ); // Derived values const displayName = computed(() => `🤖 ${agentName.get()}`); const statusColor = computed(() => { const s = status.get(); if (s === "running") return "var(--cf-colors-blue-500, #3b82f6)"; if (s === "error") return "var(--cf-colors-red-500, #ef4444)"; return "var(--cf-colors-gray-400, #9ca3af)"; }); const summaryText = computed(() => { const s = status.get(); const name = agentName.get(); const last = lastRunSummary.get(); if (s === "running") return `${name} is running`; if (last) return `${name}: ${last}`; return `${name} (no runs yet)`; }); const lastRunSectionDisplay = computed(() => lastRun.get() ? "block" : "none" ); const lastRunTimestamp = computed(() => { const ts = lastRun.get(); if (!ts) return ""; try { return new Date(ts).toLocaleString(); } catch { return ts; } }); const nameDisplayStyle = computed(() => isEditingName.get() ? "none" : "flex" ); const nameInputDisplayStyle = computed(() => isEditingName.get() ? "flex" : "none" ); const learnedDisplay = computed(() => learnedExpanded.get() ? "block" : "none" ); const learnedToggleLabel = computed(() => learnedExpanded.get() ? "▼ Learned" : "▶ Learned" ); const learnedSectionDisplay = computed(() => learned.get() ? "flex" : "none" ); return { [NAME]: displayName, [UI]: ( {/* Header: name, status badge, enabled toggle */} {/* Click-to-edit name */}
{displayName}
{/* Status badge */} {status} {/* Enabled toggle */}
{/* Directive section */} Directive {/* Learned section (collapsible) */}
{learnedToggleLabel}
{/* Last run section */} Last Run {lastRunTimestamp} {lastRunSummary}
), agentName, directive, enabled, learned, status, lastRun, lastRunSummary, isAgent, summary: summaryText, setDirective, setLearned, appendLearned, toggleEnabled, markRunning, markIdle, markError, }; }, );