gsd-pi 2.28.0-dev.b23c118 → 2.28.0-dev.e19bf89
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/dist/resources/extensions/gsd/auto-post-unit.ts +3 -8
- package/dist/resources/extensions/gsd/auto-start.ts +9 -24
- package/dist/resources/extensions/gsd/auto.ts +2 -45
- package/dist/resources/extensions/gsd/commands.ts +0 -19
- package/dist/resources/extensions/gsd/doctor-types.ts +0 -13
- package/dist/resources/extensions/gsd/doctor.ts +6 -2
- package/dist/resources/extensions/gsd/export.ts +2 -28
- package/dist/resources/extensions/gsd/gsd-db.ts +0 -19
- package/package.json +3 -3
- package/src/resources/extensions/gsd/auto-post-unit.ts +3 -8
- package/src/resources/extensions/gsd/auto-start.ts +9 -24
- package/src/resources/extensions/gsd/auto.ts +2 -45
- package/src/resources/extensions/gsd/commands.ts +0 -19
- package/src/resources/extensions/gsd/doctor-types.ts +0 -13
- package/src/resources/extensions/gsd/doctor.ts +6 -2
- package/src/resources/extensions/gsd/export.ts +2 -28
- package/src/resources/extensions/gsd/gsd-db.ts +0 -19
- package/dist/resources/extensions/gsd/commands-logs.ts +0 -537
- package/dist/resources/extensions/gsd/session-lock.ts +0 -284
- package/dist/resources/extensions/gsd/tests/commands-logs.test.ts +0 -241
- package/dist/resources/extensions/gsd/tests/session-lock.test.ts +0 -315
- package/src/resources/extensions/gsd/commands-logs.ts +0 -537
- package/src/resources/extensions/gsd/session-lock.ts +0 -284
- package/src/resources/extensions/gsd/tests/commands-logs.test.ts +0 -241
- package/src/resources/extensions/gsd/tests/session-lock.test.ts +0 -315
|
@@ -1,537 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* /gsd logs — Browse activity logs, debug logs, and metrics.
|
|
3
|
-
*
|
|
4
|
-
* Subcommands:
|
|
5
|
-
* /gsd logs — List recent activity + debug logs
|
|
6
|
-
* /gsd logs <N> — Show summary of activity log #N
|
|
7
|
-
* /gsd logs debug — List debug log files
|
|
8
|
-
* /gsd logs debug <N> — Show debug log summary #N
|
|
9
|
-
* /gsd logs tail [N] — Show last N activity log entries (default 5)
|
|
10
|
-
* /gsd logs clear — Remove old activity and debug logs
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import type { ExtensionCommandContext } from "@gsd/pi-coding-agent";
|
|
14
|
-
import { existsSync, readdirSync, readFileSync, statSync, unlinkSync } from "node:fs";
|
|
15
|
-
import { join } from "node:path";
|
|
16
|
-
import { gsdRoot } from "./paths.js";
|
|
17
|
-
|
|
18
|
-
// ─── Types ──────────────────────────────────────────────────────────────────
|
|
19
|
-
|
|
20
|
-
interface LogEntry {
|
|
21
|
-
seq: number;
|
|
22
|
-
filename: string;
|
|
23
|
-
unitType: string;
|
|
24
|
-
unitId: string;
|
|
25
|
-
size: number;
|
|
26
|
-
mtime: Date;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
interface DebugLogEntry {
|
|
30
|
-
filename: string;
|
|
31
|
-
size: number;
|
|
32
|
-
mtime: Date;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
36
|
-
|
|
37
|
-
function activityDir(basePath: string): string {
|
|
38
|
-
return join(gsdRoot(basePath), "activity");
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function debugDir(basePath: string): string {
|
|
42
|
-
return join(gsdRoot(basePath), "debug");
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function listActivityLogs(basePath: string): LogEntry[] {
|
|
46
|
-
const dir = activityDir(basePath);
|
|
47
|
-
if (!existsSync(dir)) return [];
|
|
48
|
-
|
|
49
|
-
const entries: LogEntry[] = [];
|
|
50
|
-
try {
|
|
51
|
-
for (const f of readdirSync(dir)) {
|
|
52
|
-
if (!f.endsWith(".jsonl")) continue;
|
|
53
|
-
// Filename format: {seq}-{unitType}-{unitId}.jsonl
|
|
54
|
-
// unitType is lowercase-with-hyphens (e.g., "execute-task", "complete-slice")
|
|
55
|
-
// unitId starts with M followed by digits (e.g., "M001-S01-T01")
|
|
56
|
-
const match = f.match(/^(\d+)-([\w-]+?)-(M\d[\w-]*)\.jsonl$/);
|
|
57
|
-
if (!match) continue;
|
|
58
|
-
|
|
59
|
-
const filePath = join(dir, f);
|
|
60
|
-
let stat;
|
|
61
|
-
try { stat = statSync(filePath); } catch { continue; }
|
|
62
|
-
|
|
63
|
-
entries.push({
|
|
64
|
-
seq: parseInt(match[1], 10),
|
|
65
|
-
filename: f,
|
|
66
|
-
unitType: match[2],
|
|
67
|
-
unitId: match[3].replace(/-/g, "/"),
|
|
68
|
-
size: stat.size,
|
|
69
|
-
mtime: stat.mtime,
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
} catch { /* dir not readable */ }
|
|
73
|
-
|
|
74
|
-
return entries.sort((a, b) => a.seq - b.seq);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function listDebugLogs(basePath: string): DebugLogEntry[] {
|
|
78
|
-
const dir = debugDir(basePath);
|
|
79
|
-
if (!existsSync(dir)) return [];
|
|
80
|
-
|
|
81
|
-
const entries: DebugLogEntry[] = [];
|
|
82
|
-
try {
|
|
83
|
-
for (const f of readdirSync(dir)) {
|
|
84
|
-
if (!f.endsWith(".log")) continue;
|
|
85
|
-
const filePath = join(dir, f);
|
|
86
|
-
let stat;
|
|
87
|
-
try { stat = statSync(filePath); } catch { continue; }
|
|
88
|
-
entries.push({ filename: f, size: stat.size, mtime: stat.mtime });
|
|
89
|
-
}
|
|
90
|
-
} catch { /* dir not readable */ }
|
|
91
|
-
|
|
92
|
-
return entries.sort((a, b) => a.mtime.getTime() - b.mtime.getTime());
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function formatSize(bytes: number): string {
|
|
96
|
-
if (bytes < 1024) return `${bytes}B`;
|
|
97
|
-
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
|
|
98
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function formatAge(date: Date): string {
|
|
102
|
-
const ms = Date.now() - date.getTime();
|
|
103
|
-
const mins = Math.floor(ms / 60_000);
|
|
104
|
-
if (mins < 1) return "just now";
|
|
105
|
-
if (mins < 60) return `${mins}m ago`;
|
|
106
|
-
const hrs = Math.floor(mins / 60);
|
|
107
|
-
if (hrs < 24) return `${hrs}h ago`;
|
|
108
|
-
const days = Math.floor(hrs / 24);
|
|
109
|
-
return `${days}d ago`;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Extract a summary from an activity log JSONL file.
|
|
114
|
-
* Parses the entries to count tool calls, errors, and extract key events.
|
|
115
|
-
*/
|
|
116
|
-
function summarizeActivityLog(filePath: string): {
|
|
117
|
-
toolCalls: number;
|
|
118
|
-
errors: number;
|
|
119
|
-
filesWritten: string[];
|
|
120
|
-
commandsRun: Array<{ command: string; failed: boolean }>;
|
|
121
|
-
lastReasoning: string;
|
|
122
|
-
entryCount: number;
|
|
123
|
-
} {
|
|
124
|
-
const result = {
|
|
125
|
-
toolCalls: 0,
|
|
126
|
-
errors: 0,
|
|
127
|
-
filesWritten: new Set<string>(),
|
|
128
|
-
commandsRun: [] as Array<{ command: string; failed: boolean }>,
|
|
129
|
-
lastReasoning: "",
|
|
130
|
-
entryCount: 0,
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
let raw: string;
|
|
134
|
-
try { raw = readFileSync(filePath, "utf-8"); } catch { return { ...result, filesWritten: [] }; }
|
|
135
|
-
|
|
136
|
-
const lines = raw.split("\n").filter(l => l.trim());
|
|
137
|
-
result.entryCount = lines.length;
|
|
138
|
-
|
|
139
|
-
for (const line of lines) {
|
|
140
|
-
let entry: Record<string, unknown>;
|
|
141
|
-
try { entry = JSON.parse(line); } catch { continue; }
|
|
142
|
-
|
|
143
|
-
// Count tool calls
|
|
144
|
-
if (entry.type === "toolCall" || (entry.role === "assistant" && entry.content && Array.isArray(entry.content))) {
|
|
145
|
-
if (entry.type === "toolCall") {
|
|
146
|
-
result.toolCalls++;
|
|
147
|
-
const name = entry.name as string | undefined;
|
|
148
|
-
const args = entry.arguments as Record<string, unknown> | undefined;
|
|
149
|
-
|
|
150
|
-
if (name === "write" || name === "edit") {
|
|
151
|
-
const path = args?.file_path as string | undefined;
|
|
152
|
-
if (path) result.filesWritten.add(path);
|
|
153
|
-
}
|
|
154
|
-
if (name === "bash") {
|
|
155
|
-
const cmd = args?.command as string | undefined;
|
|
156
|
-
if (cmd) result.commandsRun.push({ command: cmd.slice(0, 80), failed: false });
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// Count errors
|
|
162
|
-
if (entry.role === "toolResult" && entry.isError) {
|
|
163
|
-
result.errors++;
|
|
164
|
-
// Mark last command as failed
|
|
165
|
-
if (result.commandsRun.length > 0) {
|
|
166
|
-
result.commandsRun[result.commandsRun.length - 1].failed = true;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Track assistant reasoning
|
|
171
|
-
if (entry.role === "assistant" && typeof entry.content === "string") {
|
|
172
|
-
result.lastReasoning = entry.content.slice(0, 200);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
return {
|
|
177
|
-
...result,
|
|
178
|
-
filesWritten: [...result.filesWritten],
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Extract summary events from a debug log file.
|
|
184
|
-
*/
|
|
185
|
-
function summarizeDebugLog(filePath: string): {
|
|
186
|
-
events: number;
|
|
187
|
-
duration: string;
|
|
188
|
-
dispatches: number;
|
|
189
|
-
errors: Array<{ event: string; message: string }>;
|
|
190
|
-
} {
|
|
191
|
-
const result = {
|
|
192
|
-
events: 0,
|
|
193
|
-
duration: "unknown",
|
|
194
|
-
dispatches: 0,
|
|
195
|
-
errors: [] as Array<{ event: string; message: string }>,
|
|
196
|
-
};
|
|
197
|
-
|
|
198
|
-
let raw: string;
|
|
199
|
-
try { raw = readFileSync(filePath, "utf-8"); } catch { return result; }
|
|
200
|
-
|
|
201
|
-
const lines = raw.split("\n").filter(l => l.trim());
|
|
202
|
-
result.events = lines.length;
|
|
203
|
-
|
|
204
|
-
let firstTs = 0;
|
|
205
|
-
let lastTs = 0;
|
|
206
|
-
|
|
207
|
-
for (const line of lines) {
|
|
208
|
-
let entry: Record<string, unknown>;
|
|
209
|
-
try { entry = JSON.parse(line); } catch { continue; }
|
|
210
|
-
|
|
211
|
-
const ts = entry.ts as string | undefined;
|
|
212
|
-
if (ts) {
|
|
213
|
-
const t = new Date(ts).getTime();
|
|
214
|
-
if (!firstTs) firstTs = t;
|
|
215
|
-
lastTs = t;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
const event = entry.event as string | undefined;
|
|
219
|
-
if (!event) continue;
|
|
220
|
-
|
|
221
|
-
if (event === "debug-summary") {
|
|
222
|
-
result.dispatches = (entry.dispatches as number) ?? 0;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
if (event.includes("error") || event.includes("failed")) {
|
|
226
|
-
const msg = (entry.error as string) ?? (entry.message as string) ?? JSON.stringify(entry).slice(0, 100);
|
|
227
|
-
result.errors.push({ event, message: msg });
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
if (firstTs && lastTs) {
|
|
232
|
-
const elapsed = lastTs - firstTs;
|
|
233
|
-
const mins = Math.floor(elapsed / 60_000);
|
|
234
|
-
if (mins < 1) result.duration = `${Math.floor(elapsed / 1000)}s`;
|
|
235
|
-
else if (mins < 60) result.duration = `${mins}m`;
|
|
236
|
-
else result.duration = `${Math.floor(mins / 60)}h ${mins % 60}m`;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
return result;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// ─── Main Handler ───────────────────────────────────────────────────────────
|
|
243
|
-
|
|
244
|
-
export async function handleLogs(args: string, ctx: ExtensionCommandContext): Promise<void> {
|
|
245
|
-
const basePath = process.cwd();
|
|
246
|
-
const parts = args.trim().split(/\s+/).filter(Boolean);
|
|
247
|
-
const subCmd = parts[0] ?? "";
|
|
248
|
-
|
|
249
|
-
// /gsd logs clear
|
|
250
|
-
if (subCmd === "clear") {
|
|
251
|
-
await handleLogsClear(basePath, ctx);
|
|
252
|
-
return;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// /gsd logs debug [N]
|
|
256
|
-
if (subCmd === "debug") {
|
|
257
|
-
const idx = parts[1] ? parseInt(parts[1], 10) : undefined;
|
|
258
|
-
await handleLogsDebug(basePath, ctx, idx);
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
// /gsd logs tail [N]
|
|
263
|
-
if (subCmd === "tail") {
|
|
264
|
-
const count = parts[1] ? parseInt(parts[1], 10) : 5;
|
|
265
|
-
await handleLogsTail(basePath, ctx, count);
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// /gsd logs <N> — show specific activity log
|
|
270
|
-
if (subCmd && /^\d+$/.test(subCmd)) {
|
|
271
|
-
const seq = parseInt(subCmd, 10);
|
|
272
|
-
await handleLogsShow(basePath, ctx, seq);
|
|
273
|
-
return;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// /gsd logs — list overview
|
|
277
|
-
await handleLogsList(basePath, ctx);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// ─── Subcommand Handlers ────────────────────────────────────────────────────
|
|
281
|
-
|
|
282
|
-
async function handleLogsList(basePath: string, ctx: ExtensionCommandContext): Promise<void> {
|
|
283
|
-
const activities = listActivityLogs(basePath);
|
|
284
|
-
const debugLogs = listDebugLogs(basePath);
|
|
285
|
-
|
|
286
|
-
if (activities.length === 0 && debugLogs.length === 0) {
|
|
287
|
-
ctx.ui.notify(
|
|
288
|
-
"No logs found.\n\nActivity logs are created during auto-mode.\nDebug logs require GSD_DEBUG=1.",
|
|
289
|
-
"info",
|
|
290
|
-
);
|
|
291
|
-
return;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
const lines: string[] = [];
|
|
295
|
-
|
|
296
|
-
if (activities.length > 0) {
|
|
297
|
-
lines.push("Activity Logs (.gsd/activity/):");
|
|
298
|
-
lines.push(" # Unit Type Unit ID Size Age");
|
|
299
|
-
lines.push(" " + "─".repeat(70));
|
|
300
|
-
|
|
301
|
-
// Show last 15 entries
|
|
302
|
-
const recent = activities.slice(-15);
|
|
303
|
-
for (const e of recent) {
|
|
304
|
-
const seq = String(e.seq).padStart(3, " ");
|
|
305
|
-
const type = e.unitType.padEnd(18, " ");
|
|
306
|
-
const id = e.unitId.padEnd(20, " ");
|
|
307
|
-
const size = formatSize(e.size).padStart(7, " ");
|
|
308
|
-
const age = formatAge(e.mtime);
|
|
309
|
-
lines.push(` ${seq} ${type} ${id} ${size} ${age}`);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
if (activities.length > 15) {
|
|
313
|
-
lines.push(` ... and ${activities.length - 15} older entries`);
|
|
314
|
-
}
|
|
315
|
-
lines.push("");
|
|
316
|
-
lines.push(" View details: /gsd logs <#>");
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
if (debugLogs.length > 0) {
|
|
320
|
-
lines.push("");
|
|
321
|
-
lines.push("Debug Logs (.gsd/debug/):");
|
|
322
|
-
for (let i = 0; i < debugLogs.length; i++) {
|
|
323
|
-
const d = debugLogs[i];
|
|
324
|
-
const size = formatSize(d.size).padStart(7, " ");
|
|
325
|
-
const age = formatAge(d.mtime);
|
|
326
|
-
lines.push(` ${i + 1}. ${d.filename} ${size} ${age}`);
|
|
327
|
-
}
|
|
328
|
-
lines.push("");
|
|
329
|
-
lines.push(" View details: /gsd logs debug <#>");
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
// Metrics summary
|
|
333
|
-
const metricsPath = join(gsdRoot(basePath), "metrics.json");
|
|
334
|
-
if (existsSync(metricsPath)) {
|
|
335
|
-
try {
|
|
336
|
-
const metrics = JSON.parse(readFileSync(metricsPath, "utf-8"));
|
|
337
|
-
const units = metrics?.units;
|
|
338
|
-
if (Array.isArray(units) && units.length > 0) {
|
|
339
|
-
const totalCost = units.reduce((sum: number, u: Record<string, unknown>) => sum + ((u.cost as number) ?? 0), 0);
|
|
340
|
-
const totalTokens = units.reduce((sum: number, u: Record<string, unknown>) => {
|
|
341
|
-
const t = u.tokens as Record<string, number> | undefined;
|
|
342
|
-
return sum + (t?.total ?? 0);
|
|
343
|
-
}, 0);
|
|
344
|
-
lines.push("");
|
|
345
|
-
lines.push(`Metrics: ${units.length} units tracked · $${totalCost.toFixed(2)} · ${(totalTokens / 1000).toFixed(0)}K tokens`);
|
|
346
|
-
}
|
|
347
|
-
} catch { /* ignore */ }
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
lines.push("");
|
|
351
|
-
lines.push("Tip: Enable debug logging with GSD_DEBUG=1 before /gsd auto");
|
|
352
|
-
|
|
353
|
-
ctx.ui.notify(lines.join("\n"), "info");
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
async function handleLogsShow(basePath: string, ctx: ExtensionCommandContext, seq: number): Promise<void> {
|
|
357
|
-
const activities = listActivityLogs(basePath);
|
|
358
|
-
const entry = activities.find(e => e.seq === seq);
|
|
359
|
-
|
|
360
|
-
if (!entry) {
|
|
361
|
-
ctx.ui.notify(`Activity log #${seq} not found. Run /gsd logs to see available logs.`, "warning");
|
|
362
|
-
return;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
const filePath = join(activityDir(basePath), entry.filename);
|
|
366
|
-
const summary = summarizeActivityLog(filePath);
|
|
367
|
-
|
|
368
|
-
const lines: string[] = [];
|
|
369
|
-
lines.push(`Activity Log #${entry.seq}: ${entry.unitType} — ${entry.unitId}`);
|
|
370
|
-
lines.push("─".repeat(60));
|
|
371
|
-
lines.push(`File: ${entry.filename}`);
|
|
372
|
-
lines.push(`Size: ${formatSize(entry.size)} | Age: ${formatAge(entry.mtime)}`);
|
|
373
|
-
lines.push(`Entries: ${summary.entryCount} | Tool calls: ${summary.toolCalls} | Errors: ${summary.errors}`);
|
|
374
|
-
|
|
375
|
-
if (summary.filesWritten.length > 0) {
|
|
376
|
-
lines.push("");
|
|
377
|
-
lines.push("Files written/edited:");
|
|
378
|
-
for (const f of summary.filesWritten.slice(0, 10)) {
|
|
379
|
-
lines.push(` ${f}`);
|
|
380
|
-
}
|
|
381
|
-
if (summary.filesWritten.length > 10) {
|
|
382
|
-
lines.push(` ... and ${summary.filesWritten.length - 10} more`);
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
if (summary.commandsRun.length > 0) {
|
|
387
|
-
lines.push("");
|
|
388
|
-
lines.push("Commands run:");
|
|
389
|
-
for (const c of summary.commandsRun.slice(0, 10)) {
|
|
390
|
-
const status = c.failed ? " FAILED" : "";
|
|
391
|
-
lines.push(` ${c.command}${status}`);
|
|
392
|
-
}
|
|
393
|
-
if (summary.commandsRun.length > 10) {
|
|
394
|
-
lines.push(` ... and ${summary.commandsRun.length - 10} more`);
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
if (summary.errors > 0) {
|
|
399
|
-
lines.push("");
|
|
400
|
-
lines.push(`${summary.errors} error(s) encountered during this unit.`);
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
if (summary.lastReasoning) {
|
|
404
|
-
lines.push("");
|
|
405
|
-
lines.push("Last reasoning:");
|
|
406
|
-
lines.push(` "${summary.lastReasoning}${summary.lastReasoning.length >= 200 ? "..." : ""}"`);
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
lines.push("");
|
|
410
|
-
lines.push(`Full log: ${filePath}`);
|
|
411
|
-
|
|
412
|
-
ctx.ui.notify(lines.join("\n"), "info");
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
async function handleLogsDebug(basePath: string, ctx: ExtensionCommandContext, idx?: number): Promise<void> {
|
|
416
|
-
const debugLogs = listDebugLogs(basePath);
|
|
417
|
-
|
|
418
|
-
if (debugLogs.length === 0) {
|
|
419
|
-
ctx.ui.notify(
|
|
420
|
-
"No debug logs found.\n\nEnable debug logging: GSD_DEBUG=1 gsd auto",
|
|
421
|
-
"info",
|
|
422
|
-
);
|
|
423
|
-
return;
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
if (idx === undefined) {
|
|
427
|
-
// List debug logs
|
|
428
|
-
const lines: string[] = ["Debug Logs (.gsd/debug/):", ""];
|
|
429
|
-
for (let i = 0; i < debugLogs.length; i++) {
|
|
430
|
-
const d = debugLogs[i];
|
|
431
|
-
lines.push(` ${i + 1}. ${d.filename} ${formatSize(d.size)} ${formatAge(d.mtime)}`);
|
|
432
|
-
}
|
|
433
|
-
lines.push("");
|
|
434
|
-
lines.push("View details: /gsd logs debug <#>");
|
|
435
|
-
ctx.ui.notify(lines.join("\n"), "info");
|
|
436
|
-
return;
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
// Show specific debug log
|
|
440
|
-
if (idx < 1 || idx > debugLogs.length) {
|
|
441
|
-
ctx.ui.notify(`Debug log #${idx} not found. Available: 1-${debugLogs.length}`, "warning");
|
|
442
|
-
return;
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
const entry = debugLogs[idx - 1];
|
|
446
|
-
const filePath = join(debugDir(basePath), entry.filename);
|
|
447
|
-
const summary = summarizeDebugLog(filePath);
|
|
448
|
-
|
|
449
|
-
const lines: string[] = [];
|
|
450
|
-
lines.push(`Debug Log: ${entry.filename}`);
|
|
451
|
-
lines.push("─".repeat(60));
|
|
452
|
-
lines.push(`Size: ${formatSize(entry.size)} | Age: ${formatAge(entry.mtime)}`);
|
|
453
|
-
lines.push(`Events: ${summary.events} | Duration: ${summary.duration} | Dispatches: ${summary.dispatches}`);
|
|
454
|
-
|
|
455
|
-
if (summary.errors.length > 0) {
|
|
456
|
-
lines.push("");
|
|
457
|
-
lines.push("Errors/failures:");
|
|
458
|
-
for (const e of summary.errors.slice(0, 10)) {
|
|
459
|
-
lines.push(` [${e.event}] ${e.message}`);
|
|
460
|
-
}
|
|
461
|
-
if (summary.errors.length > 10) {
|
|
462
|
-
lines.push(` ... and ${summary.errors.length - 10} more`);
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
lines.push("");
|
|
467
|
-
lines.push(`Full log: ${filePath}`);
|
|
468
|
-
|
|
469
|
-
ctx.ui.notify(lines.join("\n"), "info");
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
async function handleLogsTail(basePath: string, ctx: ExtensionCommandContext, count: number): Promise<void> {
|
|
473
|
-
const activities = listActivityLogs(basePath);
|
|
474
|
-
|
|
475
|
-
if (activities.length === 0) {
|
|
476
|
-
ctx.ui.notify("No activity logs found. Logs are created during auto-mode.", "info");
|
|
477
|
-
return;
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
const recent = activities.slice(-Math.max(1, Math.min(count, 20)));
|
|
481
|
-
const lines: string[] = [`Last ${recent.length} activity log(s):`, ""];
|
|
482
|
-
|
|
483
|
-
for (const e of recent) {
|
|
484
|
-
const filePath = join(activityDir(basePath), e.filename);
|
|
485
|
-
const summary = summarizeActivityLog(filePath);
|
|
486
|
-
const status = summary.errors > 0 ? `${summary.errors} err` : "ok";
|
|
487
|
-
lines.push(` #${e.seq} ${e.unitType} ${e.unitId} — ${summary.toolCalls} tools, ${status}, ${formatAge(e.mtime)}`);
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
ctx.ui.notify(lines.join("\n"), "info");
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
async function handleLogsClear(basePath: string, ctx: ExtensionCommandContext): Promise<void> {
|
|
494
|
-
let removedActivity = 0;
|
|
495
|
-
let removedDebug = 0;
|
|
496
|
-
|
|
497
|
-
// Clear activity logs older than 7 days, keep the 5 most recent
|
|
498
|
-
const activities = listActivityLogs(basePath);
|
|
499
|
-
const keepRecent = activities.slice(-5);
|
|
500
|
-
const keepSeqs = new Set(keepRecent.map(e => e.seq));
|
|
501
|
-
const cutoff = Date.now() - 7 * 24 * 60 * 60 * 1000;
|
|
502
|
-
|
|
503
|
-
for (const e of activities) {
|
|
504
|
-
if (keepSeqs.has(e.seq)) continue;
|
|
505
|
-
if (e.mtime.getTime() < cutoff) {
|
|
506
|
-
try {
|
|
507
|
-
unlinkSync(join(activityDir(basePath), e.filename));
|
|
508
|
-
removedActivity++;
|
|
509
|
-
} catch { /* ignore */ }
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
// Clear debug logs older than 3 days, keep latest 2
|
|
514
|
-
const debugLogs = listDebugLogs(basePath);
|
|
515
|
-
const keepDebug = debugLogs.slice(-2);
|
|
516
|
-
const keepDebugNames = new Set(keepDebug.map(d => d.filename));
|
|
517
|
-
const debugCutoff = Date.now() - 3 * 24 * 60 * 60 * 1000;
|
|
518
|
-
|
|
519
|
-
for (const d of debugLogs) {
|
|
520
|
-
if (keepDebugNames.has(d.filename)) continue;
|
|
521
|
-
if (d.mtime.getTime() < debugCutoff) {
|
|
522
|
-
try {
|
|
523
|
-
unlinkSync(join(debugDir(basePath), d.filename));
|
|
524
|
-
removedDebug++;
|
|
525
|
-
} catch { /* ignore */ }
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
if (removedActivity === 0 && removedDebug === 0) {
|
|
530
|
-
ctx.ui.notify("No old logs to clear.", "info");
|
|
531
|
-
} else {
|
|
532
|
-
ctx.ui.notify(
|
|
533
|
-
`Cleared ${removedActivity} activity log(s) and ${removedDebug} debug log(s).`,
|
|
534
|
-
"info",
|
|
535
|
-
);
|
|
536
|
-
}
|
|
537
|
-
}
|