@mrclrchtr/supi-debug 4.10.0 → 6.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 +15 -7
- package/node_modules/@mrclrchtr/supi-core/README.md +2 -0
- package/node_modules/@mrclrchtr/supi-core/package.json +2 -2
- package/node_modules/@mrclrchtr/supi-core/src/api.ts +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 -6
- package/node_modules/@mrclrchtr/supi-core/src/debug.ts +9 -0
- package/node_modules/@mrclrchtr/supi-core/src/index.ts +1 -1
- package/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +3 -0
- package/package.json +3 -2
- package/prompts/supi-tooling-retro.md +77 -0
- package/src/command.ts +132 -0
- package/src/config.ts +83 -0
- package/src/debug.ts +19 -309
- package/src/format-events.ts +101 -0
- package/src/query.ts +45 -0
- package/src/render-details.ts +336 -0
- package/src/renderer.ts +134 -59
- package/src/session-events.ts +88 -14
- package/src/tool/debug/execute.ts +20 -0
- package/src/tool/debug/guidance.ts +11 -0
- package/src/tool/debug/register.ts +16 -0
- package/src/tool/debug/render.ts +105 -0
- package/src/tool/debug/result.ts +113 -0
- package/src/tool/debug/spec.ts +35 -0
- package/src/tool/guidance.ts +0 -11
package/src/debug.ts
CHANGED
|
@@ -1,110 +1,27 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
DEFAULT_MAX_LINES,
|
|
5
|
-
type ExtensionAPI,
|
|
6
|
-
formatSize,
|
|
7
|
-
type TruncationResult,
|
|
8
|
-
truncateHead,
|
|
9
|
-
} from "@earendil-works/pi-coding-agent";
|
|
10
|
-
import { loadSupiConfig } from "@mrclrchtr/supi-core/config";
|
|
1
|
+
import { dirname, join } from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
11
4
|
import { registerContextProvider } from "@mrclrchtr/supi-core/context";
|
|
12
5
|
import {
|
|
13
6
|
clearDebugEvents,
|
|
14
|
-
configureDebugRegistry,
|
|
15
|
-
DEBUG_REGISTRY_DEFAULTS,
|
|
16
|
-
type DebugAgentAccess,
|
|
17
|
-
type DebugEventQuery,
|
|
18
|
-
type DebugEventView,
|
|
19
|
-
getDebugEvents,
|
|
20
7
|
getDebugSummary,
|
|
21
|
-
isDebugLevel,
|
|
22
8
|
subscribeDebugEvents,
|
|
23
9
|
} from "@mrclrchtr/supi-core/debug";
|
|
24
10
|
import { defineConfigSettings, registerSettings } from "@mrclrchtr/supi-core/settings";
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
11
|
+
import { registerDebugCommand } from "./command.ts";
|
|
12
|
+
import {
|
|
13
|
+
applyDebugConfig,
|
|
14
|
+
DEBUG_DEFAULTS,
|
|
15
|
+
DEBUG_SECTION,
|
|
16
|
+
normalizeMaxEvents,
|
|
17
|
+
syncLiveDebugRegistry,
|
|
18
|
+
} from "./config.ts";
|
|
27
19
|
import { registerDebugMessageRenderer } from "./renderer.ts";
|
|
28
|
-
import { DEBUG_EVENT_ENTRY_TYPE
|
|
20
|
+
import { DEBUG_EVENT_ENTRY_TYPE } from "./session-events.ts";
|
|
29
21
|
import { maybeLogLoadStatus } from "./status-log.ts";
|
|
30
|
-
import {
|
|
31
|
-
|
|
32
|
-
const DEBUG_SECTION = "debug";
|
|
33
|
-
const DEBUG_REPORT_TYPE = "supi-debug-report";
|
|
34
|
-
|
|
35
|
-
interface DebugConfig extends Record<string, unknown> {
|
|
36
|
-
enabled: boolean;
|
|
37
|
-
agentAccess: DebugAgentAccess;
|
|
38
|
-
maxEvents: number;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const DEBUG_DEFAULTS: DebugConfig = { ...DEBUG_REGISTRY_DEFAULTS };
|
|
22
|
+
import { registerDebugTool } from "./tool/debug/register.ts";
|
|
42
23
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
function normalizeAgentAccess(value: string): DebugAgentAccess {
|
|
46
|
-
return value === "off" || value === "raw" ? value : "sanitized";
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function normalizeMaxEvents(value: string | number): number {
|
|
50
|
-
const parsed = typeof value === "number" ? value : Number.parseInt(value, 10);
|
|
51
|
-
return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : DEBUG_DEFAULTS.maxEvents;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function normalizeEnabled(value: unknown): boolean {
|
|
55
|
-
if (typeof value === "boolean") {
|
|
56
|
-
return value;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (typeof value === "string") {
|
|
60
|
-
const normalized = value.trim().toLowerCase();
|
|
61
|
-
if (
|
|
62
|
-
normalized === "true" ||
|
|
63
|
-
normalized === "on" ||
|
|
64
|
-
normalized === "1" ||
|
|
65
|
-
normalized === "yes"
|
|
66
|
-
) {
|
|
67
|
-
return true;
|
|
68
|
-
}
|
|
69
|
-
if (
|
|
70
|
-
normalized === "false" ||
|
|
71
|
-
normalized === "off" ||
|
|
72
|
-
normalized === "0" ||
|
|
73
|
-
normalized === "no" ||
|
|
74
|
-
normalized === ""
|
|
75
|
-
) {
|
|
76
|
-
return false;
|
|
77
|
-
}
|
|
78
|
-
return DEBUG_DEFAULTS.enabled;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (value === 1) return true;
|
|
82
|
-
if (value === 0) return false;
|
|
83
|
-
return DEBUG_DEFAULTS.enabled;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function loadDebugConfig(cwd: string): DebugConfig {
|
|
87
|
-
const config = loadSupiConfig(DEBUG_SECTION, cwd, DEBUG_DEFAULTS);
|
|
88
|
-
return {
|
|
89
|
-
enabled: normalizeEnabled(config.enabled),
|
|
90
|
-
agentAccess: normalizeAgentAccess(String(config.agentAccess)),
|
|
91
|
-
maxEvents: normalizeMaxEvents(config.maxEvents),
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function applyDebugConfig(cwd: string): DebugConfig {
|
|
96
|
-
const config = loadDebugConfig(cwd);
|
|
97
|
-
configureDebugRegistry(config);
|
|
98
|
-
return config;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function syncLiveDebugRegistry(cwd: string): DebugConfig {
|
|
102
|
-
const config = applyDebugConfig(cwd);
|
|
103
|
-
if (!config.enabled) {
|
|
104
|
-
clearDebugEvents();
|
|
105
|
-
}
|
|
106
|
-
return config;
|
|
107
|
-
}
|
|
24
|
+
const baseDir = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
108
25
|
|
|
109
26
|
function registerDebugSettings(pi: ExtensionAPI): void {
|
|
110
27
|
registerSettings(
|
|
@@ -143,88 +60,6 @@ function registerDebugSettings(pi: ExtensionAPI): void {
|
|
|
143
60
|
);
|
|
144
61
|
}
|
|
145
62
|
|
|
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
63
|
function buildSummaryData(): Record<string, string | number> | null {
|
|
229
64
|
const summary = getDebugSummary();
|
|
230
65
|
if (!summary) return null;
|
|
@@ -239,60 +74,6 @@ function buildSummaryData(): Record<string, string | number> | null {
|
|
|
239
74
|
return data;
|
|
240
75
|
}
|
|
241
76
|
|
|
242
|
-
async function buildToolResult(params: DebugToolParams, config: DebugConfig) {
|
|
243
|
-
if (!config.enabled && !params.sessionFile) {
|
|
244
|
-
throw new Error(
|
|
245
|
-
"SuPi debug event capture is disabled. Enable Debug in /supi-settings to retain events.",
|
|
246
|
-
);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
if (config.agentAccess === "off") {
|
|
250
|
-
throw new Error("Agent access to SuPi debug events is disabled.");
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
const filters = {
|
|
254
|
-
source: params.source,
|
|
255
|
-
level: params.level,
|
|
256
|
-
category: params.category,
|
|
257
|
-
limit: params.limit,
|
|
258
|
-
};
|
|
259
|
-
const query: DebugEventQuery = {
|
|
260
|
-
...filters,
|
|
261
|
-
includeRaw: params.includeRaw,
|
|
262
|
-
allowRaw: config.agentAccess === "raw",
|
|
263
|
-
};
|
|
264
|
-
let events: DebugEventView[];
|
|
265
|
-
let rawAccessDenied: boolean;
|
|
266
|
-
let rawDataUnavailable = false;
|
|
267
|
-
let persistedEventCount: number | undefined;
|
|
268
|
-
if (params.sessionFile) {
|
|
269
|
-
const persisted = await readSessionDebugEvents(params.sessionFile, filters);
|
|
270
|
-
events = persisted.events;
|
|
271
|
-
persistedEventCount = persisted.persistedEventCount;
|
|
272
|
-
rawAccessDenied = Boolean(params.includeRaw);
|
|
273
|
-
rawDataUnavailable = rawAccessDenied;
|
|
274
|
-
} else {
|
|
275
|
-
const result = getDebugEvents(query);
|
|
276
|
-
events = result.events;
|
|
277
|
-
rawAccessDenied = result.rawAccessDenied;
|
|
278
|
-
}
|
|
279
|
-
const output = truncateDebugOutput(
|
|
280
|
-
formatEvents(events, rawAccessDenied, rawDataUnavailable, persistedEventCount).join("\n"),
|
|
281
|
-
);
|
|
282
|
-
return {
|
|
283
|
-
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
|
-
},
|
|
293
|
-
};
|
|
294
|
-
}
|
|
295
|
-
|
|
296
77
|
/** Register the shared SuPi debug command, settings, context summary, and agent tool. */
|
|
297
78
|
export default function debugExtension(pi: ExtensionAPI) {
|
|
298
79
|
applyDebugConfig(process.cwd());
|
|
@@ -315,86 +96,15 @@ export default function debugExtension(pi: ExtensionAPI) {
|
|
|
315
96
|
|
|
316
97
|
pi.on("resources_discover", async (_event, ctx) => {
|
|
317
98
|
maybeLogLoadStatus(pi, ctx.cwd, "resources_discover");
|
|
99
|
+
// Self-register the package prompt template so standalone installs and
|
|
100
|
+
// workspace-root loads expose the same `/supi-tooling-retro` surface.
|
|
101
|
+
return { promptPaths: [join(baseDir, "prompts")] };
|
|
318
102
|
});
|
|
319
103
|
|
|
320
104
|
pi.on("session_shutdown", () => {
|
|
321
105
|
unsubscribeDebugEvents();
|
|
322
106
|
});
|
|
323
107
|
|
|
324
|
-
pi
|
|
325
|
-
|
|
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
|
-
});
|
|
371
|
-
|
|
372
|
-
pi.registerTool({
|
|
373
|
-
name: "supi_debug",
|
|
374
|
-
label: "SuPi Debug",
|
|
375
|
-
description: toolDescription,
|
|
376
|
-
promptSnippet,
|
|
377
|
-
promptGuidelines,
|
|
378
|
-
parameters: Type.Object({
|
|
379
|
-
source: Type.Optional(Type.String({ description: "Filter by extension source, e.g. lsp" })),
|
|
380
|
-
level: Type.Optional(
|
|
381
|
-
StringEnum(["debug", "info", "warning", "error"], {
|
|
382
|
-
description: "Filter by debug level",
|
|
383
|
-
}),
|
|
384
|
-
),
|
|
385
|
-
category: Type.Optional(Type.String({ description: "Filter by event category" })),
|
|
386
|
-
limit: Type.Optional(Type.Number({ description: "Maximum number of events to return" })),
|
|
387
|
-
sessionFile: Type.Optional(
|
|
388
|
-
Type.String({ description: "PI session JSONL file containing persisted debug events" }),
|
|
389
|
-
),
|
|
390
|
-
includeRaw: Type.Optional(
|
|
391
|
-
Type.Boolean({ description: "Request raw event data when settings permit it" }),
|
|
392
|
-
),
|
|
393
|
-
}),
|
|
394
|
-
// biome-ignore lint/complexity/useMaxParams: pi ToolDefinition.execute signature
|
|
395
|
-
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
396
|
-
const config = applyDebugConfig(ctx.cwd);
|
|
397
|
-
return buildToolResult(params as DebugToolParams, config);
|
|
398
|
-
},
|
|
399
|
-
});
|
|
108
|
+
registerDebugCommand(pi, applyDebugConfig, normalizeMaxEvents);
|
|
109
|
+
registerDebugTool(pi);
|
|
400
110
|
}
|
|
@@ -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
|
+
}
|