@mrclrchtr/supi-review 2.6.1 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -3
- package/node_modules/@mrclrchtr/supi-core/README.md +1 -1
- package/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-core/src/settings/scoped-settings-list.ts +1 -0
- package/node_modules/@mrclrchtr/supi-core/src/settings/settings-schema.ts +14 -0
- package/node_modules/@mrclrchtr/supi-core/src/settings/settings-submenus.ts +31 -14
- package/node_modules/@mrclrchtr/supi-core/src/settings.ts +1 -0
- package/package.json +2 -2
- package/src/config.ts +55 -0
- package/src/model.ts +33 -0
- package/src/review.ts +45 -29
- package/src/tool/agent-review-tools.ts +20 -11
- package/src/tool/agent-review-workflow.ts +41 -23
- package/src/tool/brief-runner.ts +25 -24
- package/src/tool/child-failure-diagnostics.ts +145 -0
- package/src/tool/child-lifecycle-trace.ts +329 -0
- package/src/tool/review-handlers.ts +9 -30
- package/src/tool/review-runner.ts +25 -43
- package/src/tool/runner-helpers.ts +0 -14
- package/src/tool/session-lifecycle.ts +105 -10
- package/src/types.ts +51 -37
- package/src/ui/format-content.ts +47 -38
- package/src/ui/renderer.ts +67 -45
- package/src/ui/review-tool-renderer.ts +32 -1
- package/src/tool/review-debug.ts +0 -124
package/src/ui/renderer.ts
CHANGED
|
@@ -1,12 +1,38 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { Box, Container, Spacer, Text } from "@earendil-works/pi-tui";
|
|
3
|
-
import
|
|
4
|
-
|
|
3
|
+
import {
|
|
4
|
+
formatChildFailureCopy,
|
|
5
|
+
formatChildFailureDiagnostics,
|
|
6
|
+
} from "../tool/child-failure-diagnostics.ts";
|
|
7
|
+
import type {
|
|
8
|
+
BriefSynthesisRunResult,
|
|
9
|
+
ChildFailureDiagnostics,
|
|
10
|
+
ReviewItem,
|
|
11
|
+
ReviewResult,
|
|
12
|
+
ReviewSnapshot,
|
|
13
|
+
} from "../types.ts";
|
|
14
|
+
import { formatBriefSynthesisFailureCopy, formatLevel, formatLocation } from "./format-content.ts";
|
|
5
15
|
|
|
6
16
|
/** Register the custom TUI renderer for `supi-review` messages. */
|
|
17
|
+
interface BriefSynthesisFailureMessage {
|
|
18
|
+
result: Exclude<BriefSynthesisRunResult, { kind: "success" }>;
|
|
19
|
+
snapshot: ReviewSnapshot;
|
|
20
|
+
modelId: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface ReviewMessageDetails {
|
|
24
|
+
result?: ReviewResult;
|
|
25
|
+
briefSynthesisFailure?: BriefSynthesisFailureMessage;
|
|
26
|
+
}
|
|
27
|
+
|
|
7
28
|
export function registerReviewRenderer(pi: ExtensionAPI): void {
|
|
8
29
|
pi.registerMessageRenderer("supi-review", (message, { expanded }, theme) => {
|
|
9
|
-
const
|
|
30
|
+
const details = message.details as ReviewMessageDetails | undefined;
|
|
31
|
+
if (details?.briefSynthesisFailure) {
|
|
32
|
+
return renderBriefSynthesisFailure(details.briefSynthesisFailure, theme);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const result = details?.result;
|
|
10
36
|
if (!result) {
|
|
11
37
|
return new Text(theme.fg("dim", "No review data"), 1, 0);
|
|
12
38
|
}
|
|
@@ -26,6 +52,31 @@ export function registerReviewRenderer(pi: ExtensionAPI): void {
|
|
|
26
52
|
});
|
|
27
53
|
}
|
|
28
54
|
|
|
55
|
+
function renderBriefSynthesisFailure(
|
|
56
|
+
failure: BriefSynthesisFailureMessage,
|
|
57
|
+
theme: Parameters<Parameters<ExtensionAPI["registerMessageRenderer"]>[1]>[2],
|
|
58
|
+
): Container {
|
|
59
|
+
const container = new Container();
|
|
60
|
+
const title =
|
|
61
|
+
failure.result.kind === "failed"
|
|
62
|
+
? "◆ Brief Synthesis Failed"
|
|
63
|
+
: failure.result.kind === "timeout"
|
|
64
|
+
? "◆ Brief Synthesis Timed Out"
|
|
65
|
+
: "◆ Brief Synthesis Canceled";
|
|
66
|
+
const color = failure.result.kind === "failed" ? "error" : "warning";
|
|
67
|
+
|
|
68
|
+
container.addChild(new Text(theme.fg(color, title), 1, 0));
|
|
69
|
+
container.addChild(new Spacer(1));
|
|
70
|
+
container.addChild(new Text(theme.fg("muted", `Model: ${failure.modelId}`), 1, 0));
|
|
71
|
+
container.addChild(new Text(theme.fg("muted", `Snapshot: ${failure.snapshot.title}`), 1, 0));
|
|
72
|
+
container.addChild(new Spacer(1));
|
|
73
|
+
container.addChild(
|
|
74
|
+
new Text(theme.fg(color, formatBriefSynthesisFailureCopy(failure.result)), 1, 0),
|
|
75
|
+
);
|
|
76
|
+
renderChildFailureDiagnostics(container, failure.result.diagnostics, theme);
|
|
77
|
+
return container;
|
|
78
|
+
}
|
|
79
|
+
|
|
29
80
|
function renderSuccess(
|
|
30
81
|
result: Extract<ReviewResult, { kind: "success" }>,
|
|
31
82
|
theme: Parameters<Parameters<ExtensionAPI["registerMessageRenderer"]>[1]>[2],
|
|
@@ -160,8 +211,10 @@ function renderFailed(
|
|
|
160
211
|
container.addChild(new Text(theme.fg("muted", `Model: ${result.modelId}`), 1, 0));
|
|
161
212
|
container.addChild(new Text(theme.fg("muted", `Snapshot: ${result.snapshot.title}`), 1, 0));
|
|
162
213
|
container.addChild(new Spacer(1));
|
|
163
|
-
container.addChild(
|
|
164
|
-
|
|
214
|
+
container.addChild(
|
|
215
|
+
new Text(theme.fg("error", formatChildFailureCopy("reviewer", result.failureCode)), 1, 0),
|
|
216
|
+
);
|
|
217
|
+
renderChildFailureDiagnostics(container, result.diagnostics, theme);
|
|
165
218
|
return container;
|
|
166
219
|
}
|
|
167
220
|
|
|
@@ -175,19 +228,8 @@ function renderTimeout(
|
|
|
175
228
|
container.addChild(new Text(theme.fg("muted", `Model: ${result.modelId}`), 1, 0));
|
|
176
229
|
container.addChild(new Text(theme.fg("muted", `Snapshot: ${result.snapshot.title}`), 1, 0));
|
|
177
230
|
container.addChild(new Spacer(1));
|
|
178
|
-
container.addChild(
|
|
179
|
-
|
|
180
|
-
theme.fg("warning", `Reviewer exceeded the ${(result.timeoutMs / 1000).toFixed(0)}s timeout`),
|
|
181
|
-
1,
|
|
182
|
-
0,
|
|
183
|
-
),
|
|
184
|
-
);
|
|
185
|
-
if (result.partialOutput) {
|
|
186
|
-
container.addChild(new Spacer(1));
|
|
187
|
-
container.addChild(new Text(theme.fg("dim", "Partial output:"), 1, 0));
|
|
188
|
-
container.addChild(new Text(theme.fg("dim", result.partialOutput.slice(0, 500)), 1, 0));
|
|
189
|
-
}
|
|
190
|
-
renderFailureDebug(container, result.debug, theme);
|
|
231
|
+
container.addChild(new Text(theme.fg("warning", "Reviewer timed out."), 1, 0));
|
|
232
|
+
renderChildFailureDiagnostics(container, result.diagnostics, theme);
|
|
191
233
|
return container;
|
|
192
234
|
}
|
|
193
235
|
|
|
@@ -197,41 +239,21 @@ function renderCanceled(
|
|
|
197
239
|
): Container {
|
|
198
240
|
const container = new Container();
|
|
199
241
|
container.addChild(new Text(theme.fg("warning", "◆ Review Canceled"), 1, 0));
|
|
200
|
-
|
|
242
|
+
container.addChild(new Text(theme.fg("warning", "Reviewer was canceled."), 1, 0));
|
|
243
|
+
renderChildFailureDiagnostics(container, result.diagnostics, theme);
|
|
201
244
|
return container;
|
|
202
245
|
}
|
|
203
246
|
|
|
204
|
-
function
|
|
247
|
+
function renderChildFailureDiagnostics(
|
|
205
248
|
container: Container,
|
|
206
|
-
|
|
249
|
+
diagnostics: ChildFailureDiagnostics | undefined,
|
|
207
250
|
theme: Parameters<Parameters<ExtensionAPI["registerMessageRenderer"]>[1]>[2],
|
|
208
251
|
): void {
|
|
209
|
-
if (!
|
|
210
|
-
|
|
211
|
-
const lines = [
|
|
212
|
-
`Turns: ${debug.turns} · Tool uses: ${debug.toolUses}`,
|
|
213
|
-
debug.tokens
|
|
214
|
-
? `Tokens: ${debug.tokens.input} in / ${debug.tokens.output} out / ${debug.tokens.total} total`
|
|
215
|
-
: undefined,
|
|
216
|
-
debug.recentEvents && debug.recentEvents.length > 0
|
|
217
|
-
? `Recent events: ${debug.recentEvents.join(" → ")}`
|
|
218
|
-
: undefined,
|
|
219
|
-
debug.lastAssistantStopReason
|
|
220
|
-
? `Last assistant stop: ${debug.lastAssistantStopReason}`
|
|
221
|
-
: undefined,
|
|
222
|
-
debug.lastAssistantToolCalls && debug.lastAssistantToolCalls.length > 0
|
|
223
|
-
? `Last assistant tools: ${debug.lastAssistantToolCalls.join(", ")}`
|
|
224
|
-
: undefined,
|
|
225
|
-
debug.lastAssistantErrorMessage
|
|
226
|
-
? `Last assistant error: ${debug.lastAssistantErrorMessage}`
|
|
227
|
-
: undefined,
|
|
228
|
-
].filter((line): line is string => !!line);
|
|
229
|
-
|
|
230
|
-
if (lines.length === 0) return;
|
|
252
|
+
if (!diagnostics) return;
|
|
231
253
|
|
|
232
254
|
container.addChild(new Spacer(1));
|
|
233
|
-
container.addChild(new Text(theme.fg("dim", "
|
|
234
|
-
for (const line of
|
|
255
|
+
container.addChild(new Text(theme.fg("dim", "Diagnostics:"), 1, 0));
|
|
256
|
+
for (const line of formatChildFailureDiagnostics(diagnostics)) {
|
|
235
257
|
container.addChild(new Text(theme.fg("dim", line), 1, 0));
|
|
236
258
|
}
|
|
237
259
|
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { Container, Spacer, Text } from "@earendil-works/pi-tui";
|
|
3
3
|
import type { PrepareAgentReviewInput, RunAgentReviewInput } from "../tool/agent-review-schemas.ts";
|
|
4
|
+
import {
|
|
5
|
+
formatChildFailureCopy,
|
|
6
|
+
formatChildFailureDiagnostics,
|
|
7
|
+
} from "../tool/child-failure-diagnostics.ts";
|
|
4
8
|
import type {
|
|
5
9
|
AgentReviewBatchDetails,
|
|
6
10
|
PreparedAgentReviewDetails,
|
|
@@ -150,6 +154,7 @@ export function renderRunReviewResult(
|
|
|
150
154
|
}
|
|
151
155
|
|
|
152
156
|
if (expanded) {
|
|
157
|
+
renderReviewerDiagnostics(container, details, theme);
|
|
153
158
|
container.addChild(new Spacer(1));
|
|
154
159
|
for (const line of formatCritique(details.evaluation.critique)) {
|
|
155
160
|
container.addChild(new Text(theme.fg("dim", line), 0, 0));
|
|
@@ -165,6 +170,28 @@ export function renderRunReviewResult(
|
|
|
165
170
|
return container;
|
|
166
171
|
}
|
|
167
172
|
|
|
173
|
+
function renderReviewerDiagnostics(
|
|
174
|
+
container: Container,
|
|
175
|
+
details: AgentReviewBatchDetails,
|
|
176
|
+
theme: ReviewTheme,
|
|
177
|
+
): void {
|
|
178
|
+
if (
|
|
179
|
+
!details.results.some((entry) => entry.result.kind !== "success" && entry.result.diagnostics)
|
|
180
|
+
) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
container.addChild(new Spacer(1));
|
|
185
|
+
container.addChild(new Text(theme.fg("accent", "Reviewer diagnostics"), 0, 0));
|
|
186
|
+
for (const { assignment, result } of details.results) {
|
|
187
|
+
if (result.kind === "success" || !result.diagnostics) continue;
|
|
188
|
+
container.addChild(new Text(theme.fg("muted", `${assignment.id}:`), 0, 0));
|
|
189
|
+
for (const line of formatChildFailureDiagnostics(result.diagnostics)) {
|
|
190
|
+
container.addChild(new Text(theme.fg("dim", line), 0, 0));
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
168
195
|
function renderProgress(details: AgentReviewProgressDetails, theme: ReviewTheme): Text {
|
|
169
196
|
if (details.phase === "prepare") {
|
|
170
197
|
return new Text(theme.fg("warning", "Synthesizing review brief…"), 0, 0);
|
|
@@ -189,7 +216,11 @@ function summarizeResult(result: AgentReviewBatchDetails["results"][number]["res
|
|
|
189
216
|
text: `${result.output.items.length} item(s), ${result.output.overall_correctness}`,
|
|
190
217
|
};
|
|
191
218
|
case "failed":
|
|
192
|
-
return {
|
|
219
|
+
return {
|
|
220
|
+
icon: "✗",
|
|
221
|
+
color: "error",
|
|
222
|
+
text: formatChildFailureCopy("reviewer", result.failureCode),
|
|
223
|
+
};
|
|
193
224
|
case "canceled":
|
|
194
225
|
return { icon: "○", color: "warning", text: "canceled" };
|
|
195
226
|
case "timeout":
|
package/src/tool/review-debug.ts
DELETED
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import type { AgentSession, AgentSessionEvent } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import type { ReviewFailureDebugInfo, ReviewProgress } from "../types.ts";
|
|
3
|
-
import { buildProgressTokens, extractAssistantText } from "./runner-helpers.ts";
|
|
4
|
-
|
|
5
|
-
export const RECENT_EVENTS_MAX = 10;
|
|
6
|
-
export const LAST_ASSISTANT_TEXT_DEBUG_MAX = 2_000;
|
|
7
|
-
|
|
8
|
-
export interface LastAssistantDebugInfo {
|
|
9
|
-
text?: string;
|
|
10
|
-
stopReason?: string;
|
|
11
|
-
errorMessage?: string;
|
|
12
|
-
toolCalls?: string[];
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function extractLastAssistantDebugFromMessages(
|
|
16
|
-
messages: ArrayLike<Record<string, unknown>> | undefined,
|
|
17
|
-
): LastAssistantDebugInfo | undefined {
|
|
18
|
-
if (!messages) return undefined;
|
|
19
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
20
|
-
const message = messages[i];
|
|
21
|
-
if (message?.role !== "assistant") continue;
|
|
22
|
-
|
|
23
|
-
const text = extractAssistantText(message.content);
|
|
24
|
-
const stopReason =
|
|
25
|
-
typeof message.stopReason === "string" ? (message.stopReason as string) : undefined;
|
|
26
|
-
const errorMessage =
|
|
27
|
-
typeof message.errorMessage === "string" ? (message.errorMessage as string) : undefined;
|
|
28
|
-
const toolCalls = extractAssistantToolCalls(message.content);
|
|
29
|
-
|
|
30
|
-
return {
|
|
31
|
-
text: text ? truncateText(text, LAST_ASSISTANT_TEXT_DEBUG_MAX) : undefined,
|
|
32
|
-
stopReason,
|
|
33
|
-
errorMessage,
|
|
34
|
-
toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
return undefined;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export function extractLastAssistantDebug(
|
|
41
|
-
session: AgentSession,
|
|
42
|
-
): LastAssistantDebugInfo | undefined {
|
|
43
|
-
return extractLastAssistantDebugFromMessages(
|
|
44
|
-
session.messages as unknown as Array<Record<string, unknown>>,
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function extractAssistantToolCalls(content: unknown): string[] {
|
|
49
|
-
if (!Array.isArray(content)) return [];
|
|
50
|
-
|
|
51
|
-
return content
|
|
52
|
-
.map((part) => {
|
|
53
|
-
if (typeof part !== "object" || !part) return undefined;
|
|
54
|
-
const toolPart = part as { type?: unknown; name?: unknown };
|
|
55
|
-
return toolPart.type === "toolCall" && typeof toolPart.name === "string"
|
|
56
|
-
? toolPart.name
|
|
57
|
-
: undefined;
|
|
58
|
-
})
|
|
59
|
-
.filter((name): name is string => !!name);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export function summarizeSessionEvent(event: AgentSessionEvent): string | undefined {
|
|
63
|
-
switch (event.type) {
|
|
64
|
-
case "message_end": {
|
|
65
|
-
const message = event.message as unknown as Record<string, unknown>;
|
|
66
|
-
if (message.role !== "assistant") return undefined;
|
|
67
|
-
const stopReason =
|
|
68
|
-
typeof message.stopReason === "string" ? String(message.stopReason) : undefined;
|
|
69
|
-
const suffix = stopReason ? `:${stopReason}` : "";
|
|
70
|
-
return `assistant:end${suffix}`;
|
|
71
|
-
}
|
|
72
|
-
case "tool_execution_start":
|
|
73
|
-
return `tool:start:${event.toolName}`;
|
|
74
|
-
case "tool_execution_end":
|
|
75
|
-
return `tool:end:${event.toolName}${event.isError ? ":error" : ""}`;
|
|
76
|
-
case "turn_end":
|
|
77
|
-
return "turn:end";
|
|
78
|
-
case "agent_end":
|
|
79
|
-
return `agent:end${event.willRetry ? ":retry" : ""}`;
|
|
80
|
-
case "agent_settled":
|
|
81
|
-
return "agent:settled";
|
|
82
|
-
case "auto_retry_start":
|
|
83
|
-
return `retry:start:${event.attempt}/${event.maxAttempts}`;
|
|
84
|
-
case "auto_retry_end":
|
|
85
|
-
return `retry:end:${event.success ? "success" : "failed"}`;
|
|
86
|
-
default:
|
|
87
|
-
return undefined;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export function pushRecentEvent(recentEvents: string[], summary: string | undefined): void {
|
|
92
|
-
if (!summary) return;
|
|
93
|
-
recentEvents.push(summary);
|
|
94
|
-
if (recentEvents.length > RECENT_EVENTS_MAX) {
|
|
95
|
-
recentEvents.splice(0, recentEvents.length - RECENT_EVENTS_MAX);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export interface BuildFailureDebugInput {
|
|
100
|
-
progress: ReviewProgress;
|
|
101
|
-
session: AgentSession;
|
|
102
|
-
recentEvents: string[];
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
export function buildFailureDebug(input: BuildFailureDebugInput): ReviewFailureDebugInfo {
|
|
106
|
-
input.progress.tokens = buildProgressTokens(() => input.session.getSessionStats());
|
|
107
|
-
const lastAssistant = extractLastAssistantDebug(input.session);
|
|
108
|
-
|
|
109
|
-
return {
|
|
110
|
-
turns: input.progress.turns,
|
|
111
|
-
toolUses: input.progress.toolUses,
|
|
112
|
-
tokens: input.progress.tokens,
|
|
113
|
-
recentEvents: input.recentEvents.length > 0 ? [...input.recentEvents] : undefined,
|
|
114
|
-
lastAssistantText: lastAssistant?.text,
|
|
115
|
-
lastAssistantStopReason: lastAssistant?.stopReason,
|
|
116
|
-
lastAssistantErrorMessage: lastAssistant?.errorMessage,
|
|
117
|
-
lastAssistantToolCalls: lastAssistant?.toolCalls,
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function truncateText(text: string, maxLen: number): string {
|
|
122
|
-
if (text.length <= maxLen) return text;
|
|
123
|
-
return `${text.slice(0, maxLen)}... (${text.length - maxLen} more chars)`;
|
|
124
|
-
}
|