repositories / pi-ext
pi-ext
bugabingas pi extensions
owned by admin
extensions/the-system/board-runtime.ts
Rawimport { StringEnum } from "@earendil-works/pi-ai";
import { defineTool, keyHint } from "@earendil-works/pi-coding-agent";
import { Text } from "@earendil-works/pi-tui";
import { Type } from "typebox";
import { BOARD_NOTE_LIMIT } from "./board-constants.ts";
import type { BoardServer } from "./board-server.ts";
import type { BoardProposal } from "./board-state.ts";
import {
loadBoardServer,
loadBoardState,
loadOpenBrowser,
loadProjectIndex,
loadSystemRenderer,
} from "./loaders.js";
import { requireProjectRoot } from "./project-root.ts";
import { expandableFallback } from "./tool-rendering.ts";
const STATES = ["todo", "done", "open"] as const;
const proposalParameters = Type.Object({
items: Type.Array(
Type.Object({
id: Type.String({ description: "Existing spec, plan, or issue ID." }),
state: StringEnum(STATES, {
description: "Proposed board state for the item type.",
}),
rationale: Type.String({
minLength: 1,
maxLength: 1000,
description: "Terse evidence supporting this proposed state.",
}),
}),
{ minItems: 0, maxItems: 100 },
),
});
const updateParameters = Type.Object({
items: Type.Array(
Type.Object({
id: Type.String({ description: "Existing spec, plan, or issue ID." }),
state: StringEnum(STATES, {
description: "Board state for the item type.",
}),
note: Type.String({
minLength: 1,
maxLength: BOARD_NOTE_LIMIT,
description: "Terse verified evidence stored with the board entry.",
}),
}),
{ minItems: 0, maxItems: 100 },
),
});
interface CallTheme {
fg(style: string, text: string): string;
bold(text: string): string;
}
function renderBoardItems(
name: string,
args: { items?: unknown },
theme: CallTheme,
context: { expanded: boolean; lastComponent?: unknown },
): Text {
const text =
(context.lastComponent as Text | undefined) ?? new Text("", 0, 0);
const items = Array.isArray(args.items)
? (args.items as Record<string, unknown>[])
: [];
const count = items.length;
let output = `${theme.fg("toolTitle", theme.bold(name))} ${theme.fg("toolOutput", `${count} item${count === 1 ? "" : "s"}`)}`;
if (!context.expanded && count > 0) {
output += theme.fg("dim", ` (${keyHint("app.tools.expand", "to expand")})`);
} else {
for (const item of items) {
output += `\n${theme.fg("accent", String(item.state).toUpperCase())} ${theme.fg("toolOutput", String(item.id))}`;
const evidence = item.rationale ?? item.note;
if (evidence) output += `\n${theme.fg("dim", String(evidence))}`;
}
}
text.setText(output);
return text;
}
const renderBoardToolResult = (
result: Readonly<{
content: Parameters<typeof expandableFallback>[0];
details?: unknown;
}>,
expanded: boolean,
theme: CallTheme,
isError: boolean,
lastComponent: unknown,
messages: Readonly<{ empty: string; summary: (count: number) => string }>,
): Text => {
const text = (lastComponent as Text | undefined) ?? new Text("", 0, 0);
const items = (result.details as { items?: BoardProposal[] } | undefined)
?.items;
if (!items) {
text.setText(
expandableFallback(
result.content,
expanded,
theme,
isError ? "error" : "muted",
),
);
return text;
}
text.setText(
theme.fg(
"success",
items.length === 0 ? messages.empty : messages.summary(items.length),
),
);
return text;
};
export function createBoardRuntime() {
let proposalRoot: string | undefined;
let proposalFinished = false;
let updateRoot: string | undefined;
let updateFinished = false;
const proposals = new Map<string, Map<string, BoardProposal>>();
const servers = new Set<BoardServer>();
const loadBoard = async (cwd: string) => {
const root = requireProjectRoot(cwd);
const [projectIndex, boardState] = await Promise.all([
loadProjectIndex(),
loadBoardState(),
]);
const index = projectIndex.loadProjectIndex(root);
const state = boardState.readBoardState(root, index.prefix);
return { boardState, index, root, state };
};
const tool = defineTool({
name: "system_board_propose",
label: "system_board_propose",
description:
"Record evidence-backed proposed board states for human review. Accepts existing spec, plan, or issue IDs and does not modify .system/board.json.",
promptSnippet: "Propose System board states for human review",
promptGuidelines: [
"During /system assess, call system_board_propose exactly once with evidence-backed state changes; use an empty items list when no changes are justified.",
],
parameters: proposalParameters,
renderCall(args, theme, context) {
return renderBoardItems("system_board_propose", args, theme, context);
},
renderResult(result, { expanded }, theme, context) {
return renderBoardToolResult(
result,
expanded,
theme,
Boolean(context.isError),
context.lastComponent,
{
empty: "✓ no changes proposed",
summary: (count) =>
`✓ ${count} change${count === 1 ? "" : "s"} proposed`,
},
);
},
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
const root = requireProjectRoot(ctx.cwd);
if (root !== proposalRoot)
throw new Error(
"system_board_propose is only available during /system assess or /system exec",
);
const { index } = await loadBoard(root);
if (root !== proposalRoot)
throw new Error(
"system_board_propose is only available during /system assess or /system exec",
);
const byId = new Map(
index.documents.map((document) => [document.metadata.id, document]),
);
const proposed = proposals.get(root) ?? new Map<string, BoardProposal>();
for (const raw of params.items) {
const document = byId.get(raw.id);
if (!document) throw new Error(`No System board item: ${raw.id}`);
const valid =
document.metadata.type === "issue"
? raw.state === "open" || raw.state === "done"
: (document.metadata.type === "spec" ||
document.metadata.type === "plan") &&
(raw.state === "todo" || raw.state === "done");
if (!valid)
throw new Error(
`Invalid ${document.metadata.type} board state ${raw.state}: ${raw.id}`,
);
proposed.set(raw.id, {
id: raw.id,
state: raw.state,
rationale: raw.rationale.trim(),
});
}
proposals.set(root, proposed);
proposalFinished = true;
const items = [...proposed.values()].sort((left, right) =>
left.id.localeCompare(right.id),
);
return {
content: [
{
type: "text" as const,
text: `${params.items.length} board state proposal(s) recorded for human review. Run /system board to inspect and apply them.`,
},
],
details: { items },
};
},
});
const updateTool = defineTool({
name: "system_board_update",
label: "system_board_update",
description:
"Apply verified board states to .system/board.json and record their source as agent. Accepts existing spec, plan, or issue IDs and rejects states that violate board invariants.",
promptSnippet: "Update System board states from verified work",
promptGuidelines: [
"After fresh verification in /system exec, call system_board_update exactly once with the states your work proves; use an empty items list when no change is justified.",
],
parameters: updateParameters,
renderCall(args, theme, context) {
return renderBoardItems("system_board_update", args, theme, context);
},
renderResult(result, { expanded }, theme, context) {
return renderBoardToolResult(
result,
expanded,
theme,
Boolean(context.isError),
context.lastComponent,
{
empty: "✓ no board changes",
summary: (count) =>
`✓ ${count} board change${count === 1 ? "" : "s"} applied`,
},
);
},
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
const root = requireProjectRoot(ctx.cwd);
if (root !== updateRoot)
throw new Error(
"system_board_update is only available during /system exec",
);
const loaded = await loadBoard(root);
if (root !== updateRoot)
throw new Error(
"system_board_update is only available during /system exec",
);
let state = loaded.state;
const updatedAt = new Date().toISOString();
const applied: BoardProposal[] = [];
for (const raw of params.items) {
state = loaded.boardState.updateBoardState(
state,
loaded.index.documents,
raw.id,
raw.state,
"agent",
updatedAt,
raw.note,
);
applied.push({
id: raw.id,
state: raw.state,
rationale: raw.note.trim(),
});
}
if (applied.length > 0) loaded.boardState.writeBoardState(root, state);
updateFinished = true;
const items = [...applied].sort((left, right) =>
left.id.localeCompare(right.id),
);
return {
content: [
{
type: "text" as const,
text:
applied.length === 0
? "No board states changed."
: `${applied.length} board state(s) applied. Run /system board to review or override them.`,
},
],
details: { items },
};
},
});
return {
tool,
updateTool,
async beginUpdate(cwd: string) {
const { root } = await loadBoard(cwd);
updateRoot = root;
updateFinished = false;
},
updateComplete() {
return updateRoot !== undefined && updateFinished;
},
endUpdate() {
updateRoot = undefined;
updateFinished = false;
},
async beginProposal(cwd: string) {
const { root } = await loadBoard(cwd);
proposalRoot = root;
proposalFinished = false;
proposals.set(root, new Map());
},
proposalComplete() {
return proposalRoot !== undefined && proposalFinished;
},
endProposal() {
proposalRoot = undefined;
proposalFinished = false;
},
async open(cwd: string): Promise<string> {
const { index } = await loadBoard(cwd);
const [{ renderSystemBoardSite }, { startBoardServer }, { openUrl }] =
await Promise.all([
loadSystemRenderer(),
loadBoardServer(),
loadOpenBrowser(),
]);
const current = proposals.get(index.root) ?? new Map();
proposals.set(index.root, current);
const server = await startBoardServer(
index.root,
current,
(site, state, pending, revision) =>
renderSystemBoardSite(site, state, pending, revision),
);
servers.add(server);
const opened = await openUrl(server.url);
return opened.ok
? `System board opened: ${server.url}\nBrowser: ${opened.command}`
: `System board running: ${server.url}\nBrowser: ${opened.error}`;
},
shutdown() {
proposalRoot = undefined;
proposalFinished = false;
updateRoot = undefined;
updateFinished = false;
for (const server of servers) server.close();
servers.clear();
},
};
}