@wrongstack/acp 0.296.4 → 0.298.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/dist/agent/protocol-handler.d.ts +39 -46
- package/dist/agent/session-store.d.ts +55 -0
- package/dist/agent/stdio-transport.d.ts +74 -1
- package/dist/agent.js +472 -210
- package/dist/client/terminal-server.d.ts +5 -1
- package/dist/client/websocket-transport.d.ts +2 -0
- package/dist/client.js +122 -56
- package/dist/index.js +484 -264
- package/dist/win32-cmd.d.ts +9 -6
- package/dist/wrongstack-acp-agent.js +312 -157
- package/package.json +4 -3
- package/dist/agent/index.d.ts.map +0 -1
- package/dist/agent/protocol-contract.d.ts.map +0 -1
- package/dist/agent/protocol-handler.d.ts.map +0 -1
- package/dist/agent/server-agent-turn.d.ts.map +0 -1
- package/dist/agent/session-store.d.ts.map +0 -1
- package/dist/agent/stdio-transport.d.ts.map +0 -1
- package/dist/agent/tools-registry.d.ts.map +0 -1
- package/dist/agent/wrongstack-acp-agent.d.ts.map +0 -1
- package/dist/agent/ws-bridge-transport.d.ts.map +0 -1
- package/dist/agent.js.map +0 -7
- package/dist/client/acp-message-routing.d.ts.map +0 -1
- package/dist/client/acp-session-callbacks.d.ts.map +0 -1
- package/dist/client/acp-session-content.d.ts.map +0 -1
- package/dist/client/acp-session-errors.d.ts.map +0 -1
- package/dist/client/acp-session-types.d.ts.map +0 -1
- package/dist/client/acp-session-updates.d.ts.map +0 -1
- package/dist/client/acp-session.d.ts.map +0 -1
- package/dist/client/file-server.d.ts.map +0 -1
- package/dist/client/index.d.ts.map +0 -1
- package/dist/client/permission.d.ts.map +0 -1
- package/dist/client/terminal-server.d.ts.map +0 -1
- package/dist/client/tool-translator.d.ts.map +0 -1
- package/dist/client/trust-boundary-permission.d.ts.map +0 -1
- package/dist/client/websocket-transport.d.ts.map +0 -1
- package/dist/client.js.map +0 -7
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -7
- package/dist/integration/acp-bench.d.ts.map +0 -1
- package/dist/integration/acp-subagent-runner.d.ts.map +0 -1
- package/dist/integration/ensemble-runner.d.ts.map +0 -1
- package/dist/integration/run-one-acp-task.d.ts.map +0 -1
- package/dist/legacy.d.ts.map +0 -1
- package/dist/legacy.js.map +0 -7
- package/dist/registry/acp-registry-fetch.d.ts.map +0 -1
- package/dist/registry/agents.catalog.d.ts.map +0 -1
- package/dist/registry/ensemble-registry.d.ts.map +0 -1
- package/dist/sdk.d.ts.map +0 -1
- package/dist/sdk.js.map +0 -7
- package/dist/types/acp-messages.d.ts.map +0 -1
- package/dist/types/acp-v1.d.ts.map +0 -1
- package/dist/v1.d.ts.map +0 -1
- package/dist/v1.js.map +0 -7
- package/dist/version.d.ts.map +0 -1
- package/dist/win32-cmd.d.ts.map +0 -1
- package/dist/wrongstack-acp-agent.js.map +0 -7
package/dist/index.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
// src/agent/protocol-handler.ts
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import * as fsp from "node:fs/promises";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
|
|
1
6
|
// src/types/acp-v1.ts
|
|
2
7
|
var ACP_PROTOCOL_VERSION = 1;
|
|
3
8
|
function assertNeverSessionUpdate(x) {
|
|
@@ -92,12 +97,12 @@ var ACPProtocolHandler = class {
|
|
|
92
97
|
*/
|
|
93
98
|
request(method, params, timeoutMs = 6e4) {
|
|
94
99
|
const id = `srv_${this.nextOutId++}`;
|
|
95
|
-
return new Promise((
|
|
100
|
+
return new Promise((resolve4, reject) => {
|
|
96
101
|
const timer = setTimeout(() => {
|
|
97
102
|
this.pendingOut.delete(id);
|
|
98
103
|
reject(new Error(`${method} timed out after ${timeoutMs}ms`));
|
|
99
104
|
}, timeoutMs);
|
|
100
|
-
this.pendingOut.set(id, { resolve:
|
|
105
|
+
this.pendingOut.set(id, { resolve: resolve4, reject, timer });
|
|
101
106
|
this.transport.send(toWire({ jsonrpc: "2.0", id, method, params })).catch((e) => {
|
|
102
107
|
clearTimeout(timer);
|
|
103
108
|
this.pendingOut.delete(id);
|
|
@@ -214,62 +219,68 @@ var ACPProtocolHandler = class {
|
|
|
214
219
|
this.clientCapabilities = p.clientCapabilities;
|
|
215
220
|
}
|
|
216
221
|
this.initialized = true;
|
|
217
|
-
await this.transport.send(
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
222
|
+
await this.transport.send(
|
|
223
|
+
toWire({
|
|
224
|
+
jsonrpc: "2.0",
|
|
225
|
+
id,
|
|
226
|
+
result: {
|
|
227
|
+
protocolVersion: ACP_PROTOCOL_VERSION,
|
|
228
|
+
agentCapabilities: {
|
|
229
|
+
loadSession: true,
|
|
230
|
+
promptCapabilities: {
|
|
231
|
+
// We route ACP image blocks into the core agent's multimodal
|
|
232
|
+
// input (server-agent-turn.promptToAgentInput); whether the
|
|
233
|
+
// model can see them is the configured provider's concern.
|
|
234
|
+
image: true,
|
|
235
|
+
audio: false,
|
|
236
|
+
embeddedContext: true
|
|
237
|
+
},
|
|
238
|
+
mcpCapabilities: {
|
|
239
|
+
http: false,
|
|
240
|
+
sse: false
|
|
241
|
+
},
|
|
242
|
+
sessionCapabilities: {
|
|
243
|
+
close: {},
|
|
244
|
+
list: {},
|
|
245
|
+
delete: {},
|
|
246
|
+
resume: {},
|
|
247
|
+
fork: {}
|
|
248
|
+
},
|
|
249
|
+
auth: {
|
|
250
|
+
logout: {}
|
|
251
|
+
}
|
|
235
252
|
},
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
resume: {},
|
|
241
|
-
fork: {}
|
|
253
|
+
agentInfo: {
|
|
254
|
+
name: this.agentName,
|
|
255
|
+
title: "WrongStack",
|
|
256
|
+
version: WRONGSTACK_VERSION
|
|
242
257
|
},
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
title: "WrongStack",
|
|
250
|
-
version: WRONGSTACK_VERSION
|
|
251
|
-
},
|
|
252
|
-
authMethods: WRONGSTACK_AUTH_METHODS,
|
|
253
|
-
modes: this.modes,
|
|
254
|
-
configOptions: this.configOptions
|
|
255
|
-
}
|
|
256
|
-
}));
|
|
258
|
+
authMethods: WRONGSTACK_AUTH_METHODS,
|
|
259
|
+
modes: this.modes,
|
|
260
|
+
configOptions: this.configOptions
|
|
261
|
+
}
|
|
262
|
+
})
|
|
263
|
+
);
|
|
257
264
|
return false;
|
|
258
265
|
}
|
|
259
266
|
async handleAuthenticate(id, _params) {
|
|
260
|
-
await this.transport.send(
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
267
|
+
await this.transport.send(
|
|
268
|
+
toWire({
|
|
269
|
+
jsonrpc: "2.0",
|
|
270
|
+
id,
|
|
271
|
+
result: { outcome: "unauthenticated" }
|
|
272
|
+
})
|
|
273
|
+
);
|
|
265
274
|
return false;
|
|
266
275
|
}
|
|
267
276
|
async handleLogout(id, _params) {
|
|
268
|
-
await this.transport.send(
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
277
|
+
await this.transport.send(
|
|
278
|
+
toWire({
|
|
279
|
+
jsonrpc: "2.0",
|
|
280
|
+
id,
|
|
281
|
+
result: {}
|
|
282
|
+
})
|
|
283
|
+
);
|
|
273
284
|
return false;
|
|
274
285
|
}
|
|
275
286
|
async handleSessionNew(id, params) {
|
|
@@ -278,7 +289,15 @@ var ACPProtocolHandler = class {
|
|
|
278
289
|
return false;
|
|
279
290
|
}
|
|
280
291
|
const p = params ?? {};
|
|
281
|
-
|
|
292
|
+
let cwd = this.defaultCwd;
|
|
293
|
+
if (typeof p.cwd === "string") {
|
|
294
|
+
const resolved = await this.resolveSessionCwd(p.cwd);
|
|
295
|
+
if (resolved === null) {
|
|
296
|
+
await this.sendError(id, -32602, `cwd must be an absolute path to an existing directory`);
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
cwd = resolved;
|
|
300
|
+
}
|
|
282
301
|
const sessionId = `sess_${this.allocId()}`;
|
|
283
302
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
284
303
|
const state = {
|
|
@@ -308,15 +327,17 @@ var ACPProtocolHandler = class {
|
|
|
308
327
|
}
|
|
309
328
|
});
|
|
310
329
|
}
|
|
311
|
-
await this.transport.send(
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
330
|
+
await this.transport.send(
|
|
331
|
+
toWire({
|
|
332
|
+
jsonrpc: "2.0",
|
|
333
|
+
id,
|
|
334
|
+
result: {
|
|
335
|
+
sessionId,
|
|
336
|
+
modes: this.modes,
|
|
337
|
+
configOptions: this.configOptions
|
|
338
|
+
}
|
|
339
|
+
})
|
|
340
|
+
);
|
|
320
341
|
return false;
|
|
321
342
|
}
|
|
322
343
|
async handleSessionLoad(id, params) {
|
|
@@ -331,9 +352,15 @@ var ACPProtocolHandler = class {
|
|
|
331
352
|
await this.sendError(id, -32e3, `active session limit reached (${this.maxSessions})`);
|
|
332
353
|
return false;
|
|
333
354
|
}
|
|
355
|
+
if (loadCwd !== void 0 && await this.resolveSessionCwd(loadCwd) === null) {
|
|
356
|
+
await this.sendError(id, -32602, `cwd must be an absolute path to an existing directory`);
|
|
357
|
+
return false;
|
|
358
|
+
}
|
|
359
|
+
const candidateCwd = persisted.cwd ?? loadCwd ?? this.defaultCwd;
|
|
360
|
+
const restoredCwd = await this.resolveSessionCwd(candidateCwd) ?? this.defaultCwd;
|
|
334
361
|
const restored = {
|
|
335
362
|
id: sessionId,
|
|
336
|
-
cwd:
|
|
363
|
+
cwd: restoredCwd,
|
|
337
364
|
abort: new AbortController(),
|
|
338
365
|
modeId: persisted.modeId ?? DEFAULT_MODE_ID,
|
|
339
366
|
createdAt: persisted.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -349,13 +376,15 @@ var ACPProtocolHandler = class {
|
|
|
349
376
|
sessionId,
|
|
350
377
|
update: { sessionUpdate: "current_mode_update", modeId: restored.modeId }
|
|
351
378
|
});
|
|
352
|
-
await this.transport.send(
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
379
|
+
await this.transport.send(
|
|
380
|
+
toWire({
|
|
381
|
+
jsonrpc: "2.0",
|
|
382
|
+
id,
|
|
383
|
+
result: {
|
|
384
|
+
initialMode: { currentModeId: restored.modeId, availableModes: this.modes }
|
|
385
|
+
}
|
|
386
|
+
})
|
|
387
|
+
);
|
|
359
388
|
return false;
|
|
360
389
|
}
|
|
361
390
|
}
|
|
@@ -381,16 +410,18 @@ var ACPProtocolHandler = class {
|
|
|
381
410
|
modeId: existing.modeId
|
|
382
411
|
}
|
|
383
412
|
});
|
|
384
|
-
await this.transport.send(
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
413
|
+
await this.transport.send(
|
|
414
|
+
toWire({
|
|
415
|
+
jsonrpc: "2.0",
|
|
416
|
+
id,
|
|
417
|
+
result: {
|
|
418
|
+
initialMode: {
|
|
419
|
+
currentModeId: existing.modeId,
|
|
420
|
+
availableModes: this.modes
|
|
421
|
+
}
|
|
391
422
|
}
|
|
392
|
-
}
|
|
393
|
-
|
|
423
|
+
})
|
|
424
|
+
);
|
|
394
425
|
return false;
|
|
395
426
|
}
|
|
396
427
|
await this.sendError(id, -32e3, `session not found: ${sessionId}`);
|
|
@@ -402,16 +433,18 @@ var ACPProtocolHandler = class {
|
|
|
402
433
|
const existing = sessionId ? this.sessions.get(sessionId) : void 0;
|
|
403
434
|
if (existing) {
|
|
404
435
|
existing.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
405
|
-
await this.transport.send(
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
436
|
+
await this.transport.send(
|
|
437
|
+
toWire({
|
|
438
|
+
jsonrpc: "2.0",
|
|
439
|
+
id,
|
|
440
|
+
result: {
|
|
441
|
+
initialMode: {
|
|
442
|
+
currentModeId: existing.modeId,
|
|
443
|
+
availableModes: this.modes
|
|
444
|
+
}
|
|
412
445
|
}
|
|
413
|
-
}
|
|
414
|
-
|
|
446
|
+
})
|
|
447
|
+
);
|
|
415
448
|
return false;
|
|
416
449
|
}
|
|
417
450
|
await this.sendError(id, -32e3, `session not found: ${sessionId}`);
|
|
@@ -428,11 +461,13 @@ var ACPProtocolHandler = class {
|
|
|
428
461
|
session.abort.abort();
|
|
429
462
|
this.sessions.delete(sessionId);
|
|
430
463
|
this.disposeSession(sessionId);
|
|
431
|
-
await this.transport.send(
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
464
|
+
await this.transport.send(
|
|
465
|
+
toWire({
|
|
466
|
+
jsonrpc: "2.0",
|
|
467
|
+
id,
|
|
468
|
+
result: {}
|
|
469
|
+
})
|
|
470
|
+
);
|
|
436
471
|
return false;
|
|
437
472
|
}
|
|
438
473
|
async handleSessionDelete(id, params) {
|
|
@@ -443,18 +478,22 @@ var ACPProtocolHandler = class {
|
|
|
443
478
|
return false;
|
|
444
479
|
}
|
|
445
480
|
if (!this.sessions.has(sessionId)) {
|
|
446
|
-
await this.transport.send(
|
|
481
|
+
await this.transport.send(
|
|
482
|
+
toWire({ jsonrpc: "2.0", id, result: { configOptions: [...this.configOptions] } })
|
|
483
|
+
);
|
|
447
484
|
return false;
|
|
448
485
|
}
|
|
449
486
|
const session = this.sessions.get(sessionId);
|
|
450
487
|
session.abort.abort();
|
|
451
488
|
this.sessions.delete(sessionId);
|
|
452
489
|
this.disposeSession(sessionId);
|
|
453
|
-
await this.transport.send(
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
490
|
+
await this.transport.send(
|
|
491
|
+
toWire({
|
|
492
|
+
jsonrpc: "2.0",
|
|
493
|
+
id,
|
|
494
|
+
result: {}
|
|
495
|
+
})
|
|
496
|
+
);
|
|
458
497
|
return false;
|
|
459
498
|
}
|
|
460
499
|
async handleSessionFork(id, params) {
|
|
@@ -469,11 +508,20 @@ var ACPProtocolHandler = class {
|
|
|
469
508
|
await this.sendError(id, -32e3, `active session limit reached (${this.maxSessions})`);
|
|
470
509
|
return false;
|
|
471
510
|
}
|
|
511
|
+
let forkCwd = source.cwd;
|
|
512
|
+
if (typeof p.cwd === "string") {
|
|
513
|
+
const resolved = await this.resolveSessionCwd(p.cwd);
|
|
514
|
+
if (resolved === null) {
|
|
515
|
+
await this.sendError(id, -32602, `cwd must be an absolute path to an existing directory`);
|
|
516
|
+
return false;
|
|
517
|
+
}
|
|
518
|
+
forkCwd = resolved;
|
|
519
|
+
}
|
|
472
520
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
473
521
|
const sessionId = `sess_${this.allocId()}`;
|
|
474
522
|
const forked = {
|
|
475
523
|
id: sessionId,
|
|
476
|
-
cwd:
|
|
524
|
+
cwd: forkCwd,
|
|
477
525
|
abort: new AbortController(),
|
|
478
526
|
modeId: source.modeId,
|
|
479
527
|
createdAt: now,
|
|
@@ -492,38 +540,48 @@ var ACPProtocolHandler = class {
|
|
|
492
540
|
sessionId,
|
|
493
541
|
update: { sessionUpdate: "current_mode_update", modeId: forked.modeId }
|
|
494
542
|
});
|
|
495
|
-
await this.transport.send(
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
543
|
+
await this.transport.send(
|
|
544
|
+
toWire({
|
|
545
|
+
jsonrpc: "2.0",
|
|
546
|
+
id,
|
|
547
|
+
result: {
|
|
548
|
+
sessionId,
|
|
549
|
+
modes: this.modes,
|
|
550
|
+
configOptions: this.configOptions
|
|
551
|
+
}
|
|
552
|
+
})
|
|
553
|
+
);
|
|
504
554
|
return false;
|
|
505
555
|
}
|
|
506
556
|
async handleProvidersList(id, _params) {
|
|
507
|
-
await this.transport.send(
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
557
|
+
await this.transport.send(
|
|
558
|
+
toWire({
|
|
559
|
+
jsonrpc: "2.0",
|
|
560
|
+
id,
|
|
561
|
+
result: {
|
|
562
|
+
providers: [],
|
|
563
|
+
currentProviderId: null
|
|
564
|
+
}
|
|
565
|
+
})
|
|
566
|
+
);
|
|
515
567
|
return false;
|
|
516
568
|
}
|
|
517
569
|
async handleProvidersSet(id, _params) {
|
|
518
|
-
await this.sendError(
|
|
570
|
+
await this.sendError(
|
|
571
|
+
id,
|
|
572
|
+
-32e3,
|
|
573
|
+
"provider configuration not available through ACP; use wstack auth"
|
|
574
|
+
);
|
|
519
575
|
return false;
|
|
520
576
|
}
|
|
521
577
|
async handleProvidersDisable(id, _params) {
|
|
522
|
-
await this.transport.send(
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
578
|
+
await this.transport.send(
|
|
579
|
+
toWire({
|
|
580
|
+
jsonrpc: "2.0",
|
|
581
|
+
id,
|
|
582
|
+
result: {}
|
|
583
|
+
})
|
|
584
|
+
);
|
|
527
585
|
return false;
|
|
528
586
|
}
|
|
529
587
|
async handleMcpMessage(id, _params) {
|
|
@@ -576,7 +634,10 @@ var ACPProtocolHandler = class {
|
|
|
576
634
|
const terminalId = created?.terminalId;
|
|
577
635
|
if (!terminalId) return { output: "", exitCode: null };
|
|
578
636
|
try {
|
|
579
|
-
const exit = await this.request("terminal/wait_for_exit", {
|
|
637
|
+
const exit = await this.request("terminal/wait_for_exit", {
|
|
638
|
+
sessionId,
|
|
639
|
+
terminalId
|
|
640
|
+
});
|
|
580
641
|
const out = await this.request("terminal/output", { sessionId, terminalId });
|
|
581
642
|
return {
|
|
582
643
|
output: String(out?.output ?? ""),
|
|
@@ -606,11 +667,13 @@ var ACPProtocolHandler = class {
|
|
|
606
667
|
session.abort.signal.removeEventListener("abort", onCancel);
|
|
607
668
|
session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
608
669
|
await this.persist(session);
|
|
609
|
-
await this.transport.send(
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
670
|
+
await this.transport.send(
|
|
671
|
+
toWire({
|
|
672
|
+
jsonrpc: "2.0",
|
|
673
|
+
id,
|
|
674
|
+
result: { stopReason: result.stopReason }
|
|
675
|
+
})
|
|
676
|
+
);
|
|
614
677
|
return false;
|
|
615
678
|
}
|
|
616
679
|
async handleSetMode(id, params) {
|
|
@@ -651,7 +714,9 @@ var ACPProtocolHandler = class {
|
|
|
651
714
|
configOptions: [...this.configOptions]
|
|
652
715
|
}
|
|
653
716
|
});
|
|
654
|
-
await this.transport.send(
|
|
717
|
+
await this.transport.send(
|
|
718
|
+
toWire({ jsonrpc: "2.0", id, result: { configOptions: [...this.configOptions] } })
|
|
719
|
+
);
|
|
655
720
|
return false;
|
|
656
721
|
}
|
|
657
722
|
async handleSessionList(id) {
|
|
@@ -664,11 +729,13 @@ var ACPProtocolHandler = class {
|
|
|
664
729
|
if (s.title !== void 0) out.title = s.title;
|
|
665
730
|
return out;
|
|
666
731
|
});
|
|
667
|
-
await this.transport.send(
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
732
|
+
await this.transport.send(
|
|
733
|
+
toWire({
|
|
734
|
+
jsonrpc: "2.0",
|
|
735
|
+
id,
|
|
736
|
+
result: { sessions }
|
|
737
|
+
})
|
|
738
|
+
);
|
|
672
739
|
return false;
|
|
673
740
|
}
|
|
674
741
|
// ────────────────────────────────────────────────────────────────────
|
|
@@ -714,8 +781,54 @@ var ACPProtocolHandler = class {
|
|
|
714
781
|
if (data !== void 0) error.data = data;
|
|
715
782
|
await this.transport.send(toWire({ jsonrpc: "2.0", id, error }));
|
|
716
783
|
}
|
|
784
|
+
/**
|
|
785
|
+
* Allocate a session id (WS-015).
|
|
786
|
+
*
|
|
787
|
+
* This was `this.nextId++`, so ids were `sess_1`, `sess_2`, … — and the
|
|
788
|
+
* handler has no per-connection ownership: any caller that names a session
|
|
789
|
+
* id can `session/load`, `session/prompt`, `session/cancel` or
|
|
790
|
+
* `session/delete` it. Over stdio that is academic (one client per process),
|
|
791
|
+
* but the agent also serves over HTTP, where a guessable id is the whole
|
|
792
|
+
* authorization story for any local process or page that reaches the port.
|
|
793
|
+
*
|
|
794
|
+
* Random ids do not create ownership — they remove the trivial enumeration
|
|
795
|
+
* that made its absence exploitable. Real per-connection ownership is the
|
|
796
|
+
* larger fix and is noted in the WS-015 test file.
|
|
797
|
+
*
|
|
798
|
+
* The counter is retained: it keeps ids ordered for debugging and guarantees
|
|
799
|
+
* uniqueness within a process even in the (impossible) event of a UUID
|
|
800
|
+
* collision. The random half is what makes the id unguessable.
|
|
801
|
+
*/
|
|
802
|
+
/**
|
|
803
|
+
* Resolve a client-supplied `cwd` for a session, or `null` when it is not
|
|
804
|
+
* usable (WS-015).
|
|
805
|
+
*
|
|
806
|
+
* `session/new`, `session/load` and `session/fork` all took `params.cwd`
|
|
807
|
+
* with a single `typeof === 'string'` check and nothing else. That value is
|
|
808
|
+
* the working directory the agent then reads, writes and executes in.
|
|
809
|
+
*
|
|
810
|
+
* SCOPE, deliberately stated: this does NOT confine the session to a root.
|
|
811
|
+
* In ACP the client IS the editor and legitimately names its own workspace —
|
|
812
|
+
* Zed and JetBrains pass the project root — so a fixed boundary here would
|
|
813
|
+
* break the integration this package exists for. What it enforces is that
|
|
814
|
+
* the directory is absolute and actually exists as a directory: a relative
|
|
815
|
+
* or missing `cwd` is a bug or an attack under either reading, and silently
|
|
816
|
+
* running the agent somewhere other than where the client asked is worse
|
|
817
|
+
* than refusing. Confinement, if wanted, belongs in an operator-set option
|
|
818
|
+
* on top of this, not in place of it.
|
|
819
|
+
*/
|
|
820
|
+
async resolveSessionCwd(requested) {
|
|
821
|
+
if (!path.isAbsolute(requested)) return null;
|
|
822
|
+
const resolved = path.resolve(requested);
|
|
823
|
+
try {
|
|
824
|
+
const stat3 = await fsp.stat(resolved);
|
|
825
|
+
return stat3.isDirectory() ? resolved : null;
|
|
826
|
+
} catch {
|
|
827
|
+
return null;
|
|
828
|
+
}
|
|
829
|
+
}
|
|
717
830
|
allocId() {
|
|
718
|
-
return this.nextId
|
|
831
|
+
return `${this.nextId++}_${randomUUID().replaceAll("-", "")}`;
|
|
719
832
|
}
|
|
720
833
|
};
|
|
721
834
|
function errorToJsonRpc(err) {
|
|
@@ -739,32 +852,14 @@ import { expectDefined, writeErr } from "@wrongstack/core/utils";
|
|
|
739
852
|
import { treeKill } from "@wrongstack/core/utils/tree-kill";
|
|
740
853
|
|
|
741
854
|
// src/win32-cmd.ts
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
const line = ["call", quoteWin32CmdArg(command), ...args.map(quoteWin32CmdArg)].join(" ");
|
|
746
|
-
return {
|
|
747
|
-
command: process.env["COMSPEC"] ?? "cmd.exe",
|
|
748
|
-
args: ["/d", "/c", line],
|
|
749
|
-
windowsVerbatimArguments: true
|
|
750
|
-
};
|
|
751
|
-
}
|
|
752
|
-
function assertSafeWin32CmdArgs(args) {
|
|
753
|
-
for (const arg of args) {
|
|
754
|
-
if (typeof arg === "string" && WIN32_CMD_META.test(arg)) {
|
|
755
|
-
throw new Error(
|
|
756
|
-
'win32 cmd shim spawn: argument contains a shell metacharacter (one of & | < > ", or a newline) that could enable command injection through the .cmd/.bat wrapper - refusing to run. Offending argument: ' + JSON.stringify(arg)
|
|
757
|
-
);
|
|
758
|
-
}
|
|
759
|
-
}
|
|
760
|
-
}
|
|
761
|
-
function quoteWin32CmdArg(arg) {
|
|
762
|
-
return `"${arg}"`;
|
|
763
|
-
}
|
|
855
|
+
import {
|
|
856
|
+
buildWin32CmdShimInvocation
|
|
857
|
+
} from "@wrongstack/core/utils";
|
|
764
858
|
|
|
765
859
|
// src/agent/stdio-transport.ts
|
|
766
860
|
var DEFAULT_MAX_FRAME_CHARS = 20 * 1024 * 1024;
|
|
767
861
|
var DEFAULT_MAX_QUEUED_MESSAGES = 1e3;
|
|
862
|
+
var DEFAULT_MAX_QUEUED_CHARS = 32 * 1024 * 1024;
|
|
768
863
|
function positiveLimit(value, fallback) {
|
|
769
864
|
return value !== void 0 && Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
|
|
770
865
|
}
|
|
@@ -774,53 +869,82 @@ var StdioTransport = class {
|
|
|
774
869
|
stderr = process.stderr;
|
|
775
870
|
buffer = "";
|
|
776
871
|
handlers = /* @__PURE__ */ new Set();
|
|
872
|
+
claimHandlers = /* @__PURE__ */ new Set();
|
|
777
873
|
closed = false;
|
|
778
874
|
resolveRead = null;
|
|
779
875
|
messageQueue = [];
|
|
876
|
+
queuedChars = 0;
|
|
780
877
|
maxFrameChars;
|
|
781
878
|
maxQueuedMessages;
|
|
879
|
+
maxQueuedChars;
|
|
880
|
+
onStdinData = (chunk) => this.onData(chunk);
|
|
881
|
+
onStdinEnd = () => this.handleClose();
|
|
882
|
+
onStdinError = (err) => this.failAll(err);
|
|
782
883
|
constructor(opts = {}) {
|
|
783
884
|
this.maxFrameChars = positiveLimit(opts.maxFrameChars, DEFAULT_MAX_FRAME_CHARS);
|
|
784
885
|
this.maxQueuedMessages = positiveLimit(opts.maxQueuedMessages, DEFAULT_MAX_QUEUED_MESSAGES);
|
|
886
|
+
this.maxQueuedChars = positiveLimit(opts.maxQueuedChars, DEFAULT_MAX_QUEUED_CHARS);
|
|
785
887
|
this.stdin.resume();
|
|
786
888
|
this.stdin.setEncoding("utf8");
|
|
787
|
-
this.stdin.on("data",
|
|
788
|
-
this.stdin.on("end",
|
|
789
|
-
this.stdin.on("error",
|
|
889
|
+
this.stdin.on("data", this.onStdinData);
|
|
890
|
+
this.stdin.on("end", this.onStdinEnd);
|
|
891
|
+
this.stdin.on("error", this.onStdinError);
|
|
790
892
|
}
|
|
791
893
|
sendStartupMarker() {
|
|
792
894
|
this.stdout.write("[wstack-acp]\n", "utf8");
|
|
793
895
|
}
|
|
794
896
|
send(msg) {
|
|
795
897
|
if (this.closed) return Promise.resolve();
|
|
796
|
-
return new Promise((
|
|
898
|
+
return new Promise((resolve4) => {
|
|
797
899
|
const line = JSON.stringify(msg) + "\n";
|
|
798
|
-
this.stdout.write(line, "utf8", () =>
|
|
900
|
+
this.stdout.write(line, "utf8", () => resolve4());
|
|
799
901
|
});
|
|
800
902
|
}
|
|
801
903
|
sendRaw(chunk) {
|
|
802
904
|
this.stdout.write(chunk, "utf8");
|
|
803
905
|
}
|
|
804
906
|
read() {
|
|
805
|
-
if (this.messageQueue.length > 0)
|
|
806
|
-
|
|
907
|
+
if (this.messageQueue.length > 0) {
|
|
908
|
+
const queued = expectDefined(this.messageQueue.shift());
|
|
909
|
+
this.queuedChars = Math.max(0, this.queuedChars - queued.chars);
|
|
910
|
+
return Promise.resolve(queued.message);
|
|
911
|
+
}
|
|
807
912
|
if (this.closed) return Promise.resolve(null);
|
|
808
|
-
return new Promise((
|
|
809
|
-
this.resolveRead =
|
|
913
|
+
return new Promise((resolve4) => {
|
|
914
|
+
this.resolveRead = resolve4;
|
|
810
915
|
});
|
|
811
916
|
}
|
|
812
917
|
onMessage(handler) {
|
|
813
918
|
this.handlers.add(handler);
|
|
814
919
|
return () => this.handlers.delete(handler);
|
|
815
920
|
}
|
|
921
|
+
/**
|
|
922
|
+
* Register a handler that MAY claim a message by returning `true`.
|
|
923
|
+
* Claimed messages are NOT enqueued for the read() loop — they are
|
|
924
|
+
* considered fully consumed by the handler. This is how the ACP
|
|
925
|
+
* server transport prevents the correlation handler (which always
|
|
926
|
+
* fires on every message but only actually correlates responses)
|
|
927
|
+
* from starving the read() loop of pipelined requests, notifications,
|
|
928
|
+
* and any message the handler chose to ignore. See dispatch() for
|
|
929
|
+
* the gating logic.
|
|
930
|
+
*/
|
|
931
|
+
onMessageClaim(handler) {
|
|
932
|
+
this.claimHandlers.add(handler);
|
|
933
|
+
return () => this.claimHandlers.delete(handler);
|
|
934
|
+
}
|
|
816
935
|
close() {
|
|
817
936
|
this.closed = true;
|
|
937
|
+
this.stdin.off("data", this.onStdinData);
|
|
938
|
+
this.stdin.off("end", this.onStdinEnd);
|
|
939
|
+
this.stdin.off("error", this.onStdinError);
|
|
818
940
|
this.stdin.pause();
|
|
819
941
|
this.resolveRead?.(null);
|
|
820
942
|
this.resolveRead = null;
|
|
821
943
|
this.buffer = "";
|
|
822
944
|
this.messageQueue.length = 0;
|
|
945
|
+
this.queuedChars = 0;
|
|
823
946
|
this.handlers.clear();
|
|
947
|
+
this.claimHandlers.clear();
|
|
824
948
|
}
|
|
825
949
|
onData(chunk) {
|
|
826
950
|
this.buffer += chunk;
|
|
@@ -847,29 +971,41 @@ var StdioTransport = class {
|
|
|
847
971
|
return;
|
|
848
972
|
}
|
|
849
973
|
try {
|
|
850
|
-
this.dispatch(JSON.parse(raw));
|
|
974
|
+
this.dispatch(JSON.parse(raw), raw.length);
|
|
851
975
|
} catch (err) {
|
|
852
976
|
this.stderr.write(`[wstack-acp parse error] ${err}
|
|
853
977
|
`, "utf8");
|
|
854
978
|
}
|
|
855
979
|
}
|
|
856
980
|
}
|
|
857
|
-
dispatch(msg) {
|
|
981
|
+
dispatch(msg, chars = JSON.stringify(msg).length) {
|
|
858
982
|
if (this.resolveRead) {
|
|
859
|
-
const
|
|
983
|
+
const resolve4 = this.resolveRead;
|
|
860
984
|
this.resolveRead = null;
|
|
861
|
-
|
|
985
|
+
resolve4(msg);
|
|
862
986
|
} else {
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
987
|
+
let claimed = false;
|
|
988
|
+
for (const handler of this.claimHandlers) {
|
|
989
|
+
try {
|
|
990
|
+
if (handler(msg)) claimed = true;
|
|
991
|
+
} catch (err) {
|
|
992
|
+
this.stderr.write(`[wstack-acp handler error] ${err}
|
|
993
|
+
`, "utf8");
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
if (!claimed) {
|
|
997
|
+
if (this.messageQueue.length >= this.maxQueuedMessages || this.queuedChars + chars > this.maxQueuedChars) {
|
|
998
|
+
this.stderr.write(
|
|
999
|
+
`[wstack-acp queue error] pending message queue exceeds ${this.maxQueuedMessages} entries or ${this.maxQueuedChars} characters
|
|
866
1000
|
`,
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
1001
|
+
"utf8"
|
|
1002
|
+
);
|
|
1003
|
+
this.close();
|
|
1004
|
+
return;
|
|
1005
|
+
}
|
|
1006
|
+
this.messageQueue.push({ message: msg, chars });
|
|
1007
|
+
this.queuedChars += chars;
|
|
871
1008
|
}
|
|
872
|
-
this.messageQueue.push(msg);
|
|
873
1009
|
}
|
|
874
1010
|
for (const handler of this.handlers) {
|
|
875
1011
|
try {
|
|
@@ -893,12 +1029,15 @@ var ClientTransport = class {
|
|
|
893
1029
|
child = null;
|
|
894
1030
|
buffer = "";
|
|
895
1031
|
handlers = /* @__PURE__ */ new Set();
|
|
1032
|
+
claimHandlers = /* @__PURE__ */ new Set();
|
|
896
1033
|
closed = false;
|
|
897
1034
|
resolveRead = null;
|
|
898
1035
|
messageQueue = [];
|
|
1036
|
+
queuedChars = 0;
|
|
899
1037
|
opts;
|
|
900
1038
|
maxFrameChars;
|
|
901
1039
|
maxQueuedMessages;
|
|
1040
|
+
maxQueuedChars;
|
|
902
1041
|
constructor(options) {
|
|
903
1042
|
this.opts = {
|
|
904
1043
|
handshakeTimeoutMs: 3e4,
|
|
@@ -906,6 +1045,7 @@ var ClientTransport = class {
|
|
|
906
1045
|
};
|
|
907
1046
|
this.maxFrameChars = positiveLimit(options.maxFrameChars, DEFAULT_MAX_FRAME_CHARS);
|
|
908
1047
|
this.maxQueuedMessages = positiveLimit(options.maxQueuedMessages, DEFAULT_MAX_QUEUED_MESSAGES);
|
|
1048
|
+
this.maxQueuedChars = positiveLimit(options.maxQueuedChars, DEFAULT_MAX_QUEUED_CHARS);
|
|
909
1049
|
}
|
|
910
1050
|
async start() {
|
|
911
1051
|
if (this.child) return;
|
|
@@ -914,8 +1054,16 @@ var ClientTransport = class {
|
|
|
914
1054
|
import("@wrongstack/core/utils"),
|
|
915
1055
|
import("node:os")
|
|
916
1056
|
]);
|
|
917
|
-
return new Promise((
|
|
1057
|
+
return new Promise((resolve4, reject) => {
|
|
918
1058
|
const timeout = setTimeout(() => {
|
|
1059
|
+
const child2 = this.child;
|
|
1060
|
+
this.child = null;
|
|
1061
|
+
if (child2) {
|
|
1062
|
+
try {
|
|
1063
|
+
treeKill(child2);
|
|
1064
|
+
} catch {
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
919
1067
|
reject(
|
|
920
1068
|
new Error(`ACP child process failed to start within ${this.opts.handshakeTimeoutMs}ms`)
|
|
921
1069
|
);
|
|
@@ -959,7 +1107,7 @@ var ClientTransport = class {
|
|
|
959
1107
|
if (settled) return;
|
|
960
1108
|
settled = true;
|
|
961
1109
|
clearTimeout(timeout);
|
|
962
|
-
|
|
1110
|
+
resolve4();
|
|
963
1111
|
});
|
|
964
1112
|
return;
|
|
965
1113
|
}
|
|
@@ -970,7 +1118,7 @@ var ClientTransport = class {
|
|
|
970
1118
|
child.stderr.on("data", (c) => this.onChildError(c));
|
|
971
1119
|
child.on("close", (code) => this.onChildClose(code));
|
|
972
1120
|
clearTimeout(timeout);
|
|
973
|
-
|
|
1121
|
+
resolve4();
|
|
974
1122
|
};
|
|
975
1123
|
const waitForMarker = (chunk) => {
|
|
976
1124
|
this.buffer += chunk;
|
|
@@ -989,33 +1137,52 @@ var ClientTransport = class {
|
|
|
989
1137
|
}
|
|
990
1138
|
send(msg) {
|
|
991
1139
|
if (!this.child) return Promise.reject(new Error("ClientTransport not started"));
|
|
992
|
-
return new Promise((
|
|
1140
|
+
return new Promise((resolve4, reject) => {
|
|
993
1141
|
const line = JSON.stringify(msg) + "\n";
|
|
994
1142
|
this.child?.stdin.write(line, "utf8", (err) => {
|
|
995
1143
|
if (err) reject(err);
|
|
996
|
-
else
|
|
1144
|
+
else resolve4();
|
|
997
1145
|
});
|
|
998
1146
|
});
|
|
999
1147
|
}
|
|
1000
1148
|
read() {
|
|
1001
|
-
if (this.messageQueue.length > 0)
|
|
1002
|
-
|
|
1149
|
+
if (this.messageQueue.length > 0) {
|
|
1150
|
+
const queued = expectDefined(this.messageQueue.shift());
|
|
1151
|
+
this.queuedChars = Math.max(0, this.queuedChars - queued.chars);
|
|
1152
|
+
return Promise.resolve(queued.message);
|
|
1153
|
+
}
|
|
1003
1154
|
if (this.closed) return Promise.resolve(null);
|
|
1004
|
-
return new Promise((
|
|
1005
|
-
this.resolveRead =
|
|
1155
|
+
return new Promise((resolve4) => {
|
|
1156
|
+
this.resolveRead = resolve4;
|
|
1006
1157
|
});
|
|
1007
1158
|
}
|
|
1008
1159
|
onMessage(handler) {
|
|
1009
1160
|
this.handlers.add(handler);
|
|
1010
1161
|
return () => this.handlers.delete(handler);
|
|
1011
1162
|
}
|
|
1163
|
+
/**
|
|
1164
|
+
* Register a handler that MAY claim a message by returning `true`.
|
|
1165
|
+
* Claimed messages are NOT enqueued for the read() loop — they are
|
|
1166
|
+
* considered fully consumed by the handler. This is how the ACP
|
|
1167
|
+
* server transport prevents the correlation handler (which always
|
|
1168
|
+
* fires on every message but only actually correlates responses)
|
|
1169
|
+
* from starving the read() loop of pipelined requests, notifications,
|
|
1170
|
+
* and any message the handler chose to ignore. See dispatch() for
|
|
1171
|
+
* the gating logic.
|
|
1172
|
+
*/
|
|
1173
|
+
onMessageClaim(handler) {
|
|
1174
|
+
this.claimHandlers.add(handler);
|
|
1175
|
+
return () => this.claimHandlers.delete(handler);
|
|
1176
|
+
}
|
|
1012
1177
|
stop() {
|
|
1013
1178
|
this.closed = true;
|
|
1014
1179
|
this.resolveRead?.(null);
|
|
1015
1180
|
this.resolveRead = null;
|
|
1016
1181
|
this.buffer = "";
|
|
1017
1182
|
this.messageQueue.length = 0;
|
|
1183
|
+
this.queuedChars = 0;
|
|
1018
1184
|
this.handlers.clear();
|
|
1185
|
+
this.claimHandlers.clear();
|
|
1019
1186
|
const child = this.child;
|
|
1020
1187
|
if (!child) return;
|
|
1021
1188
|
treeKill(child);
|
|
@@ -1040,7 +1207,7 @@ var ClientTransport = class {
|
|
|
1040
1207
|
return;
|
|
1041
1208
|
}
|
|
1042
1209
|
try {
|
|
1043
|
-
this.dispatch(JSON.parse(raw));
|
|
1210
|
+
this.dispatch(JSON.parse(raw), raw.length);
|
|
1044
1211
|
} catch {
|
|
1045
1212
|
}
|
|
1046
1213
|
}
|
|
@@ -1050,29 +1217,34 @@ var ClientTransport = class {
|
|
|
1050
1217
|
}
|
|
1051
1218
|
onChildClose(code) {
|
|
1052
1219
|
this.closed = true;
|
|
1220
|
+
this.child = null;
|
|
1053
1221
|
this.resolveRead?.(null);
|
|
1054
1222
|
this.resolveRead = null;
|
|
1055
1223
|
this.buffer = "";
|
|
1056
1224
|
this.messageQueue.length = 0;
|
|
1225
|
+
this.queuedChars = 0;
|
|
1057
1226
|
this.handlers.clear();
|
|
1058
1227
|
if (code !== 0 && code !== null) {
|
|
1059
1228
|
writeErr(`[acp-child exited with code ${code}]
|
|
1060
1229
|
`);
|
|
1061
1230
|
}
|
|
1062
1231
|
}
|
|
1063
|
-
dispatch(msg) {
|
|
1232
|
+
dispatch(msg, chars = JSON.stringify(msg).length) {
|
|
1064
1233
|
if (this.resolveRead) {
|
|
1065
|
-
const
|
|
1234
|
+
const resolve4 = this.resolveRead;
|
|
1066
1235
|
this.resolveRead = null;
|
|
1067
|
-
|
|
1236
|
+
resolve4(msg);
|
|
1068
1237
|
} else if (this.handlers.size === 0) {
|
|
1069
|
-
if (this.messageQueue.length >= this.maxQueuedMessages) {
|
|
1070
|
-
writeErr(
|
|
1071
|
-
`
|
|
1238
|
+
if (this.messageQueue.length >= this.maxQueuedMessages || this.queuedChars + chars > this.maxQueuedChars) {
|
|
1239
|
+
writeErr(
|
|
1240
|
+
`[acp-child message queue exceeds ${this.maxQueuedMessages} entries or ${this.maxQueuedChars} characters]
|
|
1241
|
+
`
|
|
1242
|
+
);
|
|
1072
1243
|
this.stop();
|
|
1073
1244
|
return;
|
|
1074
1245
|
}
|
|
1075
|
-
this.messageQueue.push(msg);
|
|
1246
|
+
this.messageQueue.push({ message: msg, chars });
|
|
1247
|
+
this.queuedChars += chars;
|
|
1076
1248
|
}
|
|
1077
1249
|
for (const handler of this.handlers) {
|
|
1078
1250
|
try {
|
|
@@ -1366,12 +1538,12 @@ var WrongStackACPServer = class {
|
|
|
1366
1538
|
res.end(JSON.stringify({ error: { code: -32603, message: "Internal error" } }));
|
|
1367
1539
|
}
|
|
1368
1540
|
});
|
|
1369
|
-
return new Promise((
|
|
1541
|
+
return new Promise((resolve4) => {
|
|
1370
1542
|
this.httpServer.listen(port, host, () => {
|
|
1371
1543
|
writeErr2(`[wstack-acp] HTTP server listening on http://${host}:${port}
|
|
1372
1544
|
`);
|
|
1373
1545
|
this.running = true;
|
|
1374
|
-
|
|
1546
|
+
resolve4();
|
|
1375
1547
|
});
|
|
1376
1548
|
});
|
|
1377
1549
|
}
|
|
@@ -1454,26 +1626,26 @@ function emptyRunResult(stopReason) {
|
|
|
1454
1626
|
// src/client/file-server.ts
|
|
1455
1627
|
import { randomBytes } from "node:crypto";
|
|
1456
1628
|
import { realpathSync } from "node:fs";
|
|
1457
|
-
import * as
|
|
1458
|
-
import * as
|
|
1629
|
+
import * as fsp2 from "node:fs/promises";
|
|
1630
|
+
import * as path2 from "node:path";
|
|
1459
1631
|
var DEFAULT_FILE_OPERATIONS = {
|
|
1460
|
-
stat:
|
|
1461
|
-
readFile:
|
|
1462
|
-
writeFile:
|
|
1463
|
-
realpath:
|
|
1464
|
-
rename:
|
|
1465
|
-
unlink:
|
|
1632
|
+
stat: fsp2.stat,
|
|
1633
|
+
readFile: fsp2.readFile,
|
|
1634
|
+
writeFile: fsp2.writeFile,
|
|
1635
|
+
realpath: fsp2.realpath,
|
|
1636
|
+
rename: fsp2.rename,
|
|
1637
|
+
unlink: fsp2.unlink
|
|
1466
1638
|
};
|
|
1467
1639
|
var DEFAULT_MAX_READ_BYTES = 5 * 1024 * 1024;
|
|
1468
1640
|
var DEFAULT_MAX_WRITE_BYTES = 5 * 1024 * 1024;
|
|
1469
1641
|
var FsError = class extends Error {
|
|
1470
1642
|
code;
|
|
1471
1643
|
path;
|
|
1472
|
-
constructor(code,
|
|
1644
|
+
constructor(code, path5, message) {
|
|
1473
1645
|
super(message);
|
|
1474
1646
|
this.name = "FsError";
|
|
1475
1647
|
this.code = code;
|
|
1476
|
-
this.path =
|
|
1648
|
+
this.path = path5;
|
|
1477
1649
|
}
|
|
1478
1650
|
};
|
|
1479
1651
|
var FileServer = class {
|
|
@@ -1484,7 +1656,7 @@ var FileServer = class {
|
|
|
1484
1656
|
maxWriteBytes;
|
|
1485
1657
|
operations;
|
|
1486
1658
|
constructor(opts) {
|
|
1487
|
-
this.root =
|
|
1659
|
+
this.root = path2.resolve(opts.projectRoot);
|
|
1488
1660
|
this.realRoot = safeRealpathSync(this.root);
|
|
1489
1661
|
this.timeoutMs = opts.timeoutMs ?? 3e4;
|
|
1490
1662
|
this.maxReadBytes = opts.maxReadBytes ?? DEFAULT_MAX_READ_BYTES;
|
|
@@ -1497,14 +1669,14 @@ var FileServer = class {
|
|
|
1497
1669
|
const controller = new AbortController();
|
|
1498
1670
|
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
1499
1671
|
try {
|
|
1500
|
-
const
|
|
1672
|
+
const stat3 = await this.operations.stat(safe).catch((err) => {
|
|
1501
1673
|
throw mapFsError(err, safe);
|
|
1502
1674
|
});
|
|
1503
|
-
if (
|
|
1675
|
+
if (stat3.size > this.maxReadBytes) {
|
|
1504
1676
|
throw new FsError(
|
|
1505
1677
|
"TOO_LARGE",
|
|
1506
1678
|
safe,
|
|
1507
|
-
`file is ${
|
|
1679
|
+
`file is ${stat3.size} bytes, max read is ${this.maxReadBytes} bytes`
|
|
1508
1680
|
);
|
|
1509
1681
|
}
|
|
1510
1682
|
const content = await this.operations.readFile(safe, {
|
|
@@ -1542,7 +1714,7 @@ var FileServer = class {
|
|
|
1542
1714
|
signal: controller.signal
|
|
1543
1715
|
});
|
|
1544
1716
|
await this.assertRealInside(tmp);
|
|
1545
|
-
await this.assertRealInside(
|
|
1717
|
+
await this.assertRealInside(path2.dirname(safe));
|
|
1546
1718
|
await this.operations.rename(tmp, safe);
|
|
1547
1719
|
} catch (err) {
|
|
1548
1720
|
if (err instanceof FsError) {
|
|
@@ -1573,11 +1745,11 @@ var FileServer = class {
|
|
|
1573
1745
|
if (typeof p !== "string" || p.length === 0) {
|
|
1574
1746
|
throw new FsError("INVALID_PATH", p, "path is empty or not a string");
|
|
1575
1747
|
}
|
|
1576
|
-
if (!
|
|
1748
|
+
if (!path2.isAbsolute(p)) {
|
|
1577
1749
|
throw new FsError("INVALID_PATH", p, "path must be absolute (ACP requirement)");
|
|
1578
1750
|
}
|
|
1579
|
-
const resolved =
|
|
1580
|
-
const rootWithSep = this.root.endsWith(
|
|
1751
|
+
const resolved = path2.resolve(p);
|
|
1752
|
+
const rootWithSep = this.root.endsWith(path2.sep) ? this.root : this.root + path2.sep;
|
|
1581
1753
|
if (resolved !== this.root && !resolved.startsWith(rootWithSep)) {
|
|
1582
1754
|
throw new FsError("OUTSIDE_ROOT", resolved, "path is outside the project root");
|
|
1583
1755
|
}
|
|
@@ -1598,7 +1770,7 @@ var FileServer = class {
|
|
|
1598
1770
|
} catch (err) {
|
|
1599
1771
|
const code = err.code;
|
|
1600
1772
|
if (code === "ENOENT") {
|
|
1601
|
-
const parent =
|
|
1773
|
+
const parent = path2.dirname(probe);
|
|
1602
1774
|
if (parent === probe) {
|
|
1603
1775
|
throw new FsError("ENOENT", resolvedPath, `no existing ancestor: ${resolvedPath}`);
|
|
1604
1776
|
}
|
|
@@ -1607,7 +1779,7 @@ var FileServer = class {
|
|
|
1607
1779
|
}
|
|
1608
1780
|
throw mapFsError(err, resolvedPath);
|
|
1609
1781
|
}
|
|
1610
|
-
if (real === this.realRoot || real.startsWith(this.realRoot +
|
|
1782
|
+
if (real === this.realRoot || real.startsWith(this.realRoot + path2.sep)) return;
|
|
1611
1783
|
throw new FsError(
|
|
1612
1784
|
"OUTSIDE_ROOT",
|
|
1613
1785
|
resolvedPath,
|
|
@@ -1680,8 +1852,9 @@ function makePermissionPolicy(decide) {
|
|
|
1680
1852
|
// src/client/terminal-server.ts
|
|
1681
1853
|
import { spawn } from "node:child_process";
|
|
1682
1854
|
import { realpathSync as realpathSync2 } from "node:fs";
|
|
1683
|
-
import * as
|
|
1855
|
+
import * as path3 from "node:path";
|
|
1684
1856
|
import { buildChildEnv } from "@wrongstack/core/utils";
|
|
1857
|
+
import { treeKill as treeKill2 } from "@wrongstack/core/utils/tree-kill";
|
|
1685
1858
|
var EMPTY_BUFFER = Buffer.alloc(0);
|
|
1686
1859
|
var TerminalServer = class {
|
|
1687
1860
|
terminals = /* @__PURE__ */ new Map();
|
|
@@ -1694,7 +1867,7 @@ var TerminalServer = class {
|
|
|
1694
1867
|
abortHandler = () => this.releaseAll();
|
|
1695
1868
|
nextId = 1;
|
|
1696
1869
|
constructor(opts) {
|
|
1697
|
-
this.projectRoot =
|
|
1870
|
+
this.projectRoot = path3.resolve(opts.projectRoot);
|
|
1698
1871
|
this.commandTimeoutMs = opts.commandTimeoutMs ?? 5 * 6e4;
|
|
1699
1872
|
this.outputByteLimit = opts.outputByteLimit ?? 1024 * 1024;
|
|
1700
1873
|
this.maxOutputByteLimit = opts.maxOutputByteLimit ?? 16 * 1024 * 1024;
|
|
@@ -1741,7 +1914,7 @@ var TerminalServer = class {
|
|
|
1741
1914
|
truncated: false,
|
|
1742
1915
|
exitStatus: void 0,
|
|
1743
1916
|
timeoutHandle: null,
|
|
1744
|
-
exitPromise: new Promise((
|
|
1917
|
+
exitPromise: new Promise((resolve4) => {
|
|
1745
1918
|
proc.on("close", (code, signalName) => {
|
|
1746
1919
|
if (state.timeoutHandle) {
|
|
1747
1920
|
clearTimeout(state.timeoutHandle);
|
|
@@ -1752,7 +1925,7 @@ var TerminalServer = class {
|
|
|
1752
1925
|
signal: typeof signalName === "string" ? signalName : null
|
|
1753
1926
|
};
|
|
1754
1927
|
state.exitStatus = exitStatus;
|
|
1755
|
-
|
|
1928
|
+
resolve4(exitStatus);
|
|
1756
1929
|
});
|
|
1757
1930
|
proc.on("error", (err) => {
|
|
1758
1931
|
if (state.timeoutHandle) {
|
|
@@ -1771,7 +1944,7 @@ var TerminalServer = class {
|
|
|
1771
1944
|
}
|
|
1772
1945
|
state.outputChunks.push(errorOutput);
|
|
1773
1946
|
state.retainedBytes = errorOutput.length;
|
|
1774
|
-
|
|
1947
|
+
resolve4(exitStatus);
|
|
1775
1948
|
});
|
|
1776
1949
|
})
|
|
1777
1950
|
};
|
|
@@ -1801,13 +1974,11 @@ var TerminalServer = class {
|
|
|
1801
1974
|
state.outputHead = 0;
|
|
1802
1975
|
}
|
|
1803
1976
|
};
|
|
1977
|
+
state.onData = onData;
|
|
1804
1978
|
proc.stdout?.on("data", onData);
|
|
1805
1979
|
proc.stderr?.on("data", onData);
|
|
1806
1980
|
state.timeoutHandle = setTimeout(() => {
|
|
1807
|
-
|
|
1808
|
-
proc.kill("SIGTERM");
|
|
1809
|
-
} catch {
|
|
1810
|
-
}
|
|
1981
|
+
treeKill2(proc);
|
|
1811
1982
|
}, this.commandTimeoutMs);
|
|
1812
1983
|
this.terminals.set(id, state);
|
|
1813
1984
|
return { terminalId: id };
|
|
@@ -1831,14 +2002,15 @@ var TerminalServer = class {
|
|
|
1831
2002
|
if (!state) throw new Error(`unknown terminal: ${terminalId}`);
|
|
1832
2003
|
return state.exitPromise;
|
|
1833
2004
|
}
|
|
1834
|
-
/**
|
|
2005
|
+
/**
|
|
2006
|
+
* Kill the process but keep the terminal record (agent can still read output).
|
|
2007
|
+
* On POSIX this signals only the direct child; descendants may survive because
|
|
2008
|
+
* terminal processes are not spawned as process-group leaders.
|
|
2009
|
+
*/
|
|
1835
2010
|
kill(terminalId) {
|
|
1836
2011
|
const state = this.terminals.get(terminalId);
|
|
1837
2012
|
if (!state) throw new Error(`unknown terminal: ${terminalId}`);
|
|
1838
|
-
|
|
1839
|
-
state.proc.kill("SIGTERM");
|
|
1840
|
-
} catch {
|
|
1841
|
-
}
|
|
2013
|
+
treeKill2(state.proc);
|
|
1842
2014
|
}
|
|
1843
2015
|
/** Kill the process if alive and remove the record. */
|
|
1844
2016
|
release(terminalId) {
|
|
@@ -1848,10 +2020,17 @@ var TerminalServer = class {
|
|
|
1848
2020
|
clearTimeout(state.timeoutHandle);
|
|
1849
2021
|
state.timeoutHandle = null;
|
|
1850
2022
|
}
|
|
1851
|
-
|
|
1852
|
-
state.proc.
|
|
1853
|
-
|
|
1854
|
-
|
|
2023
|
+
if (state.onData) {
|
|
2024
|
+
state.proc.stdout?.off("data", state.onData);
|
|
2025
|
+
state.proc.stderr?.off("data", state.onData);
|
|
2026
|
+
state.onData = void 0;
|
|
2027
|
+
}
|
|
2028
|
+
state.proc.stdout?.destroy?.();
|
|
2029
|
+
state.proc.stderr?.destroy?.();
|
|
2030
|
+
state.outputChunks.length = 0;
|
|
2031
|
+
state.outputHead = 0;
|
|
2032
|
+
state.retainedBytes = 0;
|
|
2033
|
+
treeKill2(state.proc, { force: true });
|
|
1855
2034
|
this.terminals.delete(terminalId);
|
|
1856
2035
|
}
|
|
1857
2036
|
/** Kill all active terminals. Used on session close. */
|
|
@@ -1863,15 +2042,15 @@ var TerminalServer = class {
|
|
|
1863
2042
|
}
|
|
1864
2043
|
resolveCwd(cwd) {
|
|
1865
2044
|
if (!cwd) return this.projectRoot;
|
|
1866
|
-
const resolved =
|
|
1867
|
-
const rootWithSep = this.projectRoot.endsWith(
|
|
2045
|
+
const resolved = path3.resolve(cwd);
|
|
2046
|
+
const rootWithSep = this.projectRoot.endsWith(path3.sep) ? this.projectRoot : this.projectRoot + path3.sep;
|
|
1868
2047
|
if (resolved !== this.projectRoot && !resolved.startsWith(rootWithSep)) {
|
|
1869
2048
|
return this.projectRoot;
|
|
1870
2049
|
}
|
|
1871
2050
|
try {
|
|
1872
2051
|
const realRoot = realpathSync2(this.projectRoot);
|
|
1873
2052
|
const realCwd = realpathSync2(resolved);
|
|
1874
|
-
const realRootWithSep = realRoot.endsWith(
|
|
2053
|
+
const realRootWithSep = realRoot.endsWith(path3.sep) ? realRoot : realRoot + path3.sep;
|
|
1875
2054
|
if (realCwd !== realRoot && !realCwd.startsWith(realRootWithSep)) {
|
|
1876
2055
|
return realRoot;
|
|
1877
2056
|
}
|
|
@@ -2014,7 +2193,12 @@ var WebSocketClientTransport = class {
|
|
|
2014
2193
|
this.maxBufferedBytes = finitePositiveLimit(opts.maxBufferedBytes, 32 * 1024 * 1024);
|
|
2015
2194
|
this.maxMessageChars = finitePositiveLimit(opts.maxMessageChars, 20 * 1024 * 1024);
|
|
2016
2195
|
}
|
|
2196
|
+
/** Pending start() promise resolve/reject — settled in stop() to avoid leaking. */
|
|
2197
|
+
pendingStart = null;
|
|
2017
2198
|
start() {
|
|
2199
|
+
if (this.closed || this.ws !== null || this.pendingStart !== null) {
|
|
2200
|
+
return Promise.reject(new Error("WebSocket transport has already been started or stopped"));
|
|
2201
|
+
}
|
|
2018
2202
|
const WS = globalThis.WebSocket;
|
|
2019
2203
|
if (!WS) {
|
|
2020
2204
|
return Promise.reject(
|
|
@@ -2024,36 +2208,54 @@ var WebSocketClientTransport = class {
|
|
|
2024
2208
|
);
|
|
2025
2209
|
}
|
|
2026
2210
|
const timeoutMs = this.opts.handshakeTimeoutMs ?? 3e4;
|
|
2027
|
-
return new Promise((
|
|
2028
|
-
let settled = false;
|
|
2211
|
+
return new Promise((resolve4, reject) => {
|
|
2029
2212
|
const ws = new WS(this.opts.url, this.opts.protocols);
|
|
2030
2213
|
this.ws = ws;
|
|
2031
2214
|
const timer = setTimeout(() => {
|
|
2032
|
-
|
|
2215
|
+
const pending = this.pendingStart;
|
|
2216
|
+
if (pending === null) return;
|
|
2217
|
+
this.pendingStart = null;
|
|
2218
|
+
this.closed = true;
|
|
2219
|
+
if (this.ws === ws) this.ws = null;
|
|
2220
|
+
this.handlers.clear();
|
|
2033
2221
|
try {
|
|
2034
2222
|
ws.close();
|
|
2035
2223
|
} catch {
|
|
2036
2224
|
}
|
|
2037
|
-
reject(new Error(`WebSocket failed to open within ${timeoutMs}ms`));
|
|
2225
|
+
pending.reject(new Error(`WebSocket failed to open within ${timeoutMs}ms`));
|
|
2038
2226
|
}, timeoutMs);
|
|
2227
|
+
this.pendingStart = { resolve: resolve4, reject, timer };
|
|
2039
2228
|
ws.addEventListener("open", () => {
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2229
|
+
const pending = this.pendingStart;
|
|
2230
|
+
if (pending === null) return;
|
|
2231
|
+
this.pendingStart = null;
|
|
2232
|
+
clearTimeout(pending.timer);
|
|
2233
|
+
pending.resolve();
|
|
2044
2234
|
});
|
|
2045
2235
|
ws.addEventListener("error", (ev) => {
|
|
2046
|
-
|
|
2047
|
-
|
|
2236
|
+
const pending = this.pendingStart;
|
|
2237
|
+
if (pending === null) {
|
|
2238
|
+
this.stop();
|
|
2048
2239
|
return;
|
|
2049
2240
|
}
|
|
2050
|
-
|
|
2051
|
-
|
|
2241
|
+
this.pendingStart = null;
|
|
2242
|
+
this.closed = true;
|
|
2243
|
+
if (this.ws === ws) this.ws = null;
|
|
2244
|
+
this.handlers.clear();
|
|
2245
|
+
clearTimeout(pending.timer);
|
|
2052
2246
|
const message = ev && typeof ev === "object" && "message" in ev ? String(ev.message) : "WebSocket error";
|
|
2053
|
-
reject(new Error(message));
|
|
2247
|
+
pending.reject(new Error(message));
|
|
2054
2248
|
});
|
|
2055
2249
|
ws.addEventListener("close", () => {
|
|
2056
2250
|
this.closed = true;
|
|
2251
|
+
if (this.ws === ws) this.ws = null;
|
|
2252
|
+
this.handlers.clear();
|
|
2253
|
+
const pending = this.pendingStart;
|
|
2254
|
+
if (pending !== null) {
|
|
2255
|
+
this.pendingStart = null;
|
|
2256
|
+
clearTimeout(pending.timer);
|
|
2257
|
+
pending.reject(new Error("WebSocket closed before the connection opened"));
|
|
2258
|
+
}
|
|
2057
2259
|
});
|
|
2058
2260
|
ws.addEventListener("message", (ev) => {
|
|
2059
2261
|
this.onData(ev.data);
|
|
@@ -2083,6 +2285,16 @@ var WebSocketClientTransport = class {
|
|
|
2083
2285
|
}
|
|
2084
2286
|
stop() {
|
|
2085
2287
|
this.closed = true;
|
|
2288
|
+
this.handlers.clear();
|
|
2289
|
+
if (this.pendingStart !== null) {
|
|
2290
|
+
const pending = this.pendingStart;
|
|
2291
|
+
this.pendingStart = null;
|
|
2292
|
+
clearTimeout(pending.timer);
|
|
2293
|
+
try {
|
|
2294
|
+
pending.reject(new Error("WebSocket transport stopped while connecting"));
|
|
2295
|
+
} catch {
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2086
2298
|
if (this.ws) {
|
|
2087
2299
|
try {
|
|
2088
2300
|
this.ws.close();
|
|
@@ -2495,7 +2707,15 @@ var ACPSession = class _ACPSession {
|
|
|
2495
2707
|
if (opts.env !== void 0) transportOpts.env = opts.env;
|
|
2496
2708
|
if (opts.cwd !== void 0) transportOpts.cwd = opts.cwd;
|
|
2497
2709
|
const transport = new ClientTransport(transportOpts);
|
|
2498
|
-
|
|
2710
|
+
try {
|
|
2711
|
+
return await _ACPSession.attach(opts, transport, `failed to spawn ${opts.command}`);
|
|
2712
|
+
} catch (err) {
|
|
2713
|
+
try {
|
|
2714
|
+
transport.stop();
|
|
2715
|
+
} catch {
|
|
2716
|
+
}
|
|
2717
|
+
throw err;
|
|
2718
|
+
}
|
|
2499
2719
|
}
|
|
2500
2720
|
/**
|
|
2501
2721
|
* Connect to a REMOTE ACP agent over a WebSocket instead of spawning a
|
|
@@ -3046,7 +3266,7 @@ var ACPSession = class _ACPSession {
|
|
|
3046
3266
|
return this.nextId++;
|
|
3047
3267
|
}
|
|
3048
3268
|
async sendRequest(id, method, params, timeoutMs) {
|
|
3049
|
-
return new Promise((
|
|
3269
|
+
return new Promise((resolve4, reject) => {
|
|
3050
3270
|
const effectiveTimeout = timeoutMs ?? this.timeoutMs;
|
|
3051
3271
|
const handle = setTimeout(() => {
|
|
3052
3272
|
this.pending.delete(id);
|
|
@@ -3056,7 +3276,7 @@ var ACPSession = class _ACPSession {
|
|
|
3056
3276
|
}, effectiveTimeout);
|
|
3057
3277
|
this.pending.set(id, {
|
|
3058
3278
|
method,
|
|
3059
|
-
resolve:
|
|
3279
|
+
resolve: resolve4,
|
|
3060
3280
|
reject,
|
|
3061
3281
|
timeoutMs: effectiveTimeout,
|
|
3062
3282
|
timeoutHandle: handle
|
|
@@ -3222,12 +3442,12 @@ var ToolTranslator = class {
|
|
|
3222
3442
|
id: callId,
|
|
3223
3443
|
params: { name, arguments: args }
|
|
3224
3444
|
});
|
|
3225
|
-
return new Promise((
|
|
3445
|
+
return new Promise((resolve4, reject) => {
|
|
3226
3446
|
const timeout = setTimeout(() => {
|
|
3227
3447
|
this.pending.delete(callId);
|
|
3228
3448
|
reject(new Error(`Tool call ${name} timed out after ${this.opts.totalTimeoutMs}ms`));
|
|
3229
3449
|
}, this.opts.totalTimeoutMs);
|
|
3230
|
-
this.pending.set(callId, { resolve:
|
|
3450
|
+
this.pending.set(callId, { resolve: resolve4, reject, timeout });
|
|
3231
3451
|
});
|
|
3232
3452
|
}
|
|
3233
3453
|
cancelAll() {
|
|
@@ -3239,8 +3459,8 @@ var ToolTranslator = class {
|
|
|
3239
3459
|
};
|
|
3240
3460
|
|
|
3241
3461
|
// src/integration/acp-bench.ts
|
|
3242
|
-
import * as
|
|
3243
|
-
import * as
|
|
3462
|
+
import * as fsp3 from "node:fs/promises";
|
|
3463
|
+
import * as path4 from "node:path";
|
|
3244
3464
|
function firstLine(s) {
|
|
3245
3465
|
const line = s.split("\n").map((l) => l.trim()).find((l) => l.length > 0) ?? "";
|
|
3246
3466
|
return line.length > 120 ? `${line.slice(0, 117)}\u2026` : line;
|
|
@@ -3307,11 +3527,11 @@ async function benchOne(agentId, cmd, opts) {
|
|
|
3307
3527
|
if (opts.checkFs) {
|
|
3308
3528
|
const fileToken = `FILE_${opts.marker}`;
|
|
3309
3529
|
const fileName = `acp-bench-${opts.marker}.txt`;
|
|
3310
|
-
const filePath =
|
|
3530
|
+
const filePath = path4.join(opts.projectRoot, fileName);
|
|
3311
3531
|
let fsOk = false;
|
|
3312
3532
|
let fsDetail;
|
|
3313
3533
|
try {
|
|
3314
|
-
await
|
|
3534
|
+
await fsp3.writeFile(filePath, fileToken, "utf8");
|
|
3315
3535
|
const fsRes = await session.prompt(
|
|
3316
3536
|
[
|
|
3317
3537
|
textContent(
|
|
@@ -3447,7 +3667,7 @@ function renderAcpBenchText(result) {
|
|
|
3447
3667
|
);
|
|
3448
3668
|
return lines.join("\n");
|
|
3449
3669
|
}
|
|
3450
|
-
async function removeBenchFile(filePath, remove =
|
|
3670
|
+
async function removeBenchFile(filePath, remove = fsp3.rm) {
|
|
3451
3671
|
await remove(filePath, { force: true }).catch(() => void 0);
|
|
3452
3672
|
}
|
|
3453
3673
|
|
|
@@ -3987,7 +4207,7 @@ import { SubagentBudget } from "@wrongstack/core/coordination";
|
|
|
3987
4207
|
|
|
3988
4208
|
// src/registry/ensemble-registry.ts
|
|
3989
4209
|
import { spawn as spawn2 } from "node:child_process";
|
|
3990
|
-
import { treeKill as
|
|
4210
|
+
import { treeKill as treeKill3 } from "@wrongstack/core/utils/tree-kill";
|
|
3991
4211
|
var PROBE_TIMEOUT_MS = 5e3;
|
|
3992
4212
|
var PROBE_CACHE_MS = 5e3;
|
|
3993
4213
|
var MAX_PARALLEL_PROBES = 4;
|
|
@@ -4013,7 +4233,7 @@ async function probeWithBound(items, worker, limit) {
|
|
|
4013
4233
|
}
|
|
4014
4234
|
async function defaultProbe(desc, timeoutMs, spawnProcess = spawn2, platform = process.platform) {
|
|
4015
4235
|
const start = Date.now();
|
|
4016
|
-
return new Promise((
|
|
4236
|
+
return new Promise((resolve4) => {
|
|
4017
4237
|
let settled = false;
|
|
4018
4238
|
let stdout = "";
|
|
4019
4239
|
let stderr = "";
|
|
@@ -4022,11 +4242,11 @@ async function defaultProbe(desc, timeoutMs, spawnProcess = spawn2, platform = p
|
|
|
4022
4242
|
settled = true;
|
|
4023
4243
|
try {
|
|
4024
4244
|
if (child.exitCode === null && child.signalCode === null) {
|
|
4025
|
-
|
|
4245
|
+
treeKill3(child);
|
|
4026
4246
|
}
|
|
4027
4247
|
} catch {
|
|
4028
4248
|
}
|
|
4029
|
-
|
|
4249
|
+
resolve4(result);
|
|
4030
4250
|
};
|
|
4031
4251
|
let child;
|
|
4032
4252
|
try {
|