@yemi33/minions 0.1.2383 → 0.1.2385
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/dashboard/js/refresh.js +2 -1
- package/dashboard/js/settings.js +1 -1
- package/dashboard.js +133 -46
- package/docs/keep-processes.md +7 -2
- package/docs/live-checkout-mode.md +7 -7
- package/docs/managed-spawn.md +14 -1
- package/docs/runtime-adapters.md +7 -4
- package/docs/worktree-lifecycle.md +22 -0
- package/engine/acp-transport.js +216 -29
- package/engine/agent-worker-pool.js +268 -51
- package/engine/cc-worker-pool.js +8 -1
- package/engine/cleanup.js +15 -11
- package/engine/cli.js +47 -27
- package/engine/create-pr-worktree.js +57 -79
- package/engine/keep-process-sweep.js +131 -9
- package/engine/lifecycle.js +1 -1
- package/engine/live-checkout.js +4 -2
- package/engine/managed-spawn.js +112 -5
- package/engine/pooled-agent-process.js +486 -21
- package/engine/pr-action.js +6 -8
- package/engine/process-utils.js +154 -18
- package/engine/runtimes/copilot.js +22 -4
- package/engine/shared.js +254 -61
- package/engine/spawn-agent.js +6 -3
- package/engine/timeout.js +14 -10
- package/engine/worktree-gc.js +55 -46
- package/engine.js +237 -134
- package/package.json +1 -1
- package/playbooks/shared-rules.md +2 -2
- package/prompts/cc-system.md +11 -11
package/engine/acp-transport.js
CHANGED
|
@@ -93,6 +93,10 @@ function typedError(message, code, retriable = true) {
|
|
|
93
93
|
// consumers that pre-warm (like cc-worker-pool.js's warmTab) can share the
|
|
94
94
|
// same knob instead of hardcoding their own copy.
|
|
95
95
|
const WARM_MAX_CONCURRENT = 3;
|
|
96
|
+
const ACP_HANDSHAKE_TIMEOUT_MS = 60000;
|
|
97
|
+
const SESSION_LOAD_RETRY_ATTEMPTS = 10;
|
|
98
|
+
const SESSION_LOAD_RETRY_DELAY_MS = 100;
|
|
99
|
+
const SESSION_ROTATION_CANCEL_WAIT_MS = 1000;
|
|
96
100
|
|
|
97
101
|
function resolveWorkerRuntime(runtime) {
|
|
98
102
|
const adapter = typeof runtime === 'string'
|
|
@@ -106,7 +110,7 @@ function resolveWorkerRuntime(runtime) {
|
|
|
106
110
|
|
|
107
111
|
// Real spawn implementation. Binary resolution and worker argv are both
|
|
108
112
|
// adapter-owned so harness CLI flag changes do not leak into the transport.
|
|
109
|
-
function spawnAcp({ runtime, cwd, env } = {}) {
|
|
113
|
+
function spawnAcp({ runtime, cwd, env, workerOptions } = {}) {
|
|
110
114
|
const adapter = resolveWorkerRuntime(runtime);
|
|
111
115
|
const workerEnv = env || process.env;
|
|
112
116
|
const resolved = adapter.resolveBinary({ env: workerEnv });
|
|
@@ -116,7 +120,10 @@ function spawnAcp({ runtime, cwd, env } = {}) {
|
|
|
116
120
|
);
|
|
117
121
|
}
|
|
118
122
|
const { bin, native, leadingArgs = [] } = resolved;
|
|
119
|
-
const acpArgs = [
|
|
123
|
+
const acpArgs = [
|
|
124
|
+
...leadingArgs,
|
|
125
|
+
...buildWorkerArgs(adapter, { ...(workerOptions || {}), cwd, env: workerEnv }),
|
|
126
|
+
];
|
|
120
127
|
// Mirror engine/spawn-agent.js: native binaries run directly; a non-native
|
|
121
128
|
// (JS entry / npm shim) runs under process.execPath as `node <bin> ...`.
|
|
122
129
|
const execBin = native ? bin : process.execPath;
|
|
@@ -143,6 +150,10 @@ function killImmediate(proc) {
|
|
|
143
150
|
}
|
|
144
151
|
}
|
|
145
152
|
|
|
153
|
+
function killRoot(proc) {
|
|
154
|
+
try { proc.kill('SIGKILL'); } catch { /* already dead */ }
|
|
155
|
+
}
|
|
156
|
+
|
|
146
157
|
function hashMcpServers(mcpServers) {
|
|
147
158
|
// Stable hash via JSON.stringify; mcpServers is an array of plain objects
|
|
148
159
|
// in practice (name/command/env) so the natural key order is fine.
|
|
@@ -150,18 +161,31 @@ function hashMcpServers(mcpServers) {
|
|
|
150
161
|
return crypto.createHash('sha256').update(json).digest('hex');
|
|
151
162
|
}
|
|
152
163
|
|
|
153
|
-
function
|
|
154
|
-
if (
|
|
164
|
+
function _stableValue(value) {
|
|
165
|
+
if (Array.isArray(value)) return value.map(_stableValue);
|
|
166
|
+
if (!value || typeof value !== 'object') return value;
|
|
155
167
|
return Object.fromEntries(
|
|
156
168
|
Object.entries(value)
|
|
157
169
|
.filter(([, entry]) => entry !== undefined)
|
|
158
170
|
.sort(([a], [b]) => a.localeCompare(b))
|
|
171
|
+
.map(([key, entry]) => [key, _stableValue(entry)])
|
|
159
172
|
);
|
|
160
173
|
}
|
|
161
174
|
|
|
162
|
-
function
|
|
175
|
+
function _stableObject(value) {
|
|
176
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return {};
|
|
177
|
+
return _stableValue(value);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function hashProcessContext(processEnv, processOptions) {
|
|
181
|
+
const context = processOptions === undefined
|
|
182
|
+
? _stableObject(processEnv)
|
|
183
|
+
: {
|
|
184
|
+
env: _stableObject(processEnv),
|
|
185
|
+
options: _stableValue(processOptions),
|
|
186
|
+
};
|
|
163
187
|
return crypto.createHash('sha256')
|
|
164
|
-
.update(JSON.stringify(
|
|
188
|
+
.update(JSON.stringify(context))
|
|
165
189
|
.digest('hex');
|
|
166
190
|
}
|
|
167
191
|
|
|
@@ -203,6 +227,10 @@ function extractSessionModel(result) {
|
|
|
203
227
|
return null;
|
|
204
228
|
}
|
|
205
229
|
|
|
230
|
+
function buildSessionLoadParams({ sessionId, cwd, mcpServers }) {
|
|
231
|
+
return { sessionId, cwd, mcpServers: mcpServers || [] };
|
|
232
|
+
}
|
|
233
|
+
|
|
206
234
|
function extractChunkText(content) {
|
|
207
235
|
if (content == null) return '';
|
|
208
236
|
if (typeof content === 'string') return content;
|
|
@@ -270,7 +298,7 @@ class Worker {
|
|
|
270
298
|
constructor({
|
|
271
299
|
runtime,
|
|
272
300
|
id, model, effort, mcpServers, mcpServersHash, processEnv, processContextHash,
|
|
273
|
-
systemPromptHash, cwd, internals, trace,
|
|
301
|
+
workerOptions, resumeSessionId, systemPromptHash, cwd, processCwd, internals, trace,
|
|
274
302
|
}) {
|
|
275
303
|
this.runtime = resolveWorkerRuntime(runtime);
|
|
276
304
|
this.id = id;
|
|
@@ -280,12 +308,16 @@ class Worker {
|
|
|
280
308
|
this.mcpServersHash = mcpServersHash;
|
|
281
309
|
this.processEnv = processEnv;
|
|
282
310
|
this.processContextHash = processContextHash;
|
|
311
|
+
this.workerOptions = workerOptions && typeof workerOptions === 'object' ? { ...workerOptions } : {};
|
|
312
|
+
this.resumeSessionId = resumeSessionId || null;
|
|
283
313
|
this.systemPromptHash = systemPromptHash;
|
|
284
314
|
this.cwd = cwd || process.cwd();
|
|
315
|
+
this.processCwd = processCwd || this.cwd;
|
|
285
316
|
this._internals = internals;
|
|
286
317
|
this._trace = typeof trace === 'function' ? trace : () => {};
|
|
287
318
|
|
|
288
319
|
this.proc = null;
|
|
320
|
+
this.processStartedAt = 0;
|
|
289
321
|
this.sessionId = null;
|
|
290
322
|
this.pending = new Map(); // id → { resolve, reject }
|
|
291
323
|
this.inflight = null; // current { id, sessionId, onChunk, onDone, onError, signal, signalHandler, settled }
|
|
@@ -295,6 +327,7 @@ class Worker {
|
|
|
295
327
|
this.killed = false;
|
|
296
328
|
this.spawnError = null;
|
|
297
329
|
this.firstSystemPromptSent = false;
|
|
330
|
+
this.preservedDescendantPids = new Set();
|
|
298
331
|
// In-flight spawn+initialize+session/new promise. Set by the consumer
|
|
299
332
|
// before the worker is registered in its own pool bookkeeping, cleared
|
|
300
333
|
// after the handshake settles. Racing callers await this to avoid the
|
|
@@ -307,7 +340,12 @@ class Worker {
|
|
|
307
340
|
async _spawnAndInit() {
|
|
308
341
|
let proc;
|
|
309
342
|
try {
|
|
310
|
-
proc = this._internals.spawnAcp({
|
|
343
|
+
proc = this._internals.spawnAcp({
|
|
344
|
+
runtime: this.runtime,
|
|
345
|
+
cwd: this.processCwd,
|
|
346
|
+
env: this.processEnv,
|
|
347
|
+
workerOptions: this.workerOptions,
|
|
348
|
+
});
|
|
311
349
|
} catch (err) {
|
|
312
350
|
const hint = this.runtime.workerErrorHint || `${this.runtime.name} persistent worker failed`;
|
|
313
351
|
throw typedError(
|
|
@@ -359,30 +397,63 @@ class Worker {
|
|
|
359
397
|
};
|
|
360
398
|
proc.on('error', errorHandler);
|
|
361
399
|
|
|
400
|
+
const configuredHandshakeTimeout = Number(this._internals.handshakeTimeoutMs);
|
|
401
|
+
const handshakeTimeoutMs = Number.isFinite(configuredHandshakeTimeout) && configuredHandshakeTimeout > 0
|
|
402
|
+
? configuredHandshakeTimeout
|
|
403
|
+
: ACP_HANDSHAKE_TIMEOUT_MS;
|
|
404
|
+
let handshakeTimer;
|
|
405
|
+
const handshakeTimeoutPromise = new Promise((_, reject) => {
|
|
406
|
+
handshakeTimer = setTimeout(() => {
|
|
407
|
+
reject(typedError(
|
|
408
|
+
`copilot --acp handshake timed out after ${handshakeTimeoutMs}ms`,
|
|
409
|
+
ERROR_CODES.ACP_HANDSHAKE_FAILED,
|
|
410
|
+
true
|
|
411
|
+
));
|
|
412
|
+
}, handshakeTimeoutMs);
|
|
413
|
+
});
|
|
414
|
+
|
|
362
415
|
try {
|
|
363
416
|
await Promise.race([
|
|
364
417
|
this._call('initialize', { protocolVersion: 1, clientCapabilities: {} }),
|
|
365
418
|
earlyExitPromise,
|
|
419
|
+
handshakeTimeoutPromise,
|
|
366
420
|
]);
|
|
421
|
+
const sessionMethod = this.resumeSessionId ? 'session/load' : 'session/new';
|
|
422
|
+
const sessionParams = this.resumeSessionId
|
|
423
|
+
? buildSessionLoadParams({
|
|
424
|
+
sessionId: this.resumeSessionId,
|
|
425
|
+
cwd: this.cwd,
|
|
426
|
+
mcpServers: this.mcpServers,
|
|
427
|
+
})
|
|
428
|
+
: buildSessionNewParams({
|
|
429
|
+
cwd: this.cwd,
|
|
430
|
+
mcpServers: this.mcpServers,
|
|
431
|
+
model: this.model,
|
|
432
|
+
effort: this.effort,
|
|
433
|
+
});
|
|
434
|
+
const sessionPromise = sessionMethod === 'session/load'
|
|
435
|
+
? this._loadSession(sessionParams)
|
|
436
|
+
: this._call(sessionMethod, sessionParams);
|
|
367
437
|
const result = await Promise.race([
|
|
368
|
-
|
|
369
|
-
cwd: this.cwd, mcpServers: this.mcpServers, model: this.model, effort: this.effort,
|
|
370
|
-
})),
|
|
438
|
+
sessionPromise,
|
|
371
439
|
earlyExitPromise,
|
|
440
|
+
handshakeTimeoutPromise,
|
|
372
441
|
]);
|
|
373
|
-
this.sessionId = result && result.sessionId;
|
|
442
|
+
this.sessionId = (result && result.sessionId) || this.resumeSessionId;
|
|
374
443
|
this.currentModel = extractSessionModel(result) || normalizeExecutionModel(this.model);
|
|
375
444
|
if (!this.sessionId) {
|
|
376
445
|
// Handshake completed without an error but the daemon didn't hand
|
|
377
446
|
// back a sessionId — protocol violation or partial init failure.
|
|
378
447
|
const hint = this.runtime.workerErrorHint || `${this.runtime.name} persistent worker failed`;
|
|
379
448
|
throw typedError(
|
|
380
|
-
`${hint} (
|
|
449
|
+
`${hint} (${sessionMethod} returned no sessionId)`,
|
|
381
450
|
ERROR_CODES.ACP_HANDSHAKE_FAILED,
|
|
382
451
|
true
|
|
383
452
|
);
|
|
384
453
|
}
|
|
454
|
+
this.firstSystemPromptSent = !!this.resumeSessionId;
|
|
385
455
|
} finally {
|
|
456
|
+
clearTimeout(handshakeTimer);
|
|
386
457
|
// Either the handshake finished (swap to a persistent exit handler that
|
|
387
458
|
// just marks killed) or it failed (proc is dying anyway).
|
|
388
459
|
proc.removeListener('exit', earlyExitHandler);
|
|
@@ -431,9 +502,13 @@ class Worker {
|
|
|
431
502
|
_handleMessage(obj) {
|
|
432
503
|
// Response (matches an outbound id)
|
|
433
504
|
if (obj.id != null && this.pending.has(obj.id)) {
|
|
434
|
-
const { resolve, reject } = this.pending.get(obj.id);
|
|
505
|
+
const { resolve, reject, method } = this.pending.get(obj.id);
|
|
435
506
|
this.pending.delete(obj.id);
|
|
436
|
-
if (obj.error)
|
|
507
|
+
if (obj.error) {
|
|
508
|
+
const err = new Error(obj.error.message || 'ACP error');
|
|
509
|
+
err.acpMethod = method || null;
|
|
510
|
+
reject(err);
|
|
511
|
+
}
|
|
437
512
|
else resolve(obj.result);
|
|
438
513
|
return;
|
|
439
514
|
}
|
|
@@ -483,10 +558,35 @@ class Worker {
|
|
|
483
558
|
catch { /* pipe may be broken; exit handler will fire */ }
|
|
484
559
|
}
|
|
485
560
|
|
|
486
|
-
_call(method, params) {
|
|
561
|
+
_call(method, params, timeoutMs) {
|
|
562
|
+
if (this.killed || !this.proc?.stdin || this.proc.stdin.destroyed) {
|
|
563
|
+
return Promise.reject(new Error('acp-transport: worker is closed'));
|
|
564
|
+
}
|
|
487
565
|
return new Promise((resolve, reject) => {
|
|
488
566
|
const id = this.nextReqId++;
|
|
489
|
-
|
|
567
|
+
let timer;
|
|
568
|
+
const settle = (callback, value) => {
|
|
569
|
+
if (timer) clearTimeout(timer);
|
|
570
|
+
callback(value);
|
|
571
|
+
};
|
|
572
|
+
this.pending.set(id, {
|
|
573
|
+
resolve: (value) => settle(resolve, value),
|
|
574
|
+
reject: (err) => settle(reject, err),
|
|
575
|
+
method,
|
|
576
|
+
});
|
|
577
|
+
const parsedTimeout = Number(timeoutMs);
|
|
578
|
+
if (Number.isFinite(parsedTimeout) && parsedTimeout > 0) {
|
|
579
|
+
timer = setTimeout(() => {
|
|
580
|
+
if (!this.pending.delete(id)) return;
|
|
581
|
+
const err = typedError(
|
|
582
|
+
`acp-transport: ${method} timed out after ${parsedTimeout}ms`,
|
|
583
|
+
ERROR_CODES.ACP_HANDSHAKE_FAILED,
|
|
584
|
+
true
|
|
585
|
+
);
|
|
586
|
+
err.acpMethod = method;
|
|
587
|
+
reject(err);
|
|
588
|
+
}, parsedTimeout);
|
|
589
|
+
}
|
|
490
590
|
this._writeFrame({ jsonrpc: '2.0', id, method, params });
|
|
491
591
|
});
|
|
492
592
|
}
|
|
@@ -495,6 +595,20 @@ class Worker {
|
|
|
495
595
|
this._writeFrame({ jsonrpc: '2.0', method, params });
|
|
496
596
|
}
|
|
497
597
|
|
|
598
|
+
async _loadSession(params) {
|
|
599
|
+
let lastError;
|
|
600
|
+
for (let attempt = 0; attempt < SESSION_LOAD_RETRY_ATTEMPTS; attempt++) {
|
|
601
|
+
try {
|
|
602
|
+
return await this._call('session/load', params, ACP_HANDSHAKE_TIMEOUT_MS);
|
|
603
|
+
} catch (err) {
|
|
604
|
+
lastError = err;
|
|
605
|
+
if (!/already loaded/i.test(String(err && err.message))) throw err;
|
|
606
|
+
await new Promise((resolve) => setTimeout(resolve, SESSION_LOAD_RETRY_DELAY_MS));
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
throw lastError;
|
|
610
|
+
}
|
|
611
|
+
|
|
498
612
|
// ── Stream a single turn ───────────────────────────────────────────────
|
|
499
613
|
stream(promptText, opts = {}) {
|
|
500
614
|
const {
|
|
@@ -519,8 +633,11 @@ class Worker {
|
|
|
519
633
|
// message remains correct.
|
|
520
634
|
let prompt = promptText;
|
|
521
635
|
const trustedContextText = buildTrustedSessionContext(trustedSessionContext);
|
|
522
|
-
const trustedSystemText = [
|
|
523
|
-
|
|
636
|
+
const trustedSystemText = [
|
|
637
|
+
!this.firstSystemPromptSent ? systemPromptText : null,
|
|
638
|
+
trustedContextText,
|
|
639
|
+
].filter(Boolean).join('\n\n');
|
|
640
|
+
if (trustedSystemText) {
|
|
524
641
|
prompt = `<system>\n${trustedSystemText}\n</system>\n\n${promptText}`;
|
|
525
642
|
}
|
|
526
643
|
if (!deferSystemPrompt) this.firstSystemPromptSent = true;
|
|
@@ -586,8 +703,8 @@ class Worker {
|
|
|
586
703
|
this._notify('session/cancel', { sessionId: this.inflight.sessionId });
|
|
587
704
|
}
|
|
588
705
|
|
|
589
|
-
async
|
|
590
|
-
const { mcpServers, systemPromptHash, model, effort, cwd } = options;
|
|
706
|
+
async _prepareSession(options = {}) {
|
|
707
|
+
const { sessionId, mcpServers, systemPromptHash, model, effort, cwd } = options;
|
|
591
708
|
// Cancel any inflight before swapping the underlying session.
|
|
592
709
|
if (this.inflight) {
|
|
593
710
|
this.cancel();
|
|
@@ -597,11 +714,15 @@ class Worker {
|
|
|
597
714
|
await new Promise((resolve) => {
|
|
598
715
|
const start = this._internals.now();
|
|
599
716
|
const wait = () => {
|
|
600
|
-
if (!inflight || inflight.settled
|
|
717
|
+
if (!inflight || inflight.settled
|
|
718
|
+
|| this._internals.now() - start > SESSION_ROTATION_CANCEL_WAIT_MS) resolve();
|
|
601
719
|
else setTimeout(wait, 5);
|
|
602
720
|
};
|
|
603
721
|
wait();
|
|
604
722
|
});
|
|
723
|
+
if (!inflight.settled) {
|
|
724
|
+
throw new Error('acp-transport: cancelled prompt did not settle before session rotation');
|
|
725
|
+
}
|
|
605
726
|
}
|
|
606
727
|
// Bug B (issue #2479): if the caller is rotating the session because the
|
|
607
728
|
// system prompt changed, they may also be passing a fresh model/effort —
|
|
@@ -613,14 +734,71 @@ class Worker {
|
|
|
613
734
|
// agent-worker-pool.js reuses a warm worker across dispatches with
|
|
614
735
|
// different cwds (unlike cc-worker-pool.js, whose tabs never change
|
|
615
736
|
// cwd) — allow the caller to rotate it in on a session swap.
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
737
|
+
const nextCwd = cwd !== undefined && cwd !== null && cwd !== '' ? cwd : this.cwd;
|
|
738
|
+
if (sessionId && this.sessionId === sessionId) {
|
|
739
|
+
if (nextCwd !== this.cwd) {
|
|
740
|
+
const err = new Error('acp-transport: loaded session cannot change cwd without a worker respawn');
|
|
741
|
+
err.acpMethod = 'session/load';
|
|
742
|
+
throw err;
|
|
743
|
+
}
|
|
744
|
+
this.systemPromptHash = systemPromptHash;
|
|
745
|
+
this.firstSystemPromptSent = true;
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
this.cwd = nextCwd;
|
|
749
|
+
const method = sessionId ? 'session/load' : 'session/new';
|
|
750
|
+
const params = sessionId
|
|
751
|
+
? buildSessionLoadParams({ sessionId, cwd: this.cwd, mcpServers: mcpServers || [] })
|
|
752
|
+
: buildSessionNewParams({
|
|
753
|
+
cwd: this.cwd,
|
|
754
|
+
mcpServers: mcpServers || [],
|
|
755
|
+
model: this.model,
|
|
756
|
+
effort: this.effort,
|
|
757
|
+
});
|
|
758
|
+
const result = method === 'session/load'
|
|
759
|
+
? await this._loadSession(params)
|
|
760
|
+
: await this._call(method, params, ACP_HANDSHAKE_TIMEOUT_MS);
|
|
761
|
+
this.sessionId = (result && result.sessionId) || sessionId;
|
|
762
|
+
if (!this.sessionId) {
|
|
763
|
+
const hint = this.runtime.workerErrorHint || `${this.runtime.name} persistent worker failed`;
|
|
764
|
+
throw typedError(
|
|
765
|
+
`${hint} (${method} returned no sessionId)`,
|
|
766
|
+
ERROR_CODES.ACP_HANDSHAKE_FAILED,
|
|
767
|
+
true
|
|
768
|
+
);
|
|
769
|
+
}
|
|
621
770
|
this.currentModel = extractSessionModel(result) || normalizeExecutionModel(this.model);
|
|
622
771
|
this.systemPromptHash = systemPromptHash;
|
|
623
|
-
this.firstSystemPromptSent =
|
|
772
|
+
this.firstSystemPromptSent = !!sessionId;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
async newSession(opts) {
|
|
776
|
+
return this._prepareSession({ ...opts, sessionId: null });
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
async loadSession(sessionId, opts) {
|
|
780
|
+
return this._prepareSession({ ...opts, sessionId });
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
preserveDescendants(pids) {
|
|
784
|
+
for (const pid of pids || []) {
|
|
785
|
+
const value = Number(pid);
|
|
786
|
+
if (Number.isInteger(value) && value > 0) this.preservedDescendantPids.add(value);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
abandon() {
|
|
791
|
+
if (this.killed) return;
|
|
792
|
+
this.killed = true;
|
|
793
|
+
try { this.proc?.stdin?.end(); } catch { /* pipe already closed */ }
|
|
794
|
+
const err = new Error('acp-transport: unverified worker abandoned');
|
|
795
|
+
this._failAllPending(err);
|
|
796
|
+
if (this.inflight && !this.inflight.settled) {
|
|
797
|
+
const cb = this.inflight.onError;
|
|
798
|
+
this.inflight.settled = true;
|
|
799
|
+
this.inflight = null;
|
|
800
|
+
if (cb) try { cb(err); } catch { /* swallow */ }
|
|
801
|
+
}
|
|
624
802
|
}
|
|
625
803
|
|
|
626
804
|
close() {
|
|
@@ -633,7 +811,13 @@ class Worker {
|
|
|
633
811
|
catch { /* swallow */ }
|
|
634
812
|
}
|
|
635
813
|
if (this.proc) {
|
|
636
|
-
try {
|
|
814
|
+
try {
|
|
815
|
+
if (this.preservedDescendantPids.size > 0 && typeof this._internals.killRoot === 'function') {
|
|
816
|
+
this._internals.killRoot(this.proc);
|
|
817
|
+
} else {
|
|
818
|
+
this._internals.killImmediate(this.proc);
|
|
819
|
+
}
|
|
820
|
+
} catch { /* already dead */ }
|
|
637
821
|
}
|
|
638
822
|
this._failAllPending(new Error('acp-transport: worker closed'));
|
|
639
823
|
if (this.inflight && !this.inflight.settled) {
|
|
@@ -651,13 +835,16 @@ module.exports = {
|
|
|
651
835
|
spawnAcp,
|
|
652
836
|
resolveWorkerRuntime,
|
|
653
837
|
killImmediate,
|
|
838
|
+
killRoot,
|
|
654
839
|
hashMcpServers,
|
|
655
840
|
hashProcessContext,
|
|
656
841
|
buildTrustedSessionContext,
|
|
657
842
|
buildSessionNewParams,
|
|
658
843
|
extractSessionModel,
|
|
844
|
+
buildSessionLoadParams,
|
|
659
845
|
extractChunkText,
|
|
660
846
|
mapAcpToolCallToToolUse,
|
|
661
847
|
Worker,
|
|
662
848
|
WARM_MAX_CONCURRENT,
|
|
849
|
+
ACP_HANDSHAKE_TIMEOUT_MS,
|
|
663
850
|
};
|