@adhisang/minecraft-modding-mcp 7.0.0-rc.3 → 7.1.0
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/CHANGELOG.md +43 -0
- package/README.md +3 -2
- package/dist/cache-registry.d.ts +16 -0
- package/dist/cache-registry.js +78 -10
- package/dist/entry-tools/analyze-mod-service.d.ts +2 -2
- package/dist/entry-tools/analyze-symbol-service.d.ts +2 -2
- package/dist/entry-tools/batch-class-members-service.d.ts +3 -2
- package/dist/entry-tools/batch-class-members-service.js +20 -6
- package/dist/entry-tools/batch-class-source-service.d.ts +3 -2
- package/dist/entry-tools/batch-class-source-service.js +10 -0
- package/dist/entry-tools/compare-minecraft-service.d.ts +27 -4
- package/dist/entry-tools/compare-minecraft-service.js +65 -4
- package/dist/entry-tools/entry-tool-schema.d.ts +2 -2
- package/dist/entry-tools/inspect-minecraft-service.d.ts +2 -2
- package/dist/entry-tools/manage-cache-service.d.ts +2 -2
- package/dist/entry-tools/validate-project/cases/project-summary.js +71 -12
- package/dist/entry-tools/validate-project-service.d.ts +2 -2
- package/dist/index.js +37 -15
- package/dist/json-rpc-framing.d.ts +20 -0
- package/dist/json-rpc-framing.js +80 -7
- package/dist/mapping/loaders/tiny-maven.d.ts +9 -0
- package/dist/mapping/loaders/tiny-maven.js +10 -2
- package/dist/repo-downloader.js +13 -2
- package/dist/source/artifact-resolver.d.ts +14 -0
- package/dist/source/artifact-resolver.js +106 -12
- package/dist/source/class-source/members-builder.d.ts +7 -0
- package/dist/source/class-source/members-builder.js +4 -1
- package/dist/source/class-source.d.ts +9 -2
- package/dist/source/class-source.js +229 -26
- package/dist/source/indexer.js +69 -1
- package/dist/source/lifecycle/mapping-helpers.d.ts +20 -1
- package/dist/source/lifecycle/mapping-helpers.js +29 -3
- package/dist/source/lifecycle/runtime-check.d.ts +25 -0
- package/dist/source/lifecycle/runtime-check.js +68 -39
- package/dist/source/symbol-resolver.js +88 -0
- package/dist/source-jar-reader.d.ts +33 -0
- package/dist/source-jar-reader.js +58 -0
- package/dist/source-resolver.d.ts +7 -0
- package/dist/source-resolver.js +20 -5
- package/dist/source-service.d.ts +5 -0
- package/dist/source-service.js +7 -0
- package/dist/stdio-supervisor.js +193 -34
- package/dist/storage/db.d.ts +62 -2
- package/dist/storage/db.js +186 -21
- package/dist/storage/sqlite.d.ts +31 -1
- package/dist/storage/sqlite.js +125 -16
- package/dist/tool-guidance.js +4 -1
- package/dist/tool-schemas.d.ts +64 -52
- package/dist/tool-schemas.js +9 -7
- package/dist/types.d.ts +9 -0
- package/dist/v1-parity-schemas.js +36 -2
- package/dist/version-diff-service.d.ts +23 -0
- package/dist/version-diff-service.js +101 -0
- package/dist/version-service.d.ts +14 -0
- package/dist/version-service.js +45 -3
- package/dist/workspace-mapping-service.d.ts +8 -0
- package/dist/workspace-mapping-service.js +35 -7
- package/docs/README-ja.md +2 -0
- package/docs/tool-reference.md +55 -11
- package/package.json +1 -1
package/dist/stdio-supervisor.js
CHANGED
|
@@ -1928,8 +1928,10 @@ export class StdioSupervisor {
|
|
|
1928
1928
|
message: detail
|
|
1929
1929
|
});
|
|
1930
1930
|
});
|
|
1931
|
+
const key = requestKey(entry.pending.id);
|
|
1932
|
+
let released = false;
|
|
1933
|
+
let attemptedRelease = false;
|
|
1931
1934
|
this.runRecoveryStep("queue.dispatch_settle", () => {
|
|
1932
|
-
const key = requestKey(entry.pending.id);
|
|
1933
1935
|
// Only THIS instance may be settled here. A fault before forwardRequest
|
|
1934
1936
|
// installed anything leaves the request re-queued by its own no-child
|
|
1935
1937
|
// fallback (a later drain owns it) or already answered by the
|
|
@@ -1937,17 +1939,51 @@ export class StdioSupervisor {
|
|
|
1937
1939
|
// different live request, which keeps its own guarantee.
|
|
1938
1940
|
if (this.pendingRequests.get(key) !== entry.pending)
|
|
1939
1941
|
return;
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
this.writeSyntheticReply(entry.pending, {
|
|
1943
|
-
jsonrpc: "2.0",
|
|
1944
|
-
id: entry.pending.id,
|
|
1945
|
-
error: {
|
|
1946
|
-
code: -32603,
|
|
1947
|
-
message: `MCP supervisor failed to dispatch the queued request: ${detail}`
|
|
1948
|
-
}
|
|
1949
|
-
});
|
|
1942
|
+
attemptedRelease = true;
|
|
1943
|
+
released = this.releaseForwardedRequest(key, entry.pending);
|
|
1950
1944
|
});
|
|
1945
|
+
if (attemptedRelease && !released) {
|
|
1946
|
+
// releaseForwardedRequest deletes the entry and records its tombstone
|
|
1947
|
+
// BEFORE it returns, so a throw part-way through (recordFinalityTombstone's
|
|
1948
|
+
// only throw site today is the debug-log call during tombstone
|
|
1949
|
+
// eviction) can still have taken the id away from its entry. `released`
|
|
1950
|
+
// cannot tell "declined" apart from "threw after mutating", so the id
|
|
1951
|
+
// is read back rather than assumed — mirrors
|
|
1952
|
+
// answerFaultedWorkerResponse's worker_message.release_verify.
|
|
1953
|
+
//
|
|
1954
|
+
// Gated on `attemptedRelease`, not merely `!released`: the identity
|
|
1955
|
+
// check above (`pendingRequests.get(key) !== entry.pending`) returns
|
|
1956
|
+
// early, WITHOUT calling releaseForwardedRequest, whenever this entry
|
|
1957
|
+
// was never installed into pendingRequests to begin with — which is
|
|
1958
|
+
// exactly what happens when forwardRequest's no-child fallback re-queues
|
|
1959
|
+
// this same entry (queue not full) or already answered it itself (queue
|
|
1960
|
+
// full) before throwing later in that same fallback (e.g. from
|
|
1961
|
+
// scheduleRestart). In either of those cases the id is either still
|
|
1962
|
+
// waiting for a real dispatch that will answer it for real later, or
|
|
1963
|
+
// already answered — and this verify step cannot distinguish "never
|
|
1964
|
+
// installed" from "installed, then removed by a throwing release" using
|
|
1965
|
+
// `pendingRequests.has(key)` alone. Running it anyway would record a
|
|
1966
|
+
// spurious tombstone and send a synthetic reply for a request that gets
|
|
1967
|
+
// (or already got) a real one, i.e. two replies for the same id.
|
|
1968
|
+
this.runRecoveryStep("queue.dispatch_settle_verify", () => {
|
|
1969
|
+
if (entry.pending.method === "initialize" || this.pendingRequests.has(key))
|
|
1970
|
+
return;
|
|
1971
|
+
this.recordFinalityTombstone(key, entry.pending.mode);
|
|
1972
|
+
released = true;
|
|
1973
|
+
});
|
|
1974
|
+
}
|
|
1975
|
+
if (released) {
|
|
1976
|
+
this.runRecoveryStep("queue.dispatch_reply", () => {
|
|
1977
|
+
this.writeSyntheticReply(entry.pending, {
|
|
1978
|
+
jsonrpc: "2.0",
|
|
1979
|
+
id: entry.pending.id,
|
|
1980
|
+
error: {
|
|
1981
|
+
code: -32603,
|
|
1982
|
+
message: `MCP supervisor failed to dispatch the queued request: ${detail}`
|
|
1983
|
+
}
|
|
1984
|
+
});
|
|
1985
|
+
});
|
|
1986
|
+
}
|
|
1951
1987
|
return false;
|
|
1952
1988
|
}
|
|
1953
1989
|
}
|
|
@@ -1966,7 +2002,7 @@ export class StdioSupervisor {
|
|
|
1966
2002
|
}
|
|
1967
2003
|
catch (error) {
|
|
1968
2004
|
log("error", "supervisor.worker_spawn_throw", {
|
|
1969
|
-
message:
|
|
2005
|
+
message: describeThrown(error)
|
|
1970
2006
|
});
|
|
1971
2007
|
this.handleStartupFailure(token, { code: null, signal: null });
|
|
1972
2008
|
return;
|
|
@@ -2166,7 +2202,33 @@ export class StdioSupervisor {
|
|
|
2166
2202
|
// cap allows. That successor arms a fresh startup watchdog and, on
|
|
2167
2203
|
// ready, re-forwards the retained initialize (handleWorkerReady), so the
|
|
2168
2204
|
// handshake gets a second chance instead of stalling.
|
|
2205
|
+
//
|
|
2206
|
+
// Replacing the generation does not by itself terminalize any OTHER
|
|
2207
|
+
// request forwarded to the old child: when its `exit` eventually fires,
|
|
2208
|
+
// `handleWorkerExit` finds `this.child` already pointing at the
|
|
2209
|
+
// successor and takes its early-return branch, skipping
|
|
2210
|
+
// failPendingRequestsOnWorkerExit entirely. So that call runs here
|
|
2211
|
+
// first, mirroring the precedent at handleWorkerProcessError, and BEFORE
|
|
2212
|
+
// recoverTimedOutWorker replaces the generation. It does not touch the
|
|
2213
|
+
// retained `initialize` itself: failPendingRequestsOnWorkerExit carves
|
|
2214
|
+
// out `this.initializeRequest`'s key, which is exactly the entry this
|
|
2215
|
+
// lifecycle is about to continue on the successor.
|
|
2216
|
+
//
|
|
2217
|
+
// Split into two independent recovery steps, deliberately: these are two
|
|
2218
|
+
// unrelated effects (terminalizing OTHER stranded requests, and replacing
|
|
2219
|
+
// the generation so the retained initialize gets a second chance), and
|
|
2220
|
+
// they must not share a fault boundary. Before failPendingRequestsOnWorkerExit
|
|
2221
|
+
// existed here, recoverTimedOutWorker was the only statement in this step
|
|
2222
|
+
// and ran unconditionally on any path that reached it. Running both in one
|
|
2223
|
+
// `runRecoveryStep` would let a throw inside failPendingRequestsOnWorkerExit
|
|
2224
|
+
// (e.g. its own timerClearer call faulting for some OTHER pending
|
|
2225
|
+
// request's deadline timer) silently swallow the call to
|
|
2226
|
+
// recoverTimedOutWorker that follows it in the same callback — silently
|
|
2227
|
+
// skipping the one guarantee this whole branch exists to provide.
|
|
2169
2228
|
if (pending.method === "initialize") {
|
|
2229
|
+
this.runRecoveryStep("worker_message.initialize_recovery_fail_pending", () => {
|
|
2230
|
+
this.failPendingRequestsOnWorkerExit({ code: null, signal: null });
|
|
2231
|
+
});
|
|
2170
2232
|
this.runRecoveryStep("worker_message.initialize_recovery", () => {
|
|
2171
2233
|
this.recoverTimedOutWorker();
|
|
2172
2234
|
});
|
|
@@ -2219,6 +2281,37 @@ export class StdioSupervisor {
|
|
|
2219
2281
|
return;
|
|
2220
2282
|
}
|
|
2221
2283
|
log("warn", "supervisor.worker_stdin_error", { message: error.message });
|
|
2284
|
+
// This event fires only on child.stdin, which exists only once the child
|
|
2285
|
+
// has actually been spawned, but that does NOT collapse to a single
|
|
2286
|
+
// "always post-ready" case: handleWorkerReady's legacy-era replay of a
|
|
2287
|
+
// retained `initialize` (era === "legacy" with `this.initializeRequest`
|
|
2288
|
+
// set) forwards it to a freshly spawned successor WITHOUT calling
|
|
2289
|
+
// adoptActiveChild first — adoptActiveChild only runs once that
|
|
2290
|
+
// initialize's response actually comes back, or on the no-replay early
|
|
2291
|
+
// return. So a live child can have `child.stdin` while `this.childReady`
|
|
2292
|
+
// is still false, mirroring the fork handleWorkerProcessError already
|
|
2293
|
+
// makes on `wasReady`. Treating that window as post-ready would run
|
|
2294
|
+
// failPendingRequestsOnWorkerExit, which deliberately carves the retained
|
|
2295
|
+
// initialize's pending entry OUT of what it fails (so a later id reuse of
|
|
2296
|
+
// the completed initialize is not wrongly caught) — leaving the client's
|
|
2297
|
+
// `initialize` answered by nothing and re-replayed against every
|
|
2298
|
+
// successor forever if the stdin fault persists. Before this recovery
|
|
2299
|
+
// existed at all, a broken stdin left `this.child` pointing at a worker
|
|
2300
|
+
// nothing could ever write to again: scheduleRestart's own `this.child`
|
|
2301
|
+
// guard made every later restart attempt a permanent no-op, and any
|
|
2302
|
+
// request already forwarded to this child had nothing left that would
|
|
2303
|
+
// ever answer it.
|
|
2304
|
+
const wasReady = this.childReady;
|
|
2305
|
+
this.invalidateCurrentChild(child);
|
|
2306
|
+
this.beginTreeTermination(child);
|
|
2307
|
+
if (!wasReady) {
|
|
2308
|
+
this.handleStartupFailure(this.attemptToken, { code: null, signal: null });
|
|
2309
|
+
}
|
|
2310
|
+
else {
|
|
2311
|
+
this.consecutiveImmediateStandDowns = 0;
|
|
2312
|
+
this.failPendingRequestsOnWorkerExit({ code: null, signal: null });
|
|
2313
|
+
this.scheduleRestart(true);
|
|
2314
|
+
}
|
|
2222
2315
|
}
|
|
2223
2316
|
/**
|
|
2224
2317
|
* Reassembles the worker's stderr into lines (the ready marker may be split
|
|
@@ -2379,13 +2472,17 @@ export class StdioSupervisor {
|
|
|
2379
2472
|
if (this.isInitializationResponse(message)) {
|
|
2380
2473
|
const id = getTrackedRequestId(message);
|
|
2381
2474
|
let initializeMode;
|
|
2475
|
+
let initializeKey;
|
|
2476
|
+
let initializePending;
|
|
2382
2477
|
if (id !== undefined) {
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2478
|
+
initializeKey = requestKey(id);
|
|
2479
|
+
initializePending = this.pendingRequests.get(initializeKey);
|
|
2480
|
+
initializeMode = initializePending?.mode;
|
|
2386
2481
|
}
|
|
2387
2482
|
initializeMode ??= this.modeForMessage(this.initializeRequest);
|
|
2388
2483
|
if ("error" in message) {
|
|
2484
|
+
if (initializeKey !== undefined)
|
|
2485
|
+
this.pendingRequests.delete(initializeKey);
|
|
2389
2486
|
if (!this.replayingInitialization && id !== undefined) {
|
|
2390
2487
|
this.writeSyntheticReply({ id, era: this.era, mode: initializeMode }, buildLegacyJsonRpcError(id));
|
|
2391
2488
|
const retainedIndex = this.queuedNotifications.findIndex((entry) => isRequest(entry) && requestKey(entry.id) === requestKey(id));
|
|
@@ -2401,6 +2498,8 @@ export class StdioSupervisor {
|
|
|
2401
2498
|
return;
|
|
2402
2499
|
}
|
|
2403
2500
|
if (this.replayingInitialization) {
|
|
2501
|
+
if (initializeKey !== undefined)
|
|
2502
|
+
this.pendingRequests.delete(initializeKey);
|
|
2404
2503
|
this.replayingInitialization = false;
|
|
2405
2504
|
if (this.initializedNotification) {
|
|
2406
2505
|
this.writeToWorker(child, this.initializedNotification);
|
|
@@ -2409,9 +2508,22 @@ export class StdioSupervisor {
|
|
|
2409
2508
|
this.flushQueue();
|
|
2410
2509
|
return;
|
|
2411
2510
|
}
|
|
2511
|
+
// Mark the entry as settling and remove it BEFORE writeToClient, so a
|
|
2512
|
+
// throw there (or in the diagnostic write reporting that throw) still
|
|
2513
|
+
// leaves `settlingWorkerResponse` for `answerUndeliveredWorkerResponse`
|
|
2514
|
+
// to answer this id from later — mirrors the non-initialize response
|
|
2515
|
+
// path below, which does the same for every other successful reply.
|
|
2516
|
+
// Without this, the entry was gone and no marker recorded it, so a
|
|
2517
|
+
// faulted client write here permanently lost the reply to `initialize`.
|
|
2518
|
+
if (initializeKey !== undefined && initializePending) {
|
|
2519
|
+
this.settlingWorkerResponse = { key: initializeKey, snapshot: initializePending };
|
|
2520
|
+
}
|
|
2521
|
+
if (initializeKey !== undefined)
|
|
2522
|
+
this.pendingRequests.delete(initializeKey);
|
|
2412
2523
|
this.clientInitialized = true;
|
|
2413
2524
|
this.adoptActiveChild();
|
|
2414
2525
|
this.writeToClient(message, initializeMode);
|
|
2526
|
+
this.settlingWorkerResponse = undefined;
|
|
2415
2527
|
this.flushQueue();
|
|
2416
2528
|
return;
|
|
2417
2529
|
}
|
|
@@ -2446,13 +2558,19 @@ export class StdioSupervisor {
|
|
|
2446
2558
|
// branch above, never here.
|
|
2447
2559
|
this.settlingWorkerResponse = { key, snapshot: pending };
|
|
2448
2560
|
}
|
|
2449
|
-
|
|
2450
|
-
|
|
2561
|
+
// These two clears run BEFORE timerClearer: a validate-project id must
|
|
2562
|
+
// not stay "running" if the timer clear below throws. answerUndelivered-
|
|
2563
|
+
// WorkerResponse (which later answers this id via settlingWorkerResponse
|
|
2564
|
+
// on a writeToClient/drainQueue fault) never touches these two fields,
|
|
2565
|
+
// so leaving them ordered after a throwing call would strand the
|
|
2566
|
+
// barrier and every validate-project admitted behind it, permanently.
|
|
2451
2567
|
if (pending?.toolName === "validate-project") {
|
|
2452
2568
|
this.runningValidateKey = undefined;
|
|
2453
2569
|
if (this.validateBarrierKey === key)
|
|
2454
2570
|
this.validateBarrierKey = undefined;
|
|
2455
2571
|
}
|
|
2572
|
+
if (pending?.deadlineTimer)
|
|
2573
|
+
this.timerClearer(pending.deadlineTimer);
|
|
2456
2574
|
}
|
|
2457
2575
|
}
|
|
2458
2576
|
this.writeToClient(message, responseMode);
|
|
@@ -2558,7 +2676,12 @@ export class StdioSupervisor {
|
|
|
2558
2676
|
// count to N and falsely escalate retryRecommendation to "report-bug".
|
|
2559
2677
|
const pendingToolNames = [];
|
|
2560
2678
|
for (const [key, pending] of this.pendingRequests.entries()) {
|
|
2561
|
-
|
|
2679
|
+
// Identity, not just id: a client may legally reuse `initialize`'s id
|
|
2680
|
+
// for a later request once initialize has completed. `initializeRequest`
|
|
2681
|
+
// (and so `preservedInitializeKey`) is retained past that point, so a
|
|
2682
|
+
// bare key match would treat the REUSED entry as the still-pending
|
|
2683
|
+
// initialize and skip it here — leaving it answered by nothing.
|
|
2684
|
+
if (key === preservedInitializeKey && pending.method === "initialize")
|
|
2562
2685
|
continue;
|
|
2563
2686
|
pendingToolNames.push(pending.toolName);
|
|
2564
2687
|
}
|
|
@@ -2566,23 +2689,59 @@ export class StdioSupervisor {
|
|
|
2566
2689
|
for (const [toolName, updated] of updatedByTool) {
|
|
2567
2690
|
this.recentRestarts.set(toolName, updated);
|
|
2568
2691
|
}
|
|
2692
|
+
// Each entry's cleanup and its reply run as two SEPARATE recovery steps,
|
|
2693
|
+
// not one bundled try around the whole loop body. Fault containment must
|
|
2694
|
+
// be per-entry, not just per-function: a throw from `this.timerClearer`
|
|
2695
|
+
// (or anything else in the cleanup half) for entry N must not abort the
|
|
2696
|
+
// `for` loop, or every entry after N in Map iteration order is left
|
|
2697
|
+
// completely unprocessed — not answered, and not cleared from
|
|
2698
|
+
// runningValidateKey/validateBarrierKey either. Since the old worker's
|
|
2699
|
+
// own `exit`/`error` handling short-circuits once `this.child` already
|
|
2700
|
+
// points at a successor generation, a request stranded that way here is
|
|
2701
|
+
// stranded permanently, and a stranded validate-project holding the
|
|
2702
|
+
// barrier keys would jam every later validate-project request behind it
|
|
2703
|
+
// forever. Splitting into two steps also means a fault in ONE entry's
|
|
2704
|
+
// cleanup cannot suppress that SAME entry's own reply.
|
|
2569
2705
|
for (const [key, pending] of [...this.pendingRequests.entries()]) {
|
|
2570
|
-
|
|
2706
|
+
// See the identical guard above: a bare key match would also wrongly
|
|
2707
|
+
// skip a request that legally reused the completed initialize's id.
|
|
2708
|
+
if (key === preservedInitializeKey && pending.method === "initialize")
|
|
2571
2709
|
continue;
|
|
2572
|
-
|
|
2573
|
-
this.
|
|
2574
|
-
|
|
2575
|
-
this.
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2710
|
+
this.runRecoveryStep("worker_exit.fail_pending_cleanup", () => {
|
|
2711
|
+
if (this.runningValidateKey === key)
|
|
2712
|
+
this.runningValidateKey = undefined;
|
|
2713
|
+
if (this.validateBarrierKey === key)
|
|
2714
|
+
this.validateBarrierKey = undefined;
|
|
2715
|
+
// Nil the field immediately after clearing, matching the convention
|
|
2716
|
+
// releaseForwardedRequest already uses. writeSyntheticReply (below,
|
|
2717
|
+
// in the SEPARATE fail_pending_reply step) also does
|
|
2718
|
+
// `if (pending.deadlineTimer) this.timerClearer(...)` before it
|
|
2719
|
+
// deletes the entry from pendingRequests. If this step cleared the
|
|
2720
|
+
// timer but left the field set, that second check would still be
|
|
2721
|
+
// true and writeSyntheticReply would attempt a REDUNDANT second
|
|
2722
|
+
// clear of the SAME already-cleared timer. A timerClearer that
|
|
2723
|
+
// faults on a repeat invocation for the same timer would then throw
|
|
2724
|
+
// BEFORE writeSyntheticReply's delete/tombstone — unlike a fault
|
|
2725
|
+
// strictly after deletion (which only loses that one reply), this
|
|
2726
|
+
// would leave the entry live in pendingRequests forever, with
|
|
2727
|
+
// nothing left to remove or answer it. Nilling here makes
|
|
2728
|
+
// writeSyntheticReply's own check false, so it never attempts that
|
|
2729
|
+
// second clear at all.
|
|
2730
|
+
if (pending.deadlineTimer) {
|
|
2731
|
+
this.timerClearer(pending.deadlineTimer);
|
|
2732
|
+
pending.deadlineTimer = undefined;
|
|
2733
|
+
}
|
|
2734
|
+
});
|
|
2735
|
+
this.runRecoveryStep("worker_exit.fail_pending_reply", () => {
|
|
2736
|
+
// Forwarded entries stay in pendingRequests until writeSyntheticReply
|
|
2737
|
+
// settles them (the FORWARDED pending is what entitles the id to a
|
|
2738
|
+
// finality tombstone). Cancelled entries are already gone — the
|
|
2739
|
+
// cancellation settled them terminally at admission.
|
|
2740
|
+
const toolName = pending.toolName ?? "unknown";
|
|
2741
|
+
const pruned = prunedByTool.get(toolName) ?? [];
|
|
2742
|
+
const { reply } = buildWorkerRestartReply(pending, exit, now, pruned, { structuredRestartDisabled: STRUCTURED_RESTART_DISABLED });
|
|
2743
|
+
this.writeSyntheticReply(pending, reply);
|
|
2744
|
+
});
|
|
2586
2745
|
}
|
|
2587
2746
|
}
|
|
2588
2747
|
/**
|
|
@@ -2688,7 +2847,7 @@ export class StdioSupervisor {
|
|
|
2688
2847
|
}
|
|
2689
2848
|
catch (error) {
|
|
2690
2849
|
this.eventWriter("warn", "supervisor.client_write_error", {
|
|
2691
|
-
message:
|
|
2850
|
+
message: describeThrown(error)
|
|
2692
2851
|
});
|
|
2693
2852
|
}
|
|
2694
2853
|
}
|
package/dist/storage/db.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import Database from "./sqlite.js";
|
|
1
|
+
import Database, { isRawSqliteCorruptionError } from "./sqlite.js";
|
|
2
2
|
import type { Config } from "../types.js";
|
|
3
|
+
export { isRawSqliteCorruptionError };
|
|
3
4
|
type SqliteDatabase = InstanceType<typeof Database>;
|
|
4
5
|
type Logger = {
|
|
5
6
|
warn: (message: string, details?: Record<string, unknown>) => void;
|
|
@@ -11,5 +12,64 @@ export interface InitializedDatabase {
|
|
|
11
12
|
schemaVersion: number;
|
|
12
13
|
}
|
|
13
14
|
type DatabaseConfig = Pick<Config, "sqlitePath"> & Partial<Pick<Config, "sqliteCacheKb" | "sqliteMmapSize">>;
|
|
15
|
+
/**
|
|
16
|
+
* Best-effort request that the NEXT `openDatabase` for `sqlitePath` run the
|
|
17
|
+
* full `PRAGMA integrity_check` instead of the default `quick_check`. Creates
|
|
18
|
+
* a new, uniquely-named request file (see the block comment above) with
|
|
19
|
+
* exclusive-create so it can never collide with or overwrite another
|
|
20
|
+
* pending request. Never throws: a failure to write the file only means the
|
|
21
|
+
* escalation is missed, which is logged rather than allowed to mask the
|
|
22
|
+
* caller's real error. Returns whether the file was actually written, so a
|
|
23
|
+
* caller that cannot schedule the follow-up check can say so instead of
|
|
24
|
+
* promising one.
|
|
25
|
+
*/
|
|
26
|
+
export declare function requestFullIntegrityCheck(sqlitePath: string, logger?: Logger): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Lists the full paths of every currently pending full-integrity-check
|
|
29
|
+
* request file for `sqlitePath`. Best-effort: a missing or unreadable
|
|
30
|
+
* directory is treated as "no requests pending" rather than as an error,
|
|
31
|
+
* since the fallback (a quick_check) is always safe to run.
|
|
32
|
+
*/
|
|
33
|
+
export declare function listIntegrityCheckRequestFiles(sqlitePath: string): string[];
|
|
34
|
+
/**
|
|
35
|
+
* Deletes EXACTLY the given request file paths - the ones an earlier
|
|
36
|
+
* `listIntegrityCheckRequestFiles` call returned, never a fresh listing - so
|
|
37
|
+
* a request file created after that decision point is never deleted by a
|
|
38
|
+
* clear that only ever meant to honor the requests it actually acted on.
|
|
39
|
+
* Best-effort per file: ENOENT (already gone) is ignored, anything else is
|
|
40
|
+
* logged, not thrown.
|
|
41
|
+
*
|
|
42
|
+
* Exported so a unit test can exercise the race guard directly:
|
|
43
|
+
* `openDatabase` lists-then-clears synchronously with no `await` in between,
|
|
44
|
+
* so there is no way to interleave a concurrent request file appearing
|
|
45
|
+
* between "decide" and "clear" from outside that call.
|
|
46
|
+
*/
|
|
47
|
+
export declare function clearIntegrityCheckRequestFiles(requestFiles: string[], logger?: Logger): void;
|
|
48
|
+
/**
|
|
49
|
+
* Wires a Database instance's corruption observer (see sqlite.ts) so that a
|
|
50
|
+
* raw corruption error thrown by ANY later statement on this handle - no
|
|
51
|
+
* matter what catches and wraps it further up the call stack, including a
|
|
52
|
+
* tool-specific error wrapper that never reaches runTool's own catch block -
|
|
53
|
+
* still schedules the next open's full integrity_check. Exported so a unit
|
|
54
|
+
* test can attach the same wiring to a Database instance opened directly
|
|
55
|
+
* (bypassing openDatabase's own open-time check, which would otherwise catch
|
|
56
|
+
* a test's injected corruption before a "successfully opened" handle ever
|
|
57
|
+
* existed to query against).
|
|
58
|
+
*/
|
|
59
|
+
export declare function attachRuntimeCorruptionObserver(db: SqliteDatabase, sqlitePath: string, logger?: Logger): void;
|
|
60
|
+
/**
|
|
61
|
+
* runTool catch-path helper: when `caughtError` is a raw SQLite corruption
|
|
62
|
+
* error observed while serving a tool call (not at open time - open-time
|
|
63
|
+
* corruption is already an AppError by the time it reaches here), this
|
|
64
|
+
* requests a full integrity check on the next open and returns a typed
|
|
65
|
+
* ERR_DB_FAILURE the caller should report instead of the raw error. Any other
|
|
66
|
+
* error is returned unchanged so non-corruption failures are unaffected.
|
|
67
|
+
*
|
|
68
|
+
* The marker request here is redundant with the Database corruption observer
|
|
69
|
+
* (attachRuntimeCorruptionObserver) for an UNWRAPPED raw error, since that
|
|
70
|
+
* observer already fired deeper in the call stack; it is kept because
|
|
71
|
+
* `convertRuntimeSqliteCorruption` is the only place with reliable access to
|
|
72
|
+
* whether scheduling succeeded, which decides which `nextAction` to publish.
|
|
73
|
+
*/
|
|
74
|
+
export declare function convertRuntimeSqliteCorruption(error: unknown, sqlitePath: string, logger?: Logger): unknown;
|
|
14
75
|
export declare function openDatabase(config: DatabaseConfig, logger?: Logger): InitializedDatabase;
|
|
15
|
-
export {};
|