@otto-code/brain 0.8.10 → 0.8.13
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/dist/commands/bench.js +2 -2
- package/dist/commands/calibrate.js +11 -2
- package/dist/commands/catalog.d.ts +1 -0
- package/dist/commands/catalog.js +1 -0
- package/dist/commands/pull.d.ts +1 -0
- package/dist/commands/pull.js +12 -3
- package/dist/commands/search.d.ts +1 -0
- package/dist/commands/search.js +12 -2
- package/dist/config/index.d.ts +2 -2
- package/dist/config/index.js +2 -2
- package/dist/config/profile-edit.d.ts +88 -1
- package/dist/config/profile-edit.js +294 -43
- package/dist/config/profiles.d.ts +19 -3
- package/dist/config/profiles.js +52 -4
- package/dist/config/schema.d.ts +616 -0
- package/dist/config/schema.js +65 -3
- package/dist/config/store.js +7 -4
- package/dist/gguf.d.ts +7 -0
- package/dist/gguf.js +15 -2
- package/dist/models/download.d.ts +1 -1
- package/dist/models/download.js +2 -2
- package/dist/models/enrich.d.ts +6 -0
- package/dist/models/enrich.js +27 -1
- package/dist/models/index.d.ts +1 -1
- package/dist/models/index.js +4 -3
- package/dist/ops/archive.d.ts +14 -1
- package/dist/ops/archive.js +9 -5
- package/dist/ops/calibrate.d.ts +38 -3
- package/dist/ops/calibrate.js +68 -19
- package/dist/ops/report.js +51 -1
- package/dist/ops/results.d.ts +77 -11
- package/dist/ops/results.js +84 -14
- package/dist/ops/sweep.d.ts +38 -1
- package/dist/ops/sweep.js +61 -10
- package/dist/runtime/args.d.ts +15 -2
- package/dist/runtime/args.js +60 -5
- package/dist/runtime/managed.js +2 -2
- package/dist/service/activity.d.ts +19 -0
- package/dist/service/activity.js +47 -4
- package/dist/service/host-api.d.ts +28 -4
- package/dist/service/host-api.js +109 -28
- package/dist/service/log-format.d.ts +18 -0
- package/dist/service/log-format.js +32 -0
- package/dist/service/process-pool.d.ts +45 -0
- package/dist/service/process-pool.js +271 -0
- package/dist/service/router.d.ts +74 -3
- package/dist/service/router.js +277 -51
- package/dist/service/run-log.d.ts +6 -1
- package/dist/service/run-log.js +46 -4
- package/dist/service/scheduler.d.ts +250 -31
- package/dist/service/scheduler.js +408 -63
- package/dist/service/serve.d.ts +4 -0
- package/dist/service/serve.js +376 -142
- package/dist/service/status-events.d.ts +14 -1
- package/dist/service/status-events.js +112 -12
- package/dist/service/supervisor.d.ts +9 -7
- package/dist/service/supervisor.js +37 -12
- package/dist/sysmon.d.ts +15 -0
- package/dist/sysmon.js +56 -9
- package/dist/tui/app.d.ts +8 -2
- package/dist/tui/app.js +83 -26
- package/dist/types.d.ts +18 -0
- package/dist/vram.d.ts +37 -0
- package/dist/vram.js +57 -18
- package/package.json +1 -1
package/dist/commands/bench.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import http from "node:http";
|
|
10
10
|
import path from "node:path";
|
|
11
|
-
import { forModel,
|
|
11
|
+
import { forModel, getCalibrationForBudget, loadBrainConfig, loadProfilesStore, resolveBrainPaths, } from "../config/index.js";
|
|
12
12
|
import { query as queryGpu } from "../gpu.js";
|
|
13
13
|
import { pickModel, scanModels } from "../models/index.js";
|
|
14
14
|
import { CommandError } from "../output/types.js";
|
|
@@ -172,7 +172,7 @@ async function runBenchSuite(options, _command) {
|
|
|
172
172
|
// that ran is the profile that was configured, and the calibration is what
|
|
173
173
|
// says whether the fit's own VRAM figures were measured or guessed. A score
|
|
174
174
|
// read without them is a score nobody can diagnose.
|
|
175
|
-
const calibration =
|
|
175
|
+
const calibration = getCalibrationForBudget(store, model, profile);
|
|
176
176
|
let fit = null;
|
|
177
177
|
const gpu = await queryGpu();
|
|
178
178
|
if (gpu) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getCalibrationForBudget, forModel, loadBrainConfig, loadProfilesStore, putCalibration, saveProfilesStore, } from "../config/index.js";
|
|
2
2
|
import { query as queryGpu } from "../gpu.js";
|
|
3
3
|
import { pickModel, scanModels } from "../models/index.js";
|
|
4
4
|
import { CommandError } from "../output/types.js";
|
|
@@ -35,12 +35,19 @@ export async function runCalibrateCommand(options, _command) {
|
|
|
35
35
|
const catalog = scanModels(config);
|
|
36
36
|
const model = pickModel(catalog, options.model ?? store.lastModelId ?? undefined);
|
|
37
37
|
const profile = forModel(store, model, config.defaults);
|
|
38
|
+
// The last direct measurement is the best prior on bytes/token available for
|
|
39
|
+
// capping the next sample. It keeps the calibration path stable while a
|
|
40
|
+
// changed profile is awaiting a fresh measurement. Inherited family
|
|
41
|
+
// calibrations are excluded: they were measured on another file and are not
|
|
42
|
+
// a trustworthy budget input here.
|
|
43
|
+
const prior = getCalibrationForBudget(store, model, profile);
|
|
38
44
|
// Announced so the Brain rail can show the host as busy: a calibrate loads the
|
|
39
45
|
// model at several context sizes and will make anything else queue behind it.
|
|
40
46
|
const measurement = await withActivity("calibrate", { target: model.displayName }, () => calibrate({
|
|
41
47
|
runtime,
|
|
42
48
|
model,
|
|
43
49
|
profile,
|
|
50
|
+
priorCalibration: prior && !prior.inherited ? prior : null,
|
|
44
51
|
onProgress: (p) => {
|
|
45
52
|
if (p.phase === "loading")
|
|
46
53
|
process.stderr.write(` loading at ${p.contextSize?.toLocaleString()} ctx…\n`);
|
|
@@ -48,6 +55,8 @@ export async function runCalibrateCommand(options, _command) {
|
|
|
48
55
|
process.stderr.write(` used ${vram.formatGiB(p.deltaBytes ?? 0)}\n`);
|
|
49
56
|
if (p.phase === "skip")
|
|
50
57
|
process.stderr.write(` skipped ${p.contextSize}: ${p.reason}\n`);
|
|
58
|
+
if (p.phase === "failed")
|
|
59
|
+
process.stderr.write(` failed at ${p.contextSize?.toLocaleString()}: ${p.error ?? p.reason}\n`);
|
|
51
60
|
},
|
|
52
61
|
}));
|
|
53
62
|
putCalibration(store, model, profile, measurement);
|
|
@@ -57,7 +66,7 @@ export async function runCalibrateCommand(options, _command) {
|
|
|
57
66
|
? vram.maxContextThatFits({
|
|
58
67
|
model,
|
|
59
68
|
profile,
|
|
60
|
-
calibration:
|
|
69
|
+
calibration: getCalibrationForBudget(store, model, profile),
|
|
61
70
|
totalVramBytes: gpu.totalBytes,
|
|
62
71
|
})
|
|
63
72
|
: null;
|
package/dist/commands/catalog.js
CHANGED
|
@@ -30,6 +30,7 @@ export async function runCatalogCommand(_options, _command) {
|
|
|
30
30
|
id: model.id,
|
|
31
31
|
name: model.name,
|
|
32
32
|
family: model.family ?? null,
|
|
33
|
+
favorite: model.favorite,
|
|
33
34
|
installed: installedCatalogIds.has(model.id),
|
|
34
35
|
publisher: model.publisher ?? "",
|
|
35
36
|
repo: model.hfRepo,
|
package/dist/commands/pull.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ export interface PullOptionsInput {
|
|
|
18
18
|
quant?: string;
|
|
19
19
|
listQuants?: boolean;
|
|
20
20
|
component?: string[];
|
|
21
|
+
componentsOnly?: boolean;
|
|
21
22
|
}
|
|
22
23
|
export declare function runPullCommand(modelArg: string, options: PullOptionsInput, _command: Command): Promise<AnyCommandResult<PullRow>>;
|
|
23
24
|
//# sourceMappingURL=pull.d.ts.map
|
package/dist/commands/pull.js
CHANGED
|
@@ -40,13 +40,16 @@ function findCatalogModel(models, needle) {
|
|
|
40
40
|
return matches[0];
|
|
41
41
|
}
|
|
42
42
|
export function addPullOptions(cmd) {
|
|
43
|
-
return cmd
|
|
43
|
+
return (cmd
|
|
44
44
|
.description("Download a model from the catalog")
|
|
45
45
|
.argument("<model>", "catalog id or name fragment")
|
|
46
46
|
.option("--file <name.gguf>", "explicit GGUF file name in the repo")
|
|
47
47
|
.option("--quant <label>", "download a specific quantization (e.g. Q5_K_M)")
|
|
48
48
|
.option("--component <id...>", "download optional bundle component ids")
|
|
49
|
-
|
|
49
|
+
// The daemon uses this for a bundle job that gained companions after its
|
|
50
|
+
// primary transfer began. It is deliberately not a normal user action.
|
|
51
|
+
.option("--components-only", "download selected bundle components without the primary quant")
|
|
52
|
+
.option("--list-quants", "list the quantizations the repo offers and exit"));
|
|
50
53
|
}
|
|
51
54
|
export async function runPullCommand(modelArg, options, _command) {
|
|
52
55
|
const config = loadBrainConfig();
|
|
@@ -67,7 +70,13 @@ export async function runPullCommand(modelArg, options, _command) {
|
|
|
67
70
|
message: `${model.hfRepo} has no ${options.quant}`,
|
|
68
71
|
});
|
|
69
72
|
}
|
|
70
|
-
|
|
73
|
+
if (options.componentsOnly && (options.component?.length ?? 0) === 0) {
|
|
74
|
+
throw new CommandError({
|
|
75
|
+
code: "NO_COMPONENT",
|
|
76
|
+
message: "--components-only requires at least one bundle component",
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
const plan = bundleDownloadPlan(model, options.component ?? [], options.componentsOnly ? [] : choice?.files, options.componentsOnly ? 0 : choice?.sizeBytes, !options.componentsOnly);
|
|
71
80
|
const progressLabel = `${model.name}${choice ? ` ${choice.quant}` : ""}`;
|
|
72
81
|
const written = await downloadRepoFilesWithProgress({
|
|
73
82
|
activityTarget: model.name,
|
|
@@ -30,5 +30,6 @@ export declare function runAddCommand(repo: string, options: {
|
|
|
30
30
|
listQuants?: boolean;
|
|
31
31
|
component?: string[];
|
|
32
32
|
primaryOnly?: boolean;
|
|
33
|
+
componentsOnly?: boolean;
|
|
33
34
|
}, _command: Command): Promise<AnyCommandResult<AddRow>>;
|
|
34
35
|
//# sourceMappingURL=search.d.ts.map
|
package/dist/commands/search.js
CHANGED
|
@@ -80,6 +80,7 @@ export function addAddOptions(cmd) {
|
|
|
80
80
|
.option("--quant <label>", "quantization to download (e.g. Q5_K_M)")
|
|
81
81
|
.option("--component <id...>", "download optional discovered component ids")
|
|
82
82
|
.option("--primary-only", "download only the selected primary quant")
|
|
83
|
+
.option("--components-only", "download selected bundle components without the primary quant")
|
|
83
84
|
.option("--list-quants", "list the quantizations the repo offers and exit");
|
|
84
85
|
}
|
|
85
86
|
export async function runAddCommand(repo, options, _command) {
|
|
@@ -131,8 +132,17 @@ export async function runAddCommand(repo, options, _command) {
|
|
|
131
132
|
const includeProjector = Boolean(mmproj) &&
|
|
132
133
|
(requested.has("vision-projector") ||
|
|
133
134
|
(!options.primaryOnly && options.component === undefined));
|
|
134
|
-
|
|
135
|
-
|
|
135
|
+
if (options.componentsOnly && !includeProjector) {
|
|
136
|
+
throw new CommandError({
|
|
137
|
+
code: "NO_COMPONENT",
|
|
138
|
+
message: "--components-only requires a selected bundle component",
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
const files = [
|
|
142
|
+
...(options.componentsOnly ? [] : choice.files),
|
|
143
|
+
...(includeProjector ? mmproj.files : []),
|
|
144
|
+
];
|
|
145
|
+
const total = (options.componentsOnly ? 0 : choice.sizeBytes) + (includeProjector ? mmproj.sizeBytes : 0);
|
|
136
146
|
const written = await downloadRepoFilesWithProgress({
|
|
137
147
|
activityTarget: `${repo} (${choice.quant})`,
|
|
138
148
|
progressLabel: `${repo} ${choice.quant}`,
|
package/dist/config/index.d.ts
CHANGED
|
@@ -4,8 +4,8 @@ export { ensurePrivateDirectory, writePrivateFileAtomicSync } from "./private-fi
|
|
|
4
4
|
export { resolveBrainPaths, packageRoot, type BrainPaths } from "./paths.js";
|
|
5
5
|
export { parseBooleanEnv, applyEnvOverrides } from "./env.js";
|
|
6
6
|
export { loadBrainConfig, loadPersistedConfig, saveBrainConfig, loadProfilesStore, saveProfilesStore, loadCatalog, } from "./store.js";
|
|
7
|
-
export { defaultProfile, forModel, put, calibrationKey, geometryKey, getCalibration, putCalibration, hasStaleCalibration, } from "./profiles.js";
|
|
7
|
+
export { defaultProfile, forModel, put, calibrationKey, geometryKey, getCalibration, getCalibrationForBudget, getLastCalibration, putCalibration, hasStaleCalibration, } from "./profiles.js";
|
|
8
8
|
export { effectiveHostingProfile, resolveHostingProfileForLaunch } from "./hosting-profiles.js";
|
|
9
|
-
export { calibrationInfo, nativeContextLimit, profileFieldDescriptors, profileWarnings, sanitizeProfilePatch, formatReasoningBudget, CACHE_TYPE_CYCLE, REASONING_BUDGET_CYCLE, UNRESTRICTED_REASONING_BUDGET, type CalibrationInfo, type CalibrationState, type ProfileFieldDescriptor, type ProfileWarning, } from "./profile-edit.js";
|
|
9
|
+
export { calibrationInfo, nativeContextLimit, profileFieldDescriptors, profileWarnings, sanitizeProfilePatch, formatReasoningBudget, CACHE_TYPE_CYCLE, REASONING_BUDGET_CYCLE, UNRESTRICTED_REASONING_BUDGET, PRESERVE_REASONING_CYCLE, SAMPLING_RANGES, preserveReasoningFromOption, preserveReasoningOption, type CalibrationInfo, type CalibrationState, type ProfileFieldDescriptor, type ProfileWarning, } from "./profile-edit.js";
|
|
10
10
|
export * from "./schema.js";
|
|
11
11
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/config/index.js
CHANGED
|
@@ -4,8 +4,8 @@ export { ensurePrivateDirectory, writePrivateFileAtomicSync } from "./private-fi
|
|
|
4
4
|
export { resolveBrainPaths, packageRoot } from "./paths.js";
|
|
5
5
|
export { parseBooleanEnv, applyEnvOverrides } from "./env.js";
|
|
6
6
|
export { loadBrainConfig, loadPersistedConfig, saveBrainConfig, loadProfilesStore, saveProfilesStore, loadCatalog, } from "./store.js";
|
|
7
|
-
export { defaultProfile, forModel, put, calibrationKey, geometryKey, getCalibration, putCalibration, hasStaleCalibration, } from "./profiles.js";
|
|
7
|
+
export { defaultProfile, forModel, put, calibrationKey, geometryKey, getCalibration, getCalibrationForBudget, getLastCalibration, putCalibration, hasStaleCalibration, } from "./profiles.js";
|
|
8
8
|
export { effectiveHostingProfile, resolveHostingProfileForLaunch } from "./hosting-profiles.js";
|
|
9
|
-
export { calibrationInfo, nativeContextLimit, profileFieldDescriptors, profileWarnings, sanitizeProfilePatch, formatReasoningBudget, CACHE_TYPE_CYCLE, REASONING_BUDGET_CYCLE, UNRESTRICTED_REASONING_BUDGET, } from "./profile-edit.js";
|
|
9
|
+
export { calibrationInfo, nativeContextLimit, profileFieldDescriptors, profileWarnings, sanitizeProfilePatch, formatReasoningBudget, CACHE_TYPE_CYCLE, REASONING_BUDGET_CYCLE, UNRESTRICTED_REASONING_BUDGET, PRESERVE_REASONING_CYCLE, SAMPLING_RANGES, preserveReasoningFromOption, preserveReasoningOption, } from "./profile-edit.js";
|
|
10
10
|
export * from "./schema.js";
|
|
11
11
|
//# sourceMappingURL=index.js.map
|
|
@@ -6,19 +6,88 @@ export interface ProfileFieldDescriptor {
|
|
|
6
6
|
key: string;
|
|
7
7
|
label: string;
|
|
8
8
|
kind: ProfileFieldKind;
|
|
9
|
+
/**
|
|
10
|
+
* One sentence on what the field does, for the client's tooltip. It lives
|
|
11
|
+
* here rather than in the UI for the same reason the ranges do: a client that
|
|
12
|
+
* wrote its own copy would describe a setting the brain had since changed.
|
|
13
|
+
*/
|
|
14
|
+
description?: string;
|
|
9
15
|
/** For `number`: the increment a stepper should use. */
|
|
10
16
|
step?: number;
|
|
11
17
|
min?: number;
|
|
12
18
|
max?: number;
|
|
19
|
+
/**
|
|
20
|
+
* For `number`: decimal places the value carries. Absent means an integer.
|
|
21
|
+
* A stepper MUST round to this after adding `step`, or repeated presses walk
|
|
22
|
+
* a sampler into 0.30000000000000004 and the profile stores that.
|
|
23
|
+
*/
|
|
24
|
+
precision?: number;
|
|
13
25
|
/** For `cycle`: the values to offer, in order. */
|
|
14
26
|
options?: (string | number)[];
|
|
15
27
|
/** Labels for `options`, index-aligned, when the raw value is not presentable. */
|
|
16
28
|
optionLabels?: string[];
|
|
29
|
+
/**
|
|
30
|
+
* For `cycle`: what each option actually stores, index-aligned. Absent means
|
|
31
|
+
* the option IS the stored value, which is true of every cycle but the
|
|
32
|
+
* tri-states: `options` is limited to strings and numbers on the wire, so a
|
|
33
|
+
* field storing `true`/`false`/`null` needs somewhere to say so. A client
|
|
34
|
+
* matches the current value here first, then falls back to `options`.
|
|
35
|
+
*/
|
|
36
|
+
optionValues?: (string | number | boolean | null)[];
|
|
17
37
|
/** False when this model cannot use the field at all (vision with no projector). */
|
|
18
38
|
available: boolean;
|
|
19
39
|
/** Why it is unavailable, for the disabled-state hint. */
|
|
20
40
|
unavailableReason?: string;
|
|
21
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Sampler ranges. llama.cpp itself bounds almost none of these - it will take a
|
|
44
|
+
* temperature of 50 - so the bounds are the useful range rather than the legal
|
|
45
|
+
* one, which is what a stepper wants. The write path clamps to exactly these,
|
|
46
|
+
* so a value the editor cannot reach is a value the brain will not store.
|
|
47
|
+
*/
|
|
48
|
+
export declare const SAMPLING_RANGES: {
|
|
49
|
+
readonly temperature: {
|
|
50
|
+
readonly min: 0;
|
|
51
|
+
readonly max: 2;
|
|
52
|
+
readonly step: 0.05;
|
|
53
|
+
readonly precision: 2;
|
|
54
|
+
};
|
|
55
|
+
readonly topP: {
|
|
56
|
+
readonly min: 0;
|
|
57
|
+
readonly max: 1;
|
|
58
|
+
readonly step: 0.05;
|
|
59
|
+
readonly precision: 2;
|
|
60
|
+
};
|
|
61
|
+
readonly topK: {
|
|
62
|
+
readonly min: 0;
|
|
63
|
+
readonly max: 200;
|
|
64
|
+
readonly step: 1;
|
|
65
|
+
};
|
|
66
|
+
readonly minP: {
|
|
67
|
+
readonly min: 0;
|
|
68
|
+
readonly max: 1;
|
|
69
|
+
readonly step: 0.01;
|
|
70
|
+
readonly precision: 2;
|
|
71
|
+
};
|
|
72
|
+
readonly presencePenalty: {
|
|
73
|
+
readonly min: -2;
|
|
74
|
+
readonly max: 2;
|
|
75
|
+
readonly step: 0.05;
|
|
76
|
+
readonly precision: 2;
|
|
77
|
+
};
|
|
78
|
+
readonly repeatPenalty: {
|
|
79
|
+
readonly min: 0.5;
|
|
80
|
+
readonly max: 2;
|
|
81
|
+
readonly step: 0.01;
|
|
82
|
+
readonly precision: 2;
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
/** Tri-state preservation, in the order a cycle should offer it. */
|
|
86
|
+
export declare const PRESERVE_REASONING_CYCLE: readonly ["default", "on", "off"];
|
|
87
|
+
/** The stored value a cycle option maps to. */
|
|
88
|
+
export declare function preserveReasoningFromOption(option: string): boolean | null;
|
|
89
|
+
/** The cycle option a stored value maps to. */
|
|
90
|
+
export declare function preserveReasoningOption(value: boolean | null | undefined): string;
|
|
22
91
|
/**
|
|
23
92
|
* The cache types the editor cycles through. `CACHE_TYPE_BYTES` knows more
|
|
24
93
|
* (f32, bf16, q5_0, q4_1) and a write naming one of those is accepted; these
|
|
@@ -42,6 +111,19 @@ export declare const UNRESTRICTED_REASONING_BUDGET = -1;
|
|
|
42
111
|
*/
|
|
43
112
|
export declare function formatReasoningBudget(budget: number): string;
|
|
44
113
|
export declare const MAX_PARALLEL_SLOTS = 16;
|
|
114
|
+
/**
|
|
115
|
+
* Ceiling on `cachedChats`. Generous on purpose: entries are small for a small
|
|
116
|
+
* model and huge for a large one, so the honest guard is the RAM figure the
|
|
117
|
+
* warning shows, not an arbitrary count.
|
|
118
|
+
*/
|
|
119
|
+
export declare const MAX_CACHED_CHATS = 64;
|
|
120
|
+
/**
|
|
121
|
+
* What llama.cpp allows by default when no `--cache-ram` is emitted, in bytes.
|
|
122
|
+
* `cachedChats` of 0 emits no flag, so the estimate for the Default option is
|
|
123
|
+
* this fixed figure against the installed RAM - not the model's measured KV
|
|
124
|
+
* cost, which is irrelevant here because the size does not depend on the model.
|
|
125
|
+
*/
|
|
126
|
+
export declare const ENGINE_DEFAULT_CACHE_RAM_BYTES: number;
|
|
45
127
|
export declare const MAX_GPU_LAYERS = 999;
|
|
46
128
|
export declare const MIN_CONTEXT_SIZE = 1024;
|
|
47
129
|
export declare const CONTEXT_STEP = 8192;
|
|
@@ -54,7 +136,12 @@ export declare function profileFieldDescriptors(model: Model | null, profile?: P
|
|
|
54
136
|
/** A note attached to a field, or to the profile as a whole when `field` is null. */
|
|
55
137
|
export interface ProfileWarning {
|
|
56
138
|
field: string | null;
|
|
57
|
-
|
|
139
|
+
/**
|
|
140
|
+
* `info` renders as muted text, `warn` as yellow. `error` is red: the
|
|
141
|
+
* Cached KVs estimate earns it when the parked state would use at least the
|
|
142
|
+
* machine's whole RAM.
|
|
143
|
+
*/
|
|
144
|
+
severity: "info" | "warn" | "error";
|
|
58
145
|
message: string;
|
|
59
146
|
/** True when this combination cannot start, as opposed to merely being unwise. */
|
|
60
147
|
blocksStart: boolean;
|