@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.
- package/LICENSE +21 -0
- package/README.md +249 -0
- package/cordis.patch.yml +13 -0
- package/lib/client.js +225 -0
- package/lib/dsh-build.json +16 -0
- package/lib/envinit-bootstrap.js +334 -0
- package/lib/index.js +7021 -0
- package/lib/interface-version.json +4 -0
- package/lib/mission-core/capacity.d.ts +100 -0
- package/lib/mission-core/continuation.d.ts +106 -0
- package/lib/mission-core/dispatch.d.ts +165 -0
- package/lib/mission-core/engine.d.ts +331 -0
- package/lib/mission-core/index.d.ts +25 -0
- package/lib/mission-core/liveness.d.ts +99 -0
- package/lib/mission-core/prompt.d.ts +112 -0
- package/lib/mission-core/resources.d.ts +57 -0
- package/lib/mission-core/timing.d.ts +37 -0
- package/lib/mission-core/tree.d.ts +447 -0
- package/lib/mission-core/trouble.d.ts +40 -0
- package/lib/mission-core/types.d.ts +409 -0
- package/lib/mission-core/wellformed.d.ts +98 -0
- package/lib/types/claims.d.ts +3 -0
- package/lib/types/claims.d.ts.map +1 -0
- package/lib/types/client/MissionTreeView.d.ts +247 -0
- package/lib/types/client/MissionTreeView.d.ts.map +1 -0
- package/lib/types/client/api.d.ts +184 -0
- package/lib/types/client/api.d.ts.map +1 -0
- package/lib/types/client/contract.d.ts +209 -0
- package/lib/types/client/contract.d.ts.map +1 -0
- package/lib/types/client/index.d.ts +48 -0
- package/lib/types/client/index.d.ts.map +1 -0
- package/lib/types/client/seat.d.ts +26 -0
- package/lib/types/client/seat.d.ts.map +1 -0
- package/lib/types/client/styles.d.ts +6 -0
- package/lib/types/client/styles.d.ts.map +1 -0
- package/lib/types/coldResume.d.ts +123 -0
- package/lib/types/coldResume.d.ts.map +1 -0
- package/lib/types/domain.d.ts +93 -0
- package/lib/types/domain.d.ts.map +1 -0
- package/lib/types/envinit.d.ts +126 -0
- package/lib/types/envinit.d.ts.map +1 -0
- package/lib/types/executorSession.d.ts +129 -0
- package/lib/types/executorSession.d.ts.map +1 -0
- package/lib/types/faces.d.ts +30 -0
- package/lib/types/faces.d.ts.map +1 -0
- package/lib/types/host.d.ts +752 -0
- package/lib/types/host.d.ts.map +1 -0
- package/lib/types/index.d.ts +48 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/interface_gate.d.ts +51 -0
- package/lib/types/interface_gate.d.ts.map +1 -0
- package/lib/types/log.d.ts +18 -0
- package/lib/types/log.d.ts.map +1 -0
- package/lib/types/projectionCache.d.ts +22 -0
- package/lib/types/projectionCache.d.ts.map +1 -0
- package/lib/types/prompt.d.ts +106 -0
- package/lib/types/prompt.d.ts.map +1 -0
- package/lib/types/source.d.ts +22 -0
- package/lib/types/source.d.ts.map +1 -0
- package/lib/types/store.d.ts +20 -0
- package/lib/types/store.d.ts.map +1 -0
- package/lib/types/timeFormat.d.ts +56 -0
- package/lib/types/timeFormat.d.ts.map +1 -0
- package/lib/types/tools.d.ts +34 -0
- package/lib/types/tools.d.ts.map +1 -0
- package/lib/types/wellformed.d.ts +43 -0
- package/lib/types/wellformed.d.ts.map +1 -0
- package/lib/types/wire.d.ts +178 -0
- package/lib/types/wire.d.ts.map +1 -0
- package/lib/types/workerEvents.d.ts +36 -0
- package/lib/types/workerEvents.d.ts.map +1 -0
- package/lib/types/workerSessions.d.ts +256 -0
- package/lib/types/workerSessions.d.ts.map +1 -0
- 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
|