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.
Files changed (87) hide show
  1. package/README.md +15 -0
  2. package/dist/index.js +11660 -0
  3. package/dist/index.js.map +1 -0
  4. package/example-config.json +1 -1
  5. package/index.ts +37 -63
  6. package/package.json +21 -9
  7. package/src/commands/cleanup.ts +279 -240
  8. package/src/commands/memory.ts +236 -184
  9. package/src/commands/pi-vcc.ts +202 -152
  10. package/src/commands/vcc-recall.ts +126 -95
  11. package/src/core/brief.ts +167 -33
  12. package/src/core/build-sections.ts +8 -2
  13. package/src/core/config-env.ts +117 -0
  14. package/src/core/content.ts +31 -7
  15. package/src/core/drill-down.ts +41 -11
  16. package/src/core/filter-noise.ts +9 -3
  17. package/src/core/format-recall.ts +15 -6
  18. package/src/core/format.ts +14 -4
  19. package/src/core/lineage.ts +9 -3
  20. package/src/core/load-messages.ts +24 -5
  21. package/src/core/normalize.ts +38 -14
  22. package/src/core/recall-scope.ts +11 -3
  23. package/src/core/render-entries.ts +22 -6
  24. package/src/core/sanitize.ts +5 -1
  25. package/src/core/search-entries.ts +111 -19
  26. package/src/core/settings.ts +1 -3
  27. package/src/core/summarize.ts +42 -21
  28. package/src/core/unified-config.ts +549 -411
  29. package/src/extract/commits.ts +4 -2
  30. package/src/extract/files.ts +10 -5
  31. package/src/extract/goals.ts +7 -2
  32. package/src/hooks/before-compact.ts +210 -88
  33. package/src/om/agents/dropper/agent.ts +380 -265
  34. package/src/om/agents/dropper/coverage.ts +102 -82
  35. package/src/om/agents/observer/agent.ts +242 -206
  36. package/src/om/agents/reflector/agent.ts +212 -153
  37. package/src/om/cleanup.ts +239 -218
  38. package/src/om/clipboard.ts +59 -51
  39. package/src/om/compaction-trigger.ts +448 -333
  40. package/src/om/config.ts +13 -6
  41. package/src/om/configure-overlay.ts +518 -355
  42. package/src/om/consolidation.ts +1460 -953
  43. package/src/om/cooldown.ts +75 -65
  44. package/src/om/debug-log.ts +86 -68
  45. package/src/om/ids.ts +1 -1
  46. package/src/om/ledger/fold.ts +89 -78
  47. package/src/om/ledger/progress.ts +181 -153
  48. package/src/om/ledger/projection.ts +248 -185
  49. package/src/om/ledger/recall.ts +247 -196
  50. package/src/om/ledger/render-summary.ts +79 -50
  51. package/src/om/ledger/types.ts +146 -117
  52. package/src/om/model-budget.ts +23 -13
  53. package/src/om/pending.ts +243 -179
  54. package/src/om/provider-stream.ts +52 -7
  55. package/src/om/retryable-error.ts +12 -16
  56. package/src/om/reverse-recall.ts +97 -91
  57. package/src/om/runtime.ts +474 -375
  58. package/src/om/serialize.ts +190 -166
  59. package/src/om/status-overlay.ts +246 -195
  60. package/src/om/tokens.ts +28 -21
  61. package/src/pi-base/blackhole-settings.ts +437 -0
  62. package/src/pi-base/config-manager.ts +440 -0
  63. package/src/pi-base/config.ts +469 -0
  64. package/src/pi-base/env.ts +43 -0
  65. package/src/pi-base/paths.ts +47 -0
  66. package/src/pi-base/settings/body.ts +1648 -0
  67. package/src/pi-base/settings/fields/action.ts +43 -0
  68. package/src/pi-base/settings/fields/boolean.ts +47 -0
  69. package/src/pi-base/settings/fields/custom.ts +72 -0
  70. package/src/pi-base/settings/fields/enum.ts +310 -0
  71. package/src/pi-base/settings/fields/index.ts +46 -0
  72. package/src/pi-base/settings/fields/model.ts +452 -0
  73. package/src/pi-base/settings/fields/string.ts +527 -0
  74. package/src/pi-base/settings/fields/text.ts +115 -0
  75. package/src/pi-base/settings/frame.ts +197 -0
  76. package/src/pi-base/settings/index.ts +77 -0
  77. package/src/pi-base/settings/inline-edit.ts +313 -0
  78. package/src/pi-base/settings/modal.ts +152 -0
  79. package/src/pi-base/settings/types.ts +500 -0
  80. package/src/pi-base/settings/validate-field.ts +113 -0
  81. package/src/pi-base/shell.ts +117 -0
  82. package/src/pi-base/types.ts +6 -0
  83. package/src/pi-base/ui.ts +32 -0
  84. package/src/tools/recall.ts +347 -225
  85. package/src/types.ts +20 -3
  86. package/tsup.config.ts +23 -0
  87. package/vitest.config.ts +15 -15
