pi-blackhole 0.4.2 → 0.4.3
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 -0
- package/dist/index.js +11660 -0
- package/dist/index.js.map +1 -0
- package/example-config.json +1 -1
- package/index.ts +37 -63
- package/package.json +21 -9
- package/src/commands/cleanup.ts +279 -240
- package/src/commands/memory.ts +236 -184
- package/src/commands/pi-vcc.ts +202 -152
- package/src/commands/vcc-recall.ts +126 -95
- package/src/core/brief.ts +167 -33
- package/src/core/build-sections.ts +8 -2
- package/src/core/config-env.ts +117 -0
- package/src/core/content.ts +31 -7
- package/src/core/drill-down.ts +41 -11
- package/src/core/filter-noise.ts +9 -3
- package/src/core/format-recall.ts +15 -6
- package/src/core/format.ts +14 -4
- package/src/core/lineage.ts +9 -3
- package/src/core/load-messages.ts +24 -5
- package/src/core/normalize.ts +38 -14
- package/src/core/recall-scope.ts +11 -3
- package/src/core/render-entries.ts +22 -6
- package/src/core/sanitize.ts +5 -1
- package/src/core/search-entries.ts +111 -19
- package/src/core/settings.ts +1 -3
- package/src/core/summarize.ts +42 -21
- package/src/core/unified-config.ts +549 -411
- package/src/extract/commits.ts +4 -2
- package/src/extract/files.ts +10 -5
- package/src/extract/goals.ts +7 -2
- package/src/hooks/before-compact.ts +210 -88
- package/src/om/agents/dropper/agent.ts +380 -265
- package/src/om/agents/dropper/coverage.ts +102 -82
- package/src/om/agents/observer/agent.ts +242 -206
- package/src/om/agents/reflector/agent.ts +212 -153
- package/src/om/cleanup.ts +239 -218
- package/src/om/clipboard.ts +59 -51
- package/src/om/compaction-trigger.ts +448 -333
- package/src/om/config.ts +13 -6
- package/src/om/configure-overlay.ts +518 -355
- package/src/om/consolidation.ts +1460 -953
- package/src/om/cooldown.ts +75 -65
- package/src/om/debug-log.ts +86 -68
- package/src/om/ids.ts +1 -1
- package/src/om/ledger/fold.ts +89 -78
- package/src/om/ledger/progress.ts +181 -153
- package/src/om/ledger/projection.ts +248 -185
- package/src/om/ledger/recall.ts +247 -196
- package/src/om/ledger/render-summary.ts +79 -50
- package/src/om/ledger/types.ts +146 -117
- package/src/om/model-budget.ts +23 -13
- package/src/om/pending.ts +243 -179
- package/src/om/provider-stream.ts +52 -7
- package/src/om/retryable-error.ts +12 -16
- package/src/om/reverse-recall.ts +97 -91
- package/src/om/runtime.ts +474 -375
- package/src/om/serialize.ts +190 -166
- package/src/om/status-overlay.ts +246 -195
- package/src/om/tokens.ts +28 -21
- package/src/pi-base/blackhole-settings.ts +437 -0
- package/src/pi-base/config-manager.ts +440 -0
- package/src/pi-base/config.ts +469 -0
- package/src/pi-base/env.ts +43 -0
- package/src/pi-base/paths.ts +47 -0
- package/src/pi-base/settings/body.ts +1648 -0
- package/src/pi-base/settings/fields/action.ts +43 -0
- package/src/pi-base/settings/fields/boolean.ts +47 -0
- package/src/pi-base/settings/fields/custom.ts +72 -0
- package/src/pi-base/settings/fields/enum.ts +310 -0
- package/src/pi-base/settings/fields/index.ts +46 -0
- package/src/pi-base/settings/fields/model.ts +452 -0
- package/src/pi-base/settings/fields/string.ts +527 -0
- package/src/pi-base/settings/fields/text.ts +115 -0
- package/src/pi-base/settings/frame.ts +197 -0
- package/src/pi-base/settings/index.ts +77 -0
- package/src/pi-base/settings/inline-edit.ts +313 -0
- package/src/pi-base/settings/modal.ts +152 -0
- package/src/pi-base/settings/types.ts +500 -0
- package/src/pi-base/settings/validate-field.ts +113 -0
- package/src/pi-base/shell.ts +117 -0
- package/src/pi-base/types.ts +6 -0
- package/src/pi-base/ui.ts +32 -0
- package/src/tools/recall.ts +347 -225
- package/src/types.ts +20 -3
- package/tsup.config.ts +23 -0
- package/vitest.config.ts +15 -15
package/src/commands/cleanup.ts
CHANGED
|
@@ -18,11 +18,11 @@ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
|
18
18
|
import { matchesKey, decodeKittyPrintable } from "@earendil-works/pi-tui";
|
|
19
19
|
import { visibleWidth } from "../om/key-matcher.js";
|
|
20
20
|
import {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
analyzeOrphaned,
|
|
22
|
+
deletePendingFiles,
|
|
23
|
+
deleteOrphanedBatch,
|
|
24
|
+
describeFile,
|
|
25
|
+
type PendingFile,
|
|
26
26
|
} from "../om/cleanup.js";
|
|
27
27
|
|
|
28
28
|
// ── TUI Picker Component ────────────────────────────────────────────────────
|
|
@@ -31,29 +31,34 @@ const LIST_ROWS = 10;
|
|
|
31
31
|
|
|
32
32
|
/** Clamp a list index to [0, length-1]. */
|
|
33
33
|
function clampIndex(idx: number, length: number): number {
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
if (length === 0) return 0;
|
|
35
|
+
return Math.max(0, Math.min(idx, length - 1));
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
/** Build the framed top border with optional title and right-aligned info. */
|
|
39
39
|
function buildTopBorder(
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
width: number,
|
|
41
|
+
border: (s: string) => string,
|
|
42
|
+
dim: (s: string) => string,
|
|
43
|
+
title?: string,
|
|
44
|
+
right?: string,
|
|
45
45
|
): string {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
46
|
+
const innerW = Math.max(1, width - 2);
|
|
47
|
+
if (!title && !right) {
|
|
48
|
+
return border(`┏${"━".repeat(innerW)}┓`);
|
|
49
|
+
}
|
|
50
|
+
const rightText = right ? ` ${right} ` : "";
|
|
51
|
+
const titleBudget = Math.max(1, innerW - visibleWidth(rightText) - 1);
|
|
52
|
+
const titleFitted = title
|
|
53
|
+
? ` ${title.slice(0, Math.max(1, titleBudget - 2))}${title.length > titleBudget - 2 ? "…" : ""} `
|
|
54
|
+
: "";
|
|
55
|
+
const fill = Math.max(
|
|
56
|
+
1,
|
|
57
|
+
innerW - visibleWidth(titleFitted) - visibleWidth(rightText),
|
|
58
|
+
);
|
|
59
|
+
return border(
|
|
60
|
+
`┏${titleFitted}${"━".repeat(fill)}${right ? dim(rightText) : ""}┓`,
|
|
61
|
+
);
|
|
57
62
|
}
|
|
58
63
|
|
|
59
64
|
// ── Component ───────────────────────────────────────────────────────────────
|
|
@@ -65,183 +70,216 @@ function buildTopBorder(
|
|
|
65
70
|
* deleted (immediate feedback). The caller creates a copy if needed.
|
|
66
71
|
*/
|
|
67
72
|
function createCleanupPicker(
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
orphaned: PendingFile[],
|
|
74
|
+
theme: {
|
|
75
|
+
fg: (s: string, t: string) => string;
|
|
76
|
+
bg: (s: string, t: string) => string;
|
|
77
|
+
},
|
|
78
|
+
done: (result: "deleteAll" | "cancel") => void,
|
|
79
|
+
): {
|
|
80
|
+
render(width: number): string[];
|
|
81
|
+
handleInput(data: string): void;
|
|
82
|
+
invalidate(): void;
|
|
83
|
+
} {
|
|
84
|
+
let selectedIndex = 0;
|
|
85
|
+
let scrollOffset = 0;
|
|
86
|
+
let confirmDeleteAll = false;
|
|
75
87
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
88
|
+
function clampScroll(): void {
|
|
89
|
+
const count = orphaned.length;
|
|
90
|
+
selectedIndex = clampIndex(selectedIndex, count);
|
|
91
|
+
const maxOffset = Math.max(0, count - LIST_ROWS);
|
|
92
|
+
scrollOffset = Math.max(0, Math.min(scrollOffset, maxOffset));
|
|
93
|
+
if (selectedIndex < scrollOffset) scrollOffset = selectedIndex;
|
|
94
|
+
if (selectedIndex >= scrollOffset + LIST_ROWS && count > 0) {
|
|
95
|
+
scrollOffset = selectedIndex - LIST_ROWS + 1;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
86
98
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
99
|
+
const bdr = (t: string) => theme.fg("border", t);
|
|
100
|
+
const dim = (t: string) => theme.fg("dim", t);
|
|
101
|
+
const accent = (t: string) => theme.fg("accent", t);
|
|
102
|
+
const err = (t: string) => theme.fg("error", t);
|
|
103
|
+
const now = Date.now();
|
|
92
104
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
105
|
+
function itemLine(pf: PendingFile, isSel: boolean, cw: number): string {
|
|
106
|
+
const desc = describeFile(pf, now);
|
|
107
|
+
const prefix = isSel ? accent(" ▶") : " ";
|
|
108
|
+
const main = isSel ? accent(desc) : desc;
|
|
109
|
+
const line = `${prefix} ${main}`;
|
|
110
|
+
return ` ${line}${" ".repeat(Math.max(0, cw - visibleWidth(line)))} `;
|
|
111
|
+
}
|
|
100
112
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
113
|
+
return {
|
|
114
|
+
invalidate(): void {
|
|
115
|
+
// No cache — render is always fresh
|
|
116
|
+
},
|
|
105
117
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
118
|
+
render(width: number): string[] {
|
|
119
|
+
const w = Math.max(40, width);
|
|
120
|
+
const innerW = Math.max(1, w - 2);
|
|
121
|
+
const PX = 1;
|
|
122
|
+
const cw = Math.max(1, innerW - PX * 2);
|
|
123
|
+
const lines: string[] = [];
|
|
112
124
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
+
// ── Confirm-delete-all ──
|
|
126
|
+
if (confirmDeleteAll) {
|
|
127
|
+
const title = `Delete ${orphaned.length} orphaned file${orphaned.length === 1 ? "" : "s"}?`;
|
|
128
|
+
lines.push(buildTopBorder(w, bdr, dim, "Cleanup Pending Files"));
|
|
129
|
+
lines.push(bdr(`┃${" ".repeat(innerW)}┃`));
|
|
130
|
+
lines.push(
|
|
131
|
+
bdr(
|
|
132
|
+
`┃ ${err(title)}${" ".repeat(Math.max(0, cw - visibleWidth(title)))} ┃`,
|
|
133
|
+
),
|
|
134
|
+
);
|
|
135
|
+
lines.push(bdr(`┃${" ".repeat(innerW)}┃`));
|
|
136
|
+
const hint = "Enter confirm · Esc cancel";
|
|
137
|
+
lines.push(
|
|
138
|
+
bdr(
|
|
139
|
+
`┃ ${dim(hint)}${" ".repeat(Math.max(0, cw - visibleWidth(hint)))} ┃`,
|
|
140
|
+
),
|
|
141
|
+
);
|
|
142
|
+
lines.push(bdr(`┗${"━".repeat(innerW)}┛`));
|
|
143
|
+
return lines;
|
|
144
|
+
}
|
|
125
145
|
|
|
126
|
-
|
|
127
|
-
|
|
146
|
+
// ── List ──
|
|
147
|
+
clampScroll();
|
|
128
148
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
149
|
+
const sizeLabel = `${orphaned.length} file${orphaned.length === 1 ? "" : "s"}`;
|
|
150
|
+
lines.push(
|
|
151
|
+
buildTopBorder(w, bdr, dim, "Orphaned Pending Files", sizeLabel),
|
|
152
|
+
);
|
|
153
|
+
lines.push(bdr(`┃${" ".repeat(innerW)}┃`));
|
|
132
154
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
155
|
+
if (orphaned.length === 0) {
|
|
156
|
+
lines.push(
|
|
157
|
+
bdr(
|
|
158
|
+
`┃ ${dim("No orphaned pending files found")}${" ".repeat(Math.max(0, cw - 29))} ┃`,
|
|
159
|
+
),
|
|
160
|
+
);
|
|
161
|
+
} else {
|
|
162
|
+
const visible = orphaned.slice(scrollOffset, scrollOffset + LIST_ROWS);
|
|
163
|
+
for (let vi = 0; vi < visible.length; vi++) {
|
|
164
|
+
const idx = scrollOffset + vi;
|
|
165
|
+
const pf = visible[vi];
|
|
166
|
+
if (!pf) continue;
|
|
167
|
+
const isSel = idx === selectedIndex;
|
|
168
|
+
const row = itemLine(pf, isSel, cw);
|
|
169
|
+
lines.push(
|
|
170
|
+
isSel ? theme.bg("selectedBg", bdr(`┃${row}┃`)) : bdr(`┃${row}┃`),
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
148
174
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
175
|
+
// Filler rows
|
|
176
|
+
for (let i = Math.min(orphaned.length, LIST_ROWS); i < LIST_ROWS; i++) {
|
|
177
|
+
lines.push(bdr(`┃${" ".repeat(innerW)}┃`));
|
|
178
|
+
}
|
|
153
179
|
|
|
154
|
-
|
|
180
|
+
lines.push(bdr(`┃${" ".repeat(innerW)}┃`));
|
|
155
181
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
182
|
+
if (orphaned.length > 0) {
|
|
183
|
+
const totalBytes = orphaned.reduce((s, pf) => s + pf.sizeBytes, 0);
|
|
184
|
+
const kb = totalBytes > 0 ? (totalBytes / 1024).toFixed(1) : "0.0";
|
|
185
|
+
const totalStr = `Total: ${kb} KB`;
|
|
186
|
+
const hint = "↑↓ navigate Enter delete D delete all Esc cancel";
|
|
187
|
+
lines.push(
|
|
188
|
+
bdr(
|
|
189
|
+
`┃ ${dim(totalStr)}${" ".repeat(Math.max(0, cw - visibleWidth(totalStr)))} ┃`,
|
|
190
|
+
),
|
|
191
|
+
);
|
|
192
|
+
lines.push(
|
|
193
|
+
bdr(
|
|
194
|
+
`┃ ${dim(hint)}${" ".repeat(Math.max(0, cw - visibleWidth(hint)))} ┃`,
|
|
195
|
+
),
|
|
196
|
+
);
|
|
197
|
+
} else {
|
|
198
|
+
const hint = "Esc close";
|
|
199
|
+
lines.push(
|
|
200
|
+
bdr(
|
|
201
|
+
`┃ ${dim(hint)}${" ".repeat(Math.max(0, cw - visibleWidth(hint)))} ┃`,
|
|
202
|
+
),
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
lines.push(bdr(`┗${"━".repeat(innerW)}┛`));
|
|
206
|
+
return lines;
|
|
207
|
+
},
|
|
170
208
|
|
|
171
|
-
|
|
172
|
-
|
|
209
|
+
handleInput(data: string): void {
|
|
210
|
+
const key = decodeKittyPrintable(data) ?? data;
|
|
173
211
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
212
|
+
if (confirmDeleteAll) {
|
|
213
|
+
if (matchesKey(data, "enter") || matchesKey(data, "return")) {
|
|
214
|
+
done("deleteAll");
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (matchesKey(data, "escape")) {
|
|
218
|
+
confirmDeleteAll = false;
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
185
223
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
224
|
+
if (orphaned.length === 0) {
|
|
225
|
+
if (matchesKey(data, "escape")) done("cancel");
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
190
228
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
229
|
+
if (matchesKey(data, "up")) {
|
|
230
|
+
selectedIndex = clampIndex(selectedIndex - 1, orphaned.length);
|
|
231
|
+
clampScroll();
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
if (matchesKey(data, "down")) {
|
|
235
|
+
selectedIndex = clampIndex(selectedIndex + 1, orphaned.length);
|
|
236
|
+
clampScroll();
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
if (matchesKey(data, "pageUp") || key === "-") {
|
|
240
|
+
selectedIndex = clampIndex(selectedIndex - LIST_ROWS, orphaned.length);
|
|
241
|
+
clampScroll();
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
if (matchesKey(data, "pageDown") || key === "=") {
|
|
245
|
+
selectedIndex = clampIndex(selectedIndex + LIST_ROWS, orphaned.length);
|
|
246
|
+
clampScroll();
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
if (matchesKey(data, "home")) {
|
|
250
|
+
selectedIndex = 0;
|
|
251
|
+
scrollOffset = 0;
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (matchesKey(data, "end")) {
|
|
255
|
+
selectedIndex = Math.max(0, orphaned.length - 1);
|
|
256
|
+
clampScroll();
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
if (matchesKey(data, "enter") || matchesKey(data, "return")) {
|
|
260
|
+
const pf = orphaned[selectedIndex];
|
|
261
|
+
if (pf) {
|
|
262
|
+
deletePendingFiles(pf.sessionId);
|
|
263
|
+
orphaned.splice(selectedIndex, 1);
|
|
264
|
+
if (orphaned.length === 0) {
|
|
265
|
+
done("cancel");
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
selectedIndex = clampIndex(selectedIndex, orphaned.length);
|
|
269
|
+
clampScroll();
|
|
270
|
+
}
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
if (key === "d" || key === "D") {
|
|
274
|
+
confirmDeleteAll = true;
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
if (matchesKey(data, "escape")) {
|
|
278
|
+
done("cancel");
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
};
|
|
245
283
|
}
|
|
246
284
|
|
|
247
285
|
// ── Public handler ──────────────────────────────────────────────────────────
|
|
@@ -256,64 +294,65 @@ function createCleanupPicker(
|
|
|
256
294
|
* without deleting anything.
|
|
257
295
|
*/
|
|
258
296
|
export async function handleCleanup(ctx: ExtensionContext): Promise<void> {
|
|
259
|
-
|
|
297
|
+
const { orphaned } = analyzeOrphaned();
|
|
260
298
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
299
|
+
if (orphaned.length === 0) {
|
|
300
|
+
ctx.ui.notify("pi-blackhole: No orphaned pending files found.", "info");
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
265
303
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
304
|
+
const isRpc =
|
|
305
|
+
ctx.mode === "rpc" || ctx.mode === "json" || ctx.mode === "print";
|
|
306
|
+
if (isRpc) {
|
|
307
|
+
// Non-TUI: list only
|
|
308
|
+
const totalSize = orphaned.reduce((s, pf) => s + pf.sizeBytes, 0);
|
|
309
|
+
const lines = [
|
|
310
|
+
`Orphaned pending files: ${orphaned.length} (${(totalSize / 1024).toFixed(1)} KB)`,
|
|
311
|
+
"",
|
|
312
|
+
...orphaned.map((pf) => ` ${describeFile(pf)}`),
|
|
313
|
+
"",
|
|
314
|
+
"Use /blackhole cleanup in TUI mode to delete these files.",
|
|
315
|
+
];
|
|
316
|
+
ctx.ui.notify(lines.join("\n"), "warning");
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
280
319
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
320
|
+
// Work on a copy — the picker mutates the array for inline deletes.
|
|
321
|
+
const items = [...orphaned];
|
|
322
|
+
const result = await ctx.ui.custom<"deleteAll" | "cancel" | null>(
|
|
323
|
+
(_tui, theme, _kb, done) => {
|
|
324
|
+
return createCleanupPicker(items, theme as any, done);
|
|
325
|
+
},
|
|
326
|
+
{ overlay: true },
|
|
327
|
+
);
|
|
289
328
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
329
|
+
if (result === "deleteAll") {
|
|
330
|
+
const deleted = deleteOrphanedBatch(items);
|
|
331
|
+
const intended = items.length;
|
|
332
|
+
if (deleted === intended) {
|
|
333
|
+
ctx.ui.notify(
|
|
334
|
+
`pi-blackhole: Deleted ${intended} orphaned pending file${intended === 1 ? "" : "s"}.`,
|
|
335
|
+
"info",
|
|
336
|
+
);
|
|
337
|
+
} else {
|
|
338
|
+
ctx.ui.notify(
|
|
339
|
+
`pi-blackhole: Deleted ${deleted}/${intended} orphaned pending file${intended === 1 ? "" : "s"} (${intended - deleted} failed).`,
|
|
340
|
+
"warning",
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
} else if (items.length > 0 && items.length < orphaned.length) {
|
|
344
|
+
// Some were deleted inline, some remain
|
|
345
|
+
const remainingSize = items.reduce((s, pf) => s + pf.sizeBytes, 0);
|
|
346
|
+
ctx.ui.notify(
|
|
347
|
+
`pi-blackhole: ${orphaned.length - items.length} deleted, ${items.length} remain (${(remainingSize / 1024).toFixed(1)} KB).`,
|
|
348
|
+
"info",
|
|
349
|
+
);
|
|
350
|
+
} else if (items.length === 0 && orphaned.length > 0) {
|
|
351
|
+
// All were deleted inline
|
|
352
|
+
ctx.ui.notify(
|
|
353
|
+
`pi-blackhole: All ${orphaned.length} orphaned pending file${orphaned.length === 1 ? "" : "s"} removed.`,
|
|
354
|
+
"info",
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
// If nothing was deleted (user just pressed Esc), stay silent
|
|
319
358
|
}
|