@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,409 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Work-tree vocabulary: node states, capacities, durable record shapes. The tree is the only
|
|
3
|
+
* authoritative state holder — every execution reads what it needs from the node it was
|
|
4
|
+
* dispatched for, never from an agent's conversation.
|
|
5
|
+
*
|
|
6
|
+
* @module @avantf/mission-core/types
|
|
7
|
+
*/
|
|
8
|
+
export type NodeStatus =
|
|
9
|
+
/** Has non-terminal children; not dispatchable. */
|
|
10
|
+
'blocked'
|
|
11
|
+
/** The only spawnable status: no children yet, or all terminal. */
|
|
12
|
+
| 'ready'
|
|
13
|
+
/** Dispatched; `claimedBy` holds the worker. */
|
|
14
|
+
| 'running'
|
|
15
|
+
/** Worker vanished without submitting; dispatchable again. */
|
|
16
|
+
| 'interrupted'
|
|
17
|
+
/** Terminal: a result was submitted. */
|
|
18
|
+
| 'done'
|
|
19
|
+
/** Terminal: attempts exhausted, or rejected by a capacity check. */
|
|
20
|
+
| 'failed';
|
|
21
|
+
/** Terminal statuses never change again. */
|
|
22
|
+
export declare const TERMINAL: ReadonlySet<NodeStatus>;
|
|
23
|
+
export declare const DISPATCHABLE: ReadonlySet<NodeStatus>;
|
|
24
|
+
/** Capacities: exported so the tools and the engine validate against ONE source. */
|
|
25
|
+
export declare const CAPACITY: {
|
|
26
|
+
/** Root is depth 1, so depth 8 allows at most seven consecutive decompositions. */
|
|
27
|
+
readonly maxDepth: 8;
|
|
28
|
+
readonly maxChildrenPerDecompose: 6;
|
|
29
|
+
/** Backstop ceiling on one tree's size, checked before a `decompose` commits: depth (8) ×
|
|
30
|
+
* children (6) leaves the count bounded but enormous, and the depth limit does not fire until
|
|
31
|
+
* the eighth level. */
|
|
32
|
+
readonly maxNodesPerTree: 200;
|
|
33
|
+
/** Execution-failure ceiling: a node reclaimed (vanished or stalled) this many times fails;
|
|
34
|
+
* successful rounds do not count. The same ceiling bounds consecutive failed STARTS
|
|
35
|
+
* (`spawnFailures`), tracked separately so an outage is not charged against the mission. */
|
|
36
|
+
readonly maxAttempts: 5;
|
|
37
|
+
/** A node reclaimed for silence this many times gets a heads-up, not a decision request — the
|
|
38
|
+
* engine recovers on its own, which is why it waits for a repeat. */
|
|
39
|
+
readonly maxStallsBeforeReport: 2;
|
|
40
|
+
/** A node reclaimed as `hung` (alive but unproductive, or past its round cap) this many times IN A
|
|
41
|
+
* ROW gets the same one-message heads-up, through the same durable marker. One higher than
|
|
42
|
+
* {@link maxStallsBeforeReport} because a hung round is already a whole stale/round window long, so
|
|
43
|
+
* three in a row is unambiguous rather than jitter — and because a single long-but-legitimate step
|
|
44
|
+
* must not page the owner. This is the ceiling the old behaviour lacked: hung still charges no
|
|
45
|
+
* budget, but it can no longer repeat forever without anyone being told. */
|
|
46
|
+
readonly maxHungsBeforeReport: 3;
|
|
47
|
+
/** Longer results are spilled to the store; the node keeps the summary. */
|
|
48
|
+
readonly maxInlineResultChars: 2000;
|
|
49
|
+
};
|
|
50
|
+
export interface TreeRecord {
|
|
51
|
+
/** Root node id; also the tree's id. */
|
|
52
|
+
readonly rootId: string;
|
|
53
|
+
/** The agent that created the root: no other agent sees the tree, and the tree is destroyed
|
|
54
|
+
* when this session stops existing. */
|
|
55
|
+
readonly ownerSessionId: string;
|
|
56
|
+
readonly createdAt: number;
|
|
57
|
+
/** When the owner closed the tree out (`finish_mission`); `null` while open. Closing is archival —
|
|
58
|
+
* nodes and results stay, but the tree leaves the guidance and the wake decision. */
|
|
59
|
+
readonly closedAt: number | null;
|
|
60
|
+
/** When the owner was woken about this tree reaching a terminal state. Durable on purpose: an
|
|
61
|
+
* in-memory guard re-reports on every dispatch pass after a restart. */
|
|
62
|
+
readonly reportedAt: number | null;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* What one dispatch's prompt showed its session, frozen so a LATER wake can subtract it: "what has
|
|
66
|
+
* changed since this session last read this mission?" It is a snapshot of the node's ACCUMULATING
|
|
67
|
+
* channels, not of its execution state — the execution state is the binding's business.
|
|
68
|
+
*
|
|
69
|
+
* Every component exists for one consumer; none is decoration:
|
|
70
|
+
*
|
|
71
|
+
* - `corrections` — the second half of "corrections this session has NOT seen" (the delivery
|
|
72
|
+
* watermark is the first). Both are needed: a FRESH spawn renders every correction but does not
|
|
73
|
+
* advance the watermark, so a watermark-only reading would call those corrections unseen forever
|
|
74
|
+
* and route every corrected mission to a fresh executor instead of continuing it.
|
|
75
|
+
* - `notes` — how many analysis entries existed when the prompt was built; entries beyond it are the
|
|
76
|
+
* ones appended while this session held the node, which is what the delta reports.
|
|
77
|
+
* - `terminalChildren` — for the delta's wording only ("N 子任务达到终态"). Deliberately NOT part of
|
|
78
|
+
* the material-change judgement, because for a PARKED session "all children terminal" is the
|
|
79
|
+
* trigger of the wake itself (see `isMaterialChange`).
|
|
80
|
+
* - `fingerprint` — title+description at dispatch. A changed headline means this session would be
|
|
81
|
+
* resuming a mission it was never handed.
|
|
82
|
+
* - `attempts` — the dispatch generation this prompt was built under, compared against
|
|
83
|
+
* `analysisAttempt` to tell whether the node's latest note belongs to THIS dispatch.
|
|
84
|
+
* - `holder` — the session this prompt was delivered to. The generation number alone cannot answer
|
|
85
|
+
* "is this note mine?": a session's own note survives into a LATER dispatch of the same node
|
|
86
|
+
* (the parent that decomposed and is woken again is the common case), so `attempts` would call
|
|
87
|
+
* its own judgement somebody else's. The wake compares this holder against the note's author
|
|
88
|
+
* instead (`analysisAuthor`), and falls back to the generation comparison only when this is
|
|
89
|
+
* `null` (a baseline written before the field existed).
|
|
90
|
+
*
|
|
91
|
+
* Missing on a record written before the field existed: that reads as "no baseline", which a wake
|
|
92
|
+
* must treat as UNKNOWN rather than as "nothing changed" (see `computeContinuationDelta`).
|
|
93
|
+
*/
|
|
94
|
+
export interface DispatchBaseline {
|
|
95
|
+
readonly corrections: number;
|
|
96
|
+
readonly notes: number;
|
|
97
|
+
readonly terminalChildren: number;
|
|
98
|
+
readonly fingerprint: string;
|
|
99
|
+
readonly attempts: number;
|
|
100
|
+
/** The session holding the node when this prompt was built; `null` on a pre-`holder` record, which
|
|
101
|
+
* the delta reads as "author unknown" and judges by the generation comparison. */
|
|
102
|
+
readonly holder: string | null;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Why a `ready`/`interrupted` node has not been dispatched yet — computed live by the engine from
|
|
106
|
+
* the current admission state, never persisted. `null` (or a missing field on an older payload)
|
|
107
|
+
* means "nothing is holding it back".
|
|
108
|
+
*
|
|
109
|
+
* The four reasons are the four gates that can defer a dispatch:
|
|
110
|
+
*
|
|
111
|
+
* - `capacity` — the master gate: `Σ running.weight + weight > capacity`. `resource` distinguishes
|
|
112
|
+
* the machine-derived compute capacity (`cpu`) from the host's free-memory floor (`memory`, whose
|
|
113
|
+
* `needed`/`available` are BYTES, not cores). Only the `cpu` flavour can age into a reservation.
|
|
114
|
+
* - `unit` — another `running` node holds the same declared scope (the unit lease).
|
|
115
|
+
* - `slot` — the `maxConcurrent` ceiling on the NUMBER of units; capacity itself has room.
|
|
116
|
+
* - `aging` — an aged node has RESERVED the machine (past the aging window: no new admission until
|
|
117
|
+
* it fits), so every OTHER queued candidate is held back even though `capacity` alone might have
|
|
118
|
+
* room. Reported instead of `slot` because "capacity is free, the slots are just full" would be
|
|
119
|
+
* false, and this is the one moment the anti-starvation mechanism is supposed to be visible.
|
|
120
|
+
*
|
|
121
|
+
* `needed`/`available` are filled only where a number is meaningful; a memory deferral whose signal
|
|
122
|
+
* is `null` ("cannot tell") carries no numbers at all, because there is nothing honest to show.
|
|
123
|
+
*/
|
|
124
|
+
export interface WaitingFor {
|
|
125
|
+
readonly reason: 'capacity' | 'unit' | 'slot' | 'aging';
|
|
126
|
+
readonly resource?: 'cpu' | 'memory';
|
|
127
|
+
readonly needed?: number;
|
|
128
|
+
readonly available?: number;
|
|
129
|
+
/** The unit another node holds, for `reason: 'unit'`. */
|
|
130
|
+
readonly unit?: string;
|
|
131
|
+
}
|
|
132
|
+
export interface NodeRecord {
|
|
133
|
+
readonly id: string;
|
|
134
|
+
readonly rootId: string;
|
|
135
|
+
/** The parent that created this node; `null` for the root. Provenance only: dedup reuse can list
|
|
136
|
+
* a node under several parents' `children` while it keeps the `parentId` it was born under. */
|
|
137
|
+
readonly parentId: string | null;
|
|
138
|
+
readonly title: string;
|
|
139
|
+
readonly description: string;
|
|
140
|
+
/** How many capacity units (cores-equivalent, the same unit as `EngineOptions.capacity`) this
|
|
141
|
+
* mission is expected to occupy while it runs. Declared by `create_mission` for a root and per
|
|
142
|
+
* child by `decompose_mission`; a child that declares nothing uses the DEFAULT 1 and deliberately
|
|
143
|
+
* does NOT inherit the parent's estimate — a parent's appetite says nothing about one child's.
|
|
144
|
+
* Persisted, and a record written before the field existed loads as 1; `DOMAIN_VERSION` stays 1. */
|
|
145
|
+
readonly weight: number;
|
|
146
|
+
/** The SCOPE this mission will modify — a directory or a file — and therefore the key of its
|
|
147
|
+
* engine-enforced lease: no two `running` nodes may carry the same non-null `unit` (see
|
|
148
|
+
* `@avantf/mission-core/dispatch`). Declared by `create_mission` for a root and per child by
|
|
149
|
+
* `decompose_mission`; a child that declares nothing INHERITS its parent's unit, so same-scope
|
|
150
|
+
* siblings cannot run at the same time.
|
|
151
|
+
*
|
|
152
|
+
* `null` means "no scope declared", which is also what a record written before this field existed
|
|
153
|
+
* loads as: such a node takes no part in the lease and behaves exactly as it did before the field
|
|
154
|
+
* existed. Persisted, `DOMAIN_VERSION` stays 1. */
|
|
155
|
+
readonly unit: string | null;
|
|
156
|
+
/** Per-mission RELAXATION of the engine's `roundMs` ceiling — how long ONE round of this mission
|
|
157
|
+
* may run before it is reclaimed as `hung`. Declared by `create_mission` for a root and per child by
|
|
158
|
+
* `decompose_mission`, exactly like `weight`; a child's declaration is its own and is deliberately
|
|
159
|
+
* NOT inherited (a parent's long build says nothing about one child). Relaxation ONLY: the engine
|
|
160
|
+
* compares `max(configured roundMs, this)`, so a declaration can never shorten the backstop that
|
|
161
|
+
* catches a transport retrying forever, and `normalizeRoundMs` caps it at 24 h so it cannot opt out
|
|
162
|
+
* of the backstop either. `null` means "use the engine's configured cap", which is also what a
|
|
163
|
+
* record written before the field existed loads as. Persisted, `DOMAIN_VERSION` stays 1. */
|
|
164
|
+
readonly roundMs: number | null;
|
|
165
|
+
/** Background facts: why this mission exists (written by the decomposer), or the owner's initial
|
|
166
|
+
* analysis for a root — the only channel carrying the vertical "why" down the mission chain. */
|
|
167
|
+
readonly context: readonly string[];
|
|
168
|
+
/** The owner's corrections for this mission, newest last. The name is load-bearing because the
|
|
169
|
+
* field is persisted: renaming it would make every earlier correction load as absent. Separate
|
|
170
|
+
* from `context` on purpose — different author and lifetime. */
|
|
171
|
+
readonly corrections: readonly string[];
|
|
172
|
+
/** Delivery watermark for {@link corrections}: how many LEADING entries are confirmed delivered
|
|
173
|
+
* to the executor that was holding this node. What it decides is the WAKE message — a cold resume
|
|
174
|
+
* renders only `corrections.slice(correctionsDeliveredUpTo)`, because the corrections already
|
|
175
|
+
* handed to that very session must not be argued to it a second time. A FRESH executor ignores
|
|
176
|
+
* the watermark entirely and sees every correction: it has read none of them.
|
|
177
|
+
*
|
|
178
|
+
* `0` means "nothing is confirmed delivered", which is also what a record written before this
|
|
179
|
+
* field existed loads as — the conservative reading, since a silently skipped correction is a
|
|
180
|
+
* direction the owner gave that nobody ever reads. Monotone: a raced, older report can never pull
|
|
181
|
+
* it back. Kept as a NUMBER beside the text array rather than turning `corrections` into objects,
|
|
182
|
+
* because changing that array's shape would make every historical correction load as absent. */
|
|
183
|
+
readonly correctionsDeliveredUpTo: number;
|
|
184
|
+
/** What this mission's executors recorded with `note_mission`, oldest first. The durable channel
|
|
185
|
+
* across sessions: the round that judges the mission is a fresh session reading only this node's
|
|
186
|
+
* prompt, and notes are appended, never replaced. */
|
|
187
|
+
readonly analysisNotes: readonly string[];
|
|
188
|
+
/** The `attempts` value of the dispatch that wrote the LAST entry; `0` when none. `decompose`
|
|
189
|
+
* compares it against the current `attempts`, so an executor cannot inherit a justification
|
|
190
|
+
* written by an earlier round. */
|
|
191
|
+
readonly analysisAttempt: number;
|
|
192
|
+
/** The session that wrote the LAST entry of {@link analysisNotes}; `null` when none, or on a record
|
|
193
|
+
* written before the field existed. The cold wake compares it against {@link DispatchBaseline}'s
|
|
194
|
+
* `holder` so a session's OWN note is not mistaken for a different dispatch's — which the
|
|
195
|
+
* generation number alone cannot tell once the same session's note outlives its dispatch. Persisted,
|
|
196
|
+
* `DOMAIN_VERSION` stays 1. */
|
|
197
|
+
readonly analysisAuthor: string | null;
|
|
198
|
+
readonly status: NodeStatus;
|
|
199
|
+
/** When this node was ACCEPTED into the tree (entered `ready`). The scheduling semantics are the
|
|
200
|
+
* reason this is not "when it started": capacity is a DISPATCH gate, not an admission gate (see
|
|
201
|
+
* `@avantf/mission-core/dispatch`), so a node can sit here for a long time waiting for room or for
|
|
202
|
+
* its unit. {@link dispatchedAt} minus this is the queue time. */
|
|
203
|
+
readonly createdAt: number;
|
|
204
|
+
/** When this node was dispatched for the FIRST time — the moment an executor was bound to it (the
|
|
205
|
+
* first transition into `running`, via `dispatch`/`adoptParked`/`adoptContinuation`). `null` means
|
|
206
|
+
* "never its turn yet": still queued for capacity or for a unit. Immutable after the first write, so
|
|
207
|
+
* a reclaim, a retry or a re-dispatch never rewrites it; a record written before this field existed
|
|
208
|
+
* loads as `null` (= never dispatched, the honest reading). Persisted, `DOMAIN_VERSION` stays 1. */
|
|
209
|
+
readonly dispatchedAt: number | null;
|
|
210
|
+
/** When this node entered a TERMINAL status (`done`/`failed`). `null` means "still in play" — ready,
|
|
211
|
+
* blocked, running or interrupted. Terminal is final, so this is written exactly once, by whichever
|
|
212
|
+
* path ends the node (`submitResult`, `failExhausted`, `cancelSubworks`, `cancelTree`); a node that
|
|
213
|
+
* is cancelled before ever running carries an `endedAt` with a `null` `dispatchedAt`. A record
|
|
214
|
+
* written before this field existed loads as `null` (= still in play). Persisted, `DOMAIN_VERSION`
|
|
215
|
+
* stays 1. */
|
|
216
|
+
readonly endedAt: number | null;
|
|
217
|
+
readonly depth: number;
|
|
218
|
+
/** Durable binding to the worker session id, persisted so a hot reload can tell "someone is
|
|
219
|
+
* still on this"; the agent's existence is always resolved live, never asserted from here. */
|
|
220
|
+
readonly claimedBy: string | null;
|
|
221
|
+
readonly claimedAt: number;
|
|
222
|
+
readonly attempts: number;
|
|
223
|
+
/** Execution-failure budget: how many times this node's worker was reclaimed (vanished or
|
|
224
|
+
* stalled) without producing a result. Separate from `attempts`, which counts EVERY dispatch —
|
|
225
|
+
* including successful aggregate/convergence rounds — so succeeding never burns this budget. */
|
|
226
|
+
readonly failures: number;
|
|
227
|
+
/** How many times in a row a dispatch failed to even START a worker (the runtime refused, or the
|
|
228
|
+
* tool filter could not be applied). An infrastructure failure, so it is budgeted separately
|
|
229
|
+
* from `failures` and pushes the next dispatch back by a cooldown; a successful start resets it. */
|
|
230
|
+
readonly spawnFailures: number;
|
|
231
|
+
/** The worker session that PARKED this node by decomposing it — a wake-up ADDRESS, not a claim:
|
|
232
|
+
* `claimedBy` is cleared exactly as usual, and the address is consumed by the next dispatch
|
|
233
|
+
* (adopted, or replaced by a fresh session). `nextDispatchable` excludes a parked node, because
|
|
234
|
+
* `decompose_mission` is followed by a synchronous `pump()` that would otherwise pre-empt the wake. */
|
|
235
|
+
readonly parkedWorker: string | null;
|
|
236
|
+
/** The session id of the worker most recently bound to this node, kept when the binding is
|
|
237
|
+
* dropped by an INTERRUPTION rather than by a clean hand-off — i.e. `reconcileOnOpen` demoting a
|
|
238
|
+
* `running` node whose worker is not materialized in this process (a restart or a crash), which
|
|
239
|
+
* is exactly when `claimedBy` alone loses the address for good. The next dispatch of this node
|
|
240
|
+
* first tries to CONTINUE that session (cold wake) instead of starting a fresh executor; a
|
|
241
|
+
* delivery the runtime refuses falls back to a brand-new session with no budget charged.
|
|
242
|
+
*
|
|
243
|
+
* Deliberately NOT the same thing as `parkedWorker`, and neither may overwrite the other:
|
|
244
|
+
* a parked worker is an ALIVE, actively waiting continuation (it decomposed and expects to be
|
|
245
|
+
* woken in the same run); `lastWorkerId` is a handle to a session that may no longer exist, and
|
|
246
|
+
* the cold resume that reaches it is allowed to fail. Only `reconcileOnOpen` writes it, and only
|
|
247
|
+
* `adoptContinuation` consumes it — a same-process reclaim does not, so ordinary re-dispatch
|
|
248
|
+
* behaviour is untouched. `null` for a node that was never dispatched or whose handle was spent,
|
|
249
|
+
* which is also the value a record written before this field existed loads as. */
|
|
250
|
+
readonly lastWorkerId: string | null;
|
|
251
|
+
/** The session id of the LAST executor this node was dispatched to — the display address behind
|
|
252
|
+
* the panel's "click the node id to open the session that ran it". Written at every transition
|
|
253
|
+
* INTO `running` (`dispatch` / `adoptParked` / `adoptContinuation`) and deliberately KEPT when the
|
|
254
|
+
* node leaves `running`: a finished mission must still be able to name who executed it, which is
|
|
255
|
+
* exactly what `claimedBy` (cleared on every exit) cannot do.
|
|
256
|
+
*
|
|
257
|
+
* A display address, NOT a continuation handle: nothing consumes it, and it is deliberately not
|
|
258
|
+
* `lastWorkerId` — that one is a one-shot cold-wake address that only `reconcileOnOpen` writes and
|
|
259
|
+
* only `adoptContinuation` spends, so overloading it would change ordinary re-dispatch behaviour (a
|
|
260
|
+
* same-process reclaim would start cold-resuming). A node dispatched several times keeps only the
|
|
261
|
+
* LAST attempt, because the projection carries one string and never a history array. `null` for a
|
|
262
|
+
* node that was never dispatched, which is also the value a record written before this field
|
|
263
|
+
* existed loads as. Persisted, `DOMAIN_VERSION` stays 1. */
|
|
264
|
+
readonly executorSessionId: string | null;
|
|
265
|
+
/** What the prompt of the LAST dispatch showed its session, stamped by the host the moment that
|
|
266
|
+
* prompt was ACCEPTED (not when the node was bound: a dispatch whose prompt was never built or
|
|
267
|
+
* never delivered must not leave a baseline claiming the session read something). The cold wake
|
|
268
|
+
* subtracts it to get the delta it renders, and the same delta decides whether continuing is honest
|
|
269
|
+
* at all; a FRESH spawn ignores it entirely, because a new executor has read nothing.
|
|
270
|
+
*
|
|
271
|
+
* Deliberately NOT the same thing as `analysisAttempt` (that is `note_mission`'s generation gate)
|
|
272
|
+
* and NOT derived from `correctionsDeliveredUpTo` (that is one half of the correction story, and
|
|
273
|
+
* the weaker half — see {@link DispatchBaseline}). `null` for a node never dispatched by a host
|
|
274
|
+
* that stamps baselines, which is also the value a record written before this field existed loads
|
|
275
|
+
* as; that direction is the safe one, because "unknown" renders an honest caveat and never a
|
|
276
|
+
* fabricated "nothing changed". */
|
|
277
|
+
readonly dispatchBaseline: DispatchBaseline | null;
|
|
278
|
+
/** When this node's worker last PRODUCED something: the model's committed output, a tool it asked
|
|
279
|
+
* for, or a tool that finished. This is what the stale check compares against — `progressAt` is
|
|
280
|
+
* about output, deliberately NOT about "an event arrived" (see {@link activityAt}); a long but
|
|
281
|
+
* productive run stays safe, while a provider that only retries stops refreshing it. `0` means
|
|
282
|
+
* "nothing produced yet" and falls back to `claimedAt`. */
|
|
283
|
+
readonly progressAt: number;
|
|
284
|
+
/** When this node's worker was last HEARD FROM at all — any durable session event, output or
|
|
285
|
+
* transport-layer noise (retry attempts, per-request route snapshots). Kept apart from
|
|
286
|
+
* `progressAt` so the engine can tell the two ways a live worker stops being useful: nothing at
|
|
287
|
+
* all (both go stale → `stalled`, which charges the failure budget) versus events with no output
|
|
288
|
+
* (this stays fresh while `progressAt` goes stale → `hung`, which charges nothing). `0` means
|
|
289
|
+
* never observed; the readers fall back to `claimedAt`, and a record written before this field
|
|
290
|
+
* existed loads as `0` — "cannot tell output from noise", the conservative direction that leaves
|
|
291
|
+
* the round cap to reclaim it rather than inventing a stall. Persisted, `DOMAIN_VERSION` stays 1. */
|
|
292
|
+
readonly activityAt: number;
|
|
293
|
+
/** Times this node was reclaimed because its worker went silent past the stale window; a worker
|
|
294
|
+
* that merely vanished is not the node's fault and does not count here. A `hung` reclaim is not
|
|
295
|
+
* counted either — see `MissionTree.reclaim` and {@link hungCount}. */
|
|
296
|
+
readonly stalls: number;
|
|
297
|
+
/** CONSECUTIVE `hung` reclaims since this node last PRODUCED something (real output, i.e. the
|
|
298
|
+
* `MissionTree.touchProgress` clock). Hung rounds charge no budget by design, so without this
|
|
299
|
+
* counter a worker that hangs every round re-runs forever and nothing ever reaches the owner. It is
|
|
300
|
+
* a STREAK, not a history: any real output clears it to 0, and a re-dispatch does NOT (the round
|
|
301
|
+
* after a hung reclaim is exactly the next link in the streak). At
|
|
302
|
+
* `CAPACITY.maxHungsBeforeReport` it trips `isTroubledNode`, which both flags the mission as
|
|
303
|
+
* 「反复出过问题」 and routes it to the owner through the same one-message-per-node trouble channel
|
|
304
|
+
* the stall and failed-start heads-ups use. `0` is also what a record written before the field
|
|
305
|
+
* existed loads as. Persisted, `DOMAIN_VERSION` stays 1. */
|
|
306
|
+
readonly hungCount: number;
|
|
307
|
+
/** When the owner was told this node keeps stalling; `null` until then. Durable for the same
|
|
308
|
+
* reason as `TreeRecord.reportedAt`: an in-memory memo is empty after a restart. */
|
|
309
|
+
readonly stalledNotifiedAt: number | null;
|
|
310
|
+
/** Inline result (the summary when the full text was spilled). */
|
|
311
|
+
readonly result: string | null;
|
|
312
|
+
/** Distinguishes "no result written" from "an empty result was written". */
|
|
313
|
+
readonly hasResult: boolean;
|
|
314
|
+
readonly resultReadAt: number | null;
|
|
315
|
+
readonly resultRef: string | null;
|
|
316
|
+
/** The backend's retrieval guidance for `resultRef`, persisted because a locator without its
|
|
317
|
+
* hint leaves the reader unable to fetch the full text. `null` when nothing was spilled. */
|
|
318
|
+
readonly resultHint: string | null;
|
|
319
|
+
/** Child ids in decomposition order. */
|
|
320
|
+
readonly children: readonly string[];
|
|
321
|
+
readonly updatedAt: number;
|
|
322
|
+
}
|
|
323
|
+
export interface ChildSpec {
|
|
324
|
+
readonly title: string;
|
|
325
|
+
readonly description: string;
|
|
326
|
+
readonly context: readonly string[];
|
|
327
|
+
/** This child's scope. `undefined` (the spec said nothing) inherits the parent's unit — the safe
|
|
328
|
+
* default; a non-blank string is the child's own scope; a blank string or `null` declares that
|
|
329
|
+
* this child takes no lease at all. Resolved by `resolveChildUnit`. */
|
|
330
|
+
readonly unit?: string | null;
|
|
331
|
+
/** This child's declared capacity weight, in cores-equivalent. `undefined` reads as the default 1
|
|
332
|
+
* and is deliberately NOT inherited from the parent — the parent's estimate is not the child's. */
|
|
333
|
+
readonly weight?: number;
|
|
334
|
+
/** This child's declared round-cap relaxation (see {@link NodeRecord.roundMs}). `undefined` reads
|
|
335
|
+
* as `null` — the engine's configured cap — and is deliberately NOT inherited from the parent. */
|
|
336
|
+
readonly roundMs?: number | null;
|
|
337
|
+
}
|
|
338
|
+
/** A dispatch candidate: the node plus its resolved mission chain and, exactly when the node is a
|
|
339
|
+
* ready aggregate, the current children results. */
|
|
340
|
+
export interface DispatchView {
|
|
341
|
+
readonly node: NodeRecord;
|
|
342
|
+
/** Ancestors root → parent, each reduced to title + basic facts. */
|
|
343
|
+
readonly chain: readonly NodeRecord[];
|
|
344
|
+
/** Terminal children of an aggregate, in decomposition order. */
|
|
345
|
+
readonly children: readonly NodeRecord[];
|
|
346
|
+
}
|
|
347
|
+
export interface DecomposeOutcome {
|
|
348
|
+
readonly created: readonly string[];
|
|
349
|
+
/** Children whose equivalent already existed in the subtree and were reused. */
|
|
350
|
+
readonly reused: readonly string[];
|
|
351
|
+
}
|
|
352
|
+
/** Why a mutation was refused. Returned instead of thrown so tools can report it. */
|
|
353
|
+
export type RefusalCode = 'not-found' | 'not-owner' | 'terminal'
|
|
354
|
+
/** The node still has non-terminal children, so the mutation it refused must wait. */
|
|
355
|
+
| 'has-children' | 'not-dispatchable' | 'depth-exceeded' | 'too-many-children' | 'no-children'
|
|
356
|
+
/** Nothing below the node is unfinished, so there is nothing to cancel. */
|
|
357
|
+
| 'nothing-to-cancel' | 'unread-result'
|
|
358
|
+
/** The addressed node is not a root, and only a root takes the mutation. */
|
|
359
|
+
| 'not-root' | 'node-limit'
|
|
360
|
+
/** The node is not terminal, so it is not the owner's to delete. */
|
|
361
|
+
| 'not-deletable' | 'closed'
|
|
362
|
+
/** The caller is not a top-level session, so it has no authority to own a tree. */
|
|
363
|
+
| 'no-authority'
|
|
364
|
+
/** The tool was reached with no caller agent bound at all, so there is nobody to check authority
|
|
365
|
+
* against. Produced by the tool layer — the one place a call can arrive callerless — and listed
|
|
366
|
+
* here so a consumer that exhausts this union does not silently drop it. */
|
|
367
|
+
| 'no-caller'
|
|
368
|
+
/** `note_mission` was given text with no non-blank line, so there is nothing to record. */
|
|
369
|
+
| 'no-analysis'
|
|
370
|
+
/** A required text argument reached the engine containing nothing but whitespace. The tool layer's
|
|
371
|
+
* non-empty check only sees the EMPTY string, so without this a blank title, result or correction
|
|
372
|
+
* was accepted and persisted — then rendered back into every later dispatch. */
|
|
373
|
+
| 'blank-text'
|
|
374
|
+
/** `decompose_mission` was called by a dispatch that has not written its own analysis: a split
|
|
375
|
+
* must be argued for by the round performing it, not inherited from an earlier one. */
|
|
376
|
+
| 'analysis-missing'
|
|
377
|
+
/** The node's declared `unit` is held by another `running` node, so dispatching it would put two
|
|
378
|
+
* executors on the same scope. Refused WITHOUT charging the node: the holder always leaves
|
|
379
|
+
* `running` on its own, and the node is a candidate again then (see
|
|
380
|
+
* `@avantf/mission-core/dispatch`). */
|
|
381
|
+
| 'unit-busy'
|
|
382
|
+
/** The node does not fit the machine RIGHT NOW — its weight would push `Σ running.weight` past
|
|
383
|
+
* `capacity`, or the `maxConcurrent` slot ceiling is reached. Produced by the lock-held recheck
|
|
384
|
+
* (`MissionTree.capacityRefusal`), which re-runs the plan's own judgement against live state so two
|
|
385
|
+
* concurrent passes cannot both bind from one stale snapshot. Refused WITHOUT charging the node:
|
|
386
|
+
* capacity is a DISPATCH gate, so this is exactly the plan path's deferral — the node stays
|
|
387
|
+
* `ready`, with no `attempts`/`failures`/`spawnFailures`, no cooldown and no stall. A caller must
|
|
388
|
+
* never report it as a dispatch failure (see `MissionEngine.pass`). */
|
|
389
|
+
| 'capacity-busy';
|
|
390
|
+
/** A refused mutation, with a stable code the caller can branch on. */
|
|
391
|
+
export interface Refusal {
|
|
392
|
+
readonly ok: false;
|
|
393
|
+
readonly code: RefusalCode;
|
|
394
|
+
readonly message: string;
|
|
395
|
+
}
|
|
396
|
+
/** A successful mutation. */
|
|
397
|
+
export interface Accepted<T> {
|
|
398
|
+
readonly ok: true;
|
|
399
|
+
readonly value: T;
|
|
400
|
+
}
|
|
401
|
+
export type MutationResult<T> = Accepted<T> | Refusal;
|
|
402
|
+
/** One tree as stored: identity plus nodes keyed by id. The durable and in-memory shapes are the
|
|
403
|
+
* same value; the store validates on read and writes it whole. */
|
|
404
|
+
export interface TreeDocument {
|
|
405
|
+
tree: TreeRecord;
|
|
406
|
+
nodes: Record<string, NodeRecord>;
|
|
407
|
+
}
|
|
408
|
+
export declare function refuse(code: RefusalCode, message: string): Refusal;
|
|
409
|
+
export declare function accept<T>(value: T): Accepted<T>;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Well-formed text — the mission tree's LOCAL copy of the family's lone-surrogate repair, and the
|
|
3
|
+
* port the plugin injects the base's canonical implementation through.
|
|
4
|
+
*
|
|
5
|
+
* The defect has exactly one shape: a UTF-16 code unit in `D800–DFFF` with no partner (an unpaired
|
|
6
|
+
* high half, an unpaired low half, or half of an emoji). Such a string is not a valid Unicode scalar
|
|
7
|
+
* sequence, yet `JSON.stringify` emits it verbatim as the escape `"\ud800"`. JavaScript's own
|
|
8
|
+
* `JSON.parse` accepts that, so every test in this repository passes while a strict parser rejects
|
|
9
|
+
* the whole document — measured, not theorized:
|
|
10
|
+
*
|
|
11
|
+
* - `printf '"\\ud800"' | jq .` → `parse error: Invalid \uXXXX\uXXXX surrogate pair escape`;
|
|
12
|
+
* - Python's `json.load` accepts the document but `print` raises
|
|
13
|
+
* `UnicodeEncodeError: surrogates not allowed`;
|
|
14
|
+
* - the same half-code-unit also poisons FTS indexes, embeddings and the UI.
|
|
15
|
+
*
|
|
16
|
+
* The repair is `String.prototype.toWellFormed()` (Node ≥20): it replaces each lone surrogate with
|
|
17
|
+
* U+FFFD (`�`). {@link wellFormedText} uses the engine's own implementation when it exists and an
|
|
18
|
+
* EQUIVALENT `charCodeAt` scan when it does not, so an older Node degrades in behaviour, never in
|
|
19
|
+
* availability.
|
|
20
|
+
*
|
|
21
|
+
* ── this file is the DEGRADATION copy ───────────────────────────────────────────────────────────
|
|
22
|
+
*
|
|
23
|
+
* The CANONICAL implementation lives in the family base kit
|
|
24
|
+
* (`@avantf/dsh-plugin-base` → `src/kit/wellformed.ts`, interface v2, exported as `wellFormedText` /
|
|
25
|
+
* `wellFormedDeep`). The plugin loads that base at runtime and injects the loaded functions into the
|
|
26
|
+
* core through {@link WellFormedSource} (`TreeDeps.wellFormed`, `buildWorkerPrompt`'s third argument,
|
|
27
|
+
* `AvantfMissionHost.wellFormed`); {@link LOCAL_WELL_FORMED} is what runs only when the base is
|
|
28
|
+
* absent or predates v2 and does not carry the two functions. Per the root `AGENTS.md`, fixing the
|
|
29
|
+
* shared repair means one base release; this copy exists so a missing base still repairs instead of
|
|
30
|
+
* mounting without any repair at all. The plugin must NEVER import the base by value — it takes both
|
|
31
|
+
* halves off the module the inlined bootstrap loaded.
|
|
32
|
+
*
|
|
33
|
+
* ── NFC policy: well-formedness ONLY, deliberately no `normalize('NFC')` ─────────────────────────
|
|
34
|
+
*
|
|
35
|
+
* The base kit leaves Unicode normalization to the caller because it is a PERSISTENCE policy, and
|
|
36
|
+
* mission declines to add it. Mission's inbound texts are PROSE (titles, descriptions, analyses,
|
|
37
|
+
* results, corrections) that is rendered straight back to models; there is no index, no identity and
|
|
38
|
+
* no equivalence class that needs canonical spelling. The one consumer that could want it —
|
|
39
|
+
* decomposition dedup — compares trimmed, whitespace-collapsed, lowercased titles, which is already
|
|
40
|
+
* a heuristic and deliberately does NOT equate different descriptions. Applying NFC on the way in
|
|
41
|
+
* would silently rewrite the caller's own bytes, and mission has no caller that needs it; applying
|
|
42
|
+
* it on the way out would rewrite history. So ONE function serves both directions (this symmetry is
|
|
43
|
+
* also what makes the "inbound funnel" and the outbound fallback provably the same repair). A future
|
|
44
|
+
* feature that needs canonical identity must make that a separate, explicit write-side policy.
|
|
45
|
+
*
|
|
46
|
+
* @module @avantf/mission-core/wellformed
|
|
47
|
+
*/
|
|
48
|
+
/**
|
|
49
|
+
* Make ONE string well-formed: every lone surrogate becomes U+FFFD, every properly paired surrogate
|
|
50
|
+
* (a complete emoji, a CJK Extension B character) and every other code point is returned unchanged.
|
|
51
|
+
*
|
|
52
|
+
* Uses the engine's `String.prototype.toWellFormed()` when available and {@link scanAndRepair}
|
|
53
|
+
* otherwise; both produce the same result, so a pre-ES2024 Node never throws here. Idempotent and
|
|
54
|
+
* pure: quotes, backslashes, newlines and tabs come back byte-for-byte, and no NFC is applied
|
|
55
|
+
* (see the module note).
|
|
56
|
+
*/
|
|
57
|
+
export declare function wellFormedText(text: string): string;
|
|
58
|
+
/**
|
|
59
|
+
* {@link wellFormedText} over a JSON-shaped value, so a whole result can be handed to a strict parser.
|
|
60
|
+
*
|
|
61
|
+
* Recursion rules, deliberately narrow — this member may repair strings but must never change a
|
|
62
|
+
* value's TYPE:
|
|
63
|
+
*
|
|
64
|
+
* - strings are repaired with {@link wellFormedText};
|
|
65
|
+
* - arrays are mapped (a new array; the input is never mutated);
|
|
66
|
+
* - PLAIN records are rebuilt with well-formed keys and values. "Plain" means the prototype is
|
|
67
|
+
* `Object.prototype` (an object literal) or `null` (`Object.create(null)`), and the object carries
|
|
68
|
+
* no `toJSON` custom serialization. Object KEYS are repaired too: `JSON.stringify` emits a lone
|
|
69
|
+
* surrogate in a property name verbatim, which breaks a strict parser exactly the same way;
|
|
70
|
+
* - EVERYTHING ELSE — numbers, booleans, `null`, `undefined`, bigints, symbols, functions, and every
|
|
71
|
+
* object that is not a plain record (`Date`, `RegExp`, `Map`, a class instance, anything with a
|
|
72
|
+
* `toJSON`) — is returned AS-IS, by identity.
|
|
73
|
+
*
|
|
74
|
+
* Pure and type-preserving: the result is structurally identical to the input apart from lone
|
|
75
|
+
* surrogates, and the input value is never mutated.
|
|
76
|
+
*/
|
|
77
|
+
export declare function wellFormedDeep<T>(value: T): T;
|
|
78
|
+
/**
|
|
79
|
+
* The pair of repairs every mission boundary needs. The plugin builds this from the base kit it
|
|
80
|
+
* loaded (`{ text: kit.wellFormedText, deep: kit.wellFormedDeep }`) and injects it; the core defaults
|
|
81
|
+
* to {@link LOCAL_WELL_FORMED}.
|
|
82
|
+
*
|
|
83
|
+
* The two members are separate because the boundaries are: a single model-written STRING (an
|
|
84
|
+
* analysis, a result, a correction, a command line) needs `text`, while a record or array that is
|
|
85
|
+
* about to be persisted or serialized (a `createRoot` input, a `children` array, a whole tool
|
|
86
|
+
* result, a dispatch view) needs `deep`. Both are pure.
|
|
87
|
+
*/
|
|
88
|
+
export interface WellFormedSource {
|
|
89
|
+
/** Repair one string (see {@link wellFormedText}). */
|
|
90
|
+
readonly text: (value: string) => string;
|
|
91
|
+
/** Repair every string in a JSON-shaped value (see {@link wellFormedDeep}). */
|
|
92
|
+
readonly deep: <T>(value: T) => T;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* The degradation source: the local copy above. Used when the loaded base kit has no
|
|
96
|
+
* `wellFormedText` / `wellFormedDeep` (base absent, or older than interface v2).
|
|
97
|
+
*/
|
|
98
|
+
export declare const LOCAL_WELL_FORMED: WellFormedSource;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claims.d.ts","sourceRoot":"","sources":["../../src/claims.ts"],"names":[],"mappings":"AASA,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AAED,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAE1D"}
|