@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.
@@ -4,6 +4,7 @@ import {
4
4
  type DebugEventQuery,
5
5
  type DebugEventView,
6
6
  isDebugLevel,
7
+ isDebugOperationId,
7
8
  matchesDebugEventQuery,
8
9
  redactDebugData,
9
10
  } from "@mrclrchtr/supi-core/debug";
@@ -11,7 +12,10 @@ import {
11
12
  /** Custom session-entry type used for sanitized debug-event persistence. */
12
13
  export const DEBUG_EVENT_ENTRY_TYPE = "supi-debug-event";
13
14
 
14
- type PersistedDebugEventQuery = Pick<DebugEventQuery, "source" | "level" | "category" | "limit">;
15
+ type PersistedDebugEventQuery = Pick<
16
+ DebugEventQuery,
17
+ "operationId" | "source" | "level" | "category" | "limit"
18
+ >;
15
19
 
16
20
  /** Sanitized events and total persisted entries found in one PI session file. */
17
21
  export interface SessionDebugEvents {
@@ -19,6 +23,19 @@ export interface SessionDebugEvents {
19
23
  persistedEventCount: number;
20
24
  }
21
25
 
26
+ /** Small progress facts emitted while a persisted session file is scanned. */
27
+ export interface SessionDebugReadProgress {
28
+ scannedLines: number;
29
+ persistedEventCount: number;
30
+ matchedEvents: number;
31
+ }
32
+
33
+ /** Optional cancellation and progress controls for persisted-session reads. */
34
+ export interface SessionDebugReadOptions {
35
+ signal?: AbortSignal;
36
+ onProgress?: (progress: SessionDebugReadProgress) => void;
37
+ }
38
+
22
39
  function parsePersistedEvent(data: unknown): DebugEventView | undefined {
23
40
  if (typeof data !== "object" || data === null) return undefined;
24
41
  const event = data as Record<string, unknown>;
@@ -31,6 +48,7 @@ function parsePersistedEvent(data: unknown): DebugEventView | undefined {
31
48
  !isDebugLevel(event.level) ||
32
49
  typeof event.category !== "string" ||
33
50
  typeof event.message !== "string" ||
51
+ (event.operationId !== undefined && !isDebugOperationId(event.operationId)) ||
34
52
  (event.cwd !== undefined && typeof event.cwd !== "string")
35
53
  ) {
36
54
  return undefined;
@@ -39,6 +57,7 @@ function parsePersistedEvent(data: unknown): DebugEventView | undefined {
39
57
  return {
40
58
  id: event.id,
41
59
  timestamp: event.timestamp,
60
+ operationId: event.operationId,
42
61
  source: event.source,
43
62
  level: event.level,
44
63
  category: event.category,
@@ -57,32 +76,87 @@ function parseDebugEntry(line: string): unknown {
57
76
  }
58
77
  }
59
78
 
79
+ function throwIfAborted(signal: AbortSignal | undefined): void {
80
+ if (!signal?.aborted) return;
81
+ const error = new Error("Persisted debug-event scan was canceled.");
82
+ error.name = "AbortError";
83
+ throw error;
84
+ }
85
+
86
+ function reportProgress(
87
+ onProgress: SessionDebugReadOptions["onProgress"],
88
+ progress: SessionDebugReadProgress,
89
+ ): void {
90
+ onProgress?.(progress);
91
+ }
92
+
60
93
  /** Read sanitized debug events persisted by SuPi Debug from a PI session file. */
61
94
  export async function readSessionDebugEvents(
62
95
  sessionFile: string,
63
96
  query: PersistedDebugEventQuery = {},
97
+ options: SessionDebugReadOptions = {},
64
98
  ): Promise<SessionDebugEvents> {
65
99
  const events: DebugEventView[] = [];
66
100
  let persistedEventCount = 0;
101
+ let scannedLines = 0;
102
+ throwIfAborted(options.signal);
103
+
104
+ const input = createReadStream(sessionFile, { encoding: "utf8" });
105
+ const abortHandler = () => input.destroy();
106
+ options.signal?.addEventListener("abort", abortHandler, { once: true });
67
107
  const lines = createInterface({
68
- input: createReadStream(sessionFile, { encoding: "utf8" }),
108
+ input,
69
109
  crlfDelay: Number.POSITIVE_INFINITY,
70
110
  });
71
111
 
72
- for await (const line of lines) {
73
- const entry = parseDebugEntry(line);
74
- if (
75
- typeof entry !== "object" ||
76
- entry === null ||
77
- (entry as Record<string, unknown>).type !== "custom" ||
78
- (entry as Record<string, unknown>).customType !== DEBUG_EVENT_ENTRY_TYPE
79
- ) {
80
- continue;
112
+ try {
113
+ throwIfAborted(options.signal);
114
+ reportProgress(options.onProgress, { scannedLines, persistedEventCount, matchedEvents: 0 });
115
+
116
+ for await (const line of lines) {
117
+ scannedLines++;
118
+ throwIfAborted(options.signal);
119
+ const entry = parseDebugEntry(line);
120
+ if (
121
+ typeof entry !== "object" ||
122
+ entry === null ||
123
+ (entry as Record<string, unknown>).type !== "custom" ||
124
+ (entry as Record<string, unknown>).customType !== DEBUG_EVENT_ENTRY_TYPE
125
+ ) {
126
+ if (scannedLines % 250 === 0) {
127
+ reportProgress(options.onProgress, {
128
+ scannedLines,
129
+ persistedEventCount,
130
+ matchedEvents: events.length,
131
+ });
132
+ }
133
+ continue;
134
+ }
135
+
136
+ persistedEventCount++;
137
+ const event = parsePersistedEvent((entry as Record<string, unknown>).data);
138
+ if (event && matchesDebugEventQuery(event, query)) events.push(event);
139
+
140
+ if (scannedLines % 250 === 0) {
141
+ reportProgress(options.onProgress, {
142
+ scannedLines,
143
+ persistedEventCount,
144
+ matchedEvents: events.length,
145
+ });
146
+ }
81
147
  }
82
148
 
83
- persistedEventCount++;
84
- const event = parsePersistedEvent((entry as Record<string, unknown>).data);
85
- if (event && matchesDebugEventQuery(event, query)) events.push(event);
149
+ throwIfAborted(options.signal);
150
+ reportProgress(options.onProgress, {
151
+ scannedLines,
152
+ persistedEventCount,
153
+ matchedEvents: events.length,
154
+ });
155
+ throwIfAborted(options.signal);
156
+ } finally {
157
+ options.signal?.removeEventListener("abort", abortHandler);
158
+ lines.close();
159
+ input.destroy();
86
160
  }
87
161
 
88
162
  const limit = query.limit && query.limit > 0 ? Math.floor(query.limit) : Number.POSITIVE_INFINITY;
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize } from "@earendil-works/pi-coding-agent";
4
4
 
5
- export const toolDescription = `Fetch recent SuPi debug events, or sanitized persisted events from a PI session JSONL via sessionFile, with optional filters. Raw data is available only for the live session when allowed. Output is truncated to ${DEFAULT_MAX_LINES} lines or ${formatSize(DEFAULT_MAX_BYTES)} (whichever is hit first).`;
5
+ export const toolDescription = `Fetch recent SuPi debug events, or sanitized persisted events from a PI session JSONL via sessionFile, with optional exact Debug Operation ID and event filters. Raw data is available only for the live session when allowed. Output is truncated to ${DEFAULT_MAX_LINES} lines or ${formatSize(DEFAULT_MAX_BYTES)} (whichever is hit first).`;
6
6
 
7
7
  export const promptSnippet = "supi_debug — fetch live or persisted SuPi debug events";
8
8