@otto-code/brain 0.7.6 → 0.8.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/dist/bench/context-corpus.js +3 -3
- package/dist/bench/corpus.js +2 -2
- package/dist/bench/curated-repos.js +3 -3
- package/dist/bench/health.d.ts +1 -1
- package/dist/bench/health.js +2 -2
- package/dist/bench/tasks.js +2 -2
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +1 -1
- package/dist/commands/bench.d.ts +1 -1
- package/dist/commands/bench.js +27 -4
- package/dist/commands/calibrate.d.ts +1 -1
- package/dist/commands/calibrate.js +5 -2
- package/dist/commands/catalog.d.ts +1 -1
- package/dist/commands/config.d.ts +1 -1
- package/dist/commands/lifecycle.js +1 -1
- package/dist/commands/pull.d.ts +1 -1
- package/dist/commands/pull.js +9 -4
- package/dist/commands/report.d.ts +1 -1
- package/dist/commands/rescore.d.ts +1 -1
- package/dist/commands/rescore.js +1 -1
- package/dist/commands/runtime.d.ts +2 -1
- package/dist/commands/runtime.js +32 -3
- package/dist/commands/scan.d.ts +1 -1
- package/dist/commands/scan.js +6 -1
- package/dist/commands/search.d.ts +1 -1
- package/dist/commands/share.js +2 -2
- package/dist/commands/sweep.d.ts +2 -2
- package/dist/commands/sweep.js +22 -8
- package/dist/commands/ui.d.ts +1 -1
- package/dist/config/index.d.ts +2 -1
- package/dist/config/index.js +2 -1
- package/dist/config/otto-home.js +1 -1
- package/dist/config/paths.d.ts +1 -0
- package/dist/config/paths.js +4 -0
- package/dist/config/profile-edit.d.ts +94 -0
- package/dist/config/profile-edit.js +269 -0
- package/dist/config/profiles.d.ts +2 -2
- package/dist/config/profiles.js +1 -1
- package/dist/config/schema.d.ts +4 -4
- package/dist/config/schema.js +6 -6
- package/dist/config/store.d.ts +1 -1
- package/dist/config/store.js +1 -1
- package/dist/models/download.js +1 -1
- package/dist/models/enrich.d.ts +2 -2
- package/dist/models/index.d.ts +1 -1
- package/dist/models/index.js +1 -1
- package/dist/models/pick.d.ts +9 -0
- package/dist/models/pick.js +24 -1
- package/dist/ops/report.js +28 -28
- package/dist/ops/results.d.ts +128 -9
- package/dist/ops/results.js +77 -5
- package/dist/output/render.js +1 -1
- package/dist/output/types.d.ts +1 -1
- package/dist/runtime/args.d.ts +17 -4
- package/dist/runtime/args.js +20 -14
- package/dist/runtime/index.d.ts +9 -3
- package/dist/runtime/index.js +21 -3
- package/dist/runtime/lmstudio.d.ts +2 -2
- package/dist/runtime/lmstudio.js +115 -28
- package/dist/runtime/managed.d.ts +72 -8
- package/dist/runtime/managed.js +404 -38
- package/dist/service/activity.d.ts +83 -0
- package/dist/service/activity.js +216 -0
- package/dist/service/host-api.d.ts +132 -0
- package/dist/service/host-api.js +397 -0
- package/dist/service/http-util.d.ts +27 -0
- package/dist/service/http-util.js +72 -0
- package/dist/service/model-selector.d.ts +2 -2
- package/dist/service/model-selector.js +6 -6
- package/dist/service/router.d.ts +28 -4
- package/dist/service/router.js +128 -94
- package/dist/service/scheduler.d.ts +2 -2
- package/dist/service/scheduler.js +1 -1
- package/dist/service/serve.d.ts +2 -2
- package/dist/service/serve.js +51 -8
- package/dist/service/supervisor.d.ts +6 -0
- package/dist/service/supervisor.js +2 -0
- package/dist/service/tailscale.js +1 -1
- package/dist/service/tls.d.ts +4 -4
- package/dist/service/tls.js +3 -3
- package/dist/sysmon.d.ts +20 -4
- package/dist/sysmon.js +42 -18
- package/dist/tui/app.d.ts +12 -2
- package/dist/tui/app.js +46 -22
- package/dist/vram.d.ts +8 -1
- package/dist/vram.js +6 -3
- package/package.json +1 -1
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
2
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
3
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
4
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
5
|
+
};
|
|
6
|
+
var _ReasoningTracker_reasoning, _ReasoningTracker_content;
|
|
7
|
+
/**
|
|
8
|
+
* What long-running work currently owns the brain, and whether the loaded model
|
|
9
|
+
* is mid-reasoning.
|
|
10
|
+
*
|
|
11
|
+
* Two trackers with deliberately different lifetimes:
|
|
12
|
+
*
|
|
13
|
+
* - **Ops** (`calibrate`, `sweep`, `benchmark`, `pull`, `scan`) run in their own
|
|
14
|
+
* CLI process. The service answering `/__host/status` is a different process
|
|
15
|
+
* and cannot see them, so the op announces itself through a small file under
|
|
16
|
+
* `$OTTO_HOME/otto-brain/`, the same way `pid-lock.ts` publishes the service
|
|
17
|
+
* itself. Staleness is decided by probing the recorded pid: a `calibrate` that
|
|
18
|
+
* was killed with Ctrl-C never gets to clean up after itself, and a status
|
|
19
|
+
* that stays stuck on "calibrating" forever is worse than no status at all.
|
|
20
|
+
*
|
|
21
|
+
* - **Reasoning** is per-request and lives only as long as the stream does, so
|
|
22
|
+
* it is plain in-process state on the router. It never touches disk.
|
|
23
|
+
*
|
|
24
|
+
* Both feed the one `activity` field on the host status, which the client turns
|
|
25
|
+
* into the Brain rail's icon.
|
|
26
|
+
*/
|
|
27
|
+
import { existsSync, readFileSync, rmSync } from "node:fs";
|
|
28
|
+
import { resolveBrainPaths } from "../config/paths.js";
|
|
29
|
+
import { writePrivateFileAtomicSync } from "../config/private-files.js";
|
|
30
|
+
import { isProcessAlive } from "./pid-lock.js";
|
|
31
|
+
const ACTIVITY_KINDS = new Set(["calibrate", "sweep", "benchmark", "download", "scan"]);
|
|
32
|
+
/** How often progress may reach the disk. */
|
|
33
|
+
const PROGRESS_WRITE_INTERVAL_MS = 1000;
|
|
34
|
+
/**
|
|
35
|
+
* Announce that this process has started a long-running op.
|
|
36
|
+
*
|
|
37
|
+
* Deliberately last-writer-wins rather than refusing when a record already
|
|
38
|
+
* exists: two ops at once is a real (if unwise) thing to do, the newer one is
|
|
39
|
+
* the more interesting answer, and a failed announce must never be a reason for
|
|
40
|
+
* the op itself to fail.
|
|
41
|
+
*/
|
|
42
|
+
export function beginActivity(kind, options = {}) {
|
|
43
|
+
const env = options.env ?? process.env;
|
|
44
|
+
const record = {
|
|
45
|
+
kind,
|
|
46
|
+
pid: process.pid,
|
|
47
|
+
target: options.target ?? null,
|
|
48
|
+
progress: null,
|
|
49
|
+
startedAt: new Date().toISOString(),
|
|
50
|
+
};
|
|
51
|
+
writeActivity(record, env);
|
|
52
|
+
let lastWriteMs = Date.now();
|
|
53
|
+
let ended = false;
|
|
54
|
+
return {
|
|
55
|
+
update(progress) {
|
|
56
|
+
if (ended)
|
|
57
|
+
return;
|
|
58
|
+
const now = Date.now();
|
|
59
|
+
if (now - lastWriteMs < PROGRESS_WRITE_INTERVAL_MS)
|
|
60
|
+
return;
|
|
61
|
+
lastWriteMs = now;
|
|
62
|
+
record.progress = clampProgress(progress);
|
|
63
|
+
writeActivity(record, env);
|
|
64
|
+
},
|
|
65
|
+
end() {
|
|
66
|
+
if (ended)
|
|
67
|
+
return;
|
|
68
|
+
ended = true;
|
|
69
|
+
// Only clear a record this process owns. An op that outlived us - or a
|
|
70
|
+
// newer one that overwrote ours - must keep reporting.
|
|
71
|
+
const current = readActivityFile(env);
|
|
72
|
+
if (current && current.pid !== process.pid)
|
|
73
|
+
return;
|
|
74
|
+
clearActivity(env);
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The op currently owning the host, or null.
|
|
80
|
+
*
|
|
81
|
+
* Clears the record as a side effect when the process behind it is gone, so a
|
|
82
|
+
* killed op self-heals on the next status poll rather than needing a restart.
|
|
83
|
+
*/
|
|
84
|
+
export function readActivity(env = process.env) {
|
|
85
|
+
const record = readActivityFile(env);
|
|
86
|
+
if (!record)
|
|
87
|
+
return null;
|
|
88
|
+
if (!isProcessAlive(record.pid)) {
|
|
89
|
+
clearActivity(env);
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
return record;
|
|
93
|
+
}
|
|
94
|
+
export function clearActivity(env = process.env) {
|
|
95
|
+
const { activityFile } = resolveBrainPaths(env);
|
|
96
|
+
rmSync(activityFile, { force: true });
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Run `fn` with the op announced, clearing the record however it finishes.
|
|
100
|
+
*
|
|
101
|
+
* The announce is never allowed to break the op: this is status reporting, and
|
|
102
|
+
* a read-only home directory is not a reason for a benchmark to fail.
|
|
103
|
+
*/
|
|
104
|
+
export async function withActivity(kind, options, fn) {
|
|
105
|
+
let handle;
|
|
106
|
+
try {
|
|
107
|
+
handle = beginActivity(kind, options);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
handle = { update() { }, end() { } };
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
return await fn(handle);
|
|
114
|
+
}
|
|
115
|
+
finally {
|
|
116
|
+
try {
|
|
117
|
+
handle.end();
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
// Ignore: see above.
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function writeActivity(record, env) {
|
|
125
|
+
const { activityFile } = resolveBrainPaths(env);
|
|
126
|
+
writePrivateFileAtomicSync(activityFile, `${JSON.stringify(record, null, 2)}\n`);
|
|
127
|
+
}
|
|
128
|
+
function readActivityFile(env) {
|
|
129
|
+
const { activityFile } = resolveBrainPaths(env);
|
|
130
|
+
if (!existsSync(activityFile))
|
|
131
|
+
return null;
|
|
132
|
+
try {
|
|
133
|
+
const parsed = JSON.parse(readFileSync(activityFile, "utf8"));
|
|
134
|
+
if (!isActivityKind(parsed.kind) || typeof parsed.pid !== "number")
|
|
135
|
+
return null;
|
|
136
|
+
return {
|
|
137
|
+
kind: parsed.kind,
|
|
138
|
+
pid: parsed.pid,
|
|
139
|
+
target: typeof parsed.target === "string" ? parsed.target : null,
|
|
140
|
+
progress: clampProgress(parsed.progress ?? null),
|
|
141
|
+
startedAt: typeof parsed.startedAt === "string" ? parsed.startedAt : new Date().toISOString(),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function isActivityKind(value) {
|
|
149
|
+
return typeof value === "string" && ACTIVITY_KINDS.has(value);
|
|
150
|
+
}
|
|
151
|
+
function clampProgress(progress) {
|
|
152
|
+
if (typeof progress !== "number" || !Number.isFinite(progress))
|
|
153
|
+
return null;
|
|
154
|
+
return Math.min(1, Math.max(0, progress));
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Whether a streamed chunk carries reasoning, and whether it carries content.
|
|
158
|
+
*
|
|
159
|
+
* These are the router's own predicates, lifted out so the live flag and the
|
|
160
|
+
* end-of-stream `reasoning-only` verdict cannot drift apart. They are
|
|
161
|
+
* deliberately crude substring tests over the raw SSE bytes: the brain proxies
|
|
162
|
+
* both the Anthropic and the OpenAI shapes, chunk boundaries fall wherever the
|
|
163
|
+
* network puts them, and parsing partial JSON per chunk on the hot proxy path
|
|
164
|
+
* would cost more than the signal is worth.
|
|
165
|
+
*/
|
|
166
|
+
export function chunkHasReasoning(text) {
|
|
167
|
+
return text.includes("thinking") || text.includes("reasoning");
|
|
168
|
+
}
|
|
169
|
+
export function chunkHasContent(text) {
|
|
170
|
+
return text.includes('"text_delta"') || /"content"\s*:\s*"[^"]/.test(text);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Which in-flight completions are currently mid-thought.
|
|
174
|
+
*
|
|
175
|
+
* A request counts as thinking once reasoning has gone past and before any
|
|
176
|
+
* content has: that is the window where the model is working and the user has
|
|
177
|
+
* nothing to read yet, which is exactly what the rail's "thinking" state is
|
|
178
|
+
* claiming. The moment content starts the request is generating instead, even
|
|
179
|
+
* if more reasoning follows - a stream that is producing readable output should
|
|
180
|
+
* not report as though it were still silent.
|
|
181
|
+
*
|
|
182
|
+
* A set rather than a boolean because llama-server runs several slots at once,
|
|
183
|
+
* and one request finishing its thought must not clear the flag for another.
|
|
184
|
+
*/
|
|
185
|
+
export class ReasoningTracker {
|
|
186
|
+
constructor() {
|
|
187
|
+
_ReasoningTracker_reasoning.set(this, new Set());
|
|
188
|
+
_ReasoningTracker_content.set(this, new Set());
|
|
189
|
+
}
|
|
190
|
+
/** Note a chunk of `requestId`'s stream. Cheap enough to call per chunk. */
|
|
191
|
+
observe(requestId, text) {
|
|
192
|
+
if (__classPrivateFieldGet(this, _ReasoningTracker_content, "f").has(requestId))
|
|
193
|
+
return;
|
|
194
|
+
if (chunkHasContent(text)) {
|
|
195
|
+
__classPrivateFieldGet(this, _ReasoningTracker_content, "f").add(requestId);
|
|
196
|
+
__classPrivateFieldGet(this, _ReasoningTracker_reasoning, "f").delete(requestId);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
if (chunkHasReasoning(text)) {
|
|
200
|
+
__classPrivateFieldGet(this, _ReasoningTracker_reasoning, "f").add(requestId);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
/** Forget the request. Must be called on end *and* on error, or the flag sticks. */
|
|
204
|
+
end(requestId) {
|
|
205
|
+
__classPrivateFieldGet(this, _ReasoningTracker_reasoning, "f").delete(requestId);
|
|
206
|
+
__classPrivateFieldGet(this, _ReasoningTracker_content, "f").delete(requestId);
|
|
207
|
+
}
|
|
208
|
+
get active() {
|
|
209
|
+
return __classPrivateFieldGet(this, _ReasoningTracker_reasoning, "f").size > 0;
|
|
210
|
+
}
|
|
211
|
+
get count() {
|
|
212
|
+
return __classPrivateFieldGet(this, _ReasoningTracker_reasoning, "f").size;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
_ReasoningTracker_reasoning = new WeakMap(), _ReasoningTracker_content = new WeakMap();
|
|
216
|
+
//# sourceMappingURL=activity.js.map
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The brain's management API: everything Otto's Brain page needs that is not
|
|
3
|
+
* inference.
|
|
4
|
+
*
|
|
5
|
+
* This is deliberately served by the brain rather than driven by the daemon
|
|
6
|
+
* shelling out to the CLI. The daemon's `BrainOpsManager` runs `otto-brain
|
|
7
|
+
* <verb> --json`, which is local-only by construction, so every capability built
|
|
8
|
+
* that way needs a second implementation before a remote brain can have it. The
|
|
9
|
+
* daemon already resolves this endpoint by mode (local child or remote host), so
|
|
10
|
+
* a capability added here works the same in both without a branch anywhere.
|
|
11
|
+
*
|
|
12
|
+
* Model ids are relative file paths and therefore contain slashes, so the
|
|
13
|
+
* model-scoped routes take `?id=` rather than a path segment. Encoding a slash
|
|
14
|
+
* as %2F inside a path is the kind of thing an intermediary silently normalises,
|
|
15
|
+
* and the failure would look like "model not found" rather than a routing bug.
|
|
16
|
+
*
|
|
17
|
+
* Writes are gated on the same `allowRemoteConfig` flag as POST /__host/config.
|
|
18
|
+
* A brain that may be *used* over the network is not thereby a brain whose model
|
|
19
|
+
* files may be deleted over the network.
|
|
20
|
+
*/
|
|
21
|
+
import type http from "node:http";
|
|
22
|
+
import { calibrationInfo, profileWarnings } from "../config/profile-edit.js";
|
|
23
|
+
import type { Profile, ProfileDefaults, ProfilesStore } from "../config/schema.js";
|
|
24
|
+
import { planDelete } from "../models/manage.js";
|
|
25
|
+
import type { RankedModel } from "../ops/results.js";
|
|
26
|
+
import type { GpuInfo, Model } from "../types.js";
|
|
27
|
+
import * as vram from "../vram.js";
|
|
28
|
+
import type { SystemSample } from "../sysmon.js";
|
|
29
|
+
import type { Supervisor } from "./supervisor.js";
|
|
30
|
+
/**
|
|
31
|
+
* What this brain can serve. The daemon folds this into `brain.host.status` and
|
|
32
|
+
* Otto gates each tab on it, because the daemon and the brain version
|
|
33
|
+
* independently: a current daemon can be pointed at an older brain, and the
|
|
34
|
+
* honest answer is "update the brain on that host", not a degraded reimplementation.
|
|
35
|
+
*/
|
|
36
|
+
export interface HostCapabilities {
|
|
37
|
+
/** GET/POST /__host/model/profile */
|
|
38
|
+
profiles: boolean;
|
|
39
|
+
/** GET /__host/model/budget */
|
|
40
|
+
budget: boolean;
|
|
41
|
+
/** GET /__host/logs */
|
|
42
|
+
logs: boolean;
|
|
43
|
+
/** DELETE /__host/model */
|
|
44
|
+
delete: boolean;
|
|
45
|
+
/** POST /__host/model/load and /__host/model/unload */
|
|
46
|
+
load: boolean;
|
|
47
|
+
/** The `resources` block on /__host/status */
|
|
48
|
+
resources: boolean;
|
|
49
|
+
/** GET /__host/models */
|
|
50
|
+
inventory: boolean;
|
|
51
|
+
/** Whether writes are currently permitted (allowRemoteConfig). */
|
|
52
|
+
writable: boolean;
|
|
53
|
+
}
|
|
54
|
+
export interface HostApiDeps {
|
|
55
|
+
supervisor: Supervisor;
|
|
56
|
+
/** The live catalog. Replaced wholesale by `rescan`. */
|
|
57
|
+
getCatalog: () => Model[];
|
|
58
|
+
/** Re-read the model directories, e.g. after a delete. Returns the new catalog. */
|
|
59
|
+
rescan: () => Model[];
|
|
60
|
+
getProfilesStore: () => ProfilesStore;
|
|
61
|
+
saveProfiles: (store: ProfilesStore) => void;
|
|
62
|
+
getProfileDefaults: () => ProfileDefaults | undefined;
|
|
63
|
+
queryGpuInfo: () => Promise<GpuInfo | null>;
|
|
64
|
+
getRanking: () => RankedModel[];
|
|
65
|
+
loadModel: (model: Model) => Promise<void>;
|
|
66
|
+
/** Mirrors POST /__host/config's gate: may a network caller change things? */
|
|
67
|
+
getAllowWrite: () => boolean;
|
|
68
|
+
/** The managed models directory, for disk accounting. Null when unresolvable. */
|
|
69
|
+
getModelsDir: () => string | null;
|
|
70
|
+
sampleResources: () => Promise<SystemSample>;
|
|
71
|
+
}
|
|
72
|
+
/** One row of the model inventory: the scan, metadata, profile and score joined. */
|
|
73
|
+
export interface InventoryRow {
|
|
74
|
+
id: string;
|
|
75
|
+
displayName: string;
|
|
76
|
+
publisher: string | null;
|
|
77
|
+
quant: string | null;
|
|
78
|
+
sizeBytes: number;
|
|
79
|
+
mmprojBytes: number;
|
|
80
|
+
origin: string | null;
|
|
81
|
+
arch: string | null;
|
|
82
|
+
contextLength: number | null;
|
|
83
|
+
blockCount: number | null;
|
|
84
|
+
headCountKv: number | null;
|
|
85
|
+
/** Capability flags, matching the TUI's V / M / R badges. */
|
|
86
|
+
hasProjector: boolean;
|
|
87
|
+
reasoning: boolean;
|
|
88
|
+
mtp: boolean;
|
|
89
|
+
distilled: boolean;
|
|
90
|
+
useCases: string[];
|
|
91
|
+
tier: string | null;
|
|
92
|
+
profile: Profile;
|
|
93
|
+
calibration: ReturnType<typeof calibrationInfo>;
|
|
94
|
+
budget: vram.Budget | null;
|
|
95
|
+
maxContextThatFits: number | null;
|
|
96
|
+
score: RankedModel | null;
|
|
97
|
+
state: "loaded" | "loading" | "not-loaded";
|
|
98
|
+
warnings: ReturnType<typeof profileWarnings>;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Join one model's scan row, GGUF metadata, saved profile, calibration, VRAM
|
|
102
|
+
* budget and benchmark score into the single shape the Models tab renders.
|
|
103
|
+
*
|
|
104
|
+
* Exported for testing: the join is the part worth pinning down, since the
|
|
105
|
+
* client would otherwise have to correlate three unrelated lists by display name.
|
|
106
|
+
*/
|
|
107
|
+
export declare function buildInventoryRow(params: {
|
|
108
|
+
model: Model;
|
|
109
|
+
store: ProfilesStore;
|
|
110
|
+
defaults: ProfileDefaults | undefined;
|
|
111
|
+
gpu: GpuInfo | null;
|
|
112
|
+
ranking: RankedModel[];
|
|
113
|
+
supervisor: Supervisor;
|
|
114
|
+
}): InventoryRow;
|
|
115
|
+
export interface HostApi {
|
|
116
|
+
/** Returns true when it answered the request, false to fall through. */
|
|
117
|
+
handle: (req: http.IncomingMessage, res: http.ServerResponse) => boolean;
|
|
118
|
+
/**
|
|
119
|
+
* The current capability set. Exposed separately from the route so
|
|
120
|
+
* `/__host/status` can carry it inline: the daemon polls status constantly and
|
|
121
|
+
* would otherwise need a second round trip, or a cache that goes stale the
|
|
122
|
+
* moment `allowRemoteConfig` is toggled, since `writable` lives in here.
|
|
123
|
+
*/
|
|
124
|
+
capabilities: () => HostCapabilities;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Build the `/__host/*` management handler.
|
|
128
|
+
*/
|
|
129
|
+
export declare function createHostApi(deps: HostApiDeps): HostApi;
|
|
130
|
+
/** The delete plan without performing it, for a confirmation dialog. */
|
|
131
|
+
export declare function describeDelete(model: Model): ReturnType<typeof planDelete>;
|
|
132
|
+
//# sourceMappingURL=host-api.d.ts.map
|