@avantf/dsh-mission 0.3.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.
Files changed (74) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +249 -0
  3. package/cordis.patch.yml +13 -0
  4. package/lib/client.js +225 -0
  5. package/lib/dsh-build.json +16 -0
  6. package/lib/envinit-bootstrap.js +334 -0
  7. package/lib/index.js +7021 -0
  8. package/lib/interface-version.json +4 -0
  9. package/lib/mission-core/capacity.d.ts +100 -0
  10. package/lib/mission-core/continuation.d.ts +106 -0
  11. package/lib/mission-core/dispatch.d.ts +165 -0
  12. package/lib/mission-core/engine.d.ts +331 -0
  13. package/lib/mission-core/index.d.ts +25 -0
  14. package/lib/mission-core/liveness.d.ts +99 -0
  15. package/lib/mission-core/prompt.d.ts +112 -0
  16. package/lib/mission-core/resources.d.ts +57 -0
  17. package/lib/mission-core/timing.d.ts +37 -0
  18. package/lib/mission-core/tree.d.ts +447 -0
  19. package/lib/mission-core/trouble.d.ts +40 -0
  20. package/lib/mission-core/types.d.ts +409 -0
  21. package/lib/mission-core/wellformed.d.ts +98 -0
  22. package/lib/types/claims.d.ts +3 -0
  23. package/lib/types/claims.d.ts.map +1 -0
  24. package/lib/types/client/MissionTreeView.d.ts +247 -0
  25. package/lib/types/client/MissionTreeView.d.ts.map +1 -0
  26. package/lib/types/client/api.d.ts +184 -0
  27. package/lib/types/client/api.d.ts.map +1 -0
  28. package/lib/types/client/contract.d.ts +209 -0
  29. package/lib/types/client/contract.d.ts.map +1 -0
  30. package/lib/types/client/index.d.ts +48 -0
  31. package/lib/types/client/index.d.ts.map +1 -0
  32. package/lib/types/client/seat.d.ts +26 -0
  33. package/lib/types/client/seat.d.ts.map +1 -0
  34. package/lib/types/client/styles.d.ts +6 -0
  35. package/lib/types/client/styles.d.ts.map +1 -0
  36. package/lib/types/coldResume.d.ts +123 -0
  37. package/lib/types/coldResume.d.ts.map +1 -0
  38. package/lib/types/domain.d.ts +93 -0
  39. package/lib/types/domain.d.ts.map +1 -0
  40. package/lib/types/envinit.d.ts +126 -0
  41. package/lib/types/envinit.d.ts.map +1 -0
  42. package/lib/types/executorSession.d.ts +129 -0
  43. package/lib/types/executorSession.d.ts.map +1 -0
  44. package/lib/types/faces.d.ts +30 -0
  45. package/lib/types/faces.d.ts.map +1 -0
  46. package/lib/types/host.d.ts +752 -0
  47. package/lib/types/host.d.ts.map +1 -0
  48. package/lib/types/index.d.ts +48 -0
  49. package/lib/types/index.d.ts.map +1 -0
  50. package/lib/types/interface_gate.d.ts +51 -0
  51. package/lib/types/interface_gate.d.ts.map +1 -0
  52. package/lib/types/log.d.ts +18 -0
  53. package/lib/types/log.d.ts.map +1 -0
  54. package/lib/types/projectionCache.d.ts +22 -0
  55. package/lib/types/projectionCache.d.ts.map +1 -0
  56. package/lib/types/prompt.d.ts +106 -0
  57. package/lib/types/prompt.d.ts.map +1 -0
  58. package/lib/types/source.d.ts +22 -0
  59. package/lib/types/source.d.ts.map +1 -0
  60. package/lib/types/store.d.ts +20 -0
  61. package/lib/types/store.d.ts.map +1 -0
  62. package/lib/types/timeFormat.d.ts +56 -0
  63. package/lib/types/timeFormat.d.ts.map +1 -0
  64. package/lib/types/tools.d.ts +34 -0
  65. package/lib/types/tools.d.ts.map +1 -0
  66. package/lib/types/wellformed.d.ts +43 -0
  67. package/lib/types/wellformed.d.ts.map +1 -0
  68. package/lib/types/wire.d.ts +178 -0
  69. package/lib/types/wire.d.ts.map +1 -0
  70. package/lib/types/workerEvents.d.ts +36 -0
  71. package/lib/types/workerEvents.d.ts.map +1 -0
  72. package/lib/types/workerSessions.d.ts +256 -0
  73. package/lib/types/workerSessions.d.ts.map +1 -0
  74. package/package.json +145 -0
