instar 1.3.646 → 1.3.648
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/reflect.d.ts.map +1 -1
- package/dist/commands/reflect.js +7 -3
- package/dist/commands/reflect.js.map +1 -1
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +41 -2
- package/dist/commands/server.js.map +1 -1
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +17 -0
- package/dist/commands/setup.js.map +1 -1
- package/dist/config/ConfigDefaults.d.ts.map +1 -1
- package/dist/config/ConfigDefaults.js +14 -0
- package/dist/config/ConfigDefaults.js.map +1 -1
- package/dist/core/CoherenceGate.d.ts.map +1 -1
- package/dist/core/CoherenceGate.js +24 -0
- package/dist/core/CoherenceGate.js.map +1 -1
- package/dist/core/CoherenceReviewer.d.ts +9 -0
- package/dist/core/CoherenceReviewer.d.ts.map +1 -1
- package/dist/core/CoherenceReviewer.js +18 -2
- package/dist/core/CoherenceReviewer.js.map +1 -1
- package/dist/core/InputGuard.d.ts.map +1 -1
- package/dist/core/InputGuard.js +17 -3
- package/dist/core/InputGuard.js.map +1 -1
- package/dist/core/MessageSentinel.d.ts.map +1 -1
- package/dist/core/MessageSentinel.js +19 -2
- package/dist/core/MessageSentinel.js.map +1 -1
- package/dist/core/MessagingToneGate.d.ts +8 -0
- package/dist/core/MessagingToneGate.d.ts.map +1 -1
- package/dist/core/MessagingToneGate.js +16 -1
- package/dist/core/MessagingToneGate.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +10 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/SingleInstanceLock.d.ts +103 -0
- package/dist/core/SingleInstanceLock.d.ts.map +1 -0
- package/dist/core/SingleInstanceLock.js +300 -0
- package/dist/core/SingleInstanceLock.js.map +1 -0
- package/dist/core/SpawnCapIntelligenceProvider.d.ts +89 -0
- package/dist/core/SpawnCapIntelligenceProvider.d.ts.map +1 -0
- package/dist/core/SpawnCapIntelligenceProvider.js +164 -0
- package/dist/core/SpawnCapIntelligenceProvider.js.map +1 -0
- package/dist/core/hostSpawnSemaphore.d.ts +188 -0
- package/dist/core/hostSpawnSemaphore.d.ts.map +1 -0
- package/dist/core/hostSpawnSemaphore.js +471 -0
- package/dist/core/hostSpawnSemaphore.js.map +1 -0
- package/dist/core/intelligenceProviderFactory.d.ts +0 -12
- package/dist/core/intelligenceProviderFactory.d.ts.map +1 -1
- package/dist/core/intelligenceProviderFactory.js +27 -9
- package/dist/core/intelligenceProviderFactory.js.map +1 -1
- package/dist/core/types.d.ts +18 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/feedback-factory/store/JsonlFeedbackStore.d.ts +2 -0
- package/dist/feedback-factory/store/JsonlFeedbackStore.d.ts.map +1 -1
- package/dist/feedback-factory/store/JsonlFeedbackStore.js +36 -2
- package/dist/feedback-factory/store/JsonlFeedbackStore.js.map +1 -1
- package/dist/messaging/TelegramAdapter.d.ts.map +1 -1
- package/dist/messaging/TelegramAdapter.js +6 -2
- package/dist/messaging/TelegramAdapter.js.map +1 -1
- package/dist/monitoring/CoherenceMonitor.d.ts.map +1 -1
- package/dist/monitoring/CoherenceMonitor.js +6 -1
- package/dist/monitoring/CoherenceMonitor.js.map +1 -1
- package/dist/monitoring/CommitmentSentinel.d.ts.map +1 -1
- package/dist/monitoring/CommitmentSentinel.js +7 -2
- package/dist/monitoring/CommitmentSentinel.js.map +1 -1
- package/dist/scaffold/templates.d.ts.map +1 -1
- package/dist/scaffold/templates.js +5 -0
- package/dist/scaffold/templates.js.map +1 -1
- package/dist/server/CapabilityIndex.d.ts.map +1 -1
- package/dist/server/CapabilityIndex.js +9 -0
- package/dist/server/CapabilityIndex.js.map +1 -1
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +28 -0
- package/dist/server/routes.js.map +1 -1
- package/dist/utils/jsonl-tail.d.ts +50 -0
- package/dist/utils/jsonl-tail.d.ts.map +1 -0
- package/dist/utils/jsonl-tail.js +96 -0
- package/dist/utils/jsonl-tail.js.map +1 -0
- package/package.json +4 -2
- package/scripts/lint-no-direct-destructive.js +4 -0
- package/scripts/lint-no-unbounded-llm-spawn.js +137 -0
- package/src/data/builtin-manifest.json +63 -63
- package/src/scaffold/templates.ts +5 -0
- package/upgrades/1.3.647.md +56 -0
- package/upgrades/1.3.648.md +99 -0
- package/upgrades/eli16/eventloop-bounded-jsonl-reads.md +47 -0
- package/upgrades/side-effects/eventloop-bounded-jsonl-reads.md +96 -0
- package/upgrades/side-effects/forkbomb-prevention-simple.md +138 -0
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hostSpawnSemaphore — P1 of the SIMPLE fork-bomb prevention design.
|
|
3
|
+
*
|
|
4
|
+
* Spec: docs/specs/forkbomb-prevention-simple.md (§P1, §P3, §D-CAP).
|
|
5
|
+
* Source postmortem: the-portal/docs/postmortems/2026-06-20-echo-instar-forkbomb-oom.md.
|
|
6
|
+
*
|
|
7
|
+
* THE PRIMARY CONTROL. A single host-local COUNTING SEMAPHORE that bounds how
|
|
8
|
+
* many LLM subprocesses ("claude -p" / "codex exec" / …) run AT ONCE across
|
|
9
|
+
* EVERY compliant Instar agent + server instance on this host. The 2026-06-20
|
|
10
|
+
* incident fork-bombed a 128GB macOS host into OOM twice (~230-289 concurrent
|
|
11
|
+
* `claude -p` ≈ 90-115GB) because `evaluate()` spawned one subprocess per call
|
|
12
|
+
* with ZERO concurrency control and CoherenceGate fans ~10 reviewers in
|
|
13
|
+
* parallel per message. This bounds that.
|
|
14
|
+
*
|
|
15
|
+
* MECHANISM — a holder-SET model (NOT decrement/increment counter math):
|
|
16
|
+
* A host-local file (`~/.instar/host-spawn-holders.json`, NOT a synced
|
|
17
|
+
* volume), guarded by an exclusive O_CREAT|O_EXCL lock (the in-tree
|
|
18
|
+
* ProjectRoundLock pattern). The cap is enforced by COUNTING LIVE holder
|
|
19
|
+
* records — never by mutating a shared integer:
|
|
20
|
+
* - acquire(id): under the lock, prune dead holders, and if
|
|
21
|
+
* liveHolders < cap append `{id, pid, hostname, heartbeat}` (atomic
|
|
22
|
+
* temp+rename) → true; else false.
|
|
23
|
+
* - release(id): under the lock, remove THIS id.
|
|
24
|
+
* Crash-safe by construction: a double-release is a no-op (id already gone),
|
|
25
|
+
* a pid-reuse can't steal a slot (unique id, not pid), a partial write is
|
|
26
|
+
* discarded (temp+rename), a crashed holder is reclaimed by prune-dead
|
|
27
|
+
* (pid not alive AND heartbeat stale, on THIS host only).
|
|
28
|
+
*
|
|
29
|
+
* HOST-LOCAL-LOCK CONTRACT (mirrors ResumeQueue.ts, the 2026-06-15 lesson):
|
|
30
|
+
* - A FOREIGN-hostname holder is NEVER pruned/reclaimed (refuse-loud — a
|
|
31
|
+
* pid check is meaningless cross-host on a shared volume).
|
|
32
|
+
* - A `df -P` host-local-disk confirmation gates reclaim (fail-closed: if we
|
|
33
|
+
* cannot positively confirm the holders file is on a local disk, we NEVER
|
|
34
|
+
* prune a holder — we only ever decline to reclaim, never decline to bound).
|
|
35
|
+
*
|
|
36
|
+
* BOUNDED INGRESS (P3) lives in the wrapper (SpawnCapIntelligenceProvider) via
|
|
37
|
+
* poll-retry against `acquire()` — this module is the pure counting primitive.
|
|
38
|
+
*/
|
|
39
|
+
import fs from 'node:fs';
|
|
40
|
+
import os from 'node:os';
|
|
41
|
+
import path from 'node:path';
|
|
42
|
+
import { execFileSync } from 'node:child_process';
|
|
43
|
+
import { SafeFsExecutor } from './SafeFsExecutor.js';
|
|
44
|
+
import { isAlive } from './ProjectRoundLock.js';
|
|
45
|
+
/** Heartbeat staleness window — a holder is reclaim-eligible (pid dead OR, on a
|
|
46
|
+
* provably host-local disk, heartbeat older than this) only past it. Kept long
|
|
47
|
+
* (a slow `claude -p` cold-start + run can legitimately exceed a minute), and
|
|
48
|
+
* pid-liveness is the PRIMARY signal — heartbeat staleness is secondary and
|
|
49
|
+
* only consulted when the pid is also gone. */
|
|
50
|
+
export const HOLDER_STALE_MS = 5 * 60_000;
|
|
51
|
+
/**
|
|
52
|
+
* FD1 (from ResumeQueue) — is `p` on a HOST-LOCAL filesystem? FAIL-CLOSED:
|
|
53
|
+
* anything we cannot positively confirm as local returns false, so a holder on
|
|
54
|
+
* a genuine shared/network volume is NEVER reclaimed (the two-hosts-one-volume
|
|
55
|
+
* corruption the host-lock invariant protects against). `df -P` device-column
|
|
56
|
+
* classification — re-implemented here (not imported from monitoring/) so
|
|
57
|
+
* core/ never depends on monitoring/.
|
|
58
|
+
*/
|
|
59
|
+
export function isPathHostLocalDefault(p) {
|
|
60
|
+
let out;
|
|
61
|
+
try {
|
|
62
|
+
// lint-allow-sync-spawn: a bounded (3s) one-shot host-FS classification, run
|
|
63
|
+
// ONCE per process and then MEMOIZED (HostSpawnSemaphore._fsLocalCache) — it
|
|
64
|
+
// never runs on the hot acquire() fan-out path. Mirrors ResumeQueue's
|
|
65
|
+
// isStateDirHostLocalDefault host-lock contract; a path's FS type can't
|
|
66
|
+
// change at runtime, so there is nothing to re-probe.
|
|
67
|
+
out = execFileSync('df', ['-P', p], { timeout: 3000, encoding: 'utf-8' });
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// @silent-fallback-ok: df unavailable/failed ⇒ cannot confirm local ⇒
|
|
71
|
+
// fail-closed to NOT-local (the safe direction: never reclaim on doubt).
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
const lines = out.trim().split('\n');
|
|
75
|
+
if (lines.length < 2)
|
|
76
|
+
return false; // unparseable → fail-closed
|
|
77
|
+
const source = lines[1]?.trim().split(/\s+/)[0] ?? '';
|
|
78
|
+
return classifyDfSourceLocal(source);
|
|
79
|
+
}
|
|
80
|
+
/** Pure FD1 classifier over the `df -P` device-source column. FAIL-CLOSED. */
|
|
81
|
+
export function classifyDfSourceLocal(source) {
|
|
82
|
+
if (!source)
|
|
83
|
+
return false;
|
|
84
|
+
if (source.startsWith('//'))
|
|
85
|
+
return false; // SMB/CIFS //host/share → network
|
|
86
|
+
if (/^[^/][^:]*:/.test(source))
|
|
87
|
+
return false; // NFS host:/path → network
|
|
88
|
+
if (source.startsWith('/dev/'))
|
|
89
|
+
return true; // a real block device → local
|
|
90
|
+
return false; // map/tmpfs/anything unrecognized → fail-closed
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Resolve the concurrent-spawn cap. Precedence (D-CAP):
|
|
94
|
+
* INSTAR_HOST_SPAWN_MAX env > config intelligence.spawnCap.maxConcurrent > 8.
|
|
95
|
+
* A safety FLOOR — read with a plain `??` default, NEVER resolveDevAgentGate
|
|
96
|
+
* (it is ON by default, ships never-dark). A non-positive / non-finite value
|
|
97
|
+
* is ignored (falls through to the next source) so a typo can't disable the cap.
|
|
98
|
+
*/
|
|
99
|
+
export function resolveSpawnCap(configCap, env = process.env) {
|
|
100
|
+
const fromEnv = env['INSTAR_HOST_SPAWN_MAX'];
|
|
101
|
+
if (fromEnv !== undefined) {
|
|
102
|
+
const n = Number(fromEnv);
|
|
103
|
+
if (Number.isFinite(n) && n > 0)
|
|
104
|
+
return Math.floor(n);
|
|
105
|
+
}
|
|
106
|
+
if (typeof configCap === 'number' && Number.isFinite(configCap) && configCap > 0) {
|
|
107
|
+
return Math.floor(configCap);
|
|
108
|
+
}
|
|
109
|
+
return 8;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Resolve the bounded-acquire poll budget in ms (P3). Precedence (D-CAP):
|
|
113
|
+
* INSTAR_SPAWN_ACQUIRE_MS env > config intelligence.spawnCap.acquireMs > 5000.
|
|
114
|
+
* Read with a plain `??` default (safety floor, never resolveDevAgentGate).
|
|
115
|
+
*/
|
|
116
|
+
export function resolveSpawnAcquireMs(configMs, env = process.env) {
|
|
117
|
+
const fromEnv = env['INSTAR_SPAWN_ACQUIRE_MS'];
|
|
118
|
+
if (fromEnv !== undefined) {
|
|
119
|
+
const n = Number(fromEnv);
|
|
120
|
+
if (Number.isFinite(n) && n >= 0)
|
|
121
|
+
return Math.floor(n);
|
|
122
|
+
}
|
|
123
|
+
if (typeof configMs === 'number' && Number.isFinite(configMs) && configMs >= 0) {
|
|
124
|
+
return Math.floor(configMs);
|
|
125
|
+
}
|
|
126
|
+
return 5000;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Resolve the concurrent-pollers ceiling (P3). Precedence (D-CAP):
|
|
130
|
+
* INSTAR_SPAWN_WAITERS_MAX env > config intelligence.spawnCap.waitersMax > 64.
|
|
131
|
+
*/
|
|
132
|
+
export function resolveSpawnWaitersMax(configMax, env = process.env) {
|
|
133
|
+
const fromEnv = env['INSTAR_SPAWN_WAITERS_MAX'];
|
|
134
|
+
if (fromEnv !== undefined) {
|
|
135
|
+
const n = Number(fromEnv);
|
|
136
|
+
if (Number.isFinite(n) && n > 0)
|
|
137
|
+
return Math.floor(n);
|
|
138
|
+
}
|
|
139
|
+
if (typeof configMax === 'number' && Number.isFinite(configMax) && configMax > 0) {
|
|
140
|
+
return Math.floor(configMax);
|
|
141
|
+
}
|
|
142
|
+
return 64;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Host-wide counting semaphore over LLM-subprocess spawns. One instance per
|
|
146
|
+
* process is fine — the cross-process coordination is the file + flock, not
|
|
147
|
+
* the object. Stateless beyond the file.
|
|
148
|
+
*/
|
|
149
|
+
export class HostSpawnSemaphore {
|
|
150
|
+
holdersPath;
|
|
151
|
+
cap;
|
|
152
|
+
now;
|
|
153
|
+
host;
|
|
154
|
+
pidAlive;
|
|
155
|
+
isPathHostLocal;
|
|
156
|
+
genId;
|
|
157
|
+
/** Memoized host-local determination (a fixed path's FS type can't change at
|
|
158
|
+
* runtime; the `df -P` probe is expensive + synchronous, so it runs ONCE per
|
|
159
|
+
* instance — never per acquire() on the hot fan-out path). */
|
|
160
|
+
_fsLocalCache;
|
|
161
|
+
constructor(deps = {}) {
|
|
162
|
+
this.holdersPath = deps.holdersPath ?? defaultHoldersPath();
|
|
163
|
+
this.cap = deps.cap ?? resolveSpawnCap();
|
|
164
|
+
this.now = deps.now ?? (() => Date.now());
|
|
165
|
+
this.host = (deps.hostname ?? (() => os.hostname()))();
|
|
166
|
+
this.pidAlive = deps.pidAlive ?? isAlive;
|
|
167
|
+
this.isPathHostLocal = deps.isPathHostLocal ?? isPathHostLocalDefault;
|
|
168
|
+
this.genId =
|
|
169
|
+
deps.genId ??
|
|
170
|
+
(() => `${this.host}:${process.pid}:${this.now().toString(36)}:${Math.random().toString(36).slice(2, 10)}`);
|
|
171
|
+
}
|
|
172
|
+
getCap() {
|
|
173
|
+
return this.cap;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Try to take a slot under id `id`. Returns true if a slot was appended
|
|
177
|
+
* (liveHolders < cap after pruning), false if the host is at the cap.
|
|
178
|
+
* Holds the exclusive flock for the whole read-prune-decide-write window.
|
|
179
|
+
*/
|
|
180
|
+
acquire(id) {
|
|
181
|
+
return this.withLock(() => {
|
|
182
|
+
const file = this.readHolders();
|
|
183
|
+
const live = this.pruneDead(file.holders);
|
|
184
|
+
if (live.length >= this.cap) {
|
|
185
|
+
// At the cap — write back the pruned set (cheap GC) but take no slot.
|
|
186
|
+
this.writeHolders({ version: 1, holders: live });
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
// Crash-safe: an id already present (a retry that already landed) is a
|
|
190
|
+
// no-op-append, not a duplicate slot.
|
|
191
|
+
if (!live.some((h) => h.id === id)) {
|
|
192
|
+
live.push({ id, pid: process.pid, hostname: this.host, heartbeat: this.now() });
|
|
193
|
+
}
|
|
194
|
+
this.writeHolders({ version: 1, holders: live });
|
|
195
|
+
return true;
|
|
196
|
+
}, /* fallbackOnLockFail */ false);
|
|
197
|
+
}
|
|
198
|
+
/** Release the slot held under `id`. A double-release / unknown id is a no-op. */
|
|
199
|
+
release(id) {
|
|
200
|
+
this.withLock(() => {
|
|
201
|
+
const file = this.readHolders();
|
|
202
|
+
const remaining = file.holders.filter((h) => h.id !== id);
|
|
203
|
+
// Opportunistically GC dead holders on release too (cheap, keeps the file small).
|
|
204
|
+
const live = this.pruneDead(remaining);
|
|
205
|
+
this.writeHolders({ version: 1, holders: live });
|
|
206
|
+
return undefined;
|
|
207
|
+
}, /* fallbackOnLockFail */ undefined);
|
|
208
|
+
}
|
|
209
|
+
/** Refresh a long-held slot's heartbeat so a slow legit spawn is never reclaimed. */
|
|
210
|
+
heartbeat(id) {
|
|
211
|
+
this.withLock(() => {
|
|
212
|
+
const file = this.readHolders();
|
|
213
|
+
let changed = false;
|
|
214
|
+
for (const h of file.holders) {
|
|
215
|
+
if (h.id === id) {
|
|
216
|
+
h.heartbeat = this.now();
|
|
217
|
+
changed = true;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (changed)
|
|
221
|
+
this.writeHolders(file);
|
|
222
|
+
return undefined;
|
|
223
|
+
}, /* fallbackOnLockFail */ undefined);
|
|
224
|
+
}
|
|
225
|
+
/** Read-only status (count of live holders after a prune). Best-effort: never throws. */
|
|
226
|
+
status() {
|
|
227
|
+
let live = [];
|
|
228
|
+
try {
|
|
229
|
+
live = this.withLock(() => {
|
|
230
|
+
const file = this.readHolders();
|
|
231
|
+
const pruned = this.pruneDead(file.holders);
|
|
232
|
+
this.writeHolders({ version: 1, holders: pruned });
|
|
233
|
+
return pruned;
|
|
234
|
+
}, /* fallbackOnLockFail */ this.readHolders().holders);
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
// @silent-fallback-ok: status is observability — a read error reports
|
|
238
|
+
// zero holders rather than throwing into the /spawn-limiter route.
|
|
239
|
+
live = [];
|
|
240
|
+
}
|
|
241
|
+
const localHolders = live.filter((h) => h.hostname === this.host).length;
|
|
242
|
+
return {
|
|
243
|
+
cap: this.cap,
|
|
244
|
+
liveHolders: live.length,
|
|
245
|
+
localHolders,
|
|
246
|
+
foreignHolders: live.length - localHolders,
|
|
247
|
+
holdersPath: this.holdersPath,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
// ── Internals ─────────────────────────────────────────────────────
|
|
251
|
+
/**
|
|
252
|
+
* Prune dead holders. A holder is reclaimable ONLY if:
|
|
253
|
+
* - it is on THIS host (a foreign-hostname holder is NEVER reclaimed —
|
|
254
|
+
* refuse-loud — the cross-host shared-volume hazard), AND
|
|
255
|
+
* - its pid is no longer alive (PRIMARY signal), AND
|
|
256
|
+
* - its heartbeat is stale past HOLDER_STALE_MS (SECONDARY signal — a slow
|
|
257
|
+
* spawn whose pid is alive is NEVER reclaimed regardless of heartbeat).
|
|
258
|
+
* AND a `df -P` host-local confirmation gates ALL reclaim (fail-closed: if we
|
|
259
|
+
* cannot confirm the holders file is on a local disk, we keep ALL holders —
|
|
260
|
+
* over-counting is the safe direction for a cap; it never under-bounds).
|
|
261
|
+
*/
|
|
262
|
+
pruneDead(holders) {
|
|
263
|
+
if (this._fsLocalCache === undefined) {
|
|
264
|
+
this._fsLocalCache = this.isPathHostLocal(path.dirname(this.holdersPath));
|
|
265
|
+
}
|
|
266
|
+
const fsLocal = this._fsLocalCache;
|
|
267
|
+
if (!fsLocal) {
|
|
268
|
+
// Cannot confirm host-local → reclaim NOTHING (fail-closed). Still return
|
|
269
|
+
// the well-formed set so the file stays clean.
|
|
270
|
+
return holders.filter((h) => isWellFormedHolder(h));
|
|
271
|
+
}
|
|
272
|
+
const nowMs = this.now();
|
|
273
|
+
return holders.filter((h) => {
|
|
274
|
+
if (!isWellFormedHolder(h))
|
|
275
|
+
return false; // drop garbage rows
|
|
276
|
+
if (h.hostname !== this.host)
|
|
277
|
+
return true; // foreign — NEVER reclaimed (keep)
|
|
278
|
+
const pidDead = !this.pidAlive(h.pid);
|
|
279
|
+
const heartbeatStale = nowMs - h.heartbeat >= HOLDER_STALE_MS;
|
|
280
|
+
// Reclaim only when BOTH dead AND stale (pid primary, heartbeat secondary).
|
|
281
|
+
const reclaim = pidDead && heartbeatStale;
|
|
282
|
+
return !reclaim;
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
readHolders() {
|
|
286
|
+
try {
|
|
287
|
+
const raw = fs.readFileSync(this.holdersPath, 'utf-8');
|
|
288
|
+
const obj = JSON.parse(raw);
|
|
289
|
+
if (!obj || typeof obj !== 'object' || !Array.isArray(obj.holders)) {
|
|
290
|
+
return { version: 1, holders: [] };
|
|
291
|
+
}
|
|
292
|
+
return { version: 1, holders: obj.holders.filter(isWellFormedHolder) };
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
// @silent-fallback-ok: a missing/corrupt holders file is an EMPTY set —
|
|
296
|
+
// the safe direction is to bound from zero, never to crash the spawn path.
|
|
297
|
+
return { version: 1, holders: [] };
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
writeHolders(file) {
|
|
301
|
+
this.ensureDir();
|
|
302
|
+
const body = JSON.stringify(file);
|
|
303
|
+
const tmp = `${this.holdersPath}.tmp.${process.pid}.${Math.random().toString(36).slice(2, 8)}`;
|
|
304
|
+
try {
|
|
305
|
+
const fd = fs.openSync(tmp, 'wx', 0o600);
|
|
306
|
+
fs.writeSync(fd, body);
|
|
307
|
+
fs.closeSync(fd);
|
|
308
|
+
fs.renameSync(tmp, this.holdersPath); // atomic on the same filesystem
|
|
309
|
+
}
|
|
310
|
+
catch (err) {
|
|
311
|
+
try {
|
|
312
|
+
SafeFsExecutor.safeUnlinkSync(tmp, { operation: 'HostSpawnSemaphore.writeHolders:cleanup-tmp' });
|
|
313
|
+
}
|
|
314
|
+
catch {
|
|
315
|
+
/* ignore */
|
|
316
|
+
}
|
|
317
|
+
throw err;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Run `fn` while holding the exclusive O_CREAT|O_EXCL lock on `<holdersPath>.lock`.
|
|
322
|
+
* On a lock-contention failure (another process holds it), returns
|
|
323
|
+
* `fallbackOnLockFail` — for `acquire`, fallback=false is the SAFE direction
|
|
324
|
+
* (refuse the slot under contention rather than over-grant past the cap).
|
|
325
|
+
* The lock is short-held (a file read + JSON parse + write) so contention is
|
|
326
|
+
* brief; a crashed lock-holder's stale lock is reclaimed by pid-death.
|
|
327
|
+
*/
|
|
328
|
+
withLock(fn, fallbackOnLockFail) {
|
|
329
|
+
this.ensureDir();
|
|
330
|
+
const lockPath = `${this.holdersPath}.lock`;
|
|
331
|
+
const deadline = this.now() + 2000; // bounded spin — never block the event loop forever
|
|
332
|
+
// Spin-acquire the lock with a tiny busy-wait; the critical section is sub-ms.
|
|
333
|
+
for (;;) {
|
|
334
|
+
let fd = null;
|
|
335
|
+
try {
|
|
336
|
+
fd = fs.openSync(lockPath, 'wx', 0o600); // O_CREAT|O_EXCL
|
|
337
|
+
fs.writeSync(fd, JSON.stringify({ pid: process.pid, at: this.now() }));
|
|
338
|
+
}
|
|
339
|
+
catch (err) {
|
|
340
|
+
const e = err;
|
|
341
|
+
if (e.code === 'EEXIST') {
|
|
342
|
+
// Lock held. Reclaim it if its holder's pid is dead (crash-safe).
|
|
343
|
+
if (this.reclaimStaleLock(lockPath))
|
|
344
|
+
continue;
|
|
345
|
+
if (this.now() >= deadline)
|
|
346
|
+
return fallbackOnLockFail; // give up safely
|
|
347
|
+
busyWaitTiny();
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
// Any other open error — fail to the safe fallback rather than throw.
|
|
351
|
+
if (fd !== null) {
|
|
352
|
+
try {
|
|
353
|
+
fs.closeSync(fd);
|
|
354
|
+
}
|
|
355
|
+
catch { /* ignore */ }
|
|
356
|
+
}
|
|
357
|
+
return fallbackOnLockFail;
|
|
358
|
+
}
|
|
359
|
+
try {
|
|
360
|
+
return fn();
|
|
361
|
+
}
|
|
362
|
+
finally {
|
|
363
|
+
try {
|
|
364
|
+
fs.closeSync(fd);
|
|
365
|
+
}
|
|
366
|
+
catch { /* @silent-fallback-ok: closing an already-closed/invalid fd on lock-release is benign */ }
|
|
367
|
+
try {
|
|
368
|
+
SafeFsExecutor.safeUnlinkSync(lockPath, { operation: 'HostSpawnSemaphore.withLock:release' });
|
|
369
|
+
}
|
|
370
|
+
catch {
|
|
371
|
+
/* @silent-fallback-ok: a missing lock on release is fine — release is idempotent */
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
/** Remove the lock file if its recorded holder pid is dead (crash recovery). */
|
|
377
|
+
reclaimStaleLock(lockPath) {
|
|
378
|
+
try {
|
|
379
|
+
const raw = fs.readFileSync(lockPath, 'utf-8');
|
|
380
|
+
const obj = JSON.parse(raw);
|
|
381
|
+
const pid = typeof obj?.pid === 'number' ? obj.pid : null;
|
|
382
|
+
if (pid !== null && this.pidAlive(pid))
|
|
383
|
+
return false; // live holder — wait
|
|
384
|
+
// Dead (or unparseable) lock holder — reclaim.
|
|
385
|
+
SafeFsExecutor.safeUnlinkSync(lockPath, { operation: 'HostSpawnSemaphore.reclaimStaleLock' });
|
|
386
|
+
return true;
|
|
387
|
+
}
|
|
388
|
+
catch {
|
|
389
|
+
// @silent-fallback-ok: a read/parse failure on the lock means we couldn't
|
|
390
|
+
// confirm a live holder; treat as reclaimable so a corrupt lock can't wedge
|
|
391
|
+
// the cap permanently. The O_EXCL re-create still races safely.
|
|
392
|
+
try {
|
|
393
|
+
SafeFsExecutor.safeUnlinkSync(lockPath, { operation: 'HostSpawnSemaphore.reclaimStaleLock:corrupt' });
|
|
394
|
+
return true;
|
|
395
|
+
}
|
|
396
|
+
catch {
|
|
397
|
+
// @silent-fallback-ok: couldn't remove the corrupt lock (a race with
|
|
398
|
+
// another reclaimer) — report not-reclaimed; the caller waits + retries.
|
|
399
|
+
return false;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
ensureDir() {
|
|
404
|
+
const dir = path.dirname(this.holdersPath);
|
|
405
|
+
if (!fs.existsSync(dir))
|
|
406
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
/** Default holders file path: `~/.instar/host-spawn-holders.json` (host-local). */
|
|
410
|
+
export function defaultHoldersPath() {
|
|
411
|
+
return path.join(os.homedir(), '.instar', 'host-spawn-holders.json');
|
|
412
|
+
}
|
|
413
|
+
function isWellFormedHolder(h) {
|
|
414
|
+
if (!h || typeof h !== 'object')
|
|
415
|
+
return false;
|
|
416
|
+
const r = h;
|
|
417
|
+
return (typeof r.id === 'string' &&
|
|
418
|
+
typeof r.pid === 'number' &&
|
|
419
|
+
typeof r.hostname === 'string' &&
|
|
420
|
+
typeof r.heartbeat === 'number');
|
|
421
|
+
}
|
|
422
|
+
/** A sub-millisecond busy-wait so the spin-lock doesn't peg a core. */
|
|
423
|
+
function busyWaitTiny() {
|
|
424
|
+
const end = Date.now() + 1;
|
|
425
|
+
while (Date.now() < end) {
|
|
426
|
+
/* spin ~1ms */
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
// ── Process-wide singleton ────────────────────────────────────────────
|
|
430
|
+
//
|
|
431
|
+
// The cross-process coordination is the FILE; this singleton just avoids
|
|
432
|
+
// re-resolving deps per call within one process. Config-cap is injected once
|
|
433
|
+
// at boot via `configureHostSpawnSemaphore`; absent that, the env/8 default holds.
|
|
434
|
+
let _singleton = null;
|
|
435
|
+
let _configuredCap;
|
|
436
|
+
let _configuredAcquireMs;
|
|
437
|
+
let _configuredWaitersMax;
|
|
438
|
+
/**
|
|
439
|
+
* Inject the config-resolved spawn-cap knobs once at server boot (server.ts).
|
|
440
|
+
* Idempotent: re-resolves the singleton so a later getter sees the configured
|
|
441
|
+
* cap. The env vars still win inside the `resolve*` helpers.
|
|
442
|
+
*/
|
|
443
|
+
export function configureHostSpawnSemaphore(cfg) {
|
|
444
|
+
_configuredCap = cfg?.maxConcurrent;
|
|
445
|
+
_configuredAcquireMs = cfg?.acquireMs;
|
|
446
|
+
_configuredWaitersMax = cfg?.waitersMax;
|
|
447
|
+
_singleton = new HostSpawnSemaphore({ cap: resolveSpawnCap(_configuredCap) });
|
|
448
|
+
}
|
|
449
|
+
/** The process-wide semaphore. Lazily constructed with env/config/8 cap. */
|
|
450
|
+
export function getHostSpawnSemaphore() {
|
|
451
|
+
if (!_singleton) {
|
|
452
|
+
_singleton = new HostSpawnSemaphore({ cap: resolveSpawnCap(_configuredCap) });
|
|
453
|
+
}
|
|
454
|
+
return _singleton;
|
|
455
|
+
}
|
|
456
|
+
/** Config-aware acquire-budget resolver (env > injected config > 5000). */
|
|
457
|
+
export function configuredSpawnAcquireMs() {
|
|
458
|
+
return resolveSpawnAcquireMs(_configuredAcquireMs);
|
|
459
|
+
}
|
|
460
|
+
/** Config-aware waiters-ceiling resolver (env > injected config > 64). */
|
|
461
|
+
export function configuredSpawnWaitersMax() {
|
|
462
|
+
return resolveSpawnWaitersMax(_configuredWaitersMax);
|
|
463
|
+
}
|
|
464
|
+
/** Test seam — reset the singleton + injected config. */
|
|
465
|
+
export function _resetHostSpawnSemaphoreForTest() {
|
|
466
|
+
_singleton = null;
|
|
467
|
+
_configuredCap = undefined;
|
|
468
|
+
_configuredAcquireMs = undefined;
|
|
469
|
+
_configuredWaitersMax = undefined;
|
|
470
|
+
}
|
|
471
|
+
//# sourceMappingURL=hostSpawnSemaphore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hostSpawnSemaphore.js","sourceRoot":"","sources":["../../src/core/hostSpawnSemaphore.ts"],"names":[],"mappings":"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"}
|
|
@@ -100,18 +100,6 @@ export interface BuildIntelligenceProviderOptions {
|
|
|
100
100
|
*/
|
|
101
101
|
resolveExecJson?: () => boolean;
|
|
102
102
|
}
|
|
103
|
-
/**
|
|
104
|
-
* Build an IntelligenceProvider for the given framework. Returns null
|
|
105
|
-
* if the required binary can't be located. The caller decides what
|
|
106
|
-
* happens next — fatal error, fall-back to a different framework, or
|
|
107
|
-
* disable LLM-backed paths entirely.
|
|
108
|
-
*
|
|
109
|
-
* @example
|
|
110
|
-
* const intel = buildIntelligenceProvider({ framework: 'codex-cli' });
|
|
111
|
-
* if (intel === null) {
|
|
112
|
-
* console.warn('Codex CLI not found — LLM-backed paths disabled.');
|
|
113
|
-
* }
|
|
114
|
-
*/
|
|
115
103
|
export declare function buildIntelligenceProvider(options?: BuildIntelligenceProviderOptions): IntelligenceProvider | null;
|
|
116
104
|
/**
|
|
117
105
|
* Convenience: read the framework selection from the environment.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"intelligenceProviderFactory.d.ts","sourceRoot":"","sources":["../../src/core/intelligenceProviderFactory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"intelligenceProviderFactory.d.ts","sourceRoot":"","sources":["../../src/core/intelligenceProviderFactory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAQvD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAChE,OAAO,EAEL,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,uBAAuB,EAC7B,MAAM,kCAAkC,CAAC;AAE1C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6DAA6D,CAAC;AAE1G;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,qBAAqB,GAAG,aAAa,GAAG,WAAW,GAAG,YAAY,GAAG,QAAQ,CAAC;AAE1F,MAAM,WAAW,gCAAgC;IAC/C;;;;OAIG;IACH,SAAS,CAAC,EAAE,qBAAqB,CAAC;IAClC;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;OAEG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;;OAKG;IACH,OAAO,CAAC,EAAE,iBAAiB,CAAC;IAC5B;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;;;OAOG;IACH,gBAAgB,CAAC,EAAE;QACjB,IAAI,EAAE,oBAAoB,CAAC;QAC3B,4EAA4E;QAC5E,WAAW,EAAE,eAAe,CAAC;QAC7B,gEAAgE;QAChE,aAAa,EAAE,MAAM,OAAO,CAAC,sBAAsB,GAAG,IAAI,CAAC,CAAC;QAC5D,oBAAoB,CAAC,EAAE,MAAM,CAAC;QAC9B,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,qBAAqB,KAAK,IAAI,CAAC;QAChD,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,uBAAuB,KAAK,IAAI,CAAC;KACrD,CAAC;IACF;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,yBAAyB,CAAC,EAAE,OAAO,CAAC;IACpC;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,MAAM,OAAO,CAAC;CACjC;AA4BD,wBAAgB,yBAAyB,CACvC,OAAO,GAAE,gCAAqC,GAC7C,oBAAoB,GAAG,IAAI,CAiH7B;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,qBAAqB,GAAG,IAAI,CAOnG"}
|
|
@@ -19,6 +19,7 @@ import { CodexCliIntelligenceProvider } from './CodexCliIntelligenceProvider.js'
|
|
|
19
19
|
import { GeminiCliIntelligenceProvider } from './GeminiCliIntelligenceProvider.js';
|
|
20
20
|
import { PiCliIntelligenceProvider } from './PiCliIntelligenceProvider.js';
|
|
21
21
|
import { wrapIntelligenceWithCircuitBreaker } from './CircuitBreakingIntelligenceProvider.js';
|
|
22
|
+
import { wrapIntelligenceWithSpawnCap } from './SpawnCapIntelligenceProvider.js';
|
|
22
23
|
import { AnthropicSubscriptionRouter, } from './AnthropicSubscriptionRouter.js';
|
|
23
24
|
import { InteractivePoolIntelligenceProvider } from './InteractivePoolIntelligenceProvider.js';
|
|
24
25
|
/**
|
|
@@ -33,12 +34,29 @@ import { InteractivePoolIntelligenceProvider } from './InteractivePoolIntelligen
|
|
|
33
34
|
* console.warn('Codex CLI not found — LLM-backed paths disabled.');
|
|
34
35
|
* }
|
|
35
36
|
*/
|
|
37
|
+
/**
|
|
38
|
+
* Apply the two universal funnel wrappers in the correct order: the spawn cap
|
|
39
|
+
* INSIDE the circuit breaker (so a breaker-open shed never holds a spawn slot),
|
|
40
|
+
* then the breaker OUTSIDE. Both are per-`evaluate()` wrappers, so the acquire
|
|
41
|
+
* is per-call — load-bearing for CoherenceGate's shared-instance fan-out.
|
|
42
|
+
* No-ops on null (passes a possibly-null factory result through unchanged).
|
|
43
|
+
*/
|
|
44
|
+
function wrapForFunnel(provider, breaker) {
|
|
45
|
+
return wrapIntelligenceWithCircuitBreaker(wrapIntelligenceWithSpawnCap(provider), breaker);
|
|
46
|
+
}
|
|
36
47
|
export function buildIntelligenceProvider(options = {}) {
|
|
37
48
|
const framework = options.framework ?? 'claude-code';
|
|
38
|
-
// Every provider the factory hands out is wrapped with
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
//
|
|
49
|
+
// Every provider the factory hands out is wrapped with TWO universal funnels
|
|
50
|
+
// (Structure > Willpower — every consumer inherits both from one place):
|
|
51
|
+
// 1. the host-wide SPAWN CAP (SpawnCapIntelligenceProvider) — the SIMPLE
|
|
52
|
+
// fork-bomb prevention P1 chokepoint. Layered INSIDE the breaker so a
|
|
53
|
+
// breaker-open shed never even reaches the spawn-cap acquire (no slot is
|
|
54
|
+
// held for a call that won't spawn); the cap binds the ACTUAL spawn.
|
|
55
|
+
// 2. the account-global LLM circuit breaker (CircuitBreakingIntelligenceProvider).
|
|
56
|
+
// A closed breaker is a transparent passthrough; it only acts on a
|
|
57
|
+
// usage/rate limit. See LlmCircuitBreaker for the why.
|
|
58
|
+
// `wrapForFunnel` applies both in the correct order at EVERY return arm so a
|
|
59
|
+
// new framework case can't accidentally ship un-capped.
|
|
42
60
|
switch (framework) {
|
|
43
61
|
case 'claude-code': {
|
|
44
62
|
const path = options.binaryPath ?? detectClaudePath();
|
|
@@ -63,15 +81,15 @@ export function buildIntelligenceProvider(options = {}) {
|
|
|
63
81
|
...(sp.onRoute ? { onRoute: sp.onRoute } : {}),
|
|
64
82
|
...(sp.onDegrade ? { onDegrade: sp.onDegrade } : {}),
|
|
65
83
|
});
|
|
66
|
-
return
|
|
84
|
+
return wrapForFunnel(routed, options.breaker);
|
|
67
85
|
}
|
|
68
|
-
return
|
|
86
|
+
return wrapForFunnel(headless, options.breaker);
|
|
69
87
|
}
|
|
70
88
|
case 'codex-cli': {
|
|
71
89
|
const path = options.binaryPath ?? detectCodexPath();
|
|
72
90
|
if (!path)
|
|
73
91
|
return null;
|
|
74
|
-
return
|
|
92
|
+
return wrapForFunnel(new CodexCliIntelligenceProvider({
|
|
75
93
|
codexPath: path,
|
|
76
94
|
...(options.workingDirectory ? { workingDirectory: options.workingDirectory } : {}),
|
|
77
95
|
...(options.resolveExecJson ? { resolveExecJson: options.resolveExecJson } : {}),
|
|
@@ -81,7 +99,7 @@ export function buildIntelligenceProvider(options = {}) {
|
|
|
81
99
|
const path = options.binaryPath ?? detectGeminiPath();
|
|
82
100
|
if (!path)
|
|
83
101
|
return null;
|
|
84
|
-
return
|
|
102
|
+
return wrapForFunnel(new GeminiCliIntelligenceProvider({
|
|
85
103
|
geminiPath: path,
|
|
86
104
|
...(options.workingDirectory ? { workingDirectory: options.workingDirectory } : {}),
|
|
87
105
|
...(options.quotaStateFile ? { capacityPolicy: { quotaStateFile: options.quotaStateFile } } : {}),
|
|
@@ -104,7 +122,7 @@ export function buildIntelligenceProvider(options = {}) {
|
|
|
104
122
|
return null;
|
|
105
123
|
}
|
|
106
124
|
try {
|
|
107
|
-
return
|
|
125
|
+
return wrapForFunnel(new PiCliIntelligenceProvider({
|
|
108
126
|
piPath: path,
|
|
109
127
|
model: options.piModel,
|
|
110
128
|
...(options.piAllowAnthropicProviders !== undefined
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"intelligenceProviderFactory.js","sourceRoot":"","sources":["../../src/core/intelligenceProviderFactory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChG,OAAO,EAAE,6BAA6B,EAAE,MAAM,oCAAoC,CAAC;AACnF,OAAO,EAAE,4BAA4B,EAAE,MAAM,mCAAmC,CAAC;AACjF,OAAO,EAAE,6BAA6B,EAAE,MAAM,oCAAoC,CAAC;AACnF,OAAO,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AAC3E,OAAO,EAAE,kCAAkC,EAAE,MAAM,0CAA0C,CAAC;
|
|
1
|
+
{"version":3,"file":"intelligenceProviderFactory.js","sourceRoot":"","sources":["../../src/core/intelligenceProviderFactory.ts"],"names":[],"mappings":"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"}
|
package/dist/core/types.d.ts
CHANGED
|
@@ -3016,6 +3016,24 @@ export interface InstarConfig {
|
|
|
3016
3016
|
* injected sentinel and the REPL would otherwise never be reaped. */
|
|
3017
3017
|
maxReroutedLifetimeMinutes?: number;
|
|
3018
3018
|
};
|
|
3019
|
+
/**
|
|
3020
|
+
* Fork-bomb prevention — host-wide concurrent-LLM-subprocess cap (the SIMPLE
|
|
3021
|
+
* design, docs/specs/forkbomb-prevention-simple.md §D-CAP). A SAFETY FLOOR:
|
|
3022
|
+
* read with a plain `?? default` (env > this config > the hardcoded default),
|
|
3023
|
+
* NEVER `resolveDevAgentGate` — it ships ON for every agent, never dark. The
|
|
3024
|
+
* cap bounds how many `claude -p`/`codex exec` subprocesses run AT ONCE
|
|
3025
|
+
* across every compliant Instar process on the host (the primary control for
|
|
3026
|
+
* the 2026-06-20 OOM fork-bomb). Env overrides: INSTAR_HOST_SPAWN_MAX,
|
|
3027
|
+
* INSTAR_SPAWN_ACQUIRE_MS, INSTAR_SPAWN_WAITERS_MAX.
|
|
3028
|
+
*/
|
|
3029
|
+
spawnCap?: {
|
|
3030
|
+
/** Concurrent-spawn cap across the whole host. Default 8. */
|
|
3031
|
+
maxConcurrent?: number;
|
|
3032
|
+
/** Bounded-acquire poll budget (ms) before a saturated call sheds. Default 5000. */
|
|
3033
|
+
acquireMs?: number;
|
|
3034
|
+
/** Concurrent-poller ceiling (bounds the waiters, no queue-node heap). Default 64. */
|
|
3035
|
+
waitersMax?: number;
|
|
3036
|
+
};
|
|
3019
3037
|
};
|
|
3020
3038
|
/**
|
|
3021
3039
|
* Agent-level set of frameworks this install actively uses. Drives
|