@@ -8,212 +8,264 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
8
8
  import { copyTextToClipboard } from "../om/clipboard.js";
9
9
  import type { Runtime } from "../om/runtime.js";
10
10
  import {
11
- diffProjection,
12
- entryIndexForId,
13
- foldLedger,
14
- fullProjection,
15
- observationToSummaryLine,
16
- rawTokensAfterIndex,
17
- rawTokensSinceDropCoverage,
18
- rawTokensSinceLastCompaction,
19
- rawTokensSinceObservationCoverage,
20
- rawTokensSinceReflectionCoverage,
21
- reflectionToSummaryLine,
22
- visibleProjection,
23
- type Entry,
24
- type Projection,
11
+ diffProjection,
12
+ entryIndexForId,
13
+ foldLedger,
14
+ fullProjection,
15
+ observationToSummaryLine,
16
+ rawTokensAfterIndex,
17
+ rawTokensSinceDropCoverage,
18
+ rawTokensSinceLastCompaction,
19
+ rawTokensSinceObservationCoverage,
20
+ rawTokensSinceReflectionCoverage,
21
+ reflectionToSummaryLine,
22
+ visibleProjection,
23
+ type Entry,
24
+ type Projection,
25
25
  } from "../om/ledger/index.js";
26
26
  import { readPendingState } from "../om/pending.js";
27
+ import { isManualMode } from "../core/unified-config.js";
27
28
 
28
29
  function firstArg(args: unknown): string | undefined {
29
- if (Array.isArray(args)) return typeof args[0] === "string" ? args[0] : undefined;
30
- if (typeof args === "string") return args.trim().split(/\s+/)[0];
31
- if (args && typeof args === "object" && "mode" in args) {
32
- const mode = (args as { mode?: unknown }).mode;
33
- return typeof mode === "string" ? mode : undefined;
34
- }
35
- return undefined;
30
+ if (Array.isArray(args))
31
+ return typeof args[0] === "string" ? args[0] : undefined;
32
+ if (typeof args === "string") return args.trim().split(/\s+/)[0];
33
+ if (args && typeof args === "object" && "mode" in args) {
34
+ const mode = (args as { mode?: unknown }).mode;
35
+ return typeof mode === "string" ? mode : undefined;
36
+ }
37
+ return undefined;
36
38
  }
37
39
 
38
40
  function pct(current: number, total: number): number {
39
- return total > 0 ? Math.round((current / total) * 100) : 0;
41
+ return total > 0 ? Math.round((current / total) * 100) : 0;
40
42
  }
41
43
 
42
44
  function tokenSum(items: { tokenCount: number }[]): number {
43
- return items.reduce((sum, item) => sum + item.tokenCount, 0);
45
+ return items.reduce((sum, item) => sum + item.tokenCount, 0);
44
46
  }
45
47
 
