@mrclrchtr/supi-review 1.11.3 → 1.12.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/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/package.json +2 -4
- package/src/history/collect.ts +3 -21
- package/src/history/synthesize.ts +7 -3
- package/src/review.ts +1 -1
- package/src/target/packet.ts +54 -4
- package/src/tool/brief-runner.ts +54 -141
- package/src/tool/review-debug.ts +123 -0
- package/src/tool/review-handlers.ts +240 -0
- package/src/tool/review-runner.ts +174 -541
- package/src/tool/review-system-prompt.ts +114 -0
- package/src/tool/runner-helpers.ts +59 -0
- package/src/tool/session-lifecycle.ts +183 -0
- package/src/types.ts +43 -0
- package/src/ui/format-content.ts +10 -9
- package/src/ui/renderer.ts +1 -8
- package/src/ui/review-plan-inspector.ts +6 -11
- package/src/api.ts +0 -1
- package/src/index.ts +0 -1
- package/src/target/review-instruction-blocks.ts +0 -60
- package/src/tool/runner-types.ts +0 -45
- package/src/ui/theme-type.ts +0 -16
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrclrchtr/supi-review",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.0",
|
|
4
4
|
"description": "SuPi Review extension — structured code review via /supi-review command",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"README.md"
|
|
21
21
|
],
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@mrclrchtr/supi-core": "1.
|
|
23
|
+
"@mrclrchtr/supi-core": "1.12.0"
|
|
24
24
|
},
|
|
25
25
|
"bundledDependencies": [
|
|
26
26
|
"@mrclrchtr/supi-core"
|
|
@@ -51,9 +51,7 @@
|
|
|
51
51
|
],
|
|
52
52
|
"image": "https://raw.githubusercontent.com/mrclrchtr/supi/main/packages/supi-review/assets/logo.png"
|
|
53
53
|
},
|
|
54
|
-
"main": "src/api.ts",
|
|
55
54
|
"exports": {
|
|
56
|
-
"./api": "./src/api.ts",
|
|
57
55
|
"./extension": "./src/extension.ts",
|
|
58
56
|
"./package.json": "./package.json"
|
|
59
57
|
}
|
package/src/history/collect.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { SessionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { extractAssistantText } from "../tool/runner-helpers.ts";
|
|
2
3
|
|
|
3
4
|
type ResolvedSessionMessage = SessionContext["messages"][number];
|
|
4
5
|
|
|
@@ -101,7 +102,7 @@ function serializeEntry(
|
|
|
101
102
|
switch (message.role) {
|
|
102
103
|
case "user":
|
|
103
104
|
case "assistant": {
|
|
104
|
-
const text = normalizeText(
|
|
105
|
+
const text = normalizeText(extractAssistantText(message.content) ?? "");
|
|
105
106
|
if (!text) return undefined;
|
|
106
107
|
return {
|
|
107
108
|
label: message.role === "user" ? "User" : "Assistant",
|
|
@@ -110,7 +111,7 @@ function serializeEntry(
|
|
|
110
111
|
};
|
|
111
112
|
}
|
|
112
113
|
case "custom": {
|
|
113
|
-
const text = normalizeText(
|
|
114
|
+
const text = normalizeText(extractAssistantText(message.content) ?? "");
|
|
114
115
|
if (!text) return undefined;
|
|
115
116
|
return { label: "Custom", text, isSummary: false };
|
|
116
117
|
}
|
|
@@ -129,25 +130,6 @@ function serializeEntry(
|
|
|
129
130
|
}
|
|
130
131
|
}
|
|
131
132
|
|
|
132
|
-
function extractMessageText(content: unknown): string {
|
|
133
|
-
if (typeof content === "string") {
|
|
134
|
-
return content;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
if (!Array.isArray(content)) {
|
|
138
|
-
return "";
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
return content
|
|
142
|
-
.map((part) => {
|
|
143
|
-
if (typeof part !== "object" || !part) return "";
|
|
144
|
-
const text = (part as { text?: unknown }).text;
|
|
145
|
-
return typeof text === "string" ? text : "";
|
|
146
|
-
})
|
|
147
|
-
.filter((text) => text.length > 0)
|
|
148
|
-
.join("\n");
|
|
149
|
-
}
|
|
150
|
-
|
|
151
133
|
function normalizeText(text: string): string {
|
|
152
134
|
return text.replace(/\s+/g, " ").trim();
|
|
153
135
|
}
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import type { ModelRegistry } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { listReviewInstructionBlocks } from "../target/
|
|
2
|
+
import { listReviewInstructionBlocks } from "../target/packet.ts";
|
|
3
3
|
import { runBriefSynthesis } from "../tool/brief-runner.ts";
|
|
4
|
-
import type {
|
|
5
|
-
|
|
4
|
+
import type {
|
|
5
|
+
BriefSynthesisRunResult,
|
|
6
|
+
ReviewModelSelection,
|
|
7
|
+
ReviewProgress,
|
|
8
|
+
ReviewSnapshot,
|
|
9
|
+
} from "../types.ts";
|
|
6
10
|
|
|
7
11
|
const DIFF_EXCERPT_CHAR_BUDGET = 12_000;
|
|
8
12
|
|
package/src/review.ts
CHANGED
|
@@ -7,8 +7,8 @@ import { synthesizeReviewBrief } from "./history/synthesize.ts";
|
|
|
7
7
|
import { normalizeReviewResult } from "./review-result.ts";
|
|
8
8
|
import { buildReviewPacket } from "./target/packet.ts";
|
|
9
9
|
import { runReviewer } from "./tool/review-runner.ts";
|
|
10
|
-
import type { BriefSynthesisRunResult } from "./tool/runner-types.ts";
|
|
11
10
|
import type {
|
|
11
|
+
BriefSynthesisRunResult,
|
|
12
12
|
RawReviewResult,
|
|
13
13
|
ReviewPlan,
|
|
14
14
|
ReviewResult,
|
package/src/target/packet.ts
CHANGED
|
@@ -5,10 +5,60 @@ import type {
|
|
|
5
5
|
ReviewSnapshot,
|
|
6
6
|
SynthesizedReviewBrief,
|
|
7
7
|
} from "../types.ts";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
export interface ReviewInstructionBlock {
|
|
9
|
+
id: ReviewInstructionBlockId;
|
|
10
|
+
title: string;
|
|
11
|
+
instruction: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const REVIEW_INSTRUCTION_BLOCKS: readonly ReviewInstructionBlock[] = [
|
|
15
|
+
{
|
|
16
|
+
id: "public-surface",
|
|
17
|
+
title: "Public-surface / rename / merge audit",
|
|
18
|
+
instruction:
|
|
19
|
+
"Sweep source, tests, docs, user-facing strings, and debug/status lists for stale public names after renames, removals, or merges.",
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: "cross-layer",
|
|
23
|
+
title: "Cross-layer propagation audit",
|
|
24
|
+
instruction:
|
|
25
|
+
"Verify every provider/runtime/orchestration/presentation/test handoff and look for at least one end-to-end expectation covering the threaded behavior.",
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: "schema-widening",
|
|
29
|
+
title: "Enum / operation / schema widening audit",
|
|
30
|
+
instruction:
|
|
31
|
+
"Audit validation, unavailable paths, branch coverage, aliases, and negative tests for widened enums, operations, or schemas.",
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: "cleanup",
|
|
35
|
+
title: "Cleanup / deletion / orphan audit",
|
|
36
|
+
instruction:
|
|
37
|
+
"Check for orphan files, dead imports or re-exports, stale comments, and outdated expectations after deletions or consumer removals.",
|
|
38
|
+
},
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
/** Return the full fixed catalog of review instruction blocks. */
|
|
42
|
+
export function listReviewInstructionBlocks(): readonly ReviewInstructionBlock[] {
|
|
43
|
+
return REVIEW_INSTRUCTION_BLOCKS;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Resolve a brief-selected block ID list into canonical host-owned prompt blocks. */
|
|
47
|
+
export function resolveReviewInstructionBlocks(
|
|
48
|
+
ids: readonly ReviewInstructionBlockId[],
|
|
49
|
+
): ReviewInstructionBlock[] {
|
|
50
|
+
const resolved: ReviewInstructionBlock[] = [];
|
|
51
|
+
const seen = new Set<ReviewInstructionBlockId>();
|
|
52
|
+
|
|
53
|
+
for (const id of ids) {
|
|
54
|
+
if (seen.has(id)) continue;
|
|
55
|
+
seen.add(id);
|
|
56
|
+
const block = REVIEW_INSTRUCTION_BLOCKS.find((b) => b.id === id);
|
|
57
|
+
if (block) resolved.push(block);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return resolved;
|
|
61
|
+
}
|
|
12
62
|
|
|
13
63
|
export interface DiffSection {
|
|
14
64
|
file: string;
|
package/src/tool/brief-runner.ts
CHANGED
|
@@ -7,13 +7,14 @@ import {
|
|
|
7
7
|
defineTool,
|
|
8
8
|
SessionManager,
|
|
9
9
|
} from "@earendil-works/pi-coding-agent";
|
|
10
|
-
import type { SynthesizedReviewBrief } from "../types.ts";
|
|
11
10
|
import type {
|
|
12
11
|
BriefSynthesisInvocation,
|
|
13
12
|
BriefSynthesisRunResult,
|
|
14
|
-
|
|
15
|
-
} from "
|
|
13
|
+
SynthesizedReviewBrief,
|
|
14
|
+
} from "../types.ts";
|
|
15
|
+
import { buildProgressTokens, extractLastAssistantText } from "./runner-helpers.ts";
|
|
16
16
|
import { reviewBriefSchema } from "./schemas.ts";
|
|
17
|
+
import { type LifecycleCtx, runWithLifecycle } from "./session-lifecycle.ts";
|
|
17
18
|
|
|
18
19
|
const DEFAULT_TIMEOUT_MS = 5 * 60 * 1_000;
|
|
19
20
|
|
|
@@ -78,51 +79,12 @@ async function createBriefSession(
|
|
|
78
79
|
return session;
|
|
79
80
|
}
|
|
80
81
|
|
|
81
|
-
function
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
if (text) return text;
|
|
88
|
-
}
|
|
89
|
-
return undefined;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function extractAssistantText(content: unknown): string | undefined {
|
|
93
|
-
if (typeof content === "string") {
|
|
94
|
-
return content.length > 0 ? content : undefined;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
if (!Array.isArray(content)) {
|
|
98
|
-
return undefined;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const texts = content
|
|
102
|
-
.map((part) => {
|
|
103
|
-
if (typeof part !== "object" || !part) return "";
|
|
104
|
-
const text = (part as { text?: unknown }).text;
|
|
105
|
-
return typeof text === "string" ? text : "";
|
|
106
|
-
})
|
|
107
|
-
.filter((value) => value.length > 0);
|
|
108
|
-
|
|
109
|
-
return texts.length > 0 ? texts.join("\n") : undefined;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function emitProgress(session: AgentSession, progress: ReviewProgress): ReviewProgress {
|
|
113
|
-
try {
|
|
114
|
-
const stats = session.getSessionStats();
|
|
115
|
-
progress.tokens = stats?.tokens
|
|
116
|
-
? {
|
|
117
|
-
input: stats.tokens.input ?? 0,
|
|
118
|
-
output: stats.tokens.output ?? 0,
|
|
119
|
-
total: stats.tokens.total ?? 0,
|
|
120
|
-
}
|
|
121
|
-
: undefined;
|
|
122
|
-
} catch {
|
|
123
|
-
// ignore missing stats
|
|
124
|
-
}
|
|
125
|
-
return { ...progress, activities: [...progress.activities] };
|
|
82
|
+
function emitBriefProgress(
|
|
83
|
+
ctx: LifecycleCtx<BriefSynthesisRunResult>,
|
|
84
|
+
invocation: BriefSynthesisInvocation,
|
|
85
|
+
): void {
|
|
86
|
+
ctx.progress.tokens = buildProgressTokens(() => ctx.session.getSessionStats());
|
|
87
|
+
invocation.onProgress?.({ ...ctx.progress, activities: [...ctx.progress.activities] });
|
|
126
88
|
}
|
|
127
89
|
|
|
128
90
|
function handleAgentEnd(options: {
|
|
@@ -140,7 +102,7 @@ function handleAgentEnd(options: {
|
|
|
140
102
|
if (brief) {
|
|
141
103
|
return cleanup({ kind: "success", brief });
|
|
142
104
|
}
|
|
143
|
-
const lastText = extractLastAssistantText(session);
|
|
105
|
+
const lastText = extractLastAssistantText(session.messages);
|
|
144
106
|
return cleanup({
|
|
145
107
|
kind: "failed",
|
|
146
108
|
reason: lastText
|
|
@@ -170,99 +132,50 @@ export async function runBriefSynthesis(
|
|
|
170
132
|
};
|
|
171
133
|
}
|
|
172
134
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
event,
|
|
208
|
-
session,
|
|
209
|
-
brief: resultHolder.value,
|
|
210
|
-
state,
|
|
211
|
-
cleanup,
|
|
212
|
-
});
|
|
213
|
-
if (result) {
|
|
214
|
-
resolve(result);
|
|
135
|
+
return runWithLifecycle<BriefSynthesisRunResult>({
|
|
136
|
+
session,
|
|
137
|
+
prompt: invocation.prompt,
|
|
138
|
+
signal: invocation.signal,
|
|
139
|
+
timeoutMs: invocation.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
140
|
+
onEvent: (event, ctx) => {
|
|
141
|
+
switch (event.type) {
|
|
142
|
+
case "turn_end":
|
|
143
|
+
ctx.progress.turns++;
|
|
144
|
+
emitBriefProgress(ctx, invocation);
|
|
145
|
+
break;
|
|
146
|
+
case "tool_execution_start":
|
|
147
|
+
ctx.progress.toolUses++;
|
|
148
|
+
ctx.progress.activities = [
|
|
149
|
+
event.toolName === "submit_review_brief" ? "submitting brief" : event.toolName,
|
|
150
|
+
];
|
|
151
|
+
emitBriefProgress(ctx, invocation);
|
|
152
|
+
break;
|
|
153
|
+
case "tool_execution_end":
|
|
154
|
+
ctx.progress.activities = [];
|
|
155
|
+
emitBriefProgress(ctx, invocation);
|
|
156
|
+
break;
|
|
157
|
+
case "agent_end": {
|
|
158
|
+
const result = handleAgentEnd({
|
|
159
|
+
event,
|
|
160
|
+
session,
|
|
161
|
+
brief: resultHolder.value,
|
|
162
|
+
state: ctx.state,
|
|
163
|
+
cleanup: ctx.cleanup,
|
|
164
|
+
});
|
|
165
|
+
if (result) {
|
|
166
|
+
ctx.resolve(result);
|
|
167
|
+
}
|
|
168
|
+
break;
|
|
215
169
|
}
|
|
216
|
-
|
|
170
|
+
default:
|
|
171
|
+
break;
|
|
217
172
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
const onAbort = () => {
|
|
227
|
-
if (state.settled) return;
|
|
228
|
-
state.aborting = true;
|
|
229
|
-
void session
|
|
230
|
-
.abort()
|
|
231
|
-
.catch(() => {})
|
|
232
|
-
.finally(() => {
|
|
233
|
-
resolve(cleanup({ kind: "canceled" }));
|
|
234
|
-
});
|
|
235
|
-
};
|
|
236
|
-
invocation.signal?.addEventListener("abort", onAbort, { once: true });
|
|
237
|
-
|
|
238
|
-
const timeoutId = setTimeout(() => {
|
|
239
|
-
if (state.settled) return;
|
|
240
|
-
state.aborting = true;
|
|
241
|
-
void session
|
|
242
|
-
.abort()
|
|
243
|
-
.catch(() => {})
|
|
244
|
-
.finally(() => {
|
|
245
|
-
resolve(
|
|
246
|
-
cleanup({ kind: "timeout", timeoutMs: invocation.timeoutMs ?? DEFAULT_TIMEOUT_MS }),
|
|
247
|
-
);
|
|
248
|
-
});
|
|
249
|
-
}, invocation.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
250
|
-
timeoutId.unref?.();
|
|
251
|
-
|
|
252
|
-
cancelTeardown = () => {
|
|
253
|
-
invocation.signal?.removeEventListener("abort", onAbort);
|
|
254
|
-
clearTimeout(timeoutId);
|
|
255
|
-
};
|
|
256
|
-
|
|
257
|
-
session.prompt(invocation.prompt).catch((error: unknown) => {
|
|
258
|
-
if (!state.settled) {
|
|
259
|
-
resolve(
|
|
260
|
-
cleanup({
|
|
261
|
-
kind: "failed",
|
|
262
|
-
reason: `Brief synthesis session error: ${error instanceof Error ? error.message : String(error)}`,
|
|
263
|
-
}),
|
|
264
|
-
);
|
|
265
|
-
}
|
|
266
|
-
});
|
|
173
|
+
},
|
|
174
|
+
canceledResult: () => ({ kind: "canceled" as const }),
|
|
175
|
+
failedResult: (reason) => ({
|
|
176
|
+
kind: "failed" as const,
|
|
177
|
+
reason,
|
|
178
|
+
}),
|
|
179
|
+
timeoutResult: (ms) => ({ kind: "timeout" as const, timeoutMs: ms }),
|
|
267
180
|
});
|
|
268
181
|
}
|
|
@@ -0,0 +1,123 @@
|
|
|
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 "auto_retry_start":
|
|
81
|
+
return `retry:start:${event.attempt}/${event.maxAttempts}`;
|
|
82
|
+
case "auto_retry_end":
|
|
83
|
+
return `retry:end:${event.success ? "success" : "failed"}`;
|
|
84
|
+
default:
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function pushRecentEvent(recentEvents: string[], summary: string | undefined): void {
|
|
90
|
+
if (!summary) return;
|
|
91
|
+
recentEvents.push(summary);
|
|
92
|
+
if (recentEvents.length > RECENT_EVENTS_MAX) {
|
|
93
|
+
recentEvents.splice(0, recentEvents.length - RECENT_EVENTS_MAX);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface BuildFailureDebugInput {
|
|
98
|
+
progress: ReviewProgress;
|
|
99
|
+
session: AgentSession;
|
|
100
|
+
recentEvents: string[];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function buildFailureDebug(input: BuildFailureDebugInput): ReviewFailureDebugInfo {
|
|
104
|
+
input.progress.tokens = buildProgressTokens(() => input.session.getSessionStats());
|
|
105
|
+
const lastAssistant = extractLastAssistantDebug(input.session);
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
turns: input.progress.turns,
|
|
109
|
+
toolUses: input.progress.toolUses,
|
|
110
|
+
activities: input.progress.activities.length > 0 ? [...input.progress.activities] : undefined,
|
|
111
|
+
tokens: input.progress.tokens,
|
|
112
|
+
recentEvents: input.recentEvents.length > 0 ? [...input.recentEvents] : undefined,
|
|
113
|
+
lastAssistantText: lastAssistant?.text,
|
|
114
|
+
lastAssistantStopReason: lastAssistant?.stopReason,
|
|
115
|
+
lastAssistantErrorMessage: lastAssistant?.errorMessage,
|
|
116
|
+
lastAssistantToolCalls: lastAssistant?.toolCalls,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function truncateText(text: string, maxLen: number): string {
|
|
121
|
+
if (text.length <= maxLen) return text;
|
|
122
|
+
return `${text.slice(0, maxLen)}... (${text.length - maxLen} more chars)`;
|
|
123
|
+
}
|