@mrclrchtr/supi-debug 4.9.0 → 5.0.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 +10 -2
- package/node_modules/@mrclrchtr/supi-core/README.md +2 -0
- package/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-core/src/config/config.ts +31 -0
- package/node_modules/@mrclrchtr/supi-core/src/config.ts +2 -0
- package/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +19 -3
- package/node_modules/@mrclrchtr/supi-core/src/debug-timing.ts +34 -21
- package/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +3 -0
- package/package.json +3 -2
- package/prompts/supi-tooling-retro.md +62 -0
- package/src/command.ts +132 -0
- package/src/debug.ts +92 -158
- package/src/output.ts +101 -0
- package/src/query.ts +45 -0
- package/src/render-details.ts +336 -0
- package/src/renderer.ts +232 -57
- package/src/session-events.ts +88 -14
- package/src/tool/guidance.ts +1 -1
package/src/debug.ts
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
|
+
import { dirname, join } from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
1
3
|
import { StringEnum } from "@earendil-works/pi-ai";
|
|
2
|
-
import {
|
|
3
|
-
DEFAULT_MAX_BYTES,
|
|
4
|
-
DEFAULT_MAX_LINES,
|
|
5
|
-
type ExtensionAPI,
|
|
6
|
-
formatSize,
|
|
7
|
-
type TruncationResult,
|
|
8
|
-
truncateHead,
|
|
9
|
-
} from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import type { AgentToolUpdateCallback, ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
10
5
|
import { loadSupiConfig } from "@mrclrchtr/supi-core/config";
|
|
11
6
|
import { registerContextProvider } from "@mrclrchtr/supi-core/context";
|
|
12
7
|
import {
|
|
@@ -18,20 +13,28 @@ import {
|
|
|
18
13
|
type DebugEventView,
|
|
19
14
|
getDebugEvents,
|
|
20
15
|
getDebugSummary,
|
|
21
|
-
|
|
16
|
+
isDebugOperationId,
|
|
22
17
|
subscribeDebugEvents,
|
|
23
18
|
} from "@mrclrchtr/supi-core/debug";
|
|
19
|
+
import { resolveToolPath } from "@mrclrchtr/supi-core/path";
|
|
24
20
|
import { defineConfigSettings, registerSettings } from "@mrclrchtr/supi-core/settings";
|
|
25
21
|
import { Type } from "typebox";
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
22
|
+
import { registerDebugCommand } from "./command.ts";
|
|
23
|
+
import { formatDebugEvents, truncateDebugOutput } from "./output.ts";
|
|
24
|
+
import type { DebugToolParams } from "./query.ts";
|
|
25
|
+
import { createDebugRenderDetails } from "./render-details.ts";
|
|
26
|
+
import {
|
|
27
|
+
registerDebugMessageRenderer,
|
|
28
|
+
renderDebugToolCall,
|
|
29
|
+
renderDebugToolResult,
|
|
30
|
+
} from "./renderer.ts";
|
|
28
31
|
import { DEBUG_EVENT_ENTRY_TYPE, readSessionDebugEvents } from "./session-events.ts";
|
|
29
32
|
import { maybeLogLoadStatus } from "./status-log.ts";
|
|
30
33
|
import { promptGuidelines, promptSnippet, toolDescription } from "./tool/guidance.ts";
|
|
31
34
|
|
|
32
|
-
const
|
|
33
|
-
const DEBUG_REPORT_TYPE = "supi-debug-report";
|
|
35
|
+
const baseDir = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
34
36
|
|
|
37
|
+
const DEBUG_SECTION = "debug";
|
|
35
38
|
interface DebugConfig extends Record<string, unknown> {
|
|
36
39
|
enabled: boolean;
|
|
37
40
|
agentAccess: DebugAgentAccess;
|
|
@@ -40,8 +43,6 @@ interface DebugConfig extends Record<string, unknown> {
|
|
|
40
43
|
|
|
41
44
|
const DEBUG_DEFAULTS: DebugConfig = { ...DEBUG_REGISTRY_DEFAULTS };
|
|
42
45
|
|
|
43
|
-
type DebugToolParams = DebugEventQuery & { sessionFile?: string };
|
|
44
|
-
|
|
45
46
|
function normalizeAgentAccess(value: string): DebugAgentAccess {
|
|
46
47
|
return value === "off" || value === "raw" ? value : "sanitized";
|
|
47
48
|
}
|
|
@@ -143,88 +144,6 @@ function registerDebugSettings(pi: ExtensionAPI): void {
|
|
|
143
144
|
);
|
|
144
145
|
}
|
|
145
146
|
|
|
146
|
-
function parseCommandArgs(args: string): DebugToolParams {
|
|
147
|
-
const query: DebugToolParams = {};
|
|
148
|
-
const parts = args.trim().split(/\s+/).filter(Boolean);
|
|
149
|
-
for (const part of parts) {
|
|
150
|
-
const [key, value] = part.split("=", 2);
|
|
151
|
-
if (!value) continue;
|
|
152
|
-
if (key === "source") query.source = value;
|
|
153
|
-
if (key === "category") query.category = value;
|
|
154
|
-
if (key === "level" && isDebugLevel(value)) query.level = value;
|
|
155
|
-
if (key === "limit") query.limit = normalizeMaxEvents(value);
|
|
156
|
-
if (key === "sessionFile") query.sessionFile = value;
|
|
157
|
-
}
|
|
158
|
-
return query;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function pushFormattedData(lines: string[], label: string, value: unknown): void {
|
|
162
|
-
const dataLines = formatDataLines(value);
|
|
163
|
-
if (dataLines.length === 0) return;
|
|
164
|
-
if (dataLines.length === 1) {
|
|
165
|
-
lines.push(` ${label}: ${dataLines[0]}`);
|
|
166
|
-
} else {
|
|
167
|
-
lines.push(` ${label}:`);
|
|
168
|
-
for (const dl of dataLines) {
|
|
169
|
-
lines.push(` ${dl}`);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
function formatEvents(
|
|
175
|
-
events: DebugEventView[],
|
|
176
|
-
rawAccessDenied: boolean,
|
|
177
|
-
rawDataUnavailable = false,
|
|
178
|
-
persistedEventCount?: number,
|
|
179
|
-
): string[] {
|
|
180
|
-
if (events.length === 0) {
|
|
181
|
-
return persistedEventCount === 0
|
|
182
|
-
? [
|
|
183
|
-
"This session has no persisted debug events; sessions recorded before persistence cannot be backfilled.",
|
|
184
|
-
]
|
|
185
|
-
: ["No matching debug events available."];
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
const lines: string[] = [];
|
|
189
|
-
for (const event of events) {
|
|
190
|
-
lines.push(
|
|
191
|
-
`[${new Date(event.timestamp).toISOString()}] ${event.level.toUpperCase()} ${event.source}/${event.category}: ${event.message}`,
|
|
192
|
-
);
|
|
193
|
-
if (event.cwd) lines.push(` cwd: ${event.cwd}`);
|
|
194
|
-
pushFormattedData(lines, "data", event.data);
|
|
195
|
-
pushFormattedData(lines, "rawData", event.rawData);
|
|
196
|
-
}
|
|
197
|
-
if (rawDataUnavailable) {
|
|
198
|
-
lines.push("");
|
|
199
|
-
lines.push("Raw debug data is not persisted for historical sessions.");
|
|
200
|
-
} else if (rawAccessDenied) {
|
|
201
|
-
lines.push("");
|
|
202
|
-
lines.push("Raw debug data was requested but is not enabled in SuPi Debug settings.");
|
|
203
|
-
}
|
|
204
|
-
return lines;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
function appendTruncationNote(content: string, truncation: TruncationResult): string {
|
|
208
|
-
if (!truncation.truncated) return content;
|
|
209
|
-
|
|
210
|
-
const omittedLines = truncation.totalLines - truncation.outputLines;
|
|
211
|
-
const omittedBytes = truncation.totalBytes - truncation.outputBytes;
|
|
212
|
-
const separator = content.length > 0 ? "\n\n" : "";
|
|
213
|
-
return `${content}${separator}[Output truncated: showing ${truncation.outputLines} of ${truncation.totalLines} lines (${formatSize(truncation.outputBytes)} of ${formatSize(truncation.totalBytes)}). ${omittedLines} lines (${formatSize(omittedBytes)}) omitted. Use filters or a smaller limit to narrow results.]`;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
function truncateDebugOutput(content: string): { text: string; truncation?: TruncationResult } {
|
|
217
|
-
const truncation = truncateHead(content, {
|
|
218
|
-
maxLines: DEFAULT_MAX_LINES,
|
|
219
|
-
maxBytes: DEFAULT_MAX_BYTES,
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
return {
|
|
223
|
-
text: appendTruncationNote(truncation.content, truncation),
|
|
224
|
-
truncation: truncation.truncated ? truncation : undefined,
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
|
|
228
147
|
function buildSummaryData(): Record<string, string | number> | null {
|
|
229
148
|
const summary = getDebugSummary();
|
|
230
149
|
if (!summary) return null;
|
|
@@ -239,7 +158,36 @@ function buildSummaryData(): Record<string, string | number> | null {
|
|
|
239
158
|
return data;
|
|
240
159
|
}
|
|
241
160
|
|
|
242
|
-
|
|
161
|
+
interface DebugProgressDetails {
|
|
162
|
+
scannedLines: number;
|
|
163
|
+
persistedEventCount: number;
|
|
164
|
+
matchedEvents: number;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function reportDebugProgress(
|
|
168
|
+
onUpdate: AgentToolUpdateCallback<unknown> | undefined,
|
|
169
|
+
progress: DebugProgressDetails,
|
|
170
|
+
): void {
|
|
171
|
+
onUpdate?.({
|
|
172
|
+
content: [
|
|
173
|
+
{
|
|
174
|
+
type: "text",
|
|
175
|
+
text: `Reading persisted debug events: ${progress.matchedEvents} matching events found.`,
|
|
176
|
+
},
|
|
177
|
+
],
|
|
178
|
+
details: progress,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
interface DebugToolExecutionOptions {
|
|
183
|
+
config: DebugConfig;
|
|
184
|
+
cwd: string;
|
|
185
|
+
signal?: AbortSignal;
|
|
186
|
+
onUpdate?: AgentToolUpdateCallback<unknown>;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async function buildToolResult(params: DebugToolParams, options: DebugToolExecutionOptions) {
|
|
190
|
+
const { config, cwd, signal, onUpdate } = options;
|
|
243
191
|
if (!config.enabled && !params.sessionFile) {
|
|
244
192
|
throw new Error(
|
|
245
193
|
"SuPi debug event capture is disabled. Enable Debug in /supi-settings to retain events.",
|
|
@@ -251,6 +199,7 @@ async function buildToolResult(params: DebugToolParams, config: DebugConfig) {
|
|
|
251
199
|
}
|
|
252
200
|
|
|
253
201
|
const filters = {
|
|
202
|
+
operationId: params.operationId,
|
|
254
203
|
source: params.source,
|
|
255
204
|
level: params.level,
|
|
256
205
|
category: params.category,
|
|
@@ -266,7 +215,14 @@ async function buildToolResult(params: DebugToolParams, config: DebugConfig) {
|
|
|
266
215
|
let rawDataUnavailable = false;
|
|
267
216
|
let persistedEventCount: number | undefined;
|
|
268
217
|
if (params.sessionFile) {
|
|
269
|
-
const
|
|
218
|
+
const sessionFile = resolveToolPath(cwd, params.sessionFile);
|
|
219
|
+
const persisted =
|
|
220
|
+
signal || onUpdate
|
|
221
|
+
? await readSessionDebugEvents(sessionFile, filters, {
|
|
222
|
+
signal,
|
|
223
|
+
onProgress: (progress) => reportDebugProgress(onUpdate, progress),
|
|
224
|
+
})
|
|
225
|
+
: await readSessionDebugEvents(sessionFile, filters);
|
|
270
226
|
events = persisted.events;
|
|
271
227
|
persistedEventCount = persisted.persistedEventCount;
|
|
272
228
|
rawAccessDenied = Boolean(params.includeRaw);
|
|
@@ -277,19 +233,28 @@ async function buildToolResult(params: DebugToolParams, config: DebugConfig) {
|
|
|
277
233
|
rawAccessDenied = result.rawAccessDenied;
|
|
278
234
|
}
|
|
279
235
|
const output = truncateDebugOutput(
|
|
280
|
-
|
|
236
|
+
formatDebugEvents(events, rawAccessDenied, rawDataUnavailable, persistedEventCount).join("\n"),
|
|
281
237
|
);
|
|
238
|
+
const details = createDebugRenderDetails(events, {
|
|
239
|
+
enabled: config.enabled,
|
|
240
|
+
agentAccess: config.agentAccess,
|
|
241
|
+
sessionFile: params.sessionFile,
|
|
242
|
+
rawAccessDenied,
|
|
243
|
+
rawDataUnavailable,
|
|
244
|
+
persistedEventCount,
|
|
245
|
+
eventCount: events.length,
|
|
246
|
+
emptyReason:
|
|
247
|
+
events.length === 0
|
|
248
|
+
? persistedEventCount === 0
|
|
249
|
+
? "no-persisted-events"
|
|
250
|
+
: "no-matches"
|
|
251
|
+
: undefined,
|
|
252
|
+
truncation: output.truncation,
|
|
253
|
+
});
|
|
254
|
+
|
|
282
255
|
return {
|
|
283
256
|
content: [{ type: "text" as const, text: output.text }],
|
|
284
|
-
details
|
|
285
|
-
enabled: config.enabled,
|
|
286
|
-
agentAccess: config.agentAccess,
|
|
287
|
-
sessionFile: params.sessionFile,
|
|
288
|
-
rawAccessDenied,
|
|
289
|
-
rawDataUnavailable,
|
|
290
|
-
events,
|
|
291
|
-
truncation: output.truncation,
|
|
292
|
-
},
|
|
257
|
+
details,
|
|
293
258
|
};
|
|
294
259
|
}
|
|
295
260
|
|
|
@@ -315,59 +280,16 @@ export default function debugExtension(pi: ExtensionAPI) {
|
|
|
315
280
|
|
|
316
281
|
pi.on("resources_discover", async (_event, ctx) => {
|
|
317
282
|
maybeLogLoadStatus(pi, ctx.cwd, "resources_discover");
|
|
283
|
+
// Self-register the package prompt template so standalone installs and
|
|
284
|
+
// workspace-root loads expose the same `/supi-tooling-retro` surface.
|
|
285
|
+
return { promptPaths: [join(baseDir, "prompts")] };
|
|
318
286
|
});
|
|
319
287
|
|
|
320
288
|
pi.on("session_shutdown", () => {
|
|
321
289
|
unsubscribeDebugEvents();
|
|
322
290
|
});
|
|
323
291
|
|
|
324
|
-
pi
|
|
325
|
-
description: "Show recent SuPi debug events",
|
|
326
|
-
handler: async (args, ctx) => {
|
|
327
|
-
const config = applyDebugConfig(ctx.cwd);
|
|
328
|
-
const query = parseCommandArgs(args);
|
|
329
|
-
if (!config.enabled && !query.sessionFile) {
|
|
330
|
-
pi.sendMessage({
|
|
331
|
-
customType: DEBUG_REPORT_TYPE,
|
|
332
|
-
content: "SuPi debug event capture is disabled. Enable Debug in /supi-settings.",
|
|
333
|
-
display: true,
|
|
334
|
-
});
|
|
335
|
-
return;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
if (query.sessionFile) {
|
|
339
|
-
const persisted = await readSessionDebugEvents(query.sessionFile, {
|
|
340
|
-
source: query.source,
|
|
341
|
-
level: query.level,
|
|
342
|
-
category: query.category,
|
|
343
|
-
limit: query.limit,
|
|
344
|
-
});
|
|
345
|
-
const output = truncateDebugOutput(
|
|
346
|
-
formatEvents(persisted.events, false, false, persisted.persistedEventCount).join("\n"),
|
|
347
|
-
);
|
|
348
|
-
pi.sendMessage({
|
|
349
|
-
customType: DEBUG_REPORT_TYPE,
|
|
350
|
-
content: output.text,
|
|
351
|
-
display: true,
|
|
352
|
-
details: {
|
|
353
|
-
sessionFile: query.sessionFile,
|
|
354
|
-
events: persisted.events,
|
|
355
|
-
truncation: output.truncation,
|
|
356
|
-
},
|
|
357
|
-
});
|
|
358
|
-
return;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
const { events, rawAccessDenied } = getDebugEvents(query);
|
|
362
|
-
const output = truncateDebugOutput(formatEvents(events, rawAccessDenied).join("\n"));
|
|
363
|
-
pi.sendMessage({
|
|
364
|
-
customType: DEBUG_REPORT_TYPE,
|
|
365
|
-
content: output.text,
|
|
366
|
-
display: true,
|
|
367
|
-
details: { events, rawAccessDenied, truncation: output.truncation },
|
|
368
|
-
});
|
|
369
|
-
},
|
|
370
|
-
});
|
|
292
|
+
registerDebugCommand(pi, applyDebugConfig, normalizeMaxEvents);
|
|
371
293
|
|
|
372
294
|
pi.registerTool({
|
|
373
295
|
name: "supi_debug",
|
|
@@ -376,6 +298,12 @@ export default function debugExtension(pi: ExtensionAPI) {
|
|
|
376
298
|
promptSnippet,
|
|
377
299
|
promptGuidelines,
|
|
378
300
|
parameters: Type.Object({
|
|
301
|
+
operationId: Type.Optional(
|
|
302
|
+
Type.String({
|
|
303
|
+
description: "Filter by exact Debug Operation ID",
|
|
304
|
+
pattern: "^op-[A-Za-z0-9_-]{21}[AQgw]$",
|
|
305
|
+
}),
|
|
306
|
+
),
|
|
379
307
|
source: Type.Optional(Type.String({ description: "Filter by extension source, e.g. lsp" })),
|
|
380
308
|
level: Type.Optional(
|
|
381
309
|
StringEnum(["debug", "info", "warning", "error"], {
|
|
@@ -392,9 +320,15 @@ export default function debugExtension(pi: ExtensionAPI) {
|
|
|
392
320
|
),
|
|
393
321
|
}),
|
|
394
322
|
// biome-ignore lint/complexity/useMaxParams: pi ToolDefinition.execute signature
|
|
395
|
-
async execute(_toolCallId, params,
|
|
323
|
+
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
324
|
+
const query = params as DebugToolParams;
|
|
325
|
+
if (query.operationId !== undefined && !isDebugOperationId(query.operationId)) {
|
|
326
|
+
throw new Error("Invalid Debug Operation ID");
|
|
327
|
+
}
|
|
396
328
|
const config = applyDebugConfig(ctx.cwd);
|
|
397
|
-
return buildToolResult(
|
|
329
|
+
return buildToolResult(query, { config, cwd: ctx.cwd, signal, onUpdate });
|
|
398
330
|
},
|
|
331
|
+
renderCall: renderDebugToolCall,
|
|
332
|
+
renderResult: renderDebugToolResult,
|
|
399
333
|
});
|
|
400
334
|
}
|
package/src/output.ts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_MAX_BYTES,
|
|
3
|
+
DEFAULT_MAX_LINES,
|
|
4
|
+
formatSize,
|
|
5
|
+
type TruncationResult,
|
|
6
|
+
truncateHead,
|
|
7
|
+
} from "@earendil-works/pi-coding-agent";
|
|
8
|
+
import type { DebugEventView } from "@mrclrchtr/supi-core/debug";
|
|
9
|
+
import { formatDataLines } from "./format.ts";
|
|
10
|
+
|
|
11
|
+
const TRUNCATION_RESERVE_LINES = 2;
|
|
12
|
+
const TRUNCATION_RESERVE_BYTES = 512;
|
|
13
|
+
|
|
14
|
+
function pushFormattedData(lines: string[], label: string, value: unknown): void {
|
|
15
|
+
const dataLines = formatDataLines(value);
|
|
16
|
+
if (dataLines.length === 0) return;
|
|
17
|
+
if (dataLines.length === 1) {
|
|
18
|
+
lines.push(` ${label}: ${dataLines[0]}`);
|
|
19
|
+
} else {
|
|
20
|
+
lines.push(` ${label}:`);
|
|
21
|
+
for (const line of dataLines) lines.push(` ${line}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function formatTimestamp(timestamp: number): string {
|
|
26
|
+
const date = new Date(timestamp);
|
|
27
|
+
return Number.isNaN(date.getTime()) ? String(timestamp) : date.toISOString();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Format debug events for model-facing or command output. */
|
|
31
|
+
export function formatDebugEvents(
|
|
32
|
+
events: readonly DebugEventView[],
|
|
33
|
+
rawAccessDenied: boolean,
|
|
34
|
+
rawDataUnavailable = false,
|
|
35
|
+
persistedEventCount?: number,
|
|
36
|
+
): string[] {
|
|
37
|
+
if (events.length === 0) {
|
|
38
|
+
return persistedEventCount === 0
|
|
39
|
+
? [
|
|
40
|
+
"This session has no persisted debug events; sessions recorded before persistence cannot be backfilled.",
|
|
41
|
+
]
|
|
42
|
+
: ["No matching debug events available."];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const lines: string[] = [];
|
|
46
|
+
for (const event of events) {
|
|
47
|
+
lines.push(
|
|
48
|
+
`[${formatTimestamp(event.timestamp)}] ${event.level.toUpperCase()} ${event.source}/${event.category}: ${event.message}`,
|
|
49
|
+
);
|
|
50
|
+
if (event.operationId) lines.push(` operationId: ${event.operationId}`);
|
|
51
|
+
if (event.cwd) lines.push(` cwd: ${event.cwd}`);
|
|
52
|
+
pushFormattedData(lines, "data", event.data);
|
|
53
|
+
pushFormattedData(lines, "rawData", event.rawData);
|
|
54
|
+
}
|
|
55
|
+
if (rawDataUnavailable) {
|
|
56
|
+
lines.push("", "Raw debug data is not persisted for historical sessions.");
|
|
57
|
+
} else if (rawAccessDenied) {
|
|
58
|
+
lines.push("", "Raw debug data was requested but is not enabled in SuPi Debug settings.");
|
|
59
|
+
}
|
|
60
|
+
return lines;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function formatTruncationNote(truncation: TruncationResult): string {
|
|
64
|
+
const omittedLines = truncation.totalLines - truncation.outputLines;
|
|
65
|
+
const omittedBytes = truncation.totalBytes - truncation.outputBytes;
|
|
66
|
+
return `[Output truncated: showing ${truncation.outputLines} of ${truncation.totalLines} lines (${formatSize(truncation.outputBytes)} of ${formatSize(truncation.totalBytes)}). ${omittedLines} lines (${formatSize(omittedBytes)}) omitted. Use filters or a smaller limit to narrow results.]`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function appendTruncationNote(content: string, truncation: TruncationResult): string {
|
|
70
|
+
const note = formatTruncationNote(truncation);
|
|
71
|
+
return content.length > 0 ? `${content}\n\n${note}` : note;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function reserveTruncationSpace(content: string): TruncationResult {
|
|
75
|
+
const initial = truncateHead(content, {
|
|
76
|
+
maxLines: DEFAULT_MAX_LINES,
|
|
77
|
+
maxBytes: DEFAULT_MAX_BYTES,
|
|
78
|
+
});
|
|
79
|
+
if (!initial.truncated) return initial;
|
|
80
|
+
|
|
81
|
+
return truncateHead(content, {
|
|
82
|
+
maxLines: DEFAULT_MAX_LINES - TRUNCATION_RESERVE_LINES,
|
|
83
|
+
maxBytes: DEFAULT_MAX_BYTES - TRUNCATION_RESERVE_BYTES,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Limit model-visible debug output to PI's standard tool-output bounds. */
|
|
88
|
+
export function truncateDebugOutput(content: string): {
|
|
89
|
+
text: string;
|
|
90
|
+
truncation?: TruncationResult;
|
|
91
|
+
} {
|
|
92
|
+
const truncation = reserveTruncationSpace(content);
|
|
93
|
+
const text = truncation.truncated
|
|
94
|
+
? appendTruncationNote(truncation.content, truncation)
|
|
95
|
+
: truncation.content;
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
text,
|
|
99
|
+
truncation: truncation.truncated ? truncation : undefined,
|
|
100
|
+
};
|
|
101
|
+
}
|
package/src/query.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type DebugEventQuery, isDebugLevel, isDebugOperationId } from "@mrclrchtr/supi-core/debug";
|
|
2
|
+
|
|
3
|
+
/** Command and Tool query with optional persisted-session selection. */
|
|
4
|
+
export type DebugToolParams = DebugEventQuery & { sessionFile?: string };
|
|
5
|
+
|
|
6
|
+
/** Parse exact key-value filters for the user-facing Debug command. */
|
|
7
|
+
export function parseDebugCommandArgs(
|
|
8
|
+
args: string,
|
|
9
|
+
normalizeLimit: (value: string) => number,
|
|
10
|
+
): DebugToolParams {
|
|
11
|
+
const query: DebugToolParams = {};
|
|
12
|
+
for (const part of args.trim().split(/\s+/).filter(Boolean)) {
|
|
13
|
+
const [key, value] = part.split("=", 2);
|
|
14
|
+
if (!value) continue;
|
|
15
|
+
applyDebugFilter(query, key, value, normalizeLimit);
|
|
16
|
+
}
|
|
17
|
+
return query;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function applyDebugFilter(
|
|
21
|
+
query: DebugToolParams,
|
|
22
|
+
key: string,
|
|
23
|
+
value: string,
|
|
24
|
+
normalizeLimit: (value: string) => number,
|
|
25
|
+
): void {
|
|
26
|
+
switch (key) {
|
|
27
|
+
case "operationId":
|
|
28
|
+
if (isDebugOperationId(value)) query.operationId = value;
|
|
29
|
+
return;
|
|
30
|
+
case "source":
|
|
31
|
+
query.source = value;
|
|
32
|
+
return;
|
|
33
|
+
case "category":
|
|
34
|
+
query.category = value;
|
|
35
|
+
return;
|
|
36
|
+
case "level":
|
|
37
|
+
if (isDebugLevel(value)) query.level = value;
|
|
38
|
+
return;
|
|
39
|
+
case "limit":
|
|
40
|
+
query.limit = normalizeLimit(value);
|
|
41
|
+
return;
|
|
42
|
+
case "sessionFile":
|
|
43
|
+
query.sessionFile = value;
|
|
44
|
+
}
|
|
45
|
+
}
|