@gmickel/gno 2.9.2 → 2.10.1
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 +2 -2
- package/assets/spa-production.json.gz +0 -0
- package/browser-extension/artifacts/{gno-browser-clipper-v2.9.2.zip → gno-browser-clipper-v2.10.1.zip} +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v2.10.1.zip.sha256 +1 -0
- package/browser-extension/dist/manifest.json +1 -1
- package/bunfig.toml +5 -1
- package/package.json +3 -2
- package/spec/cli.md +102 -12
- package/spec/mcp.md +1 -1
- package/spec/output-schemas/mcp-remove-result.schema.json +2 -2
- package/src/app/bun-runtime.ts +41 -0
- package/src/cli/commands/cleanup.ts +9 -1
- package/src/cli/commands/collection/remove.ts +50 -31
- package/src/cli/commands/collection/rename.ts +36 -8
- package/src/cli/commands/daemon.ts +3 -0
- package/src/cli/commands/doctor.ts +61 -0
- package/src/cli/commands/mcp/config-editors.ts +56 -8
- package/src/cli/commands/mcp/install.ts +159 -24
- package/src/cli/commands/mcp/launch-path.ts +87 -0
- package/src/cli/commands/mcp/paths.ts +30 -6
- package/src/cli/commands/mcp/repair-entry.ts +160 -0
- package/src/cli/commands/mcp/status.ts +55 -15
- package/src/cli/commands/mcp.ts +4 -0
- package/src/cli/commands/serve.ts +3 -0
- package/src/cli/commands/status.ts +24 -0
- package/src/cli/detach.ts +10 -0
- package/src/cli/program.ts +64 -19
- package/src/cli/runtime-health.ts +328 -0
- package/src/core/network-boundary-inventory.ts +16 -0
- package/src/ingestion/file-processor.ts +118 -17
- package/src/ingestion/prepare-worker.ts +20 -2
- package/src/mcp/tools/remove-collection.ts +2 -1
- package/src/serve/public/components/BootstrapStatus.tsx +3 -3
- package/src/serve/public/globals.built.css +1 -1
- package/src/serve/routes/api.ts +3 -4
- package/src/serve/status.ts +2 -1
- package/src/store/sqlite/adapter.ts +17 -4
- package/browser-extension/artifacts/gno-browser-clipper-v2.9.2.zip.sha256 +0 -1
|
@@ -19,6 +19,14 @@
|
|
|
19
19
|
* If no backend can start, the file fails closed with
|
|
20
20
|
* ISOLATION_UNAVAILABLE; it is never prepared unbounded.
|
|
21
21
|
*
|
|
22
|
+
* A message that cannot be deserialized on the way to or from the processor
|
|
23
|
+
* (fn-213) fails the file with PROCESSOR_MESSAGE_FAILED and replaces the
|
|
24
|
+
* processor, and one stderr line names the direction, backend, file and
|
|
25
|
+
* step. Bun 1.4.0 and later report it as a `messageerror` event on the
|
|
26
|
+
* receiving side of a worker; over the child's IPC channel Bun closes the
|
|
27
|
+
* channel on both ends instead, so the child backend cannot tell which
|
|
28
|
+
* direction failed.
|
|
29
|
+
*
|
|
22
30
|
* One processor is reused across files (starting one costs about a third of
|
|
23
31
|
* a second of module loading). It is replaced after an overrun, after a
|
|
24
32
|
* file that leaves memory above half the budget, and after a short idle
|
|
@@ -78,8 +86,26 @@ export interface ReadyMessage {
|
|
|
78
86
|
type: "ready";
|
|
79
87
|
}
|
|
80
88
|
|
|
89
|
+
/** Posted by the worker when a job it was sent could not be deserialized. */
|
|
90
|
+
export interface UndeliverableJobMessage {
|
|
91
|
+
type: "undeliverable-job";
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Which message could not be delivered; the child backend cannot tell. */
|
|
95
|
+
export type DeliveryDirection = "job" | "result" | "unknown";
|
|
96
|
+
|
|
97
|
+
/** Why a processor stopped working. */
|
|
98
|
+
interface ProcessorFailure {
|
|
99
|
+
reason: string;
|
|
100
|
+
/** Set when a message could not be delivered. */
|
|
101
|
+
delivery?: DeliveryDirection;
|
|
102
|
+
}
|
|
103
|
+
|
|
81
104
|
export type FileProcessorBackendKind = "worker" | "child";
|
|
82
105
|
|
|
106
|
+
/** Error code of a file whose message to or from the processor was lost. */
|
|
107
|
+
export const PROCESSOR_MESSAGE_FAILED = "PROCESSOR_MESSAGE_FAILED";
|
|
108
|
+
|
|
83
109
|
/** Error codes of a file stopped at its budget. */
|
|
84
110
|
export const BUDGET_ERROR_CODES: ReadonlySet<string> = new Set([
|
|
85
111
|
"TIMEOUT",
|
|
@@ -149,7 +175,7 @@ interface Processor {
|
|
|
149
175
|
/** Settles once the processor has loaded and can take a file. */
|
|
150
176
|
readonly ready: Promise<void>;
|
|
151
177
|
/** Settles, with a reason, if the processor dies or errors. */
|
|
152
|
-
readonly failed: Promise<
|
|
178
|
+
readonly failed: Promise<ProcessorFailure>;
|
|
153
179
|
/** Send one file; its messages go to `onMessage`. */
|
|
154
180
|
run(
|
|
155
181
|
request: PrepareFileRequest,
|
|
@@ -164,6 +190,8 @@ interface Processor {
|
|
|
164
190
|
/** Child process id (child backend), for shutdown and tests. */
|
|
165
191
|
readonly pid: number | null;
|
|
166
192
|
stop(): void;
|
|
193
|
+
/** Tests only: raise the event Bun raises for an undeliverable message. */
|
|
194
|
+
simulateUndeliverable?(direction: "job" | "result"): void;
|
|
167
195
|
}
|
|
168
196
|
|
|
169
197
|
const phaseName = (index: number): string => PREPARE_PHASES[index] ?? "startup";
|
|
@@ -176,25 +204,38 @@ function startWorker(): Processor {
|
|
|
176
204
|
const phaseSlot = new Int32Array(new SharedArrayBuffer(4));
|
|
177
205
|
let onMessage: ((message: FileWorkerMessage) => void) | null = null;
|
|
178
206
|
let markReady: () => void = () => undefined;
|
|
179
|
-
let markFailed: (
|
|
207
|
+
let markFailed: (failure: ProcessorFailure) => void = () => undefined;
|
|
180
208
|
let dead = false;
|
|
181
209
|
const ready = new Promise<void>((resolve) => {
|
|
182
210
|
markReady = resolve;
|
|
183
211
|
});
|
|
184
|
-
const failed = new Promise<
|
|
185
|
-
markFailed = (
|
|
212
|
+
const failed = new Promise<ProcessorFailure>((resolve) => {
|
|
213
|
+
markFailed = (failure) => {
|
|
186
214
|
dead = true;
|
|
187
|
-
resolve(
|
|
215
|
+
resolve(failure);
|
|
188
216
|
};
|
|
189
217
|
});
|
|
190
218
|
worker.onmessage = (
|
|
191
|
-
event: MessageEvent<
|
|
219
|
+
event: MessageEvent<
|
|
220
|
+
FileWorkerMessage | ReadyMessage | UndeliverableJobMessage
|
|
221
|
+
>
|
|
192
222
|
) => {
|
|
193
223
|
if (event.data.type === "ready") markReady();
|
|
194
|
-
else
|
|
224
|
+
else if (event.data.type === "undeliverable-job") {
|
|
225
|
+
markFailed({
|
|
226
|
+
reason: "the file worker could not deserialize its job",
|
|
227
|
+
delivery: "job",
|
|
228
|
+
});
|
|
229
|
+
} else onMessage?.(event.data);
|
|
230
|
+
};
|
|
231
|
+
worker.onmessageerror = () => {
|
|
232
|
+
markFailed({
|
|
233
|
+
reason: "a message from the file worker could not be deserialized",
|
|
234
|
+
delivery: "result",
|
|
235
|
+
});
|
|
195
236
|
};
|
|
196
237
|
worker.onerror = (event: ErrorEvent) => {
|
|
197
|
-
markFailed(event.message || "file worker failed");
|
|
238
|
+
markFailed({ reason: event.message || "file worker failed" });
|
|
198
239
|
};
|
|
199
240
|
return {
|
|
200
241
|
kind: "worker",
|
|
@@ -212,7 +253,12 @@ function startWorker(): Processor {
|
|
|
212
253
|
pid: null,
|
|
213
254
|
stop() {
|
|
214
255
|
worker.terminate();
|
|
215
|
-
markFailed("file processor was stopped");
|
|
256
|
+
markFailed({ reason: "file processor was stopped" });
|
|
257
|
+
},
|
|
258
|
+
simulateUndeliverable(direction) {
|
|
259
|
+
if (direction === "result") {
|
|
260
|
+
worker.dispatchEvent(new MessageEvent("messageerror"));
|
|
261
|
+
} else worker.postMessage({ simulateUndeliverable: true });
|
|
216
262
|
},
|
|
217
263
|
};
|
|
218
264
|
}
|
|
@@ -267,12 +313,21 @@ function startChild(): Processor {
|
|
|
267
313
|
child.unref();
|
|
268
314
|
liveChildren.add(child);
|
|
269
315
|
ensureShutdownHooks();
|
|
270
|
-
const failed = child.exited.then((code) => {
|
|
316
|
+
const failed = child.exited.then((code): ProcessorFailure => {
|
|
271
317
|
exited = true;
|
|
272
318
|
liveChildren.delete(child);
|
|
273
|
-
return
|
|
274
|
-
|
|
275
|
-
|
|
319
|
+
if (stopping) return { reason: "file processor was stopped" };
|
|
320
|
+
// Bun closes the channel on both ends when either side receives a
|
|
321
|
+
// message it cannot deserialize. The child exits cleanly only once its
|
|
322
|
+
// channel is gone (file-child.ts), and a crash never exits cleanly; the
|
|
323
|
+
// exit can be reported before Bun's own disconnect callback.
|
|
324
|
+
if (code === 0) {
|
|
325
|
+
return {
|
|
326
|
+
reason: "the IPC channel to the file processor child closed",
|
|
327
|
+
delivery: "unknown",
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
return { reason: `file processor child exited (${code})` };
|
|
276
331
|
});
|
|
277
332
|
return {
|
|
278
333
|
kind: "child",
|
|
@@ -367,6 +422,19 @@ export function activeFileProcessorPid(): number | null {
|
|
|
367
422
|
return processor?.pid ?? null;
|
|
368
423
|
}
|
|
369
424
|
|
|
425
|
+
/**
|
|
426
|
+
* Tests only: make the running worker processor raise the event Bun raises
|
|
427
|
+
* when a message cannot be deserialized (a real payload cannot be made to
|
|
428
|
+
* fail from JavaScript). False when no worker processor is running.
|
|
429
|
+
*/
|
|
430
|
+
export function simulateUndeliverableMessage(
|
|
431
|
+
direction: "job" | "result"
|
|
432
|
+
): boolean {
|
|
433
|
+
if (!processor?.simulateUndeliverable) return false;
|
|
434
|
+
processor.simulateUndeliverable(direction);
|
|
435
|
+
return true;
|
|
436
|
+
}
|
|
437
|
+
|
|
370
438
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
371
439
|
// Processor lifecycle
|
|
372
440
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -429,11 +497,30 @@ const awaitReady = (active: Processor): Promise<string | null> => {
|
|
|
429
497
|
});
|
|
430
498
|
return Promise.race([
|
|
431
499
|
active.ready.then(() => null),
|
|
432
|
-
active.failed,
|
|
500
|
+
active.failed.then((failure) => failure.reason),
|
|
433
501
|
timedOut,
|
|
434
502
|
]).finally(() => clearTimeout(timer));
|
|
435
503
|
};
|
|
436
504
|
|
|
505
|
+
/** Failure of a file whose message to or from the processor was lost. */
|
|
506
|
+
function undeliverableFailure(
|
|
507
|
+
request: PrepareFileRequest,
|
|
508
|
+
kind: FileProcessorBackendKind,
|
|
509
|
+
phase: string,
|
|
510
|
+
failure: ProcessorFailure
|
|
511
|
+
): PrepareFailure {
|
|
512
|
+
const direction = failure.delivery ?? "unknown";
|
|
513
|
+
const file = `${request.input.collection}/${request.input.relativePath}`;
|
|
514
|
+
console.error(
|
|
515
|
+
`gno: file processor message lost (direction ${direction}, backend ${kind}) for ${file} during ${phase}: ${failure.reason}; the processor was replaced`
|
|
516
|
+
);
|
|
517
|
+
return {
|
|
518
|
+
code: PROCESSOR_MESSAGE_FAILED,
|
|
519
|
+
message: `The file processor could not deliver a message during ${phase} (${failure.reason}); the processor was replaced`,
|
|
520
|
+
details: { direction, backend: kind, phase },
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
|
|
437
524
|
const runOne = async (
|
|
438
525
|
request: PrepareFileRequest,
|
|
439
526
|
budget: FileBudgetLimits,
|
|
@@ -511,9 +598,23 @@ const runOne = async (
|
|
|
511
598
|
const sampler = setInterval(() => {
|
|
512
599
|
void checkMemory();
|
|
513
600
|
}, MEMORY_SAMPLE_INTERVAL_MS);
|
|
514
|
-
void active.failed.then((
|
|
515
|
-
|
|
516
|
-
|
|
601
|
+
void active.failed.then((failure) => {
|
|
602
|
+
if (settled) return;
|
|
603
|
+
finish(
|
|
604
|
+
{
|
|
605
|
+
ok: false,
|
|
606
|
+
error: failure.delivery
|
|
607
|
+
? undeliverableFailure(
|
|
608
|
+
request,
|
|
609
|
+
active.kind,
|
|
610
|
+
active.phase(),
|
|
611
|
+
failure
|
|
612
|
+
)
|
|
613
|
+
: { code: "INTERNAL", message: failure.reason },
|
|
614
|
+
},
|
|
615
|
+
false
|
|
616
|
+
);
|
|
617
|
+
});
|
|
517
618
|
// A process already over the budget does not start another file.
|
|
518
619
|
const rss = process.memoryUsage.rss();
|
|
519
620
|
if (rss > budget.maxMemoryBytes) {
|
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
FileWorkerMessage,
|
|
9
9
|
FileWorkerRequest,
|
|
10
10
|
ReadyMessage,
|
|
11
|
+
UndeliverableJobMessage,
|
|
11
12
|
} from "./file-processor";
|
|
12
13
|
|
|
13
14
|
import { defaultChunker } from "./chunker";
|
|
@@ -16,14 +17,31 @@ import { PREPARE_PHASES, prepareFile } from "./prepare-file";
|
|
|
16
17
|
|
|
17
18
|
declare const self: Worker;
|
|
18
19
|
|
|
19
|
-
const post = (
|
|
20
|
+
const post = (
|
|
21
|
+
message: FileWorkerMessage | ReadyMessage | UndeliverableJobMessage
|
|
22
|
+
): void => {
|
|
20
23
|
self.postMessage(message);
|
|
21
24
|
};
|
|
22
25
|
|
|
23
|
-
|
|
26
|
+
// A job that cannot be deserialized never reaches onmessage (Bun 1.4.0+
|
|
27
|
+
// raises messageerror instead); tell the parent so the file fails at once.
|
|
28
|
+
// A listener, not `onmessageerror`: Bun's worker scope never calls that
|
|
29
|
+
// property.
|
|
30
|
+
self.addEventListener("messageerror", () => {
|
|
31
|
+
post({ type: "undeliverable-job" });
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
self.onmessage = async (
|
|
35
|
+
event: MessageEvent<FileWorkerRequest | { simulateUndeliverable: true }>
|
|
36
|
+
) => {
|
|
24
37
|
// Only the parent that created this worker may drive it; a Bun Worker sees
|
|
25
38
|
// its parent's messages with an empty origin.
|
|
26
39
|
if (event.origin !== "") return;
|
|
40
|
+
if ("simulateUndeliverable" in event.data) {
|
|
41
|
+
// Tests only (simulateUndeliverableMessage): raise the real event.
|
|
42
|
+
self.dispatchEvent(new MessageEvent("messageerror"));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
27
45
|
const { request, phaseSlot } = event.data;
|
|
28
46
|
const outcome = await prepareFile(request, {
|
|
29
47
|
convert: convertForPreparation,
|
|
@@ -21,6 +21,7 @@ interface RemoveCollectionResult {
|
|
|
21
21
|
removed: true;
|
|
22
22
|
collection: string;
|
|
23
23
|
configUpdated: true;
|
|
24
|
+
/** Documents are deleted; content, chunks and vectors stay until cleanup. */
|
|
24
25
|
indexedDataRetained: true;
|
|
25
26
|
note: string;
|
|
26
27
|
}
|
|
@@ -101,7 +102,7 @@ export function handleRemoveCollection(
|
|
|
101
102
|
collection: name,
|
|
102
103
|
configUpdated: true,
|
|
103
104
|
indexedDataRetained: true,
|
|
104
|
-
note: "
|
|
105
|
+
note: "Collection removed from config and its documents deleted from the index. Content, chunks and vectors stay until gno cleanup.",
|
|
105
106
|
};
|
|
106
107
|
|
|
107
108
|
return result;
|
|
@@ -65,8 +65,8 @@ export function BootstrapStatus({
|
|
|
65
65
|
|
|
66
66
|
return (
|
|
67
67
|
<section className="space-y-4">
|
|
68
|
-
<div className="flex items-start justify-between
|
|
69
|
-
<div>
|
|
68
|
+
<div className="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
|
|
69
|
+
<div className="min-w-0">
|
|
70
70
|
<div className="mb-2 flex items-center gap-2">
|
|
71
71
|
<PackageIcon className="size-4 text-primary" />
|
|
72
72
|
<h2 className="font-semibold text-2xl">Bootstrap & Storage</h2>
|
|
@@ -77,7 +77,7 @@ export function BootstrapStatus({
|
|
|
77
77
|
</p>
|
|
78
78
|
</div>
|
|
79
79
|
{missingModels > 0 && !bootstrap.models.downloading && (
|
|
80
|
-
<Button onClick={onDownloadModels} size="sm">
|
|
80
|
+
<Button className="self-start" onClick={onDownloadModels} size="sm">
|
|
81
81
|
<DownloadIcon className="mr-2 size-4" />
|
|
82
82
|
Download missing models
|
|
83
83
|
</Button>
|