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/memory.ts
CHANGED
|
@@ -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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
41
|
+
return total > 0 ? Math.round((current / total) * 100) : 0;
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
function tokenSum(items: { tokenCount: number }[]): number {
|
|
43
|
-
|
|
45
|
+
return items.reduce((sum, item) => sum + item.tokenCount, 0);
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
function addedSuffix(count: number): string | undefined {
|
|
47
|
-
|
|
49
|
+
return count > 0 ? `+${count.toLocaleString()}` : undefined;
|
|
48
50
|
}
|
|
49
51
|
|
|
50
52
|
function removedSuffix(count: number): string | undefined {
|
|
51
|
-
|
|
53
|
+
return count > 0 ? `-${count.toLocaleString()}` : undefined;
|
|
52
54
|
}
|
|
53
55
|
|
|
54
|
-
function appendSuffixes(
|
|
55
|
-
|
|
56
|
-
|
|
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>(
|
|
60
|
-
|
|
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(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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(
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
|
|
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
|
}
|