@adhisang/minecraft-modding-mcp 7.0.0-rc.0 → 7.0.0-rc.2
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 +131 -49
- package/README.md +2 -0
- package/dist/cache-registry.d.ts +10 -0
- package/dist/cache-registry.js +44 -3
- package/dist/entry-tools/batch-class-members-service.js +8 -0
- package/dist/entry-tools/inspect-minecraft/handlers/class-members.js +21 -0
- package/dist/entry-tools/inspect-minecraft/internal.d.ts +4 -0
- package/dist/entry-tools/manage-cache-service.d.ts +2 -2
- package/dist/entry-tools/validate-project/cases/mixin.js +26 -6
- package/dist/entry-tools/validate-project/cases/project-summary.d.ts +7 -7
- package/dist/entry-tools/validate-project-service.d.ts +2 -2
- package/dist/entry-tools/verify-mixin-target-service.js +19 -1
- package/dist/error-mapping.d.ts +118 -0
- package/dist/error-mapping.js +177 -7
- package/dist/index.js +3 -1
- package/dist/json-rpc-framing.d.ts +53 -1
- package/dist/json-rpc-framing.js +190 -25
- package/dist/mapping/loaders/tiny-loom-selection.js +10 -3
- package/dist/mapping/parsers/tiny.js +4 -0
- package/dist/mapping-service.js +24 -3
- package/dist/maven-resolver.d.ts +61 -0
- package/dist/maven-resolver.js +95 -4
- package/dist/maven-token.d.ts +65 -0
- package/dist/maven-token.js +120 -0
- package/dist/mcp-helpers.js +14 -3
- package/dist/minecraft-explorer-service.d.ts +13 -1
- package/dist/minecraft-explorer-service.js +200 -7
- package/dist/repo-downloader.d.ts +260 -0
- package/dist/repo-downloader.js +1083 -14
- package/dist/source/artifact-resolver.d.ts +97 -3
- package/dist/source/artifact-resolver.js +228 -24
- package/dist/source/class-source.d.ts +15 -0
- package/dist/source/class-source.js +163 -18
- package/dist/source/indexer.js +14 -0
- package/dist/source/nested-jars.d.ts +16 -1
- package/dist/source/nested-jars.js +66 -3
- package/dist/source/workspace-target.js +39 -20
- package/dist/source-jar-reader.d.ts +30 -2
- package/dist/source-jar-reader.js +39 -8
- package/dist/source-resolver.d.ts +48 -0
- package/dist/source-resolver.js +735 -81
- package/dist/source-service.d.ts +10 -1
- package/dist/stdio-supervisor.d.ts +75 -1
- package/dist/stdio-supervisor.js +240 -14
- package/dist/storage/artifacts-repo.d.ts +2 -0
- package/dist/storage/artifacts-repo.js +22 -0
- package/dist/storage/db.js +32 -10
- package/dist/tool-guidance.js +17 -7
- package/dist/tool-schemas.d.ts +10 -2
- package/dist/tool-schemas.js +4 -0
- package/dist/v1-parity-schemas.js +16 -0
- package/dist/version-diff-service.js +25 -4
- package/dist/version-service.js +23 -8
- package/dist/warning-details.js +3 -1
- package/dist/workspace-mapping-service.d.ts +8 -1
- package/dist/workspace-mapping-service.js +28 -19
- package/docs/tool-reference.md +31 -7
- package/package.json +6 -4
package/dist/cache-registry.js
CHANGED
|
@@ -4,6 +4,7 @@ import { join, resolve } from "node:path";
|
|
|
4
4
|
import { mapWithConcurrencyLimit } from "./concurrency.js";
|
|
5
5
|
import { createError, ERROR_CODES } from "./errors.js";
|
|
6
6
|
import { normalizeOptionalPathForHost } from "./path-converter.js";
|
|
7
|
+
import { downloadSidecarPath, isDownloadSidecarPath } from "./repo-downloader.js";
|
|
7
8
|
import { openDatabase } from "./storage/db.js";
|
|
8
9
|
import { getProcessWorkspaceContextCache } from "./workspace-context-cache.js";
|
|
9
10
|
export const PUBLIC_CACHE_KINDS = [
|
|
@@ -226,7 +227,11 @@ function openDb(config) {
|
|
|
226
227
|
if (!existsSync(config.sqlitePath)) {
|
|
227
228
|
return undefined;
|
|
228
229
|
}
|
|
229
|
-
return openDatabase(
|
|
230
|
+
return openDatabase({
|
|
231
|
+
sqlitePath: config.sqlitePath,
|
|
232
|
+
sqliteCacheKb: config.sqliteCacheKb,
|
|
233
|
+
sqliteMmapSize: config.sqliteMmapSize
|
|
234
|
+
}).db;
|
|
230
235
|
}
|
|
231
236
|
function candidatePathsForEntry(entry) {
|
|
232
237
|
const paths = new Set();
|
|
@@ -467,15 +472,28 @@ async function fileBackedEntries(config, cacheKind, detectCorruption) {
|
|
|
467
472
|
}
|
|
468
473
|
const root = kindRoot(config, cacheKind);
|
|
469
474
|
const files = await listFilesRecursive(root);
|
|
470
|
-
|
|
475
|
+
// A download sidecar (`<jar>.cache.json`, or the `.<hex>.tmp` leftover of an
|
|
476
|
+
// interrupted write) is the identity record of the jar beside it, not a cached
|
|
477
|
+
// artifact in its own right. Listing one would report a `downloads` entry whose
|
|
478
|
+
// jarPath is a JSON file and would let a jarPath selector delete a description
|
|
479
|
+
// instead of the thing described. The finished record's bytes are folded into
|
|
480
|
+
// the jar's entry below, so a listed entry weighs everything that belongs to
|
|
481
|
+
// it; bytes that describe nothing - an orphan record, a half-written one - are
|
|
482
|
+
// deliberately unaccounted, because there is no entry for them to belong to.
|
|
483
|
+
// Only this kind names files that way; every other kind keeps every file.
|
|
484
|
+
const entryFiles = cacheKind === "downloads"
|
|
485
|
+
? files.filter((filePath) => !isDownloadSidecarPath(filePath))
|
|
486
|
+
: files;
|
|
487
|
+
return mapWithConcurrencyLimit(entryFiles, CACHE_STAT_CONCURRENCY, async (filePath) => {
|
|
471
488
|
const fileStat = await stat(filePath);
|
|
489
|
+
const sidecarBytes = cacheKind === "downloads" ? await downloadSidecarSizeBytes(filePath) : 0;
|
|
472
490
|
const normalizedEntryId = filePath.slice(root.length + 1);
|
|
473
491
|
const inferredScope = inferScope(filePath, normalizedEntryId) ?? (cacheKind === "decompiled-source" ? "vanilla" : undefined);
|
|
474
492
|
return {
|
|
475
493
|
cacheKind,
|
|
476
494
|
entryId: normalizedEntryId,
|
|
477
495
|
path: filePath,
|
|
478
|
-
sizeBytes: fileStat.size,
|
|
496
|
+
sizeBytes: fileStat.size + sidecarBytes,
|
|
479
497
|
status: "healthy",
|
|
480
498
|
meta: {
|
|
481
499
|
updatedAt: fileStat.mtime.toISOString(),
|
|
@@ -483,6 +501,8 @@ async function fileBackedEntries(config, cacheKind, detectCorruption) {
|
|
|
483
501
|
mapping: inferMapping(filePath, normalizedEntryId),
|
|
484
502
|
scope: inferredScope,
|
|
485
503
|
projectPath: inferProjectPath(filePath, config.pathRuntimeInfo),
|
|
504
|
+
// Health follows the cached artifact's own bytes: a zero-byte jar stays
|
|
505
|
+
// partial no matter how much its sidecar weighs.
|
|
486
506
|
partial: fileStat.size === 0,
|
|
487
507
|
corrupt: cacheKind === "registry" && detectCorruption ? await isCorruptRegistryJson(filePath) : false,
|
|
488
508
|
inUse: filePath.endsWith(".lock") ||
|
|
@@ -495,6 +515,19 @@ async function fileBackedEntries(config, cacheKind, detectCorruption) {
|
|
|
495
515
|
};
|
|
496
516
|
});
|
|
497
517
|
}
|
|
518
|
+
/**
|
|
519
|
+
* Bytes of the sidecar describing `downloadPath`, or 0 when there is none.
|
|
520
|
+
* A download cached before sidecars existed, or one whose sidecar write failed,
|
|
521
|
+
* is a normal state and must not fail the inventory.
|
|
522
|
+
*/
|
|
523
|
+
async function downloadSidecarSizeBytes(downloadPath) {
|
|
524
|
+
try {
|
|
525
|
+
return (await stat(downloadSidecarPath(downloadPath))).size;
|
|
526
|
+
}
|
|
527
|
+
catch {
|
|
528
|
+
return 0;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
498
531
|
/**
|
|
499
532
|
* Binary-remap cache entries are keyed by the final artifact id even when the
|
|
500
533
|
* on-disk entry is a corrupt final directory or a leftover temp path.
|
|
@@ -661,6 +694,14 @@ export function createCacheRegistry(config) {
|
|
|
661
694
|
workspaceCache.invalidate(entry.entryId);
|
|
662
695
|
continue;
|
|
663
696
|
}
|
|
697
|
+
if (entry.cacheKind === "downloads") {
|
|
698
|
+
// The sidecar is part of this entry, so it goes with the jar —
|
|
699
|
+
// outside the existsSync guard below, so a jar that vanished
|
|
700
|
+
// out-of-band since the listing still takes its sidecar with it
|
|
701
|
+
// instead of leaving an orphan behind. `force` makes a missing
|
|
702
|
+
// sidecar a no-op.
|
|
703
|
+
await rm(downloadSidecarPath(entry.path), { force: true });
|
|
704
|
+
}
|
|
664
705
|
if (existsSync(entry.path)) {
|
|
665
706
|
// Only binary-remap inventory can return directories as entries;
|
|
666
707
|
// other file-backed kinds keep their existing file-only contract.
|
|
@@ -48,6 +48,14 @@ export class BatchClassMembersService {
|
|
|
48
48
|
}
|
|
49
49
|
const raw = (await this.deps.getClassMembers({
|
|
50
50
|
artifactId: sharedArtifact.artifactId,
|
|
51
|
+
// This artifactId is OURS, not the caller's: the shared target was
|
|
52
|
+
// resolved above and every entry is dispatched by the result. Without
|
|
53
|
+
// saying so, get-class-members reads the bare presence of an
|
|
54
|
+
// artifactId as the caller having named the artifact, and reports a
|
|
55
|
+
// missing binary jar as their mistake - once per entry - though
|
|
56
|
+
// `target` here cannot name an artifact at all. Only a jar the caller
|
|
57
|
+
// named themselves is genuinely their choice.
|
|
58
|
+
artifactSelectedBy: input.target.kind === "jar" ? "caller" : "tool",
|
|
51
59
|
className: entry.className,
|
|
52
60
|
access: entry.access,
|
|
53
61
|
includeSynthetic: entry.includeSynthetic,
|
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
import { buildEntryToolResult, buildEntryToolMeta, createNextAction, createSummarySubject, createTruncationMeta } from "../../response-contract.js";
|
|
2
2
|
import { buildClassSubject, resolveClassArtifactReference, invalidTaskSubjectError } from "../internal.js";
|
|
3
|
+
function artifactSelectedByFor(ref) {
|
|
4
|
+
if (!ref) {
|
|
5
|
+
return "tool";
|
|
6
|
+
}
|
|
7
|
+
// A `resolved-id` reference is NOT a caller choice, despite being written by
|
|
8
|
+
// the caller. The id is an opaque handle some earlier resolve produced: it
|
|
9
|
+
// says nothing about whether the artifact carries a binary jar, and it cannot
|
|
10
|
+
// be re-resolved into one that does. Only `target: { kind: "jar", ... }`
|
|
11
|
+
// names a jar the caller actually picked and can pick differently, which is
|
|
12
|
+
// the same line `get-class-members` draws for its own `artifact` target.
|
|
13
|
+
return ref.type === "resolve-target" && ref.target.kind === "jar" ? "caller" : "tool";
|
|
14
|
+
}
|
|
3
15
|
export async function handleClassMembers(deps, subject, detail, include, limit) {
|
|
4
16
|
if (subject.kind !== "class" && !(subject.kind === "workspace" && subject.focus?.kind === "class")) {
|
|
5
17
|
invalidTaskSubjectError("class-members", subject);
|
|
@@ -9,6 +21,15 @@ export async function handleClassMembers(deps, subject, detail, include, limit)
|
|
|
9
21
|
const members = await deps.getClassMembers({
|
|
10
22
|
className: classSubject.className,
|
|
11
23
|
artifactId: artifact.artifactId || undefined,
|
|
24
|
+
// The artifactId above is one WE produced - resolveClassArtifactReference
|
|
25
|
+
// collapses every subject shape to one, including the workspace
|
|
26
|
+
// auto-resolution that happens with no artifact reference at all. Only the
|
|
27
|
+
// caller's own reference says whether they picked the artifact, and only a
|
|
28
|
+
// jar target does: it names an exact jar they can name differently.
|
|
29
|
+
// Anything else (a resolved-id handle, a version/coordinate target, or an
|
|
30
|
+
// omitted reference) leaves the artifact effectively ours, so a missing
|
|
31
|
+
// binary jar is not their input to fix.
|
|
32
|
+
artifactSelectedBy: artifactSelectedByFor(classSubject.artifact),
|
|
12
33
|
mapping: classSubject.mapping,
|
|
13
34
|
scope: classSubject.scope,
|
|
14
35
|
projectPath: classSubject.projectPath,
|
|
@@ -424,6 +424,10 @@ export type InspectMinecraftDeps = {
|
|
|
424
424
|
strictVersion?: boolean;
|
|
425
425
|
maxMembers?: number;
|
|
426
426
|
includeDescriptors?: boolean;
|
|
427
|
+
/** Who chose the artifact: see `GetClassMembersInput.artifactSelectedBy`.
|
|
428
|
+
* This tool always collapses its subject to an artifactId before calling,
|
|
429
|
+
* so without it every failure would be attributed to the caller. */
|
|
430
|
+
artifactSelectedBy?: "caller" | "tool";
|
|
427
431
|
}) => Promise<GetClassMembersOutput>;
|
|
428
432
|
searchClassSource: (input: {
|
|
429
433
|
artifactId: string;
|
|
@@ -27,9 +27,9 @@ export declare const manageCacheShape: {
|
|
|
27
27
|
jarPath: z.ZodOptional<z.ZodString>;
|
|
28
28
|
entryId: z.ZodOptional<z.ZodString>;
|
|
29
29
|
status: z.ZodOptional<z.ZodEnum<{
|
|
30
|
+
stale: "stale";
|
|
30
31
|
healthy: "healthy";
|
|
31
32
|
partial: "partial";
|
|
32
|
-
stale: "stale";
|
|
33
33
|
orphaned: "orphaned";
|
|
34
34
|
corrupt: "corrupt";
|
|
35
35
|
in_use: "in_use";
|
|
@@ -81,9 +81,9 @@ export declare const manageCacheSchema: z.ZodObject<{
|
|
|
81
81
|
jarPath: z.ZodOptional<z.ZodString>;
|
|
82
82
|
entryId: z.ZodOptional<z.ZodString>;
|
|
83
83
|
status: z.ZodOptional<z.ZodEnum<{
|
|
84
|
+
stale: "stale";
|
|
84
85
|
healthy: "healthy";
|
|
85
86
|
partial: "partial";
|
|
86
|
-
stale: "stale";
|
|
87
87
|
orphaned: "orphaned";
|
|
88
88
|
corrupt: "corrupt";
|
|
89
89
|
in_use: "in_use";
|
|
@@ -21,14 +21,34 @@ export async function handleMixin(deps, input, detail, include) {
|
|
|
21
21
|
details: {
|
|
22
22
|
task: "mixin",
|
|
23
23
|
failedStage: "input-validation",
|
|
24
|
-
nextAction: "Pass version explicitly
|
|
24
|
+
nextAction: "Pass version explicitly: the Minecraft version this project targets. task=\"project-summary\" supports preferProjectVersion for auto-detection from gradle.properties, but direct task=\"mixin\" requires an explicit version. Call the suggested list-versions to see what is available, then replay the exampleCalls template with that version substituted.",
|
|
25
|
+
// No concrete version can be derived here -- the caller omitted it and this
|
|
26
|
+
// task does not auto-detect one. A `suggestedCall` is a payload the caller
|
|
27
|
+
// may replay verbatim, so filling the hole with a made-up version produced
|
|
28
|
+
// a call that RUNS and validates the mixin against the wrong Minecraft
|
|
29
|
+
// version, which is worse than no suggestion.
|
|
30
|
+
//
|
|
31
|
+
// So the two roles are split, following what the sibling "a version is
|
|
32
|
+
// required but none was resolved" site already does in
|
|
33
|
+
// src/source/class-source.ts: `suggestedCall` is a REAL next step that
|
|
34
|
+
// needs nothing the caller does not have (list-versions takes no
|
|
35
|
+
// arguments and answers exactly the question blocking them), while the
|
|
36
|
+
// task="mixin" retry shape travels as an `exampleCalls` template whose
|
|
37
|
+
// placeholder makes the substitution the caller must perform obvious.
|
|
38
|
+
...buildSuggestedCall({ tool: "list-versions", params: {} }),
|
|
25
39
|
...buildSuggestedCall({
|
|
26
40
|
tool: "validate-project",
|
|
27
|
-
params:
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
41
|
+
params: undefined,
|
|
42
|
+
examples: [
|
|
43
|
+
{
|
|
44
|
+
params: {
|
|
45
|
+
task: "mixin",
|
|
46
|
+
subject: input.subject,
|
|
47
|
+
version: "<your-mc-version>"
|
|
48
|
+
},
|
|
49
|
+
reason: "Replace <your-mc-version> with the Minecraft version this project targets (gradle.properties, or task=\"project-summary\" with preferProjectVersion=true reports it)."
|
|
50
|
+
}
|
|
51
|
+
]
|
|
32
52
|
})
|
|
33
53
|
}
|
|
34
54
|
});
|
|
@@ -9,7 +9,7 @@ export declare function handleProjectSummary(deps: ValidateProjectDeps, input: V
|
|
|
9
9
|
warnings: string[];
|
|
10
10
|
tasks?: {
|
|
11
11
|
"workspace.detected": {
|
|
12
|
-
status: "
|
|
12
|
+
status: "ok" | "error" | "missing" | "skipped";
|
|
13
13
|
durationMs?: number;
|
|
14
14
|
error?: {
|
|
15
15
|
code: string;
|
|
@@ -20,7 +20,7 @@ export declare function handleProjectSummary(deps: ValidateProjectDeps, input: V
|
|
|
20
20
|
evidence?: string[];
|
|
21
21
|
};
|
|
22
22
|
"gradle.readable": {
|
|
23
|
-
status: "
|
|
23
|
+
status: "ok" | "error" | "missing" | "skipped";
|
|
24
24
|
durationMs?: number;
|
|
25
25
|
error?: {
|
|
26
26
|
code: string;
|
|
@@ -32,7 +32,7 @@ export declare function handleProjectSummary(deps: ValidateProjectDeps, input: V
|
|
|
32
32
|
buildScripts?: string[];
|
|
33
33
|
};
|
|
34
34
|
"loom.cache.found": {
|
|
35
|
-
status: "
|
|
35
|
+
status: "ok" | "error" | "missing" | "skipped";
|
|
36
36
|
durationMs?: number;
|
|
37
37
|
error?: {
|
|
38
38
|
code: string;
|
|
@@ -43,7 +43,7 @@ export declare function handleProjectSummary(deps: ValidateProjectDeps, input: V
|
|
|
43
43
|
cachePath?: string;
|
|
44
44
|
};
|
|
45
45
|
"minecraft.artifact.resolved": {
|
|
46
|
-
status: "
|
|
46
|
+
status: "ok" | "error" | "missing" | "skipped";
|
|
47
47
|
durationMs?: number;
|
|
48
48
|
error?: {
|
|
49
49
|
code: string;
|
|
@@ -55,7 +55,7 @@ export declare function handleProjectSummary(deps: ValidateProjectDeps, input: V
|
|
|
55
55
|
mapping?: import("../../../types.js").SourceMapping;
|
|
56
56
|
};
|
|
57
57
|
"mixins.validated": {
|
|
58
|
-
status: "
|
|
58
|
+
status: "ok" | "error" | "missing" | "skipped";
|
|
59
59
|
durationMs?: number;
|
|
60
60
|
error?: {
|
|
61
61
|
code: string;
|
|
@@ -70,7 +70,7 @@ export declare function handleProjectSummary(deps: ValidateProjectDeps, input: V
|
|
|
70
70
|
};
|
|
71
71
|
};
|
|
72
72
|
"accessWideners.validated": {
|
|
73
|
-
status: "
|
|
73
|
+
status: "ok" | "error" | "missing" | "skipped";
|
|
74
74
|
durationMs?: number;
|
|
75
75
|
error?: {
|
|
76
76
|
code: string;
|
|
@@ -84,7 +84,7 @@ export declare function handleProjectSummary(deps: ValidateProjectDeps, input: V
|
|
|
84
84
|
};
|
|
85
85
|
};
|
|
86
86
|
"accessTransformers.validated": {
|
|
87
|
-
status: "
|
|
87
|
+
status: "ok" | "error" | "missing" | "skipped";
|
|
88
88
|
durationMs?: number;
|
|
89
89
|
error?: {
|
|
90
90
|
code: string;
|
|
@@ -134,8 +134,8 @@ export declare const validateProjectShape: {
|
|
|
134
134
|
sourceRoots: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
135
135
|
configPaths: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
136
136
|
minSeverity: z.ZodDefault<z.ZodEnum<{
|
|
137
|
-
all: "all";
|
|
138
137
|
error: "error";
|
|
138
|
+
all: "all";
|
|
139
139
|
warning: "warning";
|
|
140
140
|
}>>;
|
|
141
141
|
hideUncertain: z.ZodDefault<z.ZodBoolean>;
|
|
@@ -246,8 +246,8 @@ export declare const validateProjectSchema: z.ZodObject<{
|
|
|
246
246
|
sourceRoots: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
247
247
|
configPaths: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
248
248
|
minSeverity: z.ZodDefault<z.ZodEnum<{
|
|
249
|
-
all: "all";
|
|
250
249
|
error: "error";
|
|
250
|
+
all: "all";
|
|
251
251
|
warning: "warning";
|
|
252
252
|
}>>;
|
|
253
253
|
hideUncertain: z.ZodDefault<z.ZodBoolean>;
|
|
@@ -221,7 +221,25 @@ export class VerifyMixinTargetService {
|
|
|
221
221
|
throw createError({
|
|
222
222
|
code: ERROR_CODES.CONTEXT_UNRESOLVED,
|
|
223
223
|
message: `verify-mixin-target requires a binary jar but artifact "${resolved.artifactId}" has none.`,
|
|
224
|
-
details: {
|
|
224
|
+
details: {
|
|
225
|
+
artifactId: resolved.artifactId,
|
|
226
|
+
owner,
|
|
227
|
+
// `ERR_CONTEXT_UNRESOLVED` classifies as `code_issue` by code, which
|
|
228
|
+
// is right for the sibling case (a caller naming a version no
|
|
229
|
+
// artifact carries) and for a `target: { kind: "jar", ... }` request:
|
|
230
|
+
// there the caller named the exact jar, and having none for its own
|
|
231
|
+
// sources is exactly the input the caller can change. It is wrong
|
|
232
|
+
// only when the artifact was picked by the TOOL from a non-jar target
|
|
233
|
+
// (version/coordinate/workspace/dependency), where whether it ships a
|
|
234
|
+
// binary jar is not something the request can express. Published as
|
|
235
|
+
// caller-fixable in that case it invites an endless retry of an input
|
|
236
|
+
// that was never at fault, so this site overrides the default only
|
|
237
|
+
// then.
|
|
238
|
+
issueOrigin: input.target?.kind === "jar" ? undefined : "tool_issue",
|
|
239
|
+
nextAction: `verify-mixin-target reads the target's members from bytecode, so it needs an artifact with a binary jar. `
|
|
240
|
+
+ `Re-target with target: { kind: "jar", value: "<path to the jar>" }, or with a version whose artifact carries one `
|
|
241
|
+
+ `(resolve-artifact reports binaryJarPath for the target you pass).`
|
|
242
|
+
}
|
|
225
243
|
});
|
|
226
244
|
}
|
|
227
245
|
let signature;
|
package/dist/error-mapping.d.ts
CHANGED
|
@@ -57,6 +57,23 @@ export type ProblemDetails = {
|
|
|
57
57
|
exampleCalls?: ExampleCall[];
|
|
58
58
|
/** Ranked near-miss candidates for a class/symbol that was not found. */
|
|
59
59
|
didYouMean?: DidYouMeanCandidate[];
|
|
60
|
+
/**
|
|
61
|
+
* Nested-jar inventory of the shell jar the lookup ran against, as jar-entry
|
|
62
|
+
* names (e.g. "META-INF/jars/api.jar"). A shell jar holds no classes of its
|
|
63
|
+
* own, so a class-not-found error on one is only actionable with the list of
|
|
64
|
+
* inner jars the caller can target next. Like `didYouMean`, it travels as a
|
|
65
|
+
* dedicated typed field: `context` is primitive-only and can never carry it.
|
|
66
|
+
*/
|
|
67
|
+
nestedJars?: string[];
|
|
68
|
+
/**
|
|
69
|
+
* True when `nestedJars` is a SHORTENED view of the shell's inventory —
|
|
70
|
+
* entries were dropped by the count cap, the per-entry length cap, or the
|
|
71
|
+
* total-bytes cap. Without it a caller cannot tell a complete inventory from
|
|
72
|
+
* a trimmed one and may conclude a class is bundled nowhere. Additive and
|
|
73
|
+
* omitted entirely when the published list is complete, matching the
|
|
74
|
+
* `candidatesTruncated` precedent.
|
|
75
|
+
*/
|
|
76
|
+
nestedJarsTruncated?: boolean;
|
|
60
77
|
failedStage?: string;
|
|
61
78
|
context?: Record<string, string | number | boolean>;
|
|
62
79
|
};
|
|
@@ -67,6 +84,67 @@ export type ProblemDetails = {
|
|
|
67
84
|
* whole rather than partially published.
|
|
68
85
|
*/
|
|
69
86
|
export declare function extractDidYouMean(details: unknown): DidYouMeanCandidate[] | undefined;
|
|
87
|
+
/**
|
|
88
|
+
* The `nestedJars` pair as published: the inventory plus the additive flag that
|
|
89
|
+
* says whether it is complete. Both keys are omitted when there is nothing to
|
|
90
|
+
* publish, so the object spreads directly into a ProblemDetails.
|
|
91
|
+
*/
|
|
92
|
+
export type NestedJarsField = {
|
|
93
|
+
nestedJars?: string[];
|
|
94
|
+
nestedJarsTruncated?: boolean;
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* Validates and extracts a `nestedJars` inventory from error details.
|
|
98
|
+
* `buildClassSourceNotFoundError` records it whenever the lookup ran against a
|
|
99
|
+
* shell jar, but `context` is primitive-only, so without this the inventory
|
|
100
|
+
* never left the process.
|
|
101
|
+
*
|
|
102
|
+
* Two distinct dispositions, deliberately not conflated:
|
|
103
|
+
* - MALFORMED (a non-string or empty-string element, anywhere in the array):
|
|
104
|
+
* the whole array is dropped, matching {@link extractDidYouMean}. The scan
|
|
105
|
+
* deliberately continues past the caps so a malformed element beyond them is
|
|
106
|
+
* still found — the upstream array is a real jar's own entry list, bounded by
|
|
107
|
+
* the archive, so the full scan is not an exposure.
|
|
108
|
+
* - OVERSIZED (an entry longer than {@link MAX_NESTED_JAR_ENTRY_BYTES}, an
|
|
109
|
+
* entry past the count cap, or one that would push the list past the total
|
|
110
|
+
* byte budget): that ENTRY alone is excluded and `nestedJarsTruncated` is
|
|
111
|
+
* set. An implausible name is not evidence that the rest of the inventory is
|
|
112
|
+
* untrustworthy, so it must not drop the array.
|
|
113
|
+
*
|
|
114
|
+
* An empty inventory is dropped as before: the producing site omits the key
|
|
115
|
+
* entirely in that case, so an empty array carries no information a caller could
|
|
116
|
+
* act on. If the caps leave nothing publishable, both keys are omitted rather
|
|
117
|
+
* than publishing a truncation flag with no list beside it.
|
|
118
|
+
*/
|
|
119
|
+
export declare function extractNestedJarsField(details: unknown): NestedJarsField;
|
|
120
|
+
/**
|
|
121
|
+
* Inventory-only view of {@link extractNestedJarsField}.
|
|
122
|
+
*
|
|
123
|
+
* NOT for an emission site. All three - the tool envelope, the batch entry, the
|
|
124
|
+
* error resource - publish the field pair, because dropping the flag makes a
|
|
125
|
+
* shortened inventory indistinguishable from a complete one. This remains for
|
|
126
|
+
* callers that want the list alone (tests pinning the validation and capping
|
|
127
|
+
* rules), and a new publisher should reach for the field-returning form.
|
|
128
|
+
*/
|
|
129
|
+
export declare function extractNestedJars(details: unknown): string[] | undefined;
|
|
130
|
+
/**
|
|
131
|
+
* Per-throw-site {@link IssueOrigin} override carried on an AppError's
|
|
132
|
+
* `details.issueOrigin`.
|
|
133
|
+
*
|
|
134
|
+
* The default classification is keyed purely on the error CODE, which is right
|
|
135
|
+
* for codes whose every throw site shares one origin. A few codes do not:
|
|
136
|
+
* `ERR_CONTEXT_UNRESOLVED` covers both a caller naming a version no artifact
|
|
137
|
+
* carries (genuinely `code_issue`) and the tool resolving an artifact with no
|
|
138
|
+
* binary jar behind the caller's back (`tool_issue`). Publishing the latter as
|
|
139
|
+
* caller-fixable sends an agent into a retry loop over input it cannot repair.
|
|
140
|
+
*
|
|
141
|
+
* This override is deliberately opt-in and per-error: `issueOriginForErrorCode`
|
|
142
|
+
* and its default sets stay untouched, so the exhaustive classification map in
|
|
143
|
+
* tests/runtime/error-mapping.test.ts keeps forcing a conscious decision for
|
|
144
|
+
* every new code. The key is not in `CONTEXT_ALLOWLIST`, so it cannot leak into
|
|
145
|
+
* the public `context` blob.
|
|
146
|
+
*/
|
|
147
|
+
export declare function extractIssueOriginOverride(details: unknown): IssueOrigin | undefined;
|
|
70
148
|
export declare function statusForErrorCode(code: string): number;
|
|
71
149
|
/**
|
|
72
150
|
* Single source of truth mapping an error code to its {@link RetryClass}. Used
|
|
@@ -85,6 +163,46 @@ export declare function retryClassForErrorCode(code: string): RetryClass;
|
|
|
85
163
|
* not satisfy).
|
|
86
164
|
*/
|
|
87
165
|
export declare function issueOriginForErrorCode(code: string): IssueOrigin;
|
|
166
|
+
/**
|
|
167
|
+
* The single source of truth for a ProblemDetails classification pair, and the
|
|
168
|
+
* ONLY supported way to compute one for an error the server caught. Every site
|
|
169
|
+
* that turns a caught error into a published envelope spreads this result — the
|
|
170
|
+
* tool envelope (`mapErrorToProblem`), batch entries, the batch-aborted
|
|
171
|
+
* sentinel, and the `mc://` resource read — so one failure always publishes the
|
|
172
|
+
* same `{ retryClass, issueOrigin }` no matter which access path the caller
|
|
173
|
+
* used.
|
|
174
|
+
*
|
|
175
|
+
* KNOWN EXCEPTION — the synthetic supervisor replies in `src/stdio-supervisor.ts`
|
|
176
|
+
* (`buildSupervisorQueueLimitReply`, `buildValidateProjectTimeoutReply`) write
|
|
177
|
+
* the pair as literals and never import this builder. They are not emission
|
|
178
|
+
* sites for a caught error at all: the supervisor answers a request the worker
|
|
179
|
+
* never got to run, so there is no source `AppError` whose `details` could carry
|
|
180
|
+
* an override and nothing to pass as the required `details` argument. Their
|
|
181
|
+
* literals are `transient` / `tool_issue`, which is exactly what this function
|
|
182
|
+
* returns for `ERR_LIMIT_EXCEEDED` and `ERR_TOOL_TIMEOUT` today — but that is a
|
|
183
|
+
* duplicated value held in agreement by hand, not a second call into this
|
|
184
|
+
* classifier. Reclassifying either code here must be mirrored there.
|
|
185
|
+
*
|
|
186
|
+
* It exists because that pair used to be assembled by hand at each site. The
|
|
187
|
+
* `issueOrigin` half has a per-throw-site override (see
|
|
188
|
+
* {@link extractIssueOriginOverride}), and honouring it was copy-pasted into
|
|
189
|
+
* some sites and forgotten in others — which is exactly how one identical
|
|
190
|
+
* AppError came to publish `tool_issue` through a tool call and `code_issue`
|
|
191
|
+
* through a resource read. Composing the pair here removes the opportunity:
|
|
192
|
+
* emission modules import this function and no longer reach the component
|
|
193
|
+
* classifiers at all.
|
|
194
|
+
*
|
|
195
|
+
* `details` is REQUIRED, deliberately without a default. A site that genuinely
|
|
196
|
+
* has no AppError behind it (a ZodError, a sanitized non-AppError, the
|
|
197
|
+
* batch-aborted sentinel) must pass `undefined` explicitly, so skipping the
|
|
198
|
+
* override can only ever be a written-down decision rather than an omission.
|
|
199
|
+
*
|
|
200
|
+
* Only `issueOrigin` is overridable per throw site. `retryClass` stays purely
|
|
201
|
+
* code-derived: it is a documented wire contract, and changing the value an
|
|
202
|
+
* existing code publishes is a Breaking change, so a `retryClass` override seam
|
|
203
|
+
* was deliberately deferred rather than added alongside this one.
|
|
204
|
+
*/
|
|
205
|
+
export declare function problemClassification(code: string, details: unknown): Pick<ProblemDetails, "retryClass" | "issueOrigin">;
|
|
88
206
|
/**
|
|
89
207
|
* Pick the allowlisted, primitive-valued fields out of an AppError's `details`
|
|
90
208
|
* so the public envelope can carry structured repair context without leaking
|