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/pi-vcc.ts
CHANGED
|
@@ -3,171 +3,221 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Upstream: https://github.com/sting8k/pi-vcc (src/commands/pi-vcc.ts)
|
|
5
5
|
* Modified by pi-vcc-om:
|
|
6
|
-
* - Flushes pending OM state (observations/reflections/dropped) when
|
|
6
|
+
* - Flushes pending OM state (observations/reflections/dropped) when manual mode is active
|
|
7
7
|
* before triggering compaction, so the compaction summary includes all accumulated memory.
|
|
8
8
|
*/
|
|
9
9
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
10
10
|
import type { Runtime } from "../om/runtime.js";
|
|
11
|
-
import { PI_VCC_COMPACT_INSTRUCTION, notifyMigrationReminder, formatCompactionStats } from "../hooks/before-compact";
|
|
12
|
-
import { saveUnifiedConfig, configPath } from "../core/unified-config.js";
|
|
13
|
-
import { readPendingState, clearPendingState, hasPendingData } from "../om/pending.js";
|
|
14
|
-
import { createConfigureOverlay } from "../om/configure-overlay.js";
|
|
15
11
|
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
PI_VCC_COMPACT_INSTRUCTION,
|
|
13
|
+
notifyMigrationReminder,
|
|
14
|
+
formatCompactionStats,
|
|
15
|
+
} from "../hooks/before-compact";
|
|
16
|
+
import {
|
|
17
|
+
readPendingState,
|
|
18
|
+
clearPendingState,
|
|
19
|
+
hasPendingData,
|
|
20
|
+
} from "../om/pending.js";
|
|
21
|
+
import {
|
|
22
|
+
OM_OBSERVATIONS_DROPPED,
|
|
23
|
+
OM_OBSERVATIONS_RECORDED,
|
|
24
|
+
OM_REFLECTIONS_RECORDED,
|
|
19
25
|
} from "../om/ledger/index.js";
|
|
20
26
|
import { handleCleanup } from "./cleanup.js";
|
|
27
|
+
import {
|
|
28
|
+
openBlackholeSettings,
|
|
29
|
+
config,
|
|
30
|
+
GLOBAL_CONFIG_DIR,
|
|
31
|
+
} from "../pi-base/blackhole-settings.js";
|
|
21
32
|
|
|
22
33
|
export const registerPiVccCommand = (pi: ExtensionAPI, runtime: Runtime) => {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
pi.registerCommand("blackhole", {
|
|
28
|
-
description:
|
|
29
|
-
"Compact conversation — structured summary (with observational memory when enabled). " +
|
|
30
|
-
"Subcommands: [configure] settings overlay, [cleanup] remove orphaned files, " +
|
|
31
|
-
"[om-off] / [om-on] disable / re-enable memory.",
|
|
32
|
-
getArgumentCompletions: (prefix: string) => {
|
|
33
|
-
const subcommands = [
|
|
34
|
-
{ value: "configure", label: "Open configuration overlay to edit settings [configure]" },
|
|
35
|
-
{ value: "cleanup", label: "Find and remove orphaned pending files [cleanup]" },
|
|
36
|
-
{ value: "om-off", label: "Disable observational memory [om-off]" },
|
|
37
|
-
{ value: "om-on", label: "Enable observational memory [om-on]" },
|
|
38
|
-
];
|
|
39
|
-
if (!prefix) return subcommands;
|
|
40
|
-
return subcommands.filter((s) => prefixMatch(s.value, prefix));
|
|
41
|
-
},
|
|
42
|
-
handler: async (args, ctx) => {
|
|
43
|
-
const sessionId = ctx.sessionManager.getSessionId();
|
|
34
|
+
const prefixMatch = (value: string, prefix: string): boolean => {
|
|
35
|
+
return value.toLowerCase().startsWith(prefix.toLowerCase());
|
|
36
|
+
};
|
|
44
37
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
ctx.ui.notify("Observational memory disabled. Use /blackhole om-on to re-enable.", "info");
|
|
74
|
-
} else {
|
|
75
|
-
ctx.ui.notify(
|
|
76
|
-
"Failed to save config — the config file may be read-only (e.g., managed by Nix). " +
|
|
77
|
-
"Runtime state updated for this session only.",
|
|
78
|
-
"warning",
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
if (trimmed === "om-on") {
|
|
84
|
-
const saved = saveUnifiedConfig({ memory: true });
|
|
85
|
-
runtime.config.memory = true;
|
|
86
|
-
if (saved) {
|
|
87
|
-
ctx.ui.notify("Observational memory enabled.", "info");
|
|
88
|
-
} else {
|
|
89
|
-
ctx.ui.notify(
|
|
90
|
-
"Failed to save config — the config file may be read-only (e.g., managed by Nix). " +
|
|
91
|
-
"Runtime state updated for this session only.",
|
|
92
|
-
"warning",
|
|
93
|
-
);
|
|
94
|
-
}
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
38
|
+
pi.registerCommand("blackhole", {
|
|
39
|
+
description:
|
|
40
|
+
"Manual compact with structural summary. Subcommands: [settings] config overlay, " +
|
|
41
|
+
"[cleanup] remove orphaned files, [om-off]/[om-on] disable/enable observational memory.",
|
|
42
|
+
getArgumentCompletions: (prefix: string) => {
|
|
43
|
+
const subcommands = [
|
|
44
|
+
{
|
|
45
|
+
value: "settings",
|
|
46
|
+
label: "Open configuration overlay [settings]",
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
value: "cleanup",
|
|
50
|
+
label: "Remove orphaned pending files [cleanup]",
|
|
51
|
+
},
|
|
52
|
+
{ value: "om-off", label: "Disable observational memory [om-off]" },
|
|
53
|
+
{ value: "om-on", label: "Enable observational memory [om-on]" },
|
|
54
|
+
];
|
|
55
|
+
if (!prefix) return subcommands;
|
|
56
|
+
// "configure" is an accepted alias for "settings" (routed by the
|
|
57
|
+
// handler); surface the settings entry when the user types either.
|
|
58
|
+
return subcommands.filter(
|
|
59
|
+
(s) =>
|
|
60
|
+
prefixMatch(s.value, prefix) ||
|
|
61
|
+
(s.value === "settings" && prefixMatch("configure", prefix)),
|
|
62
|
+
);
|
|
63
|
+
},
|
|
64
|
+
handler: async (args, ctx) => {
|
|
65
|
+
const sessionId = ctx.sessionManager.getSessionId();
|
|
97
66
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
67
|
+
// Handle subcommands
|
|
68
|
+
const trimmed = (typeof args === "string" ? args : "").trim();
|
|
69
|
+
if (trimmed === "configure" || trimmed === "settings") {
|
|
70
|
+
// Open the config overlay ("configure" kept as a hidden alias)
|
|
71
|
+
await openBlackholeSettings(ctx);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (trimmed === "cleanup") {
|
|
75
|
+
await handleCleanup(ctx);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (trimmed === "om-off") {
|
|
79
|
+
try {
|
|
80
|
+
config.save(
|
|
81
|
+
{ ...config.load(ctx.cwd, GLOBAL_CONFIG_DIR), memory: false },
|
|
82
|
+
"global",
|
|
83
|
+
ctx.cwd,
|
|
84
|
+
GLOBAL_CONFIG_DIR,
|
|
85
|
+
);
|
|
86
|
+
runtime.config = config.loadWithWarnings(
|
|
87
|
+
ctx.cwd,
|
|
88
|
+
GLOBAL_CONFIG_DIR,
|
|
89
|
+
).config;
|
|
90
|
+
ctx.ui.notify(
|
|
91
|
+
"Observational memory disabled. Use /blackhole om-on to re-enable.",
|
|
92
|
+
"info",
|
|
93
|
+
);
|
|
94
|
+
} catch {
|
|
95
|
+
ctx.ui.notify(
|
|
96
|
+
"Failed to save config — the config file may be read-only (e.g., managed by Nix). " +
|
|
97
|
+
"Runtime state updated for this session only.",
|
|
98
|
+
"warning",
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (trimmed === "om-on") {
|
|
104
|
+
try {
|
|
105
|
+
config.save(
|
|
106
|
+
{ ...config.load(ctx.cwd, GLOBAL_CONFIG_DIR), memory: true },
|
|
107
|
+
"global",
|
|
108
|
+
ctx.cwd,
|
|
109
|
+
GLOBAL_CONFIG_DIR,
|
|
110
|
+
);
|
|
111
|
+
runtime.config = config.loadWithWarnings(
|
|
112
|
+
ctx.cwd,
|
|
113
|
+
GLOBAL_CONFIG_DIR,
|
|
114
|
+
).config;
|
|
115
|
+
ctx.ui.notify("Observational memory enabled.", "info");
|
|
116
|
+
} catch {
|
|
117
|
+
ctx.ui.notify(
|
|
118
|
+
"Failed to save config — the config file may be read-only (e.g., managed by Nix). " +
|
|
119
|
+
"Runtime state updated for this session only.",
|
|
120
|
+
"warning",
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
return;
|
|
124
|
+
} // Warn if input starts with a known subcommand but isn't an exact match.
|
|
125
|
+
// Prevents "/blackhole configure foo" from silently becoming a follow-up.
|
|
126
|
+
const SUBCOMMAND_NAMES = [
|
|
127
|
+
"configure",
|
|
128
|
+
"settings",
|
|
129
|
+
"cleanup",
|
|
130
|
+
"om-off",
|
|
131
|
+
"om-on",
|
|
132
|
+
];
|
|
133
|
+
const nearMiss = SUBCOMMAND_NAMES.find(
|
|
134
|
+
(name) =>
|
|
135
|
+
trimmed.toLowerCase().startsWith(name.toLowerCase()) &&
|
|
136
|
+
trimmed.length > name.length,
|
|
137
|
+
);
|
|
138
|
+
if (nearMiss) {
|
|
139
|
+
ctx.ui.notify(
|
|
140
|
+
`/blackhole ${nearMiss} accepts no arguments. Did you mean \"/blackhole ${nearMiss}\"?`,
|
|
141
|
+
"warning",
|
|
142
|
+
);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
108
145
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
146
|
+
// Extract follow-up prompt: everything after the subcommand check
|
|
147
|
+
// that isn't a known subcommand is treated as follow-up text.
|
|
148
|
+
const followUpPrompt = trimmed ? trimmed : null;
|
|
112
149
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
150
|
+
// If compaction is manual (or legacy noAutoCompact): flush pending OM entries
|
|
151
|
+
// into the branch before compacting so the summary includes accumulated memory.
|
|
152
|
+
if (runtime.config.compaction === "manual" && hasPendingData(sessionId)) {
|
|
153
|
+
const pending = readPendingState(sessionId);
|
|
154
|
+
// Write all accumulated observation batches (or latest single batch
|
|
155
|
+
// as fallback for legacy pending.json without batch arrays).
|
|
156
|
+
const obsBatches = pending.observationBatches?.length
|
|
157
|
+
? pending.observationBatches
|
|
158
|
+
: pending.observation
|
|
159
|
+
? [pending.observation]
|
|
160
|
+
: [];
|
|
161
|
+
for (const batch of obsBatches) {
|
|
162
|
+
pi.appendEntry(OM_OBSERVATIONS_RECORDED, batch.data);
|
|
163
|
+
}
|
|
164
|
+
// Write all accumulated reflection batches (or latest single batch
|
|
165
|
+
// as fallback for legacy pending.json without batch arrays).
|
|
166
|
+
const reflBatches = pending.reflectionBatches?.length
|
|
167
|
+
? pending.reflectionBatches
|
|
168
|
+
: pending.reflection
|
|
169
|
+
? [pending.reflection]
|
|
170
|
+
: [];
|
|
171
|
+
for (const batch of reflBatches) {
|
|
172
|
+
pi.appendEntry(OM_REFLECTIONS_RECORDED, batch.data);
|
|
173
|
+
}
|
|
174
|
+
// Write all accumulated dropper batches (or latest single batch
|
|
175
|
+
// as fallback for legacy pending.json without batch arrays).
|
|
176
|
+
const dropBatches = pending.droppedBatches?.length
|
|
177
|
+
? pending.droppedBatches
|
|
178
|
+
: pending.dropped
|
|
179
|
+
? [pending.dropped]
|
|
180
|
+
: [];
|
|
181
|
+
for (const batch of dropBatches) {
|
|
182
|
+
pi.appendEntry(OM_OBSERVATIONS_DROPPED, batch.data);
|
|
183
|
+
}
|
|
184
|
+
clearPendingState(sessionId);
|
|
185
|
+
ctx.ui.notify("Observational memory: pending entries flushed", "info");
|
|
186
|
+
}
|
|
144
187
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
188
|
+
ctx.compact({
|
|
189
|
+
customInstructions: PI_VCC_COMPACT_INSTRUCTION,
|
|
190
|
+
onComplete: () => {
|
|
191
|
+
const stats = runtime.compactionStats;
|
|
192
|
+
if (stats) {
|
|
193
|
+
ctx.ui.notify(formatCompactionStats(stats), "info");
|
|
194
|
+
} else {
|
|
195
|
+
ctx.ui.notify("Compacted with blackhole", "info");
|
|
196
|
+
}
|
|
197
|
+
notifyMigrationReminder(sessionId, (msg, level) =>
|
|
198
|
+
ctx.ui.notify(msg, level as any),
|
|
199
|
+
);
|
|
155
200
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
201
|
+
// Fire follow-up prompt after compaction completes
|
|
202
|
+
if (followUpPrompt) {
|
|
203
|
+
try {
|
|
204
|
+
void Promise.resolve(pi.sendUserMessage(followUpPrompt)).catch(
|
|
205
|
+
() => {},
|
|
206
|
+
);
|
|
207
|
+
} catch {}
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
onError: (err) => {
|
|
211
|
+
if (
|
|
212
|
+
err.message === "Compaction cancelled" ||
|
|
213
|
+
err.message === "Already compacted"
|
|
214
|
+
) {
|
|
215
|
+
ctx.ui.notify("Nothing to compact", "warning");
|
|
216
|
+
} else {
|
|
217
|
+
ctx.ui.notify(`Compaction failed: ${err.message}`, "error");
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
},
|
|
222
|
+
});
|
|
173
223
|
};
|
|
@@ -7,13 +7,16 @@
|
|
|
7
7
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
8
8
|
import { loadAllMessages } from "../core/load-messages.js";
|
|
9
9
|
import { searchEntries, getTouchedFiles } from "../core/search-entries.js";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
formatRecallOutput,
|
|
12
|
+
formatTouchedOutput,
|
|
13
|
+
} from "../core/format-recall.js";
|
|
11
14
|
import { getActiveLineageEntryIds } from "../core/lineage.js";
|
|
12
15
|
import { parseRecallScope } from "../core/recall-scope.js";
|
|
13
16
|
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
+
findObservationsForEntryIds,
|
|
18
|
+
findReflectionsForEntryIds,
|
|
19
|
+
formatRelatedObservations,
|
|
17
20
|
} from "../om/reverse-recall.js";
|
|
18
21
|
import type { Entry } from "../om/ledger/recall.js";
|
|
19
22
|
|
|
@@ -21,106 +24,134 @@ const PAGE_SIZE = 5;
|
|
|
21
24
|
const DEFAULT_RECENT = 25;
|
|
22
25
|
|
|
23
26
|
async function augmentWithObservations(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
output: string,
|
|
28
|
+
rendered: { id: string }[],
|
|
29
|
+
ctx: any,
|
|
27
30
|
): Promise<string> {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
31
|
+
const ids = rendered.map((e) => e.id).filter(Boolean);
|
|
32
|
+
if (ids.length === 0) return output;
|
|
33
|
+
try {
|
|
34
|
+
const branchEntries = ctx.sessionManager.getBranch() as Entry[];
|
|
35
|
+
const obs = findObservationsForEntryIds(branchEntries, ids);
|
|
36
|
+
const refs = findReflectionsForEntryIds(branchEntries, ids);
|
|
37
|
+
if (obs.length > 0 || refs.length > 0) {
|
|
38
|
+
return output + "\n\n" + formatRelatedObservations(obs, refs);
|
|
39
|
+
}
|
|
40
|
+
} catch {
|
|
41
|
+
/* branch may not be available */
|
|
42
|
+
}
|
|
43
|
+
return output;
|
|
39
44
|
}
|
|
40
45
|
|
|
41
46
|
export const registerVccRecallCommand = (pi: ExtensionAPI) => {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
pi.registerCommand("blackhole-recall", {
|
|
48
|
+
description:
|
|
49
|
+
"Search session history. Defaults to active lineage. Usage: /blackhole-recall <query> [page:N] [scope:all] [mode:file|touched]",
|
|
50
|
+
handler: async (args: string, ctx) => {
|
|
51
|
+
const sessionFile = ctx.sessionManager.getSessionFile();
|
|
52
|
+
if (!sessionFile) {
|
|
53
|
+
ctx.ui.notify("No session file available.", "error");
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
51
56
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
const raw = args.trim();
|
|
58
|
+
const parsed = parseRecallScope(raw);
|
|
59
|
+
const lineageEntryIds =
|
|
60
|
+
parsed.scope === "lineage"
|
|
61
|
+
? getActiveLineageEntryIds(ctx.sessionManager)
|
|
62
|
+
: undefined;
|
|
63
|
+
const mode = parsed.mode;
|
|
59
64
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
65
|
+
if (mode === "touched") {
|
|
66
|
+
const pageMatch = raw.match(/\bpage:(\d+)\b/i);
|
|
67
|
+
const page = pageMatch ? Math.max(1, parseInt(pageMatch[1], 10)) : 1;
|
|
68
|
+
const { rendered, rawMessages } = loadAllMessages(
|
|
69
|
+
sessionFile,
|
|
70
|
+
false,
|
|
71
|
+
lineageEntryIds,
|
|
72
|
+
);
|
|
73
|
+
const touched = getTouchedFiles(rawMessages, rendered);
|
|
74
|
+
const text = formatTouchedOutput(touched, page);
|
|
75
|
+
pi.sendMessage(
|
|
76
|
+
{ customType: "blackhole-recall", content: text, display: true },
|
|
77
|
+
{ triggerTurn: true },
|
|
78
|
+
);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
72
81
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
if (!parsed.text) {
|
|
83
|
+
// No query: show recent entries
|
|
84
|
+
const { rendered } = loadAllMessages(
|
|
85
|
+
sessionFile,
|
|
86
|
+
false,
|
|
87
|
+
lineageEntryIds,
|
|
88
|
+
);
|
|
89
|
+
const recent = rendered.slice(-DEFAULT_RECENT);
|
|
90
|
+
const base =
|
|
91
|
+
(parsed.scope === "all" ? "Scope: all\n\n" : "") +
|
|
92
|
+
formatRecallOutput(recent);
|
|
93
|
+
const output = await augmentWithObservations(base, recent, ctx);
|
|
94
|
+
pi.sendMessage(
|
|
95
|
+
{ customType: "blackhole-recall", content: output, display: true },
|
|
96
|
+
{ triggerTurn: true },
|
|
97
|
+
);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
85
100
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
101
|
+
// Parse page:N from args
|
|
102
|
+
const pageMatch = parsed.text.match(/\bpage:(\d+)\b/i);
|
|
103
|
+
const page = pageMatch ? Math.max(1, parseInt(pageMatch[1], 10)) : 1;
|
|
104
|
+
const query = parsed.text.replace(/\bpage:\d+\b/i, "").trim();
|
|
90
105
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
106
|
+
if (!query) {
|
|
107
|
+
const { rendered } = loadAllMessages(
|
|
108
|
+
sessionFile,
|
|
109
|
+
false,
|
|
110
|
+
lineageEntryIds,
|
|
111
|
+
);
|
|
112
|
+
const recent = rendered.slice(-DEFAULT_RECENT);
|
|
113
|
+
const base =
|
|
114
|
+
(parsed.scope === "all" ? "Scope: all\n\n" : "") +
|
|
115
|
+
formatRecallOutput(recent);
|
|
116
|
+
const output = await augmentWithObservations(base, recent, ctx);
|
|
117
|
+
pi.sendMessage(
|
|
118
|
+
{ customType: "blackhole-recall", content: output, display: true },
|
|
119
|
+
{ triggerTurn: true },
|
|
120
|
+
);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
102
123
|
|
|
103
|
-
|
|
104
|
-
|
|
124
|
+
const { rendered, rawMessages } = loadAllMessages(
|
|
125
|
+
sessionFile,
|
|
126
|
+
false,
|
|
127
|
+
lineageEntryIds,
|
|
128
|
+
);
|
|
129
|
+
const allResults = searchEntries(
|
|
130
|
+
rendered,
|
|
131
|
+
rawMessages,
|
|
132
|
+
query,
|
|
133
|
+
undefined,
|
|
134
|
+
mode,
|
|
135
|
+
);
|
|
105
136
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
137
|
+
const start = (page - 1) * PAGE_SIZE;
|
|
138
|
+
const pageResults = allResults.slice(start, start + PAGE_SIZE);
|
|
139
|
+
const totalPages = Math.ceil(allResults.length / PAGE_SIZE);
|
|
140
|
+
const scopeSuffix = parsed.scope === "all" ? " (scope: all)" : "";
|
|
141
|
+
const header =
|
|
142
|
+
totalPages > 1
|
|
143
|
+
? `Page ${page}/${totalPages} (${allResults.length} total matches${scopeSuffix})`
|
|
144
|
+
: `${allResults.length} matches${scopeSuffix}`;
|
|
145
|
+
const footer =
|
|
146
|
+
page < totalPages
|
|
147
|
+
? `\n--- /blackhole-recall ${query}${parsed.scope === "all" ? " scope:all" : ""} page:${page + 1} ---`
|
|
148
|
+
: "";
|
|
149
|
+
const base = formatRecallOutput(pageResults, query, header) + footer;
|
|
150
|
+
const output = await augmentWithObservations(base, pageResults, ctx);
|
|
151
|
+
pi.sendMessage(
|
|
152
|
+
{ customType: "blackhole-recall", content: output, display: true },
|
|
153
|
+
{ triggerTurn: true },
|
|
154
|
+
);
|
|
155
|
+
},
|
|
156
|
+
});
|
|
126
157
|
};
|