Skip to content

Commit 72b0e72

Browse files
author
mgabor3141
committed
safeguard: show verdicts in widget instead of status bar
Replace setStatus with setWidget for verdict display. Accumulate verdicts across the entire agent flow (not per-turn) and show a compact count (e.g. '🛡️ 3 approved, 1 denied'). Widget persists until agent goes idle, then clears.
1 parent c6c81e8 commit 72b0e72

2 files changed

Lines changed: 29 additions & 20 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"pi-safeguard": patch
3+
---
4+
5+
Show verdict counts in a widget instead of the status bar. Persists until the agent goes idle.

packages/safeguard/src/index.ts

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -162,28 +162,28 @@ export default function (pi: ExtensionAPI) {
162162
let currentTurnBatch: BatchEntry[] = [];
163163
let denialInCurrentTurn = false;
164164
let denialInPreviousTurn = false;
165-
/** Accumulated verdicts for the status line within a turn. */
166-
let turnVerdicts: { action: string; verdict: string; reason: string }[] = [];
165+
/** Accumulated verdicts shown in the widget across the entire agent flow. */
166+
let flowVerdicts: { action: string; verdict: string; reason: string }[] = [];
167167

168-
pi.on("agent_start", async () => {
168+
pi.on("agent_start", async (_event, ctx) => {
169169
currentTurnBatch = [];
170170
denialInCurrentTurn = false;
171171
denialInPreviousTurn = false;
172-
turnVerdicts = [];
172+
flowVerdicts = [];
173+
ctx.ui.setWidget("safeguard", undefined);
173174
});
174175

175176
pi.on("turn_start", async () => {
176177
denialInPreviousTurn = denialInCurrentTurn;
177178
denialInCurrentTurn = false;
178179
currentTurnBatch = [];
179-
turnVerdicts = [];
180180
});
181181

182-
pi.on("turn_end", async (_event, ctx) => {
183-
// Clear the status line after the turn completes
184-
if (turnVerdicts.length > 0) {
185-
ctx.ui.setStatus("safeguard", undefined);
186-
turnVerdicts = [];
182+
pi.on("agent_end", async (_event, ctx) => {
183+
// Clear the widget when the agent goes idle and returns to the user
184+
if (flowVerdicts.length > 0) {
185+
ctx.ui.setWidget("safeguard", undefined);
186+
flowVerdicts = [];
187187
}
188188
});
189189

@@ -204,7 +204,7 @@ export default function (pi: ExtensionAPI) {
204204

205205
const action = describeAction(event);
206206
const batchContext = currentTurnBatch.length > 0 ? [...currentTurnBatch] : undefined;
207-
const result = await evaluate(pi, ctx, config, systemPrompt, action, batchContext, turnVerdicts);
207+
const result = await evaluate(pi, ctx, config, systemPrompt, action, batchContext, flowVerdicts);
208208

209209
// Track this evaluation in the batch
210210
const verdict = result ? "deny" : "approve";
@@ -232,7 +232,7 @@ async function evaluate(
232232
systemPrompt: string,
233233
action: string,
234234
batchContext: BatchEntry[] | undefined,
235-
turnVerdicts: { action: string; verdict: string; reason: string }[],
235+
flowVerdicts: { action: string; verdict: string; reason: string }[],
236236
): Promise<{ block: true; reason: string } | undefined> {
237237
let judge: BudgetModel;
238238
try {
@@ -258,8 +258,8 @@ async function evaluate(
258258
);
259259

260260
if (verdict.verdict === "approve") {
261-
turnVerdicts.push({ action, verdict: "✅", reason: verdict.reason });
262-
updateStatus(ctx, turnVerdicts);
261+
flowVerdicts.push({ action, verdict: "✅", reason: verdict.reason });
262+
updateWidget(ctx, flowVerdicts);
263263
pi.appendEntry(VERDICT_ENTRY_TYPE, {
264264
action,
265265
verdict: "approve",
@@ -269,8 +269,8 @@ async function evaluate(
269269
}
270270

271271
if (verdict.verdict === "deny") {
272-
turnVerdicts.push({ action, verdict: "❌", reason: verdict.reason });
273-
updateStatus(ctx, turnVerdicts);
272+
flowVerdicts.push({ action, verdict: "❌", reason: verdict.reason });
273+
updateWidget(ctx, flowVerdicts);
274274
pi.appendEntry(VERDICT_ENTRY_TYPE, { action, verdict: "deny", reason: verdict.reason } satisfies VerdictData);
275275
return { block: true, reason: verdict.guidance || DEFAULT_DENY_GUIDANCE };
276276
}
@@ -282,10 +282,14 @@ async function evaluate(
282282
}
283283
}
284284

285-
/** Update the footer status line with accumulated verdicts for this turn. */
286-
function updateStatus(ctx: ExtensionContext, verdicts: { action: string; verdict: string; reason: string }[]): void {
287-
const lines = verdicts.map((v) => `${v.verdict} ${v.reason}`);
288-
ctx.ui.setStatus("safeguard", `🛡️ ${lines.join(" ")}`);
285+
/** Update the widget with accumulated verdict counts for the current agent flow. */
286+
function updateWidget(ctx: ExtensionContext, verdicts: { action: string; verdict: string; reason: string }[]): void {
287+
const approved = verdicts.filter((v) => v.verdict === "✅").length;
288+
const denied = verdicts.filter((v) => v.verdict === "❌").length;
289+
const parts: string[] = [];
290+
if (approved > 0) parts.push(`${approved} approved`);
291+
if (denied > 0) parts.push(`${denied} denied`);
292+
ctx.ui.setWidget("safeguard", [`🛡️ ${parts.join(", ")}`]);
289293
}
290294

291295
// --- Judge model resolution ---

0 commit comments

Comments
 (0)