feat: chat rendering toolcalls, footer overview
This commit is contained in:
@@ -25,11 +25,11 @@ import {
|
||||
createPhaseStrip,
|
||||
type SendChatMessage,
|
||||
type CheckCompletionDetails,
|
||||
type ToolCallEntry,
|
||||
type PhaseLogEntry,
|
||||
type PhaseLogStatus,
|
||||
PHASE_LABELS,
|
||||
} from "../phases.js";
|
||||
import type { AgentSessionEvent } from "@earendil-works/pi-coding-agent";
|
||||
import { createPipelineFooter, footerPhaseItems } from "../footer.js";
|
||||
import {
|
||||
applyPhaseStatus,
|
||||
@@ -100,6 +100,12 @@ export interface RunCheckOptions {
|
||||
* overviews never compete over the same status slot.
|
||||
*/
|
||||
footer?: boolean;
|
||||
/** Optional callback that forwards raw sub-agent events to the chat
|
||||
* stream (see `pygienium-stream` in `index.ts`). */
|
||||
onAgentEvent?: (phase: string, event: AgentSessionEvent) => void;
|
||||
/** Optional callback to emit synthetic progress lines during non-agent
|
||||
* phases (verify, cleanup, recon) into the chat stream. */
|
||||
sendPhaseLine?: (phase: string, text: string) => void;
|
||||
/**
|
||||
* Ensure `<cwd>/.gitignore` excludes `.pygienium/` before this run writes
|
||||
* state/artifacts (default true). Set false with `--no-gitignore`.
|
||||
@@ -173,9 +179,20 @@ async function runCheckImplInner(
|
||||
ui: opts.ui,
|
||||
hasUI: opts.hasUI ?? false,
|
||||
checkLabel: check.label,
|
||||
widget: opts.widget,
|
||||
});
|
||||
|
||||
/** Phase tag combining check label + phase label for stream lines. */
|
||||
const phaseTag = (phaseId: string): string =>
|
||||
`${check.label}: ${PHASE_LABELS[phaseId] ?? phaseId}`;
|
||||
|
||||
/** Forward a raw agent event tagged with the current phase. */
|
||||
const forward = (phaseId: string) => (event: AgentSessionEvent) =>
|
||||
opts.onAgentEvent?.(phaseTag(phaseId), event);
|
||||
|
||||
/** Emit a synthetic stream line for non-agent phases (verify/cleanup/recon). */
|
||||
const phaseLine = (phaseId: string, text: string): void =>
|
||||
opts.sendPhaseLine?.(phaseTag(phaseId), text);
|
||||
|
||||
// Pipeline-overview footer: a static one-line view of the full phase list
|
||||
// with the cursor on the current phase and what's to come. Detail lives in
|
||||
// the chat (phase strip + completion tree); the footer is the overview.
|
||||
@@ -229,12 +246,14 @@ async function runCheckImplInner(
|
||||
footerEnter(PHASE_RECON);
|
||||
applyPhaseStatus(state, check.name, PHASE_RECON, "in_progress");
|
||||
await saveRunState(state);
|
||||
phaseLine(PHASE_RECON, "scanning project structure…");
|
||||
const snapshot = await runRecon(cwd);
|
||||
state.recon = {
|
||||
complete: true,
|
||||
path: join(stateDir(cwd), "recon.json"),
|
||||
finishedAt: snapshot.createdAt,
|
||||
};
|
||||
phaseLine(PHASE_RECON, "✓ recon complete");
|
||||
applyPhaseStatus(state, check.name, PHASE_RECON, "complete");
|
||||
await saveRunState(state);
|
||||
footerComplete(PHASE_RECON);
|
||||
@@ -250,21 +269,12 @@ async function runCheckImplInner(
|
||||
applyPhaseStatus(state, check.name, PHASE_ANALYSIS, "in_progress");
|
||||
await saveRunState(state);
|
||||
const scanTask = await check.buildScanTask(cwd, scope);
|
||||
const scanStartMs = Date.now();
|
||||
const scanResult = await runAgentTask({
|
||||
cwd: scope.target,
|
||||
agentName: check.agentName,
|
||||
task: scanTask,
|
||||
onToolCall: (entry) => strip.pushToolCall(entry),
|
||||
onEvent: forward(PHASE_ANALYSIS),
|
||||
});
|
||||
postAgentToolCalls(
|
||||
opts,
|
||||
PHASE_ANALYSIS,
|
||||
scanResult.toolCalls,
|
||||
scanResult.ok,
|
||||
Date.now() - scanStartMs,
|
||||
scanResult.error,
|
||||
);
|
||||
findings = scanResult.text;
|
||||
recordCheckOutput(state, check.name, { findings });
|
||||
if (!scanResult.ok) {
|
||||
@@ -291,21 +301,12 @@ async function runCheckImplInner(
|
||||
applyPhaseStatus(state, check.name, PHASE_FIX, "in_progress");
|
||||
await saveRunState(state);
|
||||
const fixTask = await check.buildFixTask(cwd, scope, findings);
|
||||
const fixStartMs = Date.now();
|
||||
const fixResult = await runAgentTask({
|
||||
cwd: scope.target,
|
||||
agentName: check.fixAgentName ?? "fixer",
|
||||
task: fixTask,
|
||||
onToolCall: (entry) => strip.pushToolCall(entry),
|
||||
onEvent: forward(PHASE_FIX),
|
||||
});
|
||||
postAgentToolCalls(
|
||||
opts,
|
||||
PHASE_FIX,
|
||||
fixResult.toolCalls,
|
||||
fixResult.ok,
|
||||
Date.now() - fixStartMs,
|
||||
fixResult.error,
|
||||
);
|
||||
changes = fixResult.text;
|
||||
recordCheckOutput(state, check.name, { changes });
|
||||
if (!fixResult.ok) {
|
||||
@@ -337,6 +338,7 @@ async function runCheckImplInner(
|
||||
footerEnter(PHASE_VERIFY);
|
||||
applyPhaseStatus(state, check.name, PHASE_VERIFY, "in_progress");
|
||||
await saveRunState(state);
|
||||
phaseLine(PHASE_VERIFY, "checking artifacts…");
|
||||
// Verify is a lightweight self-check. A check may supply a dedicated
|
||||
// `verify` hook to confirm its artifacts were produced (e.g.
|
||||
// findings.md / changes.md exist). When absent, fall back to re-running
|
||||
@@ -357,6 +359,7 @@ async function runCheckImplInner(
|
||||
state,
|
||||
};
|
||||
}
|
||||
phaseLine(PHASE_VERIFY, "✓ artifacts confirmed");
|
||||
applyPhaseStatus(state, check.name, PHASE_VERIFY, "complete");
|
||||
await saveRunState(state);
|
||||
footerComplete(PHASE_VERIFY);
|
||||
@@ -366,7 +369,9 @@ async function runCheckImplInner(
|
||||
footerEnter(PHASE_CLEANUP);
|
||||
applyPhaseStatus(state, check.name, PHASE_CLEANUP, "in_progress");
|
||||
await saveRunState(state);
|
||||
phaseLine(PHASE_CLEANUP, "removing transient artifacts…");
|
||||
await cleanupTransientArtifacts(cwd, check.name);
|
||||
phaseLine(PHASE_CLEANUP, "✓ done");
|
||||
applyPhaseStatus(state, check.name, PHASE_CLEANUP, "complete");
|
||||
footerComplete(PHASE_CLEANUP);
|
||||
markCheckStatus(state, check.name, "complete");
|
||||
@@ -491,35 +496,6 @@ function postCheckCompletion(
|
||||
send(header, { phase: "complete", completion: details });
|
||||
}
|
||||
|
||||
/**
|
||||
* Post a ralpi-style per-agent-execution message into the chat: header + the
|
||||
* tool-call tree captured during that sub-process (analysis or fix). One
|
||||
* message per agent run, so tool calls are broken down by which execution
|
||||
* produced them. No-op when no callback is wired or the run made no calls.
|
||||
*/
|
||||
function postAgentToolCalls(
|
||||
opts: RunCheckOptions,
|
||||
phaseId: string,
|
||||
toolCalls: ToolCallEntry[],
|
||||
okc: boolean,
|
||||
durationMs: number,
|
||||
error?: string,
|
||||
): void {
|
||||
const send = opts.sendChatMessage;
|
||||
if (!send) return;
|
||||
if (toolCalls.length === 0) return;
|
||||
const glyph = okc ? "✓" : "✗";
|
||||
const label = (PHASE_LABELS[phaseId] ?? phaseId).toLowerCase();
|
||||
const dur = formatDuration(durationMs);
|
||||
const header = `${glyph} pygienium ${opts.check.label} · ${label} (${dur})`;
|
||||
send(header, {
|
||||
phase: "agent",
|
||||
phaseId,
|
||||
toolCalls,
|
||||
error: okc ? undefined : error,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove transient per-check scratch artifacts (e.g. agent-extracted
|
||||
* manifests) written under `<cwd>/.pygienium/<check>-tmp-*`. Findings and
|
||||
|
||||
Reference in New Issue
Block a user