import { tool } from "@opencode-ai/plugin" import { readState, writeState, appendLog, resolveWorktree, debugLog } from "../state-io.ts" import type { ProjectState, TaskNode } from "../types.ts" import { EMPTY_STATE } from "../types.ts" function summarizeState(state: ProjectState) { const byStatus = (s: TaskNode["status"]) => state.tasks.filter((t) => t.status === s).length const activeTask = state.active_tasks?.length === 1 ? state.tasks.find(t => t.id === state.active_tasks[0]) : null const statusIcon = (s: TaskNode["status"]) => { if (s === "done") return "done" if (s === "running") return "run " if (s === "failed") return "fail" return "wait" } const dagTree = state.tasks .map(t => { const deps = t.deps.length > 0 ? ` <- ${t.deps.join(",")}` : "" return `[${statusIcon(t.status)}] ${t.id}${deps}` }) .join("\n ") return { phase: state.phase, dag_summary: dagTree || "(no tasks)", active_tasks: state.active_tasks, active_task_detail: activeTask ? { id: activeTask.id, desc: activeTask.desc, tier: activeTask.tier, files: activeTask.files } : null, tasks: { total: state.tasks.length, pending: byStatus("pending"), running: byStatus("running"), done: byStatus("done"), failed: byStatus("failed"), }, triage: state.triage_result ? { complexity: state.triage_result.complexity, risk: state.triage_result.risk } : null, memory_context: state.memory_context ?? null, recent_log: state.log.slice(-5), } } export default tool({ description: "Read, update, or reset AgenTeam project state. " + "action=read returns a compact summary. action=full returns complete state. " + "action=update merges patch. action=reset wipes state to idle.", args: { action: tool.schema.enum(["read", "full", "update", "reset"]).describe("read (summary) | full | update | reset"), patch: tool.schema .record(tool.schema.string(), tool.schema.unknown()) .optional() .describe("Partial ProjectState to merge. Required for update."), }, async execute(args, context) { const wt = resolveWorktree(context.worktree) const state = readState(wt) if (args.action === "reset") { const resetState = { ...EMPTY_STATE, log: state.log } const withLog = appendLog(wt, resetState, { task_id: null, event: "state.reset", detail: "manual reset" }) writeState(wt, withLog) debugLog("tool", "state_action", { action: "reset", previous_phase: state.phase, current_phase: "idle", }) return JSON.stringify({ ok: true, phase: "idle" }) } if (args.action === "read") { const summary = summarizeState(state) debugLog("tool", "state_action", { action: "read", phase: state.phase, active_tasks: state.active_tasks, tasks_summary: summary.tasks, }) return JSON.stringify(summary, null, 2) } if (args.action === "full") { debugLog("tool", "state_action", { action: "full", phase: state.phase, active_tasks: state.active_tasks, task_count: state.tasks.length, }) return JSON.stringify(state, null, 2) } if (!args.patch) throw new Error("patch is required for action=update") // Whitelist patchable fields const allowed = ["goal", "active_tasks", "phase", "memory_context"] const patch: Partial = {} for (const key of allowed) { if (key in args.patch) patch[key as keyof ProjectState] = args.patch[key] as any } const merged: ProjectState = { ...state, ...patch, log: state.log, tasks: state.tasks, } const withLog = appendLog(wt, merged, { task_id: null, event: "state.update", detail: Object.keys(args.patch).join(", "), }) writeState(wt, withLog) debugLog("tool", "state_action", { action: "update", changes: Object.keys(args.patch), previous_phase: state.phase, current_phase: withLog.phase, }) return JSON.stringify({ ok: true, phase: withLog.phase }) }, })