46
48
  function addedSuffix(count: number): string | undefined {
47
- return count > 0 ? `+${count.toLocaleString()}` : undefined;
49
+ return count > 0 ? `+${count.toLocaleString()}` : undefined;
48
50
  }
49
51
 
50
52
  function removedSuffix(count: number): string | undefined {
51
- return count > 0 ? `-${count.toLocaleString()}` : undefined;
53
+ return count > 0 ? `-${count.toLocaleString()}` : undefined;
52
54
  }
53
55
 
54
- function appendSuffixes(line: string, suffixes: (string | undefined)[]): string {
55
- const rendered = suffixes.filter((s): s is string => s !== undefined);
56
- return rendered.length > 0 ? `${line} ${rendered.join(" ")}` : line;
56
+ function appendSuffixes(
57
+ line: string,
58
+ suffixes: (string | undefined)[],
59
+ ): string {
60
+ const rendered = suffixes.filter((s): s is string => s !== undefined);
61
+ return rendered.length > 0 ? `${line} ${rendered.join(" ")}` : line;
57
62
  }
58
63
 
59
- function renderList<T>(items: T[], render: (item: T) => string, empty: string): string {
60
- return items.length > 0 ? items.map(render).join("\n") : empty;
64
+ function renderList<T>(
65
+ items: T[],
66
+ render: (item: T) => string,
67
+ empty: string,
68
+ ): string {
69
+ return items.length > 0 ? items.map(render).join("\n") : empty;
61
70
  }
62
71
 
63
- function renderContentOnlyProjection(projection: Projection, emptyScope: "visible" | "recorded"): string {
64
- return [
65
- "── Reflections ──",
66
- renderList(projection.reflections, reflectionToSummaryLine, `No ${emptyScope} reflections.`),
67
- "",
68
- "── Observations ──",
69
- renderList(projection.observations, observationToSummaryLine, `No ${emptyScope} observations.`),
70
- ].join("\n");
72
+ function renderContentOnlyProjection(
73
+ projection: Projection,
74
+ emptyScope: "visible" | "recorded",
75
+ ): string {
76
+ return [
77
+ "── Reflections ──",
78
+ renderList(
79
+ projection.reflections,
80
+ reflectionToSummaryLine,
81
+ `No ${emptyScope} reflections.`,
82
+ ),
83
+ "",
84
+ "── Observations ──",
85
+ renderList(
86
+ projection.observations,
87
+ observationToSummaryLine,
88
+ `No ${emptyScope} observations.`,
89
+ ),
90
+ ].join("\n");
71
91
  }
72
92
 
73
- export function registerMemoryCommand(pi: ExtensionAPI, runtime: Runtime): void {
74
- pi.registerCommand("blackhole-memory", {
75
- description: "Show memory pipeline status & token counters. /blackhole-memory [view] visible observations & reflections, [full] complete recorded memory (copies to clipboard).",
76
- handler: async (args, ctx) => {
77
- runtime.ensureConfig(ctx.cwd, (msg) => ctx.ui?.notify?.(msg, "warning"));
78
- const entries = ctx.sessionManager.getBranch() as Entry[];
79
- const sessionId = ctx.sessionManager.getSessionId();
80
- const mode = firstArg(args);
81
-
82
- // /blackhole-memory full show full recorded memory + copy to clipboard
83
- if (mode === "full") {
84
- const projection = fullProjection(entries);
85
- const output = renderContentOnlyProjection(projection, "recorded");
86
- const copied = await copyTextToClipboard(output).catch(() => false);
87
- ctx.ui.notify(
88
- copied ? `${output}\n\nCopied to clipboard.` : `${output}\n\nFailed to copy to clipboard.`,
89
- "info",
90
- );
91
- return;
92
- }
93
-
94
- // /blackhole-memory view — show visible memory + copy to clipboard
95
- if (mode === "view") {
96
- const projection = visibleProjection(entries);
97
- const output = renderContentOnlyProjection(projection, "visible");
98
- const copied = await copyTextToClipboard(output).catch(() => false);
99
- ctx.ui.notify(
100
- copied ? `${output}\n\nCopied to clipboard.` : `${output}\n\nFailed to copy to clipboard.`,
101
- "info",
102
- );
103
- return;
104
- }
105
-
106
- // /blackhole-memory (no args) — show status
107
- if (mode && mode !== "status") {
108
- ctx.ui.notify("Usage: /blackhole-memory [status|view|full]", "info");
109
- return;
110
- }
111
-
112
- const folded = foldLedger(entries);
113
- const visible = visibleProjection(entries);
114
- const full = fullProjection(entries);
115
- const drift = diffProjection(visible, full);
116
-
117
- const visibleObservationTokens = tokenSum(visible.observations);
118
- const visibleReflectionTokens = tokenSum(visible.reflections);
119
- const observationLine = appendSuffixes(
120
- `Observations: ${folded.observations.length} recorded / ${folded.droppedObservationIds.size} dropped / ${visible.observations.length} visible`,
121
- [
122
- addedSuffix(drift.observationsOnlyInFull.length),
123
- removedSuffix(drift.droppedOnlyInFull.length),
124
- ],
125
- );
126
- const reflectionLine = appendSuffixes(
127
- `Reflections: ${folded.reflections.length} recorded / ${visible.reflections.length} visible`,
128
- [addedSuffix(drift.reflectionsOnlyInFull.length)],
129
- );
130
- let obsProgress = rawTokensSinceObservationCoverage(entries);
131
- let reflectionProgress = rawTokensSinceReflectionCoverage(entries);
132
- let dropProgress = rawTokensSinceDropCoverage(entries);
133
- const compactionProgress = rawTokensSinceLastCompaction(entries);
134
-
135
- // In noAutoCompact mode, pending coversUpToId entries act as virtual coverage markers
136
- // that aren't reflected in the branch. Adjust accumulated counts accordingly.
137
- if (runtime.config.noAutoCompact) {
138
- const pending = readPendingState(sessionId);
139
- if (pending.observation?.coversUpToId) {
140
- const idx = entryIndexForId(entries, pending.observation.coversUpToId);
141
- if (idx >= 0) obsProgress = rawTokensAfterIndex(entries, idx);
142
- }
143
- if (pending.reflection?.coversUpToId) {
144
- const idx = entryIndexForId(entries, pending.reflection.coversUpToId);
145
- if (idx >= 0) reflectionProgress = rawTokensAfterIndex(entries, idx);
146
- }
147
- if (pending.dropped?.coversUpToId) {
148
- const idx = entryIndexForId(entries, pending.dropped.coversUpToId);
149
- if (idx >= 0) dropProgress = rawTokensAfterIndex(entries, idx);
150
- }
151
- }
152
-
153
- const passiveLines = runtime.config.passive === true
154
- ? [
155
- "── Mode ──",
156
- "Passive: automatic memory workers and auto-compaction disabled",
157
- "",
158
- ]
159
- : [];
160
-
161
- const lines = [
162
- ...passiveLines,
163
- "── Memory ──",
164
- observationLine,
165
- reflectionLine,
166
- "",
167
- "── Pipeline ──",
168
- "Transcript accumulated since last run. Triggers when exceeding threshold.",
169
- `Observer: ~${obsProgress.toLocaleString()} tokens (triggers at ${runtime.config.observeAfterTokens.toLocaleString()})`,
170
- `Reflector: ~${reflectionProgress.toLocaleString()} tokens (triggers at ${runtime.config.reflectAfterTokens.toLocaleString()})`,
171
- `Dropper: ~${dropProgress.toLocaleString()} tokens (triggers at ${runtime.config.reflectAfterTokens.toLocaleString()})`,
172
- `Compaction: ~${compactionProgress.toLocaleString()} tokens` + (runtime.config.noAutoCompact ? " [auto-disabled]" : ` (triggers at ${runtime.config.compactAfterTokens.toLocaleString()})`),
173
- `Obs pool: ~${visibleObservationTokens.toLocaleString()} / ${runtime.config.observationsPoolMaxTokens.toLocaleString()} tokens (${pct(visibleObservationTokens, runtime.config.observationsPoolMaxTokens)}%)`,
174
- `Reflect pool: ~${visibleReflectionTokens.toLocaleString()} tokens`,
175
- ];
176
-
177
- // Show pending data when noAutoCompact is active
178
- if (runtime.config.noAutoCompact) {
179
- const pending = readPendingState(sessionId);
180
- const hasObs = !!pending.observation;
181
- const hasRef = !!pending.reflection;
182
- const hasDrop = !!pending.dropped;
183
- if (hasObs || hasRef || hasDrop) {
184
- lines.push("", "── Pending (noAutoCompact) ──");
185
- if (hasObs) lines.push("Observation: waiting in pending.json");
186
- if (hasRef) lines.push("Reflection: waiting in pending.json");
187
- if (hasDrop) lines.push("Dropper: waiting in pending.json");
188
- const preambleCap = runtime.config.observerPreambleMaxTokens > 0
189
- ? runtime.config.observerPreambleMaxTokens
190
- : Math.round(runtime.config.observerChunkMaxTokens * 0.3);
191
- const pctNote = runtime.config.observerPreambleMaxTokens > 0
192
- ? ""
193
- : ` (30% of ${runtime.config.observerChunkMaxTokens.toLocaleString()} chunk)`;
194
- lines.push(`Preamble cap: ${preambleCap.toLocaleString()} tokens for observations${pctNote}`);
195
- lines.push("Run /blackhole to flush and compact.");
196
- }
197
- }
198
-
199
- if (runtime.consolidationInFlight || runtime.compactInFlight || runtime.compactHookInFlight) {
200
- lines.push("", "── In flight ──");
201
- if (runtime.consolidationInFlight) {
202
- const phase = runtime.consolidationPhase ? ` (${runtime.consolidationPhase})` : "";
203
- lines.push(`Consolidation: running${phase}`);
204
- }
205
- if (runtime.compactInFlight) lines.push("Auto-compaction: running");
206
- if (runtime.compactHookInFlight) lines.push("Compaction hook: running");
207
- }
208
-
209
- if (runtime.lastObserverError || runtime.lastReflectorError || runtime.lastDropperError) {
210
- lines.push("", "── Last error ──");
211
- if (runtime.lastObserverError) lines.push(`Observer: ${runtime.lastObserverError}`);
212
- if (runtime.lastReflectorError) lines.push(`Reflector: ${runtime.lastReflectorError}`);
213
- if (runtime.lastDropperError) lines.push(`Dropper: ${runtime.lastDropperError}`);
214
- }
215
-
216
- ctx.ui.notify(lines.join("\n"), "info");
217
- },
218
- });
93
+ export function registerMemoryCommand(
94
+ pi: ExtensionAPI,
95
+ runtime: Runtime,
96
+ ): void {
97
+ pi.registerCommand("blackhole-memory", {
98
+ description:
99
+ "Show memory pipeline status & token counters. /blackhole-memory [view] visible observations & reflections, [full] complete recorded memory (copies to clipboard).",
100
+ handler: async (args, ctx) => {
101
+ runtime.ensureConfig(ctx.cwd, (msg) => ctx.ui?.notify?.(msg, "warning"));
102
+ const entries = ctx.sessionManager.getBranch() as Entry[];
103
+ const sessionId = ctx.sessionManager.getSessionId();
104
+ const mode = firstArg(args);
105
+
106
+ // /blackhole-memory full show full recorded memory + copy to clipboard
107
+ if (mode === "full") {
108
+ const projection = fullProjection(entries);
109
+ const output = renderContentOnlyProjection(projection, "recorded");
110
+ const copied = await copyTextToClipboard(output).catch(() => false);
111
+ ctx.ui.notify(
112
+ copied
113
+ ? `${output}\n\nCopied to clipboard.`
114
+ : `${output}\n\nFailed to copy to clipboard.`,
115
+ "info",
116
+ );
117
+ return;
118
+ }
119
+
120
+ // /blackhole-memory view show visible memory + copy to clipboard
121
+ if (mode === "view") {
122
+ const projection = visibleProjection(entries);
123
+ const output = renderContentOnlyProjection(projection, "visible");
124
+ const copied = await copyTextToClipboard(output).catch(() => false);
125
+ ctx.ui.notify(
126
+ copied
127
+ ? `${output}\n\nCopied to clipboard.`
128
+ : `${output}\n\nFailed to copy to clipboard.`,
129
+ "info",
130
+ );
131
+ return;
132
+ }
133
+
134
+ // /blackhole-memory (no args) — show status
135
+ if (mode && mode !== "status") {
136
+ ctx.ui.notify("Usage: /blackhole-memory [status|view|full]", "info");
137
+ return;
138
+ }
139
+
140
+ const folded = foldLedger(entries);
141
+ const visible = visibleProjection(entries);
142
+ const full = fullProjection(entries);
143
+ const drift = diffProjection(visible, full);
144
+
145
+ const visibleObservationTokens = tokenSum(visible.observations);
146
+ const visibleReflectionTokens = tokenSum(visible.reflections);
147
+ const observationLine = appendSuffixes(
148
+ `Observations: ${folded.observations.length} recorded / ${folded.droppedObservationIds.size} dropped / ${visible.observations.length} visible`,
149
+ [
150
+ addedSuffix(drift.observationsOnlyInFull.length),
151
+ removedSuffix(drift.droppedOnlyInFull.length),
152
+ ],
153
+ );
154
+ const reflectionLine = appendSuffixes(
155
+ `Reflections: ${folded.reflections.length} recorded / ${visible.reflections.length} visible`,
156
+ [addedSuffix(drift.reflectionsOnlyInFull.length)],
157
+ );
158
+ let obsProgress = rawTokensSinceObservationCoverage(entries);
159
+ let reflectionProgress = rawTokensSinceReflectionCoverage(entries);
160
+ let dropProgress = rawTokensSinceDropCoverage(entries);
161
+ const compactionProgress = rawTokensSinceLastCompaction(entries);
162
+
163
+ // In manual mode, pending coversUpToId entries act as virtual coverage markers
164
+ // that aren't reflected in the branch. Adjust accumulated counts accordingly.
165
+ if (isManualMode(runtime.config)) {
166
+ const pending = readPendingState(sessionId);
167
+ if (pending.observation?.coversUpToId) {
168
+ const idx = entryIndexForId(
169
+ entries,
170
+ pending.observation.coversUpToId,
171
+ );
172
+ if (idx >= 0) obsProgress = rawTokensAfterIndex(entries, idx);
173
+ }
174
+ if (pending.reflection?.coversUpToId) {
175
+ const idx = entryIndexForId(entries, pending.reflection.coversUpToId);
176
+ if (idx >= 0) reflectionProgress = rawTokensAfterIndex(entries, idx);
177
+ }
178
+ if (pending.dropped?.coversUpToId) {
179
+ const idx = entryIndexForId(entries, pending.dropped.coversUpToId);
180
+ if (idx >= 0) dropProgress = rawTokensAfterIndex(entries, idx);
181
+ }
182
+ }
183
+
184
+ const passiveLines =
185
+ runtime.config.passive === true
186
+ ? [
187
+ "── Mode ──",
188
+ "Passive: automatic memory workers and auto-compaction disabled",
189
+ "",
190
+ ]
191
+ : [];
192
+
193
+ const lines = [
194
+ ...passiveLines,
195
+ "── Memory ──",
196
+ observationLine,
197
+ reflectionLine,
198
+ "",
199
+ "── Pipeline ──",
200
+ "Transcript accumulated since last run. Triggers when exceeding threshold.",
201
+ `Observer: ~${obsProgress.toLocaleString()} tokens (triggers at ${runtime.config.observeAfterTokens.toLocaleString()})`,
202
+ `Reflector: ~${reflectionProgress.toLocaleString()} tokens (triggers at ${runtime.config.reflectAfterTokens.toLocaleString()})`,
203
+ `Dropper: pool ${pct(visibleObservationTokens, runtime.config.observationsPoolMaxTokens)}% prunes at ≥${Math.round(runtime.config.dropperPoolFullnessThreshold * 100)}% pool (${dropProgress.toLocaleString()}/${runtime.config.reflectAfterTokens.toLocaleString()} new tokens)`,
204
+ `Compaction: ~${compactionProgress.toLocaleString()} tokens` +
205
+ (isManualMode(runtime.config)
206
+ ? " [manual]"
207
+ : ` (triggers at ${runtime.config.compactAfterTokens.toLocaleString()})`),
208
+ `Obs pool: ~${visibleObservationTokens.toLocaleString()} / ${runtime.config.observationsPoolMaxTokens.toLocaleString()} tokens (${pct(visibleObservationTokens, runtime.config.observationsPoolMaxTokens)}%)`,
209
+ `Reflect pool: ~${visibleReflectionTokens.toLocaleString()} tokens`,
210
+ ];
211
+
212
+ // Show pending data when manual mode is active
213
+ if (isManualMode(runtime.config)) {
214
+ const pending = readPendingState(sessionId);
215
+ const hasObs = !!pending.observation;
216
+ const hasRef = !!pending.reflection;
217
+ const hasDrop = !!pending.dropped;
218
+ if (hasObs || hasRef || hasDrop) {
219
+ lines.push("", "── Pending (manual mode) ──");
220
+ if (hasObs) lines.push("Observation: waiting in pending.json");
221
+ if (hasRef) lines.push("Reflection: waiting in pending.json");
222
+ if (hasDrop) lines.push("Dropper: waiting in pending.json");
223
+ const preambleCap =
224
+ runtime.config.observerPreambleMaxTokens > 0
225
+ ? runtime.config.observerPreambleMaxTokens
226
+ : Math.round(runtime.config.observerChunkMaxTokens * 0.3);
227
+ const pctNote =
228
+ runtime.config.observerPreambleMaxTokens > 0
229
+ ? ""
230
+ : ` (30% of ${runtime.config.observerChunkMaxTokens.toLocaleString()} chunk)`;
231
+ lines.push(
232
+ `Preamble cap: ${preambleCap.toLocaleString()} tokens for observations${pctNote}`,
233
+ );
234
+ lines.push("Run /blackhole to flush and compact.");
235
+ }
236
+ }
237
+
238
+ if (
239
+ runtime.consolidationInFlight ||
240
+ runtime.compactInFlight ||
241
+ runtime.compactHookInFlight
242
+ ) {
243
+ lines.push("", "── In flight ──");
244
+ if (runtime.consolidationInFlight) {
245
+ const phase = runtime.consolidationPhase
246
+ ? ` (${runtime.consolidationPhase})`
247
+ : "";
248
+ lines.push(`Consolidation: running${phase}`);
249
+ }
250
+ if (runtime.compactInFlight) lines.push("Auto-compaction: running");
251
+ if (runtime.compactHookInFlight) lines.push("Compaction hook: running");
252
+ }
253
+
254
+ if (
255
+ runtime.lastObserverError ||
256
+ runtime.lastReflectorError ||
257
+ runtime.lastDropperError
258
+ ) {
259
+ lines.push("", "── Last error ──");
260
+ if (runtime.lastObserverError)
261
+ lines.push(`Observer: ${runtime.lastObserverError}`);
262
+ if (runtime.lastReflectorError)
263
+ lines.push(`Reflector: ${runtime.lastReflectorError}`);
264
+ if (runtime.lastDropperError)
265
+ lines.push(`Dropper: ${runtime.lastDropperError}`);
266
+ }
267
+
268
+ ctx.ui.notify(lines.join("\n"), "info");
269
+ },
270
+ });
219
271
  }