@@ -0,0 +1,752 @@
1
+ import { Context } from '@deepseek-ai/cordis';
2
+ import { TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
3
+ import type { Agent } from '@deepseek-ai/dsh-agent';
4
+ import { type CapacityReading, type ChildSpec, type ContinuationDelta, type MutationResult, type NodeRecord, type OwnerProbe, type ResourceProbe, type TreeRecord, type WaitingFor, type WellFormedSource } from '../mission-core/index.js';
5
+ import { type MissionLogger } from './log.js';
6
+ /**
7
+ * The v1 host implementation of the core's {@link ResourceProbe}. It uses ONLY the unified Node API
8
+ * — `os.availableParallelism()` (respects cgroup CPU quotas and affinity), `os.totalmem()` /
9
+ * `os.freemem()`, and `process.constrainedMemory()` — and this function is the ONE seam where a
10
+ * platform branch is allowed to exist. Platform adapters (POSIX/Windows pressure, per-worker process
11
+ * attribution) are deliberately NOT implemented here: `pressure()` answers `null` ("no signal on
12
+ * this platform"), which no caller may read as "idle".
13
+ */
14
+ export declare function createHostResourceProbe(): ResourceProbe;
15
+ /** Whether the tree owner should be allowed into a proposed step. */
16
+ interface AdmitDecision {
17
+ readonly admit: boolean;
18
+ readonly reason: string;
19
+ }
20
+ /** One node as the browser half renders it — the ROW projection. Deliberately without `description`
21
+ * or the submitted result: both are re-sent on every engine change and a row renders neither. */
22
+ interface NodeView {
23
+ readonly id: string;
24
+ /** Where this node was BORN (`null` for a root), NOT the dependency edge: a reused prerequisite
25
+ * keeps its birth parent while appearing in several parents' `children` — read the graph via `children`. */
26
+ readonly parentId: string | null;
27
+ /** The ids this node actually depends on — the authoritative edge (see `parentId`). */
28
+ readonly children: readonly string[];
29
+ readonly depth: number;
30
+ readonly title: string;
31
+ /** Legacy: the row sends `[]` (the texts are in {@link NodeDetail.context}); an OLDER host still
32
+ * sends them, which is why the field is still on the wire. Read the count, not this. */
33
+ readonly context: readonly string[];
34
+ /** How many premises this mission carries. The row renders the count; the texts are in the detail. */
35
+ readonly contextCount: number;
36
+ /** The owner's corrections, newest last. Carried in the ROW projection (unlike `description` and
37
+ * the submitted result) because a row has to say the mission was steered: `title` is frozen at
38
+ * creation, so a corrected mission otherwise reads as "goal X, result of Y" with nothing between
39
+ * them to explain the difference. Bounded to the newest {@link ROW_CORRECTIONS_MAX}: a row renders
40
+ * only the count, and every text is in the detail. */
41
+ readonly corrections: readonly string[];
42
+ /** The owner's FULL correction count, independent of the capped array above. */
43
+ readonly correctionCount: number;
44
+ readonly status: string;
45
+ readonly attempts: number;
46
+ readonly createdAt: number;
47
+ /** When this node was FIRST dispatched, or `null` while it is still queued (see
48
+ * `@avantf/mission-core`'s `NodeRecord.dispatchedAt`). Carried in the ROW projection: the row's
49
+ * compact timing marker is derived from it, and an older host omitting it reads as `null`. */
50
+ readonly dispatchedAt: number | null;
51
+ /** When this node entered a terminal status, or `null` while it is still in play. Carried in the
52
+ * ROW projection for the same reason as {@link dispatchedAt}. */
53
+ readonly endedAt: number | null;
54
+ readonly hasResult: boolean;
55
+ readonly resultRef: string | null;
56
+ /** The session that ran this node LAST, or `null` when none ever did (see `workerSessionIdOf`). */
57
+ readonly workerSessionId: string | null;
58
+ /** Whether that handle is still running (see `workerLiveOf`), so the panel can say 进行中 / 已结束. */
59
+ readonly workerLive: boolean;
60
+ /** Declared capacity weight (cores-equivalent); persisted, so it survives a restart. */
61
+ readonly weight: number;
62
+ /** Why the engine has not dispatched this node yet, or `null`. Carried in the ROW projection
63
+ * because a queued mission that looks identical to a ready one is exactly the confusion the
64
+ * `waitingFor` projection exists to remove. */
65
+ readonly waitingFor: WaitingFor | null;
66
+ }
67
+ /** One mission's full detail, as the expanded row renders it. */
68
+ export interface NodeDetail {
69
+ readonly id: string;
70
+ readonly rootId: string;
71
+ readonly title: string;
72
+ readonly description: string;
73
+ readonly context: readonly string[];
74
+ /** The owner's corrections, newest last: the direction changes this mission has been given, in the
75
+ * order they arrived. Rendered as its own block so the goal above and the result below are read
76
+ * in the light of them, and so a mission can be traced back to why it ended up where it did. */
77
+ readonly corrections: readonly string[];
78
+ /** What this mission's executors recorded, oldest first; exposed so an operator reads the judgement a
79
+ * re-dispatched executor sees (the panel does not render it yet). */
80
+ readonly analysisNotes: readonly string[];
81
+ /** The dispatch (`attempts`) that wrote the last entry; `0` when none. */
82
+ readonly analysisAttempt: number;
83
+ readonly status: string;
84
+ readonly attempts: number;
85
+ readonly depth: number;
86
+ /** When this node was accepted into the tree. Carried in the detail projection (not the row) because
87
+ * the dialog's full timing line is the only place the 受理 instant is shown. */
88
+ readonly createdAt: number;
89
+ /** When this node was FIRST dispatched, or `null` while it is still queued. The detail dialog shows
90
+ * it beside `createdAt`/`endedAt`; the row carries the compact form. */
91
+ readonly dispatchedAt: number | null;
92
+ /** When this node entered a terminal status, or `null` while it is still in play. */
93
+ readonly endedAt: number | null;
94
+ /** The submitted conclusion, when there is one (inline; a long one is truncated). */
95
+ readonly result: string | null;
96
+ /** Where a spilled full result lives, joined with its retrieval hint. */
97
+ readonly resultPointer: string | null;
98
+ /** The session that ran this node LAST, or `null` when none ever did (see `workerSessionIdOf`). */
99
+ readonly workerSessionId: string | null;
100
+ /** Whether that handle is still running (see `workerLiveOf`), so the panel can say 进行中 / 已结束. */
101
+ readonly workerLive: boolean;
102
+ /** Declared capacity weight (cores-equivalent). */
103
+ readonly weight: number;
104
+ /** Why this node is queued rather than running, or `null`. */
105
+ readonly waitingFor: WaitingFor | null;
106
+ }
107
+ /** One sub-mission of a node, with the conclusion it submitted. */
108
+ interface NodeDetailChild {
109
+ readonly id: string;
110
+ readonly title: string;
111
+ readonly status: string;
112
+ readonly result: string | null;
113
+ readonly resultPointer: string | null;
114
+ }
115
+ /** One tree with its nodes, as the browser half renders it. */
116
+ interface TreeView {
117
+ readonly rootId: string;
118
+ readonly nodes: readonly NodeView[];
119
+ readonly closedAt: number | null;
120
+ }
121
+ /** One tree summarized for the owner-facing tools. */
122
+ interface MissionSummary {
123
+ readonly tree: TreeRecord;
124
+ readonly root: NodeRecord | undefined;
125
+ readonly counts: Readonly<Record<string, number>>;
126
+ /** Whether the owner closed the tree out; its results stay readable. */
127
+ readonly closed: boolean;
128
+ /**
129
+ * Whether this mission carries a HISTORY of trouble (stalls/failures at the engine's floors). The
130
+ * counters never reset, so it stays true for the mission's life — hence the past tense. The one engine
131
+ * state an owner tool surfaces (only one it can act on); per-state `counts` stay for the human-facing
132
+ * surfaces — the `/mission` command and the "任务" view.
133
+ */
134
+ readonly troubled: boolean;
135
+ }
136
+ /** One orphaned tree as `/clean orphans` renders it: the durable record, the probe that says why,
137
+ * and the root's status. `unobservable` is the operator's business; `missing` is already cleaned
138
+ * by the next reconciliation and is listed only so the count adds up. */
139
+ export interface OrphanTreeReport {
140
+ readonly rootId: string;
141
+ readonly ownerSessionId: string;
142
+ readonly createdAt: number;
143
+ readonly closedAt: number | null;
144
+ /** The root mission's status at read time, so a closed-but-orphaned tree reads differently. */
145
+ readonly rootStatus: string;
146
+ readonly probe: OwnerProbe;
147
+ }
148
+ declare module '@deepseek-ai/cordis' {
149
+ interface Context {
150
+ avantfMission: AvantfMissionHost;
151
+ }
152
+ }
153
+ interface HostOptions {
154
+ /** Slot ceiling on the NUMBER of concurrent units; omitted means "CPU cores minus one". Capacity is
155
+ * the master gate — both must admit a dispatch (see `@avantf/mission-core`'s `EngineOptions`). */
156
+ readonly maxConcurrent?: number;
157
+ /** The capacity gate, in cores-equivalent. Omitted means "derive it from this machine"
158
+ * (`availableParallelism` → `cpus().length` → 4, minus one reserved core). */
159
+ readonly capacity?: number;
160
+ /** How long a node may be deferred by the capacity gate before it reserves the machine; omitted
161
+ * means the core default, and a value below the floor is raised to it with a warning. */
162
+ readonly capacityWaitMs?: number;
163
+ /** Free-memory floor in bytes; omitted means the core default, `0` disables the gate. */
164
+ readonly minFreeMemoryBytes?: number;
165
+ /** Machine reader; omitted means the host's Node implementation ({@link createHostResourceProbe}).
166
+ * Injectable so a composition or test can fix every signal deterministically. */
167
+ readonly probe?: ResourceProbe;
168
+ /** How long a worker may produce nothing before it counts as stuck. */
169
+ readonly staleMs?: number;
170
+ /** Wall-clock ceiling on one dispatch round, however much it keeps reporting; omitted means one hour. */
171
+ readonly roundMs?: number;
172
+ }
173
+ export declare class AvantfMissionHost extends TypertRemoteService {
174
+ private tree?;
175
+ private engine?;
176
+ private domain?;
177
+ private sweepDispose?;
178
+ /**
179
+ * Extra best-effort passes to run after every sweep. Automatic worker retention registers here so
180
+ * it rides the EXISTING sweep cadence instead of arming a second timer. The host never awaits a
181
+ * pass: a slow or failing one must not delay dispatch, and a rejection is reported, not surfaced.
182
+ */
183
+ private readonly sweepListeners;
184
+ private readonly workerAborts;
185
+ /** Every child session id reserved for a worker, for the process lifetime: answers "is this settling
186
+ * agent one of ours?" even after the node's binding is gone. */
187
+ private readonly issuedClaims;
188
+ /** Owner session ids this host has dispatched a worker for (hook scoping). */
189
+ private readonly dispatchedFor;
190
+ /** Claims bound but not yet accepted by the child. A worker is "live" from reservation: a sweep in
191
+ * that gap would otherwise reclaim a starting worker and pay for a duplicate run per node. */
192
+ private readonly startingClaims;
193
+ /**
194
+ * Sessions whose WAKE is in flight: live from the adoption that binds them until the resumed
195
+ * agent is observed, the delivery reports a failure, or the engine gives up on the binding.
196
+ *
197
+ * Two wakes use this one guard, for the same reason — the target session is IDLE, so the liveness
198
+ * check alone reads a freshly adopted binding as vanished:
199
+ *
200
+ * - a PARKED session (it decomposed and ended its turn, which is exactly what "parked" means);
201
+ * - a session recorded in `lastWorkerId` that a restart demoted (`cold wake`).
202
+ *
203
+ * The sweep triggered by the LAST CHILD's own `subagent/end` runs in the same moment as the
204
+ * owner's wake, so without this guard it reclaims the binding the wake is delivering into: the
205
+ * cold resume still lands, the old executor burns a whole extra round, and its `submit_mission` is
206
+ * refused because the node was already re-dispatched to a fresh claim. Measured in a real run
207
+ * (2026-09-22): 8 wasted minutes, two `not-owner` refusals, and `attempts`/`failures` charged an
208
+ * extra time each — the failures are the dangerous half, because they spend the budget that ends a
209
+ * node.
210
+ */
211
+ private readonly wakingClaims;
212
+ /** Per-owner change counters and the open `watch` streams waiting on them: how "the tree changed"
213
+ * reaches the browser without polling or session-log writes. */
214
+ private readonly revisions;
215
+ private readonly waiters;
216
+ /** Recent durable owner-probe answers, so a sweep cannot hammer storage. `unobservable` is cached
217
+ * like any other verdict for the TTL; a delete re-probes fresh rather than trusting it. */
218
+ private readonly ownerChecks;
219
+ /** The orphan set the last aggregate report described, so a sweep that changes nothing is silent.
220
+ * Missing trees are absent from it: reconciliation destroys them in the same pass that reports. */
221
+ private orphanReportSignature;
222
+ /** Parked nodes the owner has already been told about. `reportParkedReady` fires on every pump, so
223
+ * without this the owner's inbox accumulates one wake per pass; an entry is dropped when the node
224
+ * leaves the parked state, so it never outlives its condition. In-memory on purpose: after a restart
225
+ * one extra signal is harmless. */
226
+ private readonly parkedSignaled;
227
+ /** Sweep failures fold into the next warning; `undefined` means none has been logged yet. */
228
+ private sweepFailureWarnedAt;
229
+ private sweepFailuresSinceWarn;
230
+ /** Own workers that settled this process; the retention pass reads it to skip empty sweeps. */
231
+ private workerSettlements;
232
+ private readonly log;
233
+ /**
234
+ * @param wellFormed - the family's well-formed repair, resolved by `apply()` from the loaded base
235
+ * kit (`resolveWellFormed(compat?.kit)`) and defaulted to the core's local copy. It is threaded to
236
+ * the three boundaries this host owns — the tree's inbound funnel (via `TreeDeps.wellFormed`), the
237
+ * worker prompt and the progress line (via `buildWorkerPrompt`'s third argument), and the model
238
+ * tool results (read by `defineWorkTools`) — so one resolution decides all of them.
239
+ */
240
+ constructor(ctx: Context, options?: HostOptions, log?: MissionLogger, wellFormed?: WellFormedSource);
241
+ /** The repair pair every model-visible boundary of this host uses; see the constructor. */
242
+ readonly wellFormed: WellFormedSource;
243
+ private readonly maxConcurrent;
244
+ private readonly capacityOption;
245
+ private readonly capacityWaitMsOption;
246
+ private readonly minFreeMemoryBytesOption;
247
+ /** The machine reader; injectable, always present (the Node implementation is the default). */
248
+ readonly probe: ResourceProbe;
249
+ private readonly staleMs;
250
+ private readonly roundMs;
251
+ private ready;
252
+ /** Record the start-up promise for a caller that wants a fully opened tree; the tools never need it,
253
+ * but a composition or test may. */
254
+ markReady(ready: Promise<void>): void;
255
+ /** Diagnostic sink for dispatch decisions: the host logger is buffered in some compositions, so a
256
+ * mount test needs an observable channel. Unset means silent. */
257
+ onDispatchTrace?: (message: string) => void;
258
+ private trace;
259
+ /** Resolve once storage is open, trees are loaded and orphans reconciled. */
260
+ whenReady(): Promise<void>;
261
+ /** Workers allowed at once: the configured value, or cores minus one (the owner's own turns are not
262
+ * the only agent here). This is the SLOT ceiling, not the capacity gate — see {@link capacityReading}. */
263
+ concurrency(): number;
264
+ /**
265
+ * The capacity the engine gates on, plus WHERE it came from (the start-up line reports the source).
266
+ *
267
+ * The derivation chain lives in the core (`deriveCapacity`), so it is testable without a machine:
268
+ * ① explicit config → ② `os.availableParallelism()` (honours cgroup quotas and affinity) →
269
+ * ③ `os.cpus().length` → ④ a default of 4; then one core is reserved for the host/UI. An explicit
270
+ * config is used AS GIVEN, clamped: the operator's number already is the answer.
271
+ */
272
+ capacityReading(): CapacityReading;
273
+ /** How long a node may be repeatedly deferred by capacity before it reserves the machine. Floored
274
+ * like the other windows: below a minute a reservation is indistinguishable from jitter. */
275
+ capacityWaitWindowMs(): number;
276
+ /** The free-memory floor in bytes; `0` disables the gate. The probe may still answer `null`, which
277
+ * DEFERS dispatch — "cannot confirm the floor" is never read as "plenty of memory". */
278
+ freeMemoryFloorBytes(): number;
279
+ /** Silent time before the engine calls a worker stuck. The 60 s floor exists because the check rides
280
+ * a 60-second sweep: a shorter window would interrupt every worker on its first pass. */
281
+ staleWindowMs(): number;
282
+ /** Wall-clock ceiling on one dispatch round. Deliberately generous, and floored like `staleMs`:
283
+ * it is the backstop for a worker that keeps being HEARD FROM (retries, route snapshots) while
284
+ * producing nothing, so a small configured value must not interrupt a round that is merely slow.
285
+ *
286
+ * The floor is `max(10 min, staleMs)`, not a bare constant: a round cap BELOW the output window
287
+ * would fire before `stalled` ever can, which would quietly retire the rule that a repeatedly
288
+ * silent node runs out of failure budget. Tying it to the window the caller actually configured
289
+ * keeps that rule true for every configuration, not just the defaults. `staleMs` is passed in so
290
+ * the two windows are resolved from ONE reading (and one warning) during open. */
291
+ roundWindowMs(staleMs?: number): number;
292
+ /**
293
+ * Open storage, reconcile, and arm the sweep. The domain opens exactly once (`storage-domain`
294
+ * refuses a second open).
295
+ *
296
+ * **This promise NEVER rejects.** Measured on the installed loader, a start-up failure has exactly
297
+ * three outcomes, and all three argue for absorbing it here:
298
+ * - `apply` throws, or awaits a rejection → only THIS entry fails (0.1.7 / 0.2.0 lines). That is
299
+ * contained, but it still costs the whole plugin row over what is an environmental fault.
300
+ * - an UNHANDLED rejection → a process-level fatal (`installFailLoud` → `exit(1)`) on every
301
+ * generation. A storage hiccup must never take down the host.
302
+ * - on the 0.1.5 line, any inactive row makes the whole boot throw.
303
+ *
304
+ * So a failure that lands while the fiber is alive is recorded as {@link degradedReason} and the
305
+ * plugin mounts DEGRADED: the tools stay registered and answer the reason, `/mission` reports it,
306
+ * and the process keeps running. A failure that lands after unmount is teardown noise.
307
+ */
308
+ start(): Promise<void>;
309
+ private starting?;
310
+ /** Why start-up ended DEGRADED, or `undefined` while healthy (or still opening). Set at most once. */
311
+ private degradedReasonValue?;
312
+ /**
313
+ * The DEGRADED marker the tool surface and `/mission` read: `undefined` means the engine opened
314
+ * normally, a string is the cause the storage domain could not be opened. Kept separate from
315
+ * `whenReady()` on purpose — readiness still RESOLVES on the degraded path, so nothing that awaits
316
+ * it can mistake a degraded mount for a rejected one.
317
+ */
318
+ degradedReason(): string | undefined;
319
+ private open;
320
+ /** Whether this plugin's Cordis fiber is still active: Cordis clears `fiber.uid` on disposal, after
321
+ * which a required-service read throws `INACTIVE_EFFECT`. Reads the FIBER-SCOPED `this.ctx` from
322
+ * construction; a context without a readable fiber (a test stub) counts as active. */
323
+ private stillActive;
324
+ stop(): Promise<void>;
325
+ /** Run one dispatch pass. Every trigger funnels here. */
326
+ pump(): Promise<number>;
327
+ /** Reclaim vanished bindings, then dispatch. Called on worker lifecycle edges (a `running` node may
328
+ * have lost its worker without submitting) and by the periodic sweep. */
329
+ sweep(): Promise<{
330
+ reclaimed: number;
331
+ dispatched: number;
332
+ }>;
333
+ /**
334
+ * Register one best-effort pass to run after every {@link sweep}. Used by automatic worker
335
+ * retention, which must ride the existing cadence rather than arm a second timer. The pass is
336
+ * never awaited: it cannot delay or fail a sweep, and a rejection becomes one warning.
337
+ */
338
+ onSweep(pass: () => void | Promise<void>): void;
339
+ /** Fire each registered post-sweep pass without awaiting it. A throw or rejection is a warning. */
340
+ private runSweepListeners;
341
+ /**
342
+ * Report a background sweep that threw. The chain the two fire-and-forget callers start is not
343
+ * cosmetic: it flushes progress (a durable write), probes storage for orphans and then reclaims
344
+ * and dispatches. Silently swallowing it hid a store that refuses `put` (a full disk, a domain
345
+ * gone) failing once a minute with zero evidence. Rate-limited, because the same condition
346
+ * repeats on every 60 s tick: the first failure speaks, later ones fold into one line per window.
347
+ */
348
+ private reportSweepFailure;
349
+ /** Every tree whose owner session did not resolve to `exists`, with the probe that says why.
350
+ * `fresh` bypasses the probe cache: a listing a person acts on must not be up to five minutes
351
+ * stale about whether a session came back. The startup/sweep path keeps the cache, because it
352
+ * asks the same question about the same trees every sixty seconds. */
353
+ orphanTreeReports(options?: {
354
+ fresh?: boolean;
355
+ }): Promise<readonly OrphanTreeReport[]>;
356
+ /** Re-probe ONE tree's owner from durable storage, ignoring the cache: the check a delete must
357
+ * make, so a session that came back between the listing and the delete keeps its tree.
358
+ * `undefined` means the tree is gone (already destroyed, or an id that never named one). */
359
+ probeOrphanTree(rootId: string): Promise<OrphanTreeReport | undefined>;
360
+ /** Destroy one orphan tree: stop the workers it still holds, then remove it through the store.
361
+ * Refuses nothing — the caller has already re-probed the owner and may only reach here for a
362
+ * tree whose owner is missing or unobservable. */
363
+ destroyOrphanTree(rootId: string): Promise<boolean>;
364
+ /** Destroy the trees of owners that are PROVEN gone, and report the orphan set as one line.
365
+ * Returns the destroyed root ids (the shape the engine's reconcile has always had). */
366
+ private reconcileOrphans;
367
+ /** The orphan set, probed one tree at a time with the cache bypassed. */
368
+ private probeEveryTreeFresh;
369
+ private orphanReportOf;
370
+ /** ONE line per distinct orphan set — never one per tree. The per-tree WARN this replaces was the
371
+ * noise the user saw on every start: twelve old trees, twelve lines, for one host-side condition.
372
+ * WARN when somebody has to decide (`unobservable`); INFO when reconciliation already cleaned up. */
373
+ private reportOrphans;
374
+ /** Record one worker's activity if the session is ours. The feed carries every session in the
375
+ * process, so the claim check comes first; only "making no progress" is a stall.
376
+ *
377
+ * The check is `isWorkerClaim`, NOT membership in `issuedClaims`: that set is in-memory and is
378
+ * never refilled from the durable tree at start-up, while `MissionTree.reconcileOnOpen` explicitly
379
+ * keeps a hot-reload survivor `running` and resets its clocks. A worker that survived the
380
+ * reload therefore had every later progress event dropped, and after `staleMs` was interrupted and
381
+ * reclaimed as stalled — burning one attempt and a `failures` slot on a worker that was working.
382
+ * `nodeHeldBy` below still requires an actual binding, so the shape check cannot touch a stranger.
383
+ *
384
+ * `output` is the CLASSIFICATION the caller made (see `workerEvents.ts`): every event is recorded
385
+ * as life (`touchActivity`), but only real output moves the clock the stale check reads. That
386
+ * split is what turns "the worker is still answering retries" from evidence of progress into what
387
+ * it is — a worker the engine should reclaim as `hung`. */
388
+ touchWorkerProgress(sessionId: string, at: number, output: boolean): void;
389
+ /** A subagent run ended. For one of our workers this is the earliest moment its node can be judged —
390
+ * reclaim now, not at the next sweep; the claim check comes first so foreign runs cost nothing. */
391
+ onSubagentEnd(childSessionId: string): void;
392
+ /**
393
+ * How many of this session's own workers have settled since start-up. Monotonic; the retention
394
+ * pass compares it against the value it last ran at, so a periodic sweep with no settlement does
395
+ * no session listing at all. A restart resets it, which is why the mount pass also runs once
396
+ * unconditionally.
397
+ */
398
+ get workerSettlementCount(): number;
399
+ /**
400
+ * Whether a claim resolves to a worker that is alive, still starting, or being WOKEN. A continuable
401
+ * child materializes asynchronously, and treating that window as "vanished" made a sweep reclaim the
402
+ * starting worker, re-dispatch, and leave the first with every `submit_mission` refused — two LLM runs
403
+ * for one attempt. A claim is live from reservation until the child accepts its prompt.
404
+ *
405
+ * The parked case is the same window with a different cause: the session is idle (that is what
406
+ * parking means) while the wake is being delivered, so a claim is live from the adoption until the
407
+ * resumed agent shows up. Seeing the agent ends the guard — it must not outlive the evidence.
408
+ */
409
+ workerLive(sessionId: string): boolean;
410
+ /**
411
+ * Steer one RUNNING root mission: record the correction and, when somebody holds it, hand it over now.
412
+ * The record is durable (a root outlives each dispatch while waiting for children); the live message
413
+ * reaches the executor already running. A finished root has nothing to steer and a non-root is
414
+ * refused — the owner steers what it handed out, not somebody's sub-tree.
415
+ */
416
+ adjustWork(agent: Agent, rootId: string, adjustment: string): Promise<MutationResult<{
417
+ readonly delivered: boolean;
418
+ readonly voided: number;
419
+ }>>;
420
+ /**
421
+ * Cancel the sub-tree below one held node. Live holders are captured BEFORE the mutation clears them,
422
+ * or a cancelled worker keeps burning a model call whose `submit_mission` can only be refused.
423
+ */
424
+ cancelSubworks(agent: Agent, nodeId: string): Promise<MutationResult<readonly string[]>>;
425
+ /** Whether a session id is a worker this host dispatched (this or a past generation). */
426
+ isWorkerClaim(sessionId: string): boolean;
427
+ /** Root a new mission. `unit` is the scope the mission will modify (a directory or file), or
428
+ * `undefined`/blank for none: a tree whose root declares a scope serializes every one of its
429
+ * executors against any other `running` mission declaring the same scope, across trees. `roundMs`
430
+ * is the declared round-cap relaxation (see `NodeRecord.roundMs`); `undefined` keeps the engine's
431
+ * configured cap. */
432
+ createWork(agent: Agent, title: string, description: string, analysis: readonly string[], unit?: string | null, weight?: number, roundMs?: number | null): Promise<MutationResult<NodeRecord>>;
433
+ /** The node is the durable carrier: the writing round is gone by the aggregate pass, which reads the
434
+ * node's prompt. The write also stamps the `attempts` that `decompose` checks. */
435
+ recordAnalysis(agent: Agent, nodeId: string, analysis: string): Promise<MutationResult<NodeRecord>>;
436
+ decompose(agent: Agent, nodeId: string, children: readonly ChildSpec[]): Promise<MutationResult<{
437
+ created: readonly string[];
438
+ reused: readonly string[];
439
+ }>>;
440
+ submitResult(agent: Agent, nodeId: string, result: string): Promise<MutationResult<{
441
+ node: NodeRecord;
442
+ parentReady: boolean;
443
+ }>>;
444
+ /** Read a node's result, recording the read so the finish gate can open. */
445
+ readResult(agent: Agent, nodeId: string): Promise<MutationResult<NodeRecord>>;
446
+ /** Trees owned by one session, newest first, with status rollups and closure. */
447
+ listWorks(agent: Agent): readonly MissionSummary[];
448
+ finishWork(agent: Agent, rootId: string): Promise<MutationResult<NodeRecord>>;
449
+ cancelWork(agent: Agent, rootId: string): Promise<MutationResult<readonly NodeRecord[]>>;
450
+ /**
451
+ * Whether a proposed step should reach the model. The wake carries only a signal, so this decides on
452
+ * STATE: a tree this session owns must have something actionable. Otherwise the step is refused,
453
+ * which also discards the trigger message.
454
+ */
455
+ admitStep(agent: Agent): AdmitDecision;
456
+ /** Whether this plugin's wake/notice filtering applies: a session that owns a tree (closed included —
457
+ * its worker may still settle) or one this host dispatched for. */
458
+ ownsTrees(agent: Agent): boolean;
459
+ /** Whether this agent may root a tree. One predicate for the tool's authority check and the prompt
460
+ * section that teaches it: a session that cannot root must not be told how. */
461
+ canCreateTree(agent: Agent): boolean;
462
+ /**
463
+ * The guidance text for one owner, or `''` when it has nothing open. Closed trees are left out: their
464
+ * conclusions live in `mission_result`, and re-stating a finished tree is the snapshot churn the anchored
465
+ * wording avoids. Granularity matches `list_missions`: running count plus trouble history, nothing deeper.
466
+ */
467
+ guidanceFor(agent: Agent): string;
468
+ /**
469
+ * Everything the "任务" view renders for one session: one Remote method, so the client makes one call.
470
+ * The session id IS a parameter because a Remote invocation carries no caller identity; the trust
471
+ * model is "the local UI asks for the session it is showing", in the user's own host process.
472
+ *
473
+ * `wire` is the panel's version marker (see `wire.ts`): the client reads it to say "the two halves
474
+ * are out of step" instead of blanking the panel. `trees` keeps its shape, so an older client that
475
+ * has never heard of `wire` simply drops the extra key.
476
+ */
477
+ snapshot(args: {
478
+ sessionId?: string;
479
+ }): Promise<{
480
+ wire: number;
481
+ trees: readonly TreeView[];
482
+ }>;
483
+ /**
484
+ * One node's FULL result, read back from where an over-long one was spilled. On demand, never part
485
+ * of `detail`: a result that was spilled is spilled because it is big, and `detail` is re-read on
486
+ * every engine change while the dialog is open.
487
+ *
488
+ * The locator is the spill backend's, and the backend's contract says it is OPAQUE — `SpillStore`
489
+ * defines `saveText` and nothing else, on purpose. The one backend in use (`dsh-spill-local`) hands
490
+ * out an absolute path, so that is the case this reads; anything else answers with an error and the
491
+ * pane keeps the locator for a reader that knows the substrate (which is what the locator is FOR).
492
+ */
493
+ result(args: {
494
+ sessionId?: string;
495
+ nodeId: string;
496
+ }): Promise<{
497
+ text: string;
498
+ error?: string;
499
+ }>;
500
+ /** One node's full detail: a second read rather than more snapshot, since results run to 2 KB and the
501
+ * summary is re-read on every change. Children's results feed an aggregate's conclusion. */
502
+ detail(args: {
503
+ sessionId?: string;
504
+ nodeId: string;
505
+ }): Promise<{
506
+ node?: NodeDetail;
507
+ children: readonly NodeDetailChild[];
508
+ error?: string;
509
+ }>;
510
+ /** The detail read itself, kept synchronous so a test can call it directly. */
511
+ detailOf(sessionId: string | undefined, nodeId: string): {
512
+ node?: NodeDetail;
513
+ children: readonly NodeDetailChild[];
514
+ error?: string;
515
+ };
516
+ /** The engine's live `waitingFor` for one node, or `null` when the engine is not up yet or nothing
517
+ * is holding the node back. One accessor, so the row projection, the detail read and the tool
518
+ * result cannot disagree about why a mission is queued. Public because the model-facing tools
519
+ * render it too. */
520
+ waitingForOf(nodeId: string): WaitingFor | null;
521
+ /**
522
+ * W18: resolve the executor session of a node whose record has no `executorSessionId` — the
523
+ * historical nodes dispatched before that field existed. Called ONLY from a click; the panel's
524
+ * mount/render path never reaches it, which is the invariant that keeps a page load free of
525
+ * session-log reads.
526
+ *
527
+ * Resolution is `executorSession.ts`'s (three metadata filters, then a few log reads), and a hit
528
+ * is written back once so the next click costs nothing. The reply carries a REASON rather than an
529
+ * exception for every way it can fail: a miss is an ordinary outcome, and the panel has to be able
530
+ * to say which one it was ("never dispatched" and "the session is gone" are different sentences).
531
+ */
532
+ resolveExecutorSession(args: {
533
+ sessionId?: string;
534
+ nodeId: string;
535
+ }): Promise<{
536
+ sessionId?: string;
537
+ status?: 'resolved' | 'never-dispatched' | 'not-found' | 'unsupported';
538
+ error?: string;
539
+ }>;
540
+ /** The optional session-log reader, read at USE time (a headless deployment has none). Structural
541
+ * typing, like every other optional service here: the plugin must not require it to mount. */
542
+ private sessionLogQuery;
543
+ /** Delete one WHOLE tree — the panel's "remove this mission" (the argument is a root id; a node is not an
544
+ * addressable target). `finish_mission` is the other ending and keeps the record (archived). */
545
+ delete(args: {
546
+ sessionId?: string;
547
+ rootId: string;
548
+ }): Promise<{
549
+ deleted: readonly string[];
550
+ error?: string;
551
+ }>;
552
+ /**
553
+ * Delete EVERY closed tree this session owns, in one pass — the panel's "清理已完成" and
554
+ * `/clean missions all`.
555
+ *
556
+ * Guardrails, per tree: `ownerSessionId` must equal the caller (another session's tree is never
557
+ * even a candidate), and `closedAt` must be set (a tree that has not been retired through
558
+ * `finish_mission` is skipped and reported, never deleted). `finish` can only set `closedAt` on a
559
+ * terminal root, and `deleteTree` re-checks terminality, so a running tree cannot slip through —
560
+ * but a refusal is still bucketed rather than thrown. Deletion goes through the same
561
+ * `tree.deleteTree` the single-tree `delete` uses; the panel is told once, by owner.
562
+ */
563
+ cleanFinished(args: {
564
+ sessionId?: string;
565
+ }): Promise<{
566
+ deleted: readonly string[];
567
+ skipped: readonly string[];
568
+ }>;
569
+ /** Root ids of every tree one session owns, newest first (empty before the engine is ready). */
570
+ ownedTreeIds(sessionId: string): readonly string[];
571
+ /**
572
+ * Distinct owner session ids the task library knows about: the scope of per-owner background
573
+ * passes (automatic worker retention runs once per owner). Read from the durable trees, so it
574
+ * covers owners whose trees survived a restart, not just the ones seen this process. Empty before
575
+ * the engine is ready.
576
+ */
577
+ ownerSessionIds(): readonly string[];
578
+ /**
579
+ * Root ids of the trees one session owns and has CLOSED OUT (`finish_mission`): the batch-clean
580
+ * candidates. A tree whose nodes all reached a terminal status but which was never retired is
581
+ * deliberately NOT a candidate — completion is the owner's `finish_mission`, and such a tree stays
582
+ * deletable one at a time from the panel (the per-tree "删除" button).
583
+ */
584
+ finishedTreeIds(sessionId: string): readonly string[];
585
+ watch(args: {
586
+ sessionId?: string;
587
+ }, signal: AbortSignal): AsyncGenerator<{
588
+ revision: number;
589
+ }>;
590
+ /** Publish one change to every open stream of an owner. In-memory on purpose: a lost revision costs a
591
+ * re-read, never correctness — nothing but the tree is authoritative. */
592
+ private announceOwner;
593
+ /** Publish a change for one tree, by its root id. */
594
+ private announceTree;
595
+ /** Publish a change for every tree the engine may have moved. */
596
+ private announceAllTrees;
597
+ /** Publish a change for the tree that holds one node. */
598
+ private announceNode;
599
+ /** Resolve on the next change for one owner, or as soon as the caller aborts. */
600
+ private nextChange;
601
+ /** Trees one session owns, flattened the way the view renders them. */
602
+ treesForSession(sessionId: string | undefined): readonly TreeView[];
603
+ /** Rollup of every tree this session owns, for diagnostics and tests. */
604
+ summary(agent: Agent): Record<string, number>;
605
+ /** Snapshot for the `/mission` command. */
606
+ describe(agent: Agent): string;
607
+ private requireTree;
608
+ /**
609
+ * What every exit of one start attempt must leave behind. A claim left in `startingClaims` reports its
610
+ * node live forever — a ghost holds a concurrency slot until the stall window — so both maps are
611
+ * cleared from every exit through this one definition.
612
+ */
613
+ private endStartAttempt;
614
+ /**
615
+ * Hand back a claim reserved for a dispatch that was then REFUSED: it was never bound to a node and
616
+ * no child was ever created for it, so it must neither stay "live" (a ghost in `startingClaims`
617
+ * reports its lane occupied) nor stay remembered as one of ours — nothing will ever settle under it.
618
+ */
619
+ private releaseUnboundClaim;
620
+ /** The parked-session address on one node, or `null`; exposed for tests proving a wake did NOT consume it. */
621
+ parkedWorkerOf(nodeId: string): string | null | undefined;
622
+ /**
623
+ * The drift a cold wake of this node would report right now, or `undefined` when the node is gone.
624
+ * Read-only and cheap: a test (and, later, the panel) can ask what the wake would say without
625
+ * performing one. The DECISION to continue or replace lives in `coldResume.ts`'s `resumeWorker`,
626
+ * which consults `isMaterialChange` on exactly this value.
627
+ */
628
+ continuationDeltaOf(nodeId: string): ContinuationDelta | undefined;
629
+ /** Test seam: the real code clears an entry only when the node leaves the parked state, so a test
630
+ * driving ONE `notifyParkedReady` batch must remove its own setup's entries. */
631
+ forgetParkedSignals(): void;
632
+ /** Test seam: how many claim ids are remembered and how many count as live-but-still-starting. A
633
+ * dispatch that was REFUSED must add to neither — otherwise the reservation leaks one per refusal. */
634
+ claimCounts(): {
635
+ issued: number;
636
+ starting: number;
637
+ };
638
+ /** The parked-and-ready nodes of one tree (test seam; the engine uses the tree's own query). */
639
+ parkedReadyNodesOf(rootId: string): readonly NodeRecord[];
640
+ /** One node's durable record as stored; for tests proving a wake charged NO budget. */
641
+ nodeFor(nodeId: string): NodeRecord | undefined;
642
+ /**
643
+ * Report the parked nodes whose children have all landed as ONE owner signal. Batched on purpose: an
644
+ * owner returning offline can find several ready at once, and one wake per node would be a storm.
645
+ * Delivery is per owner and state-based, so a pass with nothing to act on is dropped by the pre-step
646
+ * gate at zero model cost.
647
+ */
648
+ notifyParkedReady(nodes: readonly NodeRecord[]): void;
649
+ /**
650
+ * The port the continuation wake works through (see `coldResume.ts`): the host hands in the state
651
+ * and callbacks the delivery needs, and keeps the private members private. Rebuilt per call, which
652
+ * is a handful of references — and the mutable sets are passed by REFERENCE on purpose, because
653
+ * they are the same guards the rest of the host (and the sweep) reads.
654
+ */
655
+ private coldResumeDeps;
656
+ /**
657
+ * Wake every parked session whose children have landed; the protocol (guard → adopt → deliver,
658
+ * with `wake-failed` on a refused delivery) lives in `coldResume.ts`, and this is the delegation
659
+ * `index.ts` calls from the owner's pre-step.
660
+ */
661
+ wakeParkedWorkers(agent?: Agent): Promise<number>;
662
+ /**
663
+ * Try to continue a node in the session that was interrupted in it (the "cold wake"); the
664
+ * decision and the delivery live in `coldResume.ts`.
665
+ */
666
+ private resumeWorker;
667
+ /**
668
+ * Stamp the node with what the prompt that was just ACCEPTED shows the session, so a later cold
669
+ * wake can subtract it (`continuation.ts`). Called at the three places that hand a session its
670
+ * dispatch prompt, and only after the delivery resolved: a prompt the runtime refused was never
671
+ * read, so nothing may claim otherwise — and the bound-but-not-started window (`startingClaims`)
672
+ * must not grow an extra awaited writable step.
673
+ *
674
+ * Tolerant on purpose. This is bookkeeping ABOUT a prompt that already exists, so a refused or
675
+ * failed stamp (the node was reclaimed and re-dispatched in between; the store refused the write)
676
+ * must not fail the delivery. The cost is one conservative wake later: a missing baseline renders
677
+ * "the drift cannot be determined", never "nothing changed".
678
+ *
679
+ * Kept on the host rather than moved with the wakes: `startWorker` (a fresh spawn) stamps through
680
+ * the same method, so it belongs to the host's delivery bookkeeping, not to the continuation seam.
681
+ */
682
+ private recordBaseline;
683
+ private startWorker;
684
+ /**
685
+ * Start one child, dropping tool names the runtime refuses to restrict. The runtime's answer is
686
+ * authoritative and the pre-check cannot be exact: a child inherits its parent's PRESET composition,
687
+ * not the parent agent's own scope, so a name the parent can see may still fail the whole filter for
688
+ * the child. Dropping it loses isolation, which beats a tree that cannot dispatch at all.
689
+ */
690
+ private startChild;
691
+ /**
692
+ * The subset of one face's names this deployment offers. `toolFilter` (and `restrict()`) THROWS on a
693
+ * name no tool provides, so a hardcoded list would fail every dispatch in a composition without (say)
694
+ * the goal tools. Names a child cannot inherit are caught by the retry in `startChild`.
695
+ */
696
+ private deniableFor;
697
+ /** The owner-side face: executor-only tools, filtered to the names this deployment offers
698
+ * (`restrict()` throws, and one throw would cost the owner its whole turn). */
699
+ ownerFaceFor(agent: Agent): readonly string[];
700
+ private interruptWorker;
701
+ /** The owner session of the tree that holds a given worker, if any. */
702
+ private findHolderTree;
703
+ /** Wake the owner: the message carries a signal only, the guidance layer supplying the content.
704
+ *
705
+ * The timing rides along because this is the one message the owner reads at the moment a mission
706
+ * ends, and a long run's whole question is "when did it start, and how long did it take?" — the
707
+ * answer is already on the record by the time the engine reports a terminal root, so demanding a
708
+ * separate `/mission` read would be pure friction. `describeTiming` also prints 等待中/进行中, but a
709
+ * terminal root always has a concrete 派发→结束 pair. */
710
+ private notifyOwner;
711
+ /** Tell the owner that one node cannot get a worker started at all. Same shape as the stall heads-up
712
+ * — the engine keeps retrying on its own (with a cooldown), so this is information, not a request —
713
+ * and the same durable marker keeps either kind of trouble to one message per node. */
714
+ private notifySpawnTrouble;
715
+ /** Tell the owner that one node keeps going wrong. A heads-up, not a request: the engine has already
716
+ * interrupted the worker and re-queued the mission. Two causes share the channel (the engine's
717
+ * `escalateTrouble`), because both are the same fact from the owner's side — this mission keeps
718
+ * needing to be taken back — and the wording names which one so the remedy is legible. */
719
+ private notifyStalled;
720
+ /** Log a `hung` reclaim. The engine recovered on its own and charged nothing, so there is nothing
721
+ * for the owner to decide HERE and no wake is sent — but the line MUST be there, or a provider that
722
+ * hangs (or only retries) every dispatch is invisible, which is exactly how W8 stayed unnoticed
723
+ * for 7.5 hours. The streak is logged with it, because that is the number the owner escalation
724
+ * (a few lines up in the engine) counts. */
725
+ private reportHung;
726
+ /**
727
+ * Log a capacity deferral. The engine already rate-limits to one line per node per minute, so this
728
+ * only formats: the exact line is `dispatch deferred: <node> needs N, capacity C, running R`, with
729
+ * the wait and reservation appended because the whole point of the aging rule is to be observable.
730
+ * Deliberately NOT a wake and NOT an `isTroubled` fact — queuing is normal.
731
+ */
732
+ private reportDeferred;
733
+ private deliverToOwner;
734
+ /** Persist an over-long result and hand back its locator WITH the backend's retrieval guidance: a
735
+ * locator alone leaves the reader unable to fetch the text. No backend returns `null`. */
736
+ private spillText;
737
+ /**
738
+ * What durable storage says about the tree's owner, in three states rather than a boolean. The agent
739
+ * registry is the wrong question: agents are materialized on demand, so right after a restart every
740
+ * owner is absent from it while its session data is intact — treating that as "gone" would destroy
741
+ * every tree. `sessionQuery` answers the durable question instead, live-preferred; "cannot tell" stays
742
+ * its own answer (an opaque host is not evidence the owner is gone) and is only ever REPORTED.
743
+ */
744
+ private ownerProbe;
745
+ /** Ask durable storage whether the session exists. Only session-query's own "not found" means
746
+ * `missing`; every other failure is `unobservable`, carrying a one-line reason for the operator —
747
+ * NO log line here, because one line per tree per probe is exactly the noise the aggregate report
748
+ * replaced. */
749
+ private probeOwnerByStorage;
750
+ }
751
+ export type { NodeRecord, TreeRecord, OwnerProbe };
752
+ //# sourceMappingURL=host.d.ts.map