pi-session-cleanup 1.1.0 → 1.1.2
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/CHANGELOG.md +27 -17
- package/package.json +3 -3
- package/src/agent-target.ts +361 -361
- package/src/index.ts +1 -1
- package/src/session-agent.ts +103 -103
- package/src/session-cleanup-command.ts +268 -268
- package/src/session-delete.ts +165 -11
- package/src/session-entry.ts +23 -23
- package/src/session-format.ts +98 -98
- package/src/session-nix-command.ts +353 -329
- package/src/session-quit-shutdown.ts +40 -40
- package/src/session-selection.ts +167 -167
- package/src/session-sort.ts +137 -137
- package/src/session-source.ts +32 -32
- package/src/tui/agent-target-picker.ts +306 -306
- package/src/tui/session-cleanup-picker.ts +592 -592
- package/src/types-shims.d.ts +23 -9
- package/src/types.ts +1 -1
|
@@ -1,268 +1,268 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
ExtensionCommandContext,
|
|
3
|
-
SessionInfo,
|
|
4
|
-
} from "@
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
SESSION_CLEANUP_COMMAND,
|
|
8
|
-
} from "./constants.js";
|
|
9
|
-
import { getSessionTitle } from "./session-format.js";
|
|
10
|
-
import { deleteSessionFile } from "./session-delete.js";
|
|
11
|
-
import { loadSessions } from "./session-source.js";
|
|
12
|
-
import { selectSessionsForCleanup } from "./session-selection.js";
|
|
13
|
-
import type {
|
|
14
|
-
BatchDeleteResult,
|
|
15
|
-
SessionCleanupSession,
|
|
16
|
-
SessionScope,
|
|
17
|
-
} from "./types.js";
|
|
18
|
-
|
|
19
|
-
const ARG_COMPLETIONS = [
|
|
20
|
-
{
|
|
21
|
-
value: "current",
|
|
22
|
-
label: "current",
|
|
23
|
-
description: "List sessions from the current working directory only",
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
value: "all",
|
|
27
|
-
label: "all",
|
|
28
|
-
description: "List sessions across every working directory",
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
value: "help",
|
|
32
|
-
label: "help",
|
|
33
|
-
description: "Show usage",
|
|
34
|
-
},
|
|
35
|
-
] as const;
|
|
36
|
-
|
|
37
|
-
interface ParsedArgs {
|
|
38
|
-
help: boolean;
|
|
39
|
-
scope: SessionScope;
|
|
40
|
-
error?: string;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function usage(): string {
|
|
44
|
-
return `Usage: /${SESSION_CLEANUP_COMMAND} [current|all]`;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function parseArgs(args: string): ParsedArgs {
|
|
48
|
-
const normalized = args.trim().toLowerCase();
|
|
49
|
-
if (!normalized) {
|
|
50
|
-
return { help: false, scope: "current" };
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (normalized === "help") {
|
|
54
|
-
return { help: true, scope: "current" };
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (normalized === "current" || normalized === "all") {
|
|
58
|
-
return { help: false, scope: normalized };
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return {
|
|
62
|
-
help: false,
|
|
63
|
-
scope: "current",
|
|
64
|
-
error: `Unknown argument: ${args.trim()}`,
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function buildSelectionSummary(sessions: readonly SessionInfo[]): string {
|
|
69
|
-
const preview = sessions
|
|
70
|
-
.slice(0, 6)
|
|
71
|
-
.map((session) => `- ${getSessionTitle(session)} (${session.id.slice(0, 8)})`)
|
|
72
|
-
.join("\n");
|
|
73
|
-
const hiddenCount = Math.max(0, sessions.length - 6);
|
|
74
|
-
|
|
75
|
-
if (hiddenCount > 0) {
|
|
76
|
-
return `${preview}\n- …and ${hiddenCount} more`;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return preview;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function buildConfirmationMessage(sessions: readonly SessionInfo[]): string {
|
|
83
|
-
const noun = sessions.length === 1 ? "session" : "sessions";
|
|
84
|
-
return `Delete ${sessions.length} selected ${noun}?\n\n${buildSelectionSummary(sessions)}\n\nThis action removes session files. Pi will try trash first, then permanent delete if trash is unavailable.`;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function summarizeFailures(
|
|
88
|
-
failures: readonly { session: SessionInfo; error: string }[],
|
|
89
|
-
): string {
|
|
90
|
-
return failures
|
|
91
|
-
.slice(0, 4)
|
|
92
|
-
.map(
|
|
93
|
-
(failure) =>
|
|
94
|
-
`- ${failure.session.id.slice(0, 8)} (${getSessionTitle(failure.session)}): ${failure.error}`,
|
|
95
|
-
)
|
|
96
|
-
.join("\n");
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
async function deleteSelectedSessions(
|
|
100
|
-
selectedSessions: readonly SessionInfo[],
|
|
101
|
-
currentSessionFile: string | undefined,
|
|
102
|
-
): Promise<BatchDeleteResult> {
|
|
103
|
-
const result: BatchDeleteResult = {
|
|
104
|
-
deleted: [],
|
|
105
|
-
failed: [],
|
|
106
|
-
methods: {
|
|
107
|
-
trash: 0,
|
|
108
|
-
unlink: 0,
|
|
109
|
-
},
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
for (const session of selectedSessions) {
|
|
113
|
-
if (currentSessionFile && session.path === currentSessionFile) {
|
|
114
|
-
result.failed.push({
|
|
115
|
-
session,
|
|
116
|
-
error: "Refused to delete the currently active session.",
|
|
117
|
-
});
|
|
118
|
-
continue;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
try {
|
|
122
|
-
const deleteResult = await deleteSessionFile(session.path);
|
|
123
|
-
if (deleteResult.ok) {
|
|
124
|
-
result.deleted.push(session);
|
|
125
|
-
result.methods[deleteResult.method] += 1;
|
|
126
|
-
} else {
|
|
127
|
-
result.failed.push({
|
|
128
|
-
session,
|
|
129
|
-
error: deleteResult.error,
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
} catch (error) {
|
|
133
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
134
|
-
result.failed.push({
|
|
135
|
-
session,
|
|
136
|
-
error: message,
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
return result;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function notifyDeleteOutcome(ctx: ExtensionCommandContext, result: BatchDeleteResult): void {
|
|
145
|
-
const deletedCount = result.deleted.length;
|
|
146
|
-
const failedCount = result.failed.length;
|
|
147
|
-
|
|
148
|
-
if (deletedCount === 0 && failedCount > 0) {
|
|
149
|
-
ctx.ui.notify(
|
|
150
|
-
`No sessions were deleted.\n${summarizeFailures(result.failed)}`,
|
|
151
|
-
"error",
|
|
152
|
-
);
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (failedCount === 0) {
|
|
157
|
-
ctx.ui.notify(
|
|
158
|
-
`Deleted ${deletedCount} session(s) (trash: ${result.methods.trash}, permanent: ${result.methods.unlink}).`,
|
|
159
|
-
"info",
|
|
160
|
-
);
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
ctx.ui.notify(
|
|
165
|
-
`Deleted ${deletedCount} session(s), but ${failedCount} failed.\n${summarizeFailures(result.failed)}`,
|
|
166
|
-
"warning",
|
|
167
|
-
);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
export function getSessionCleanupArgumentCompletions(
|
|
171
|
-
argumentPrefix: string,
|
|
172
|
-
): Array<{ value: string; label: string; description?: string }> | null {
|
|
173
|
-
const normalizedPrefix = argumentPrefix.trim().toLowerCase();
|
|
174
|
-
if (!normalizedPrefix) {
|
|
175
|
-
return [...ARG_COMPLETIONS];
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
const matched = ARG_COMPLETIONS.filter((item) =>
|
|
179
|
-
item.value.startsWith(normalizedPrefix),
|
|
180
|
-
);
|
|
181
|
-
if (matched.length === 0) {
|
|
182
|
-
return null;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
return matched.map((item) => ({ ...item }));
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
export async function handleSessionCleanupCommand(
|
|
189
|
-
args: string,
|
|
190
|
-
ctx: ExtensionCommandContext,
|
|
191
|
-
): Promise<void> {
|
|
192
|
-
const parsed = parseArgs(args);
|
|
193
|
-
if (parsed.help) {
|
|
194
|
-
ctx.ui.notify(usage(), "info");
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
if (parsed.error) {
|
|
199
|
-
ctx.ui.notify(`${parsed.error}\n${usage()}`, "warning");
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
if (!ctx.hasUI) {
|
|
204
|
-
ctx.ui.notify(`/${SESSION_CLEANUP_COMMAND} requires interactive TUI mode.`, "warning");
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
const currentSessionFile = ctx.sessionManager.getSessionFile();
|
|
209
|
-
|
|
210
|
-
while (true) {
|
|
211
|
-
let sessions: SessionCleanupSession[];
|
|
212
|
-
try {
|
|
213
|
-
sessions = await loadSessions(ctx, parsed.scope);
|
|
214
|
-
} catch (error) {
|
|
215
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
216
|
-
ctx.ui.notify(`Failed to load sessions: ${message}`, "error");
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
const candidates = sessions.filter(
|
|
221
|
-
(session) => session.path !== currentSessionFile,
|
|
222
|
-
);
|
|
223
|
-
|
|
224
|
-
if (candidates.length === 0) {
|
|
225
|
-
ctx.ui.notify(
|
|
226
|
-
"No deletable sessions found for this scope (current active session is excluded).",
|
|
227
|
-
"info",
|
|
228
|
-
);
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
const selection = await selectSessionsForCleanup(ctx, candidates);
|
|
233
|
-
if (selection.cancelled) {
|
|
234
|
-
ctx.ui.notify("Session cleanup cancelled.", "info");
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
if (selection.refreshRequested) {
|
|
239
|
-
continue;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
const selectedSessions = candidates.filter((session) =>
|
|
243
|
-
selection.selectedPaths.has(session.path),
|
|
244
|
-
);
|
|
245
|
-
|
|
246
|
-
if (selectedSessions.length === 0) {
|
|
247
|
-
ctx.ui.notify("No sessions selected. Select one or more sessions first.", "warning");
|
|
248
|
-
continue;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
const confirmed = await ctx.ui.confirm(
|
|
252
|
-
"Delete selected sessions",
|
|
253
|
-
buildConfirmationMessage(selectedSessions),
|
|
254
|
-
);
|
|
255
|
-
|
|
256
|
-
if (!confirmed) {
|
|
257
|
-
ctx.ui.notify("Delete cancelled.", "info");
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
const deleteResult = await deleteSelectedSessions(
|
|
262
|
-
selectedSessions,
|
|
263
|
-
currentSessionFile,
|
|
264
|
-
);
|
|
265
|
-
notifyDeleteOutcome(ctx, deleteResult);
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
}
|
|
1
|
+
import type {
|
|
2
|
+
ExtensionCommandContext,
|
|
3
|
+
SessionInfo,
|
|
4
|
+
} from "@earendil-works/pi-coding-agent";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
SESSION_CLEANUP_COMMAND,
|
|
8
|
+
} from "./constants.js";
|
|
9
|
+
import { getSessionTitle } from "./session-format.js";
|
|
10
|
+
import { deleteSessionFile } from "./session-delete.js";
|
|
11
|
+
import { loadSessions } from "./session-source.js";
|
|
12
|
+
import { selectSessionsForCleanup } from "./session-selection.js";
|
|
13
|
+
import type {
|
|
14
|
+
BatchDeleteResult,
|
|
15
|
+
SessionCleanupSession,
|
|
16
|
+
SessionScope,
|
|
17
|
+
} from "./types.js";
|
|
18
|
+
|
|
19
|
+
const ARG_COMPLETIONS = [
|
|
20
|
+
{
|
|
21
|
+
value: "current",
|
|
22
|
+
label: "current",
|
|
23
|
+
description: "List sessions from the current working directory only",
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
value: "all",
|
|
27
|
+
label: "all",
|
|
28
|
+
description: "List sessions across every working directory",
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
value: "help",
|
|
32
|
+
label: "help",
|
|
33
|
+
description: "Show usage",
|
|
34
|
+
},
|
|
35
|
+
] as const;
|
|
36
|
+
|
|
37
|
+
interface ParsedArgs {
|
|
38
|
+
help: boolean;
|
|
39
|
+
scope: SessionScope;
|
|
40
|
+
error?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function usage(): string {
|
|
44
|
+
return `Usage: /${SESSION_CLEANUP_COMMAND} [current|all]`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function parseArgs(args: string): ParsedArgs {
|
|
48
|
+
const normalized = args.trim().toLowerCase();
|
|
49
|
+
if (!normalized) {
|
|
50
|
+
return { help: false, scope: "current" };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (normalized === "help") {
|
|
54
|
+
return { help: true, scope: "current" };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (normalized === "current" || normalized === "all") {
|
|
58
|
+
return { help: false, scope: normalized };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
help: false,
|
|
63
|
+
scope: "current",
|
|
64
|
+
error: `Unknown argument: ${args.trim()}`,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function buildSelectionSummary(sessions: readonly SessionInfo[]): string {
|
|
69
|
+
const preview = sessions
|
|
70
|
+
.slice(0, 6)
|
|
71
|
+
.map((session) => `- ${getSessionTitle(session)} (${session.id.slice(0, 8)})`)
|
|
72
|
+
.join("\n");
|
|
73
|
+
const hiddenCount = Math.max(0, sessions.length - 6);
|
|
74
|
+
|
|
75
|
+
if (hiddenCount > 0) {
|
|
76
|
+
return `${preview}\n- …and ${hiddenCount} more`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return preview;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function buildConfirmationMessage(sessions: readonly SessionInfo[]): string {
|
|
83
|
+
const noun = sessions.length === 1 ? "session" : "sessions";
|
|
84
|
+
return `Delete ${sessions.length} selected ${noun}?\n\n${buildSelectionSummary(sessions)}\n\nThis action removes session files. Pi will try trash first, then permanent delete if trash is unavailable.`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function summarizeFailures(
|
|
88
|
+
failures: readonly { session: SessionInfo; error: string }[],
|
|
89
|
+
): string {
|
|
90
|
+
return failures
|
|
91
|
+
.slice(0, 4)
|
|
92
|
+
.map(
|
|
93
|
+
(failure) =>
|
|
94
|
+
`- ${failure.session.id.slice(0, 8)} (${getSessionTitle(failure.session)}): ${failure.error}`,
|
|
95
|
+
)
|
|
96
|
+
.join("\n");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function deleteSelectedSessions(
|
|
100
|
+
selectedSessions: readonly SessionInfo[],
|
|
101
|
+
currentSessionFile: string | undefined,
|
|
102
|
+
): Promise<BatchDeleteResult> {
|
|
103
|
+
const result: BatchDeleteResult = {
|
|
104
|
+
deleted: [],
|
|
105
|
+
failed: [],
|
|
106
|
+
methods: {
|
|
107
|
+
trash: 0,
|
|
108
|
+
unlink: 0,
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
for (const session of selectedSessions) {
|
|
113
|
+
if (currentSessionFile && session.path === currentSessionFile) {
|
|
114
|
+
result.failed.push({
|
|
115
|
+
session,
|
|
116
|
+
error: "Refused to delete the currently active session.",
|
|
117
|
+
});
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
const deleteResult = await deleteSessionFile(session.path);
|
|
123
|
+
if (deleteResult.ok) {
|
|
124
|
+
result.deleted.push(session);
|
|
125
|
+
result.methods[deleteResult.method] += 1;
|
|
126
|
+
} else {
|
|
127
|
+
result.failed.push({
|
|
128
|
+
session,
|
|
129
|
+
error: deleteResult.error,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
} catch (error) {
|
|
133
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
134
|
+
result.failed.push({
|
|
135
|
+
session,
|
|
136
|
+
error: message,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return result;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function notifyDeleteOutcome(ctx: ExtensionCommandContext, result: BatchDeleteResult): void {
|
|
145
|
+
const deletedCount = result.deleted.length;
|
|
146
|
+
const failedCount = result.failed.length;
|
|
147
|
+
|
|
148
|
+
if (deletedCount === 0 && failedCount > 0) {
|
|
149
|
+
ctx.ui.notify(
|
|
150
|
+
`No sessions were deleted.\n${summarizeFailures(result.failed)}`,
|
|
151
|
+
"error",
|
|
152
|
+
);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (failedCount === 0) {
|
|
157
|
+
ctx.ui.notify(
|
|
158
|
+
`Deleted ${deletedCount} session(s) (trash: ${result.methods.trash}, permanent: ${result.methods.unlink}).`,
|
|
159
|
+
"info",
|
|
160
|
+
);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
ctx.ui.notify(
|
|
165
|
+
`Deleted ${deletedCount} session(s), but ${failedCount} failed.\n${summarizeFailures(result.failed)}`,
|
|
166
|
+
"warning",
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function getSessionCleanupArgumentCompletions(
|
|
171
|
+
argumentPrefix: string,
|
|
172
|
+
): Array<{ value: string; label: string; description?: string }> | null {
|
|
173
|
+
const normalizedPrefix = argumentPrefix.trim().toLowerCase();
|
|
174
|
+
if (!normalizedPrefix) {
|
|
175
|
+
return [...ARG_COMPLETIONS];
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const matched = ARG_COMPLETIONS.filter((item) =>
|
|
179
|
+
item.value.startsWith(normalizedPrefix),
|
|
180
|
+
);
|
|
181
|
+
if (matched.length === 0) {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return matched.map((item) => ({ ...item }));
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export async function handleSessionCleanupCommand(
|
|
189
|
+
args: string,
|
|
190
|
+
ctx: ExtensionCommandContext,
|
|
191
|
+
): Promise<void> {
|
|
192
|
+
const parsed = parseArgs(args);
|
|
193
|
+
if (parsed.help) {
|
|
194
|
+
ctx.ui.notify(usage(), "info");
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (parsed.error) {
|
|
199
|
+
ctx.ui.notify(`${parsed.error}\n${usage()}`, "warning");
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (!ctx.hasUI) {
|
|
204
|
+
ctx.ui.notify(`/${SESSION_CLEANUP_COMMAND} requires interactive TUI mode.`, "warning");
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const currentSessionFile = ctx.sessionManager.getSessionFile();
|
|
209
|
+
|
|
210
|
+
while (true) {
|
|
211
|
+
let sessions: SessionCleanupSession[];
|
|
212
|
+
try {
|
|
213
|
+
sessions = await loadSessions(ctx, parsed.scope);
|
|
214
|
+
} catch (error) {
|
|
215
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
216
|
+
ctx.ui.notify(`Failed to load sessions: ${message}`, "error");
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const candidates = sessions.filter(
|
|
221
|
+
(session) => session.path !== currentSessionFile,
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
if (candidates.length === 0) {
|
|
225
|
+
ctx.ui.notify(
|
|
226
|
+
"No deletable sessions found for this scope (current active session is excluded).",
|
|
227
|
+
"info",
|
|
228
|
+
);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const selection = await selectSessionsForCleanup(ctx, candidates);
|
|
233
|
+
if (selection.cancelled) {
|
|
234
|
+
ctx.ui.notify("Session cleanup cancelled.", "info");
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (selection.refreshRequested) {
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const selectedSessions = candidates.filter((session) =>
|
|
243
|
+
selection.selectedPaths.has(session.path),
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
if (selectedSessions.length === 0) {
|
|
247
|
+
ctx.ui.notify("No sessions selected. Select one or more sessions first.", "warning");
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const confirmed = await ctx.ui.confirm(
|
|
252
|
+
"Delete selected sessions",
|
|
253
|
+
buildConfirmationMessage(selectedSessions),
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
if (!confirmed) {
|
|
257
|
+
ctx.ui.notify("Delete cancelled.", "info");
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const deleteResult = await deleteSelectedSessions(
|
|
262
|
+
selectedSessions,
|
|
263
|
+
currentSessionFile,
|
|
264
|
+
);
|
|
265
|
+
notifyDeleteOutcome(ctx, deleteResult);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
}
|