@cortexkit/aft-opencode 0.59.0 → 0.60.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/bg-notifications.d.ts +5 -0
- package/dist/bg-notifications.d.ts.map +1 -1
- package/dist/bridge-bootstrap.d.ts +3 -3
- package/dist/bridge-bootstrap.d.ts.map +1 -1
- package/dist/config-live-reload.d.ts.map +1 -1
- package/dist/config.d.ts +0 -4
- package/dist/config.d.ts.map +1 -1
- package/dist/entry/server.js +1402 -536
- package/dist/index.js +1288 -376
- package/dist/lsp-github-install.d.ts +7 -1
- package/dist/lsp-github-install.d.ts.map +1 -1
- package/dist/shared/status.d.ts +9 -4
- package/dist/shared/status.d.ts.map +1 -1
- package/dist/tool-registration.d.ts +3 -0
- package/dist/tool-registration.d.ts.map +1 -1
- package/dist/tools/_shared.d.ts +2 -5
- package/dist/tools/_shared.d.ts.map +1 -1
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash_watch.d.ts.map +1 -1
- package/dist/tools/definitions/v2.d.ts +9 -1
- package/dist/tools/definitions/v2.d.ts.map +1 -1
- package/dist/tools/hoisted.d.ts.map +1 -1
- package/dist/tools/inspect.d.ts.map +1 -1
- package/dist/tui/v2-status.d.ts.map +1 -1
- package/dist/types.d.ts +7 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/v2-host-tool-names.d.ts +43 -0
- package/dist/v2-host-tool-names.d.ts.map +1 -0
- package/dist/v2-session-lifecycle.d.ts +22 -0
- package/dist/v2-session-lifecycle.d.ts.map +1 -0
- package/dist/v2-tool-hooks.d.ts +14 -2
- package/dist/v2-tool-hooks.d.ts.map +1 -1
- package/dist/wakes/runtime-consumer.d.ts +6 -1
- package/dist/wakes/runtime-consumer.d.ts.map +1 -1
- package/dist/wakes/session-delivery.d.ts +12 -7
- package/dist/wakes/session-delivery.d.ts.map +1 -1
- package/package.json +8 -8
- package/src/entry/server-runtime.mjs +26 -14
- package/src/shared/status.ts +25 -4
- package/src/tui/v2-status.ts +1 -0
- package/src/tui/v2.tsx +7 -4
- package/src/tui-compiled/v2-status.ts +1 -0
- package/src/tui-compiled/v2.tsx +7 -4
- package/dist/v2-host-tool-overlap.d.ts +0 -66
- package/dist/v2-host-tool-overlap.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -8594,11 +8594,11 @@ var DEFAULT_WORKER_WAIT_MAX_MS = 1800000;
|
|
|
8594
8594
|
var MIN_WORKER_WAIT_MAX_MS = 60000;
|
|
8595
8595
|
var LONGEST_TIMER_DELAY_MS = 2147483647;
|
|
8596
8596
|
var WORKER_WAIT_LIMIT_PHRASE = "the worker wait limit (`bash.worker_wait_max_ms`, 30 minutes by default)";
|
|
8597
|
-
var WATCH_SYNC_DEFAULTS_DESCRIPTION = "Sync waits default to 30s in a main session (max `bash.watch_sync_max_ms`, 120s by default); in a delegated session a sync wait without a timeout waits up to the worker wait limit (`bash.worker_wait_max_ms`, 30 minutes by default), then reports it is still running; watch again to keep waiting";
|
|
8598
|
-
var WATCH_TIMEOUT_PARAM_DESCRIPTION = "Sync-only timeout in milliseconds. In a main session: default 30000, max `bash.watch_sync_max_ms` (120000 by default). In a delegated session: omit it to wait up to the worker wait limit (`bash.worker_wait_max_ms`, 30 minutes by default), after which the watch reports the command is still running; a value you pass is
|
|
8597
|
+
var WATCH_SYNC_DEFAULTS_DESCRIPTION = "Sync waits default to 30s in a main session (max `bash.watch_sync_max_ms`, 120s by default); in a delegated session a sync wait without a timeout waits up to the worker wait limit (`bash.worker_wait_max_ms`, 30 minutes by default), then reports it is still running; watch again to keep waiting. An explicit timeout never exceeds that worker limit";
|
|
8598
|
+
var WATCH_TIMEOUT_PARAM_DESCRIPTION = "Sync-only timeout in milliseconds. In a main session: default 30000, max `bash.watch_sync_max_ms` (120000 by default). In a delegated session: omit it to wait up to the worker wait limit (`bash.worker_wait_max_ms`, 30 minutes by default), after which the watch reports the command is still running; a value you pass is clamped to that worker limit.";
|
|
8599
8599
|
function resolveWatchTimeoutMs(requestedMs, role, capMs, workerWaitMaxMs) {
|
|
8600
8600
|
if (role === "worker")
|
|
8601
|
-
return requestedMs ?? workerWaitMaxMs;
|
|
8601
|
+
return Math.min(requestedMs ?? workerWaitMaxMs, workerWaitMaxMs);
|
|
8602
8602
|
return Math.min(requestedMs ?? DEFAULT_PRIMARY_WATCH_TIMEOUT_MS, capMs);
|
|
8603
8603
|
}
|
|
8604
8604
|
function maxWatchTimeoutMs(role, capMs) {
|
|
@@ -8661,6 +8661,10 @@ function taskKillDeadlineText(data, role, nowMs = Date.now()) {
|
|
|
8661
8661
|
return "";
|
|
8662
8662
|
if (data.status === "unknown")
|
|
8663
8663
|
return "";
|
|
8664
|
+
if (typeof data.remote_phase_note === "string" && data.remote_phase_note !== "") {
|
|
8665
|
+
const note = data.remote_phase_note;
|
|
8666
|
+
return `${note.charAt(0).toUpperCase()}${note.slice(1)}.`;
|
|
8667
|
+
}
|
|
8664
8668
|
const hardKill = data.hard_kill;
|
|
8665
8669
|
if (!hardKill || typeof hardKill.limit_ms !== "number") {
|
|
8666
8670
|
return "This task has no kill deadline.";
|
|
@@ -9395,8 +9399,8 @@ var BASH_HOST_FALLBACK_BANNER = "[AFT host fallback - module transport down; no
|
|
|
9395
9399
|
var BASH_HOST_FALLBACK_MAX_OUTPUT_BYTES = 100 * 1024;
|
|
9396
9400
|
var BASH_HOST_FALLBACK_MAX_TIMEOUT_MS = 10 * 60 * 1000;
|
|
9397
9401
|
var BASH_HOST_FALLBACK_REFUSAL = "AFT transport is down; only foreground execution is available in host fallback";
|
|
9398
|
-
var BASH_RUNON_DESCRIPTION = 'Run
|
|
9399
|
-
var BASH_RUNON_GUIDANCE = 'When remote runs are available, put `runon: "linux"` on build and test lines (cargo, bun test), including chains and pipes. Keep git, gh, interactive and file-editing commands local, and keep a line local if it needs macOS (Seatbelt, codesign, launchd, TCC, AppKit) or runs binaries built on this machine: a remote build leaves no binaries or target/ output
|
|
9402
|
+
var BASH_RUNON_DESCRIPTION = 'Run on a remote build server: `linux` (2 vCPUs), or `linux,Nc` for N vCPUs such as `linux,8c`; add ",net" for outbound internet (offline by default). `windows` runs the command in Git Bash on a Windows build machine (no vCPU or network options).';
|
|
9403
|
+
var BASH_RUNON_GUIDANCE = 'When remote runs are available, put `runon: "linux"` on build and test lines (cargo, bun test), including chains and pipes. Keep git, gh, interactive and file-editing commands local, and keep a line local if it needs macOS (Seatbelt, codesign, launchd, TCC, AppKit) or runs binaries built on this machine: a remote build leaves no binaries or target/ output in the local worktree. Add `,Nc` to request N vCPUs (`linux,4c`); without it the job gets 2 vCPUs. The job sees exactly the CPUs it gets, so ask for what the command uses: 2c (the default) for a single test binary or script, 4c for `cargo check`/clippy on one crate, 8c for a workspace test suite, and 16c for a large release build.';
|
|
9400
9404
|
function pathKeyForPlatform(env, platform) {
|
|
9401
9405
|
return platform === "win32" ? Object.keys(env).find((key) => key.toLowerCase() === "path") : "PATH";
|
|
9402
9406
|
}
|
|
@@ -9840,6 +9844,7 @@ function isPassiveCommand(command) {
|
|
|
9840
9844
|
|
|
9841
9845
|
// ../aft-bridge/dist/transport.js
|
|
9842
9846
|
var WORKER_SESSION_FIELD = "worker_session";
|
|
9847
|
+
var HOST_TOOL_NAMES_FIELD = "host_tool_names";
|
|
9843
9848
|
var PRESET_FIELD = "preset";
|
|
9844
9849
|
function callPresetFor(workerSession) {
|
|
9845
9850
|
return workerSession ? "worker" : "head";
|
|
@@ -10004,6 +10009,8 @@ class BinaryBridge {
|
|
|
10004
10009
|
lastBinaryFingerprintCheckAt = 0;
|
|
10005
10010
|
_retiringDueToBinaryChange = false;
|
|
10006
10011
|
pending = new Map;
|
|
10012
|
+
activeSendCalls = 0;
|
|
10013
|
+
processLoopReferenced = true;
|
|
10007
10014
|
outstandingBackgroundTaskIds = new Set;
|
|
10008
10015
|
nextId = 1;
|
|
10009
10016
|
processGeneration = 0;
|
|
@@ -10235,6 +10242,32 @@ class BinaryBridge {
|
|
|
10235
10242
|
this.sessionLogVia(sessionId, `hashline registration carrier transport=ndjson phase=${phase} edit_slot_survives=${editSlotSurvives}${repeated}`);
|
|
10236
10243
|
}
|
|
10237
10244
|
async send(command, params = {}, options) {
|
|
10245
|
+
this.activeSendCalls += 1;
|
|
10246
|
+
this.syncEventLoopHold();
|
|
10247
|
+
try {
|
|
10248
|
+
return await this.dispatchSend(command, params, options);
|
|
10249
|
+
} finally {
|
|
10250
|
+
this.activeSendCalls -= 1;
|
|
10251
|
+
this.syncEventLoopHold();
|
|
10252
|
+
}
|
|
10253
|
+
}
|
|
10254
|
+
syncEventLoopHold() {
|
|
10255
|
+
const child = this.process;
|
|
10256
|
+
if (!child)
|
|
10257
|
+
return;
|
|
10258
|
+
const hold = this.activeSendCalls > 0 || this.outstandingBackgroundTaskIds.size > 0;
|
|
10259
|
+
if (hold === this.processLoopReferenced)
|
|
10260
|
+
return;
|
|
10261
|
+
this.processLoopReferenced = hold;
|
|
10262
|
+
const handles = [child, child.stdin, child.stdout, child.stderr];
|
|
10263
|
+
for (const handle of handles) {
|
|
10264
|
+
if (hold)
|
|
10265
|
+
handle?.ref?.();
|
|
10266
|
+
else
|
|
10267
|
+
handle?.unref?.();
|
|
10268
|
+
}
|
|
10269
|
+
}
|
|
10270
|
+
async dispatchSend(command, params, options) {
|
|
10238
10271
|
let dispatchParams = params;
|
|
10239
10272
|
if (command === "configure") {
|
|
10240
10273
|
dispatchParams = { ...params };
|
|
@@ -10272,11 +10305,13 @@ class BinaryBridge {
|
|
|
10272
10305
|
params.edit_slot_survives = editSlotSurvives;
|
|
10273
10306
|
this.logHashlineRegistrationCarrier("tool_call", sessionId, editSlotSurvives);
|
|
10274
10307
|
}
|
|
10275
|
-
const { preview, workerSession, ...sendOptions } = options ?? {};
|
|
10308
|
+
const { preview, workerSession, hostToolNames, ...sendOptions } = options ?? {};
|
|
10276
10309
|
if (preview === true)
|
|
10277
10310
|
params.preview = true;
|
|
10278
10311
|
if (workerSession === true)
|
|
10279
10312
|
params[WORKER_SESSION_FIELD] = true;
|
|
10313
|
+
if (hostToolNames)
|
|
10314
|
+
params[HOST_TOOL_NAMES_FIELD] = hostToolNames;
|
|
10280
10315
|
return await this.send("tool_call", params, Object.keys(sendOptions).length > 0 ? sendOptions : undefined);
|
|
10281
10316
|
}
|
|
10282
10317
|
async sendWithVersionMismatchRetry(command, params, options, canRetryAfterVersionSwap) {
|
|
@@ -10345,7 +10380,12 @@ class BinaryBridge {
|
|
|
10345
10380
|
if (command === "cancel_request" || Object.hasOwn(params, "command") || Object.hasOwn(params, "method")) {
|
|
10346
10381
|
const nested = { ...params };
|
|
10347
10382
|
const reserved = {};
|
|
10348
|
-
for (const key of [
|
|
10383
|
+
for (const key of [
|
|
10384
|
+
"session_id",
|
|
10385
|
+
"lsp_hints",
|
|
10386
|
+
WORKER_SESSION_FIELD,
|
|
10387
|
+
HOST_TOOL_NAMES_FIELD
|
|
10388
|
+
]) {
|
|
10349
10389
|
if (Object.hasOwn(nested, key)) {
|
|
10350
10390
|
reserved[key] = nested[key];
|
|
10351
10391
|
delete nested[key];
|
|
@@ -10720,6 +10760,8 @@ class BinaryBridge {
|
|
|
10720
10760
|
this.lastChildActivityAt = 0;
|
|
10721
10761
|
this.consecutiveRequestTimeouts = 0;
|
|
10722
10762
|
this.stderrTail = [];
|
|
10763
|
+
this.processLoopReferenced = true;
|
|
10764
|
+
this.syncEventLoopHold();
|
|
10723
10765
|
}
|
|
10724
10766
|
pushStderrLine(line) {
|
|
10725
10767
|
this.stderrTail.push(line);
|
|
@@ -10846,6 +10888,7 @@ class BinaryBridge {
|
|
|
10846
10888
|
const taskId = bashTaskIdFrom(response);
|
|
10847
10889
|
if (taskId)
|
|
10848
10890
|
this.outstandingBackgroundTaskIds.delete(taskId);
|
|
10891
|
+
this.syncEventLoopHold();
|
|
10849
10892
|
this.onBashCompletion?.(response, this);
|
|
10850
10893
|
return;
|
|
10851
10894
|
}
|
|
@@ -10959,7 +11002,7 @@ class BinaryBridge {
|
|
|
10959
11002
|
const delay = 100 * 2 ** this._restartCount;
|
|
10960
11003
|
this._restartCount++;
|
|
10961
11004
|
this.logVia(`Auto-restart #${this._restartCount} in ${delay}ms`);
|
|
10962
|
-
setTimeout(() => {
|
|
11005
|
+
const restartTimer = setTimeout(() => {
|
|
10963
11006
|
if (!this._shuttingDown && !this.isAlive()) {
|
|
10964
11007
|
try {
|
|
10965
11008
|
this.spawnProcess();
|
|
@@ -10968,6 +11011,7 @@ class BinaryBridge {
|
|
|
10968
11011
|
}
|
|
10969
11012
|
}
|
|
10970
11013
|
}, delay);
|
|
11014
|
+
restartTimer.unref?.();
|
|
10971
11015
|
this.scheduleRestartCountReset();
|
|
10972
11016
|
} else {
|
|
10973
11017
|
this.errorVia(`Max restarts (${this.maxRestarts}) reached, giving up. Logs: ${this.getLogFilePathVia()}${tail}`);
|
|
@@ -10988,6 +11032,7 @@ class BinaryBridge {
|
|
|
10988
11032
|
this._restartCount = 0;
|
|
10989
11033
|
this.restartResetTimer = null;
|
|
10990
11034
|
}, BinaryBridge.RESTART_RESET_MS);
|
|
11035
|
+
this.restartResetTimer.unref?.();
|
|
10991
11036
|
}
|
|
10992
11037
|
clearRestartResetTimer() {
|
|
10993
11038
|
if (this.restartResetTimer) {
|
|
@@ -11604,10 +11649,12 @@ function startLiveConfigReload(options) {
|
|
|
11604
11649
|
let acceptedTexts;
|
|
11605
11650
|
const reload = () => {
|
|
11606
11651
|
let loaded = options.load();
|
|
11652
|
+
let deletedProjectConfig = false;
|
|
11607
11653
|
if (loaded.ok) {
|
|
11608
11654
|
const read = new Set(loaded.sources);
|
|
11609
11655
|
const deleted = [...acceptedSources].find((path) => !read.has(path));
|
|
11610
|
-
|
|
11656
|
+
deletedProjectConfig = deleted === options.projectPath && loaded.projectText === null;
|
|
11657
|
+
if (deleted !== undefined && !deletedProjectConfig) {
|
|
11611
11658
|
loaded = { ok: false, message: `AFT config at ${deleted} was deleted` };
|
|
11612
11659
|
} else {
|
|
11613
11660
|
acceptedSources = read;
|
|
@@ -12379,17 +12426,17 @@ async function renameIfPresent(from, to) {
|
|
|
12379
12426
|
throw error;
|
|
12380
12427
|
}
|
|
12381
12428
|
}
|
|
12382
|
-
// ../../node_modules/.bun/@cortexkit+subc-client@0.
|
|
12429
|
+
// ../../node_modules/.bun/@cortexkit+subc-client@0.21.2/node_modules/@cortexkit/subc-client/dist/client.js
|
|
12383
12430
|
import { promises as fs3 } from "node:fs";
|
|
12384
12431
|
import { debuglog } from "node:util";
|
|
12385
12432
|
|
|
12386
|
-
// ../../node_modules/.bun/@cortexkit+subc-client@0.
|
|
12433
|
+
// ../../node_modules/.bun/@cortexkit+subc-client@0.21.2/node_modules/@cortexkit/subc-client/dist/auth.js
|
|
12387
12434
|
import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
|
|
12388
12435
|
|
|
12389
|
-
// ../../node_modules/.bun/@cortexkit+subc-client@0.
|
|
12436
|
+
// ../../node_modules/.bun/@cortexkit+subc-client@0.21.2/node_modules/@cortexkit/subc-client/dist/socket.js
|
|
12390
12437
|
import net from "node:net";
|
|
12391
12438
|
|
|
12392
|
-
// ../../node_modules/.bun/@cortexkit+subc-client@0.
|
|
12439
|
+
// ../../node_modules/.bun/@cortexkit+subc-client@0.21.2/node_modules/@cortexkit/subc-client/dist/envelope.js
|
|
12393
12440
|
var PROTOCOL_VERSION = 2;
|
|
12394
12441
|
var HEADER_LEN = 21;
|
|
12395
12442
|
var FROZEN_PREFIX_LEN = 5;
|
|
@@ -12492,12 +12539,12 @@ function validateHeaderFields(header) {
|
|
|
12492
12539
|
if (admission === AdmissionClass.Sheddable && ty !== FrameType.Push && ty !== FrameType.StreamData) {
|
|
12493
12540
|
throw new DecodeError(`SHEDDABLE admission class is illegal on ${FrameType[ty]} in flags 0b${flags.toString(2).padStart(8, "0")}`, "sheddable_illegal_frame_type");
|
|
12494
12541
|
}
|
|
12495
|
-
if (channel === 0 && epoch !== 0) {
|
|
12496
|
-
throw new DecodeError(`control channel carried nonzero epoch ${epoch}`, "nonzero_epoch_on_control_channel");
|
|
12497
|
-
}
|
|
12498
12542
|
if (isPureHeader(ty) && len !== 0) {
|
|
12499
12543
|
throw new DecodeError(`pure-header frame ${FrameType[ty]} declared non-zero body length ${len}`, "pure_header_frame_with_body");
|
|
12500
12544
|
}
|
|
12545
|
+
if (channel === 0 && epoch !== 0) {
|
|
12546
|
+
throw new DecodeError(`control channel carried nonzero epoch ${epoch}`, "nonzero_epoch_on_control_channel");
|
|
12547
|
+
}
|
|
12501
12548
|
}
|
|
12502
12549
|
function decodeHeader(bytes) {
|
|
12503
12550
|
if (bytes.length < FROZEN_PREFIX_LEN) {
|
|
@@ -12531,12 +12578,12 @@ function decodeHeader(bytes) {
|
|
|
12531
12578
|
}
|
|
12532
12579
|
const channel = view.getUint16(7, true);
|
|
12533
12580
|
const epoch = view.getUint32(9, true);
|
|
12534
|
-
if (channel === 0 && epoch !== 0) {
|
|
12535
|
-
throw new DecodeError(`control channel carried nonzero epoch ${epoch}`, "nonzero_epoch_on_control_channel");
|
|
12536
|
-
}
|
|
12537
12581
|
if (isPureHeader(ty) && len !== 0) {
|
|
12538
12582
|
throw new DecodeError(`pure-header frame ${FrameType[ty]} declared non-zero body length ${len}`, "pure_header_frame_with_body");
|
|
12539
12583
|
}
|
|
12584
|
+
if (channel === 0 && epoch !== 0) {
|
|
12585
|
+
throw new DecodeError(`control channel carried nonzero epoch ${epoch}`, "nonzero_epoch_on_control_channel");
|
|
12586
|
+
}
|
|
12540
12587
|
return { len, ver, ty, flags, channel, epoch, corr: view.getBigUint64(13, true) };
|
|
12541
12588
|
}
|
|
12542
12589
|
function buildFrame(ty, flags, channel, epoch, corr, body) {
|
|
@@ -12554,14 +12601,18 @@ function encodeFrame(frame) {
|
|
|
12554
12601
|
if (frame.header.len !== frame.body.length) {
|
|
12555
12602
|
throw new DecodeError(`frame header length ${frame.header.len} does not match body length ${frame.body.length}`, "frame_length_mismatch");
|
|
12556
12603
|
}
|
|
12604
|
+
if (frame.body.length > MAX_FRAME_BODY_LEN) {
|
|
12605
|
+
throw new DecodeError(`frame body ${frame.body.length} exceeds max ${MAX_FRAME_BODY_LEN}`, "frame_body_too_large");
|
|
12606
|
+
}
|
|
12557
12607
|
const header = encodeHeader(frame.header);
|
|
12608
|
+
decodeHeader(header);
|
|
12558
12609
|
const output = new Uint8Array(header.length + frame.body.length);
|
|
12559
12610
|
output.set(header, 0);
|
|
12560
12611
|
output.set(frame.body, header.length);
|
|
12561
12612
|
return output;
|
|
12562
12613
|
}
|
|
12563
12614
|
|
|
12564
|
-
// ../../node_modules/.bun/@cortexkit+subc-client@0.
|
|
12615
|
+
// ../../node_modules/.bun/@cortexkit+subc-client@0.21.2/node_modules/@cortexkit/subc-client/dist/socket.js
|
|
12565
12616
|
class SocketClosedError extends Error {
|
|
12566
12617
|
}
|
|
12567
12618
|
|
|
@@ -12611,6 +12662,15 @@ class SubcSocket {
|
|
|
12611
12662
|
sock.on("data", (chunk) => {
|
|
12612
12663
|
this.chunks.push(chunk);
|
|
12613
12664
|
this.buffered += chunk.length;
|
|
12665
|
+
if (this.waiter) {
|
|
12666
|
+
if (this.waiter.immediate) {
|
|
12667
|
+
clearImmediate(this.waiter.immediate);
|
|
12668
|
+
this.waiter.immediate = null;
|
|
12669
|
+
}
|
|
12670
|
+
if (this.waiter.extendOnProgress && this.buffered < this.waiter.need) {
|
|
12671
|
+
this.armWaiterTimer(this.waiter);
|
|
12672
|
+
}
|
|
12673
|
+
}
|
|
12614
12674
|
this.tryServe();
|
|
12615
12675
|
});
|
|
12616
12676
|
const fail = (err) => {
|
|
@@ -12657,30 +12717,80 @@ class SubcSocket {
|
|
|
12657
12717
|
throw new DecodeError(`frame body ${header.len} exceeds max ${MAX_FRAME_BODY_LEN}`, "frame_body_too_large");
|
|
12658
12718
|
}
|
|
12659
12719
|
onHeader?.();
|
|
12660
|
-
const
|
|
12661
|
-
const body = header.len === 0 ? new Uint8Array(0) : await this.readExact(header.len,
|
|
12720
|
+
const bodyDeadlineArg = typeof bodyDeadline === "number" ? bodyDeadline : { idleTimeoutMs: bodyDeadline.afterHeaderMs };
|
|
12721
|
+
const body = header.len === 0 ? new Uint8Array(0) : await this.readExact(header.len, bodyDeadlineArg);
|
|
12662
12722
|
return { header, body };
|
|
12663
12723
|
}
|
|
12664
|
-
|
|
12724
|
+
armWaiterTimer(w) {
|
|
12725
|
+
if (!Number.isFinite(w.budget))
|
|
12726
|
+
return;
|
|
12727
|
+
if (w.timer) {
|
|
12728
|
+
clearTimeout(w.timer);
|
|
12729
|
+
w.timer = null;
|
|
12730
|
+
}
|
|
12731
|
+
w.timer = setTimeout(() => {
|
|
12732
|
+
w.timer = null;
|
|
12733
|
+
const bufferedAtTimeout = this.buffered;
|
|
12734
|
+
const verifyTimeout = () => {
|
|
12735
|
+
if (this.waiter !== w)
|
|
12736
|
+
return;
|
|
12737
|
+
if (w.extendOnProgress && this.buffered > bufferedAtTimeout) {
|
|
12738
|
+
this.armWaiterTimer(w);
|
|
12739
|
+
return;
|
|
12740
|
+
}
|
|
12741
|
+
this.waiter = null;
|
|
12742
|
+
w.reject(new SocketTimeoutError(`timed out waiting for ${w.need} bytes`));
|
|
12743
|
+
};
|
|
12744
|
+
w.immediate = setImmediate(() => {
|
|
12745
|
+
if (this.waiter !== w)
|
|
12746
|
+
return;
|
|
12747
|
+
if (this.buffered > bufferedAtTimeout) {
|
|
12748
|
+
verifyTimeout();
|
|
12749
|
+
} else {
|
|
12750
|
+
w.immediate = setImmediate(() => {
|
|
12751
|
+
w.immediate = null;
|
|
12752
|
+
verifyTimeout();
|
|
12753
|
+
});
|
|
12754
|
+
}
|
|
12755
|
+
});
|
|
12756
|
+
}, w.budget);
|
|
12757
|
+
}
|
|
12758
|
+
readExact(n, deadline) {
|
|
12665
12759
|
if (this.waiter) {
|
|
12666
12760
|
return Promise.reject(new Error("concurrent readExact is not supported"));
|
|
12667
12761
|
}
|
|
12668
12762
|
if (n === 0)
|
|
12669
12763
|
return Promise.resolve(new Uint8Array(0));
|
|
12670
12764
|
return new Promise((resolve, reject) => {
|
|
12671
|
-
let
|
|
12672
|
-
|
|
12673
|
-
|
|
12674
|
-
|
|
12765
|
+
let budget;
|
|
12766
|
+
const extendOnProgress = typeof deadline === "object";
|
|
12767
|
+
if (typeof deadline === "object") {
|
|
12768
|
+
budget = deadline.idleTimeoutMs;
|
|
12769
|
+
if (budget <= 0) {
|
|
12675
12770
|
reject(new SocketTimeoutError(`timed out waiting for ${n} bytes`));
|
|
12676
12771
|
return;
|
|
12677
12772
|
}
|
|
12678
|
-
|
|
12679
|
-
|
|
12773
|
+
} else if (Number.isFinite(deadline)) {
|
|
12774
|
+
const remaining = deadline - Date.now();
|
|
12775
|
+
if (remaining <= 0) {
|
|
12680
12776
|
reject(new SocketTimeoutError(`timed out waiting for ${n} bytes`));
|
|
12681
|
-
|
|
12777
|
+
return;
|
|
12778
|
+
}
|
|
12779
|
+
budget = remaining;
|
|
12780
|
+
} else {
|
|
12781
|
+
budget = Number.POSITIVE_INFINITY;
|
|
12682
12782
|
}
|
|
12683
|
-
|
|
12783
|
+
const waiter = {
|
|
12784
|
+
need: n,
|
|
12785
|
+
resolve,
|
|
12786
|
+
reject,
|
|
12787
|
+
timer: null,
|
|
12788
|
+
immediate: null,
|
|
12789
|
+
budget,
|
|
12790
|
+
extendOnProgress
|
|
12791
|
+
};
|
|
12792
|
+
this.waiter = waiter;
|
|
12793
|
+
this.armWaiterTimer(waiter);
|
|
12684
12794
|
this.tryServe();
|
|
12685
12795
|
});
|
|
12686
12796
|
}
|
|
@@ -12763,6 +12873,8 @@ class SubcSocket {
|
|
|
12763
12873
|
this.waiter = null;
|
|
12764
12874
|
if (w.timer)
|
|
12765
12875
|
clearTimeout(w.timer);
|
|
12876
|
+
if (w.immediate)
|
|
12877
|
+
clearImmediate(w.immediate);
|
|
12766
12878
|
w.resolve(out);
|
|
12767
12879
|
return;
|
|
12768
12880
|
}
|
|
@@ -12770,6 +12882,8 @@ class SubcSocket {
|
|
|
12770
12882
|
this.waiter = null;
|
|
12771
12883
|
if (w.timer)
|
|
12772
12884
|
clearTimeout(w.timer);
|
|
12885
|
+
if (w.immediate)
|
|
12886
|
+
clearImmediate(w.immediate);
|
|
12773
12887
|
w.reject(this.closedErr);
|
|
12774
12888
|
}
|
|
12775
12889
|
}
|
|
@@ -12794,7 +12908,7 @@ class SubcSocket {
|
|
|
12794
12908
|
}
|
|
12795
12909
|
}
|
|
12796
12910
|
|
|
12797
|
-
// ../../node_modules/.bun/@cortexkit+subc-client@0.
|
|
12911
|
+
// ../../node_modules/.bun/@cortexkit+subc-client@0.21.2/node_modules/@cortexkit/subc-client/dist/auth.js
|
|
12798
12912
|
var NONCE_LEN = 32;
|
|
12799
12913
|
var MAX_AUTH_MESSAGE_LEN = 4096;
|
|
12800
12914
|
var SERVER_PROOF_DOMAIN = "subc-server-v1";
|
|
@@ -12868,7 +12982,7 @@ async function authenticateClient(sock, conn, deadlineMs) {
|
|
|
12868
12982
|
await writeMessage(sock, { client_auth: Array.from(clientAuth) }, deadlineMs);
|
|
12869
12983
|
}
|
|
12870
12984
|
|
|
12871
|
-
// ../../node_modules/.bun/@cortexkit+subc-client@0.
|
|
12985
|
+
// ../../node_modules/.bun/@cortexkit+subc-client@0.21.2/node_modules/@cortexkit/subc-client/dist/connection-file.js
|
|
12872
12986
|
import { promises as fs2 } from "node:fs";
|
|
12873
12987
|
var SCHEMA_VERSION = 1;
|
|
12874
12988
|
var MIN_KEY_LEN = 32;
|
|
@@ -12901,18 +13015,29 @@ function validate(info) {
|
|
|
12901
13015
|
throw new ConnectionFileError(`connection file daemon_id must be ${DAEMON_ID_LEN} bytes, got ${info.daemonId.length}`);
|
|
12902
13016
|
}
|
|
12903
13017
|
}
|
|
12904
|
-
|
|
13018
|
+
function verifyOwnerOnly(path, stat, expectedUid) {
|
|
12905
13019
|
if (process.platform === "win32")
|
|
12906
13020
|
return;
|
|
12907
|
-
|
|
13021
|
+
if (expectedUid !== undefined && stat.uid !== expectedUid) {
|
|
13022
|
+
throw new ConnectionFileError(`connection file ${path} is owned by uid ${stat.uid}, expected effective uid ${expectedUid}`);
|
|
13023
|
+
}
|
|
12908
13024
|
const mode = stat.mode & 511;
|
|
12909
13025
|
if ((mode & 63) !== 0) {
|
|
12910
13026
|
throw new ConnectionFileError(`connection file ${path} has insecure permissions 0o${mode.toString(8)}; expected owner-only 0600`);
|
|
12911
13027
|
}
|
|
12912
13028
|
}
|
|
12913
13029
|
async function readConnectionFile(path) {
|
|
12914
|
-
|
|
12915
|
-
|
|
13030
|
+
return readConnectionFileForUid(path, process.geteuid?.());
|
|
13031
|
+
}
|
|
13032
|
+
async function readConnectionFileForUid(path, expectedUid) {
|
|
13033
|
+
const handle = await fs2.open(path, "r");
|
|
13034
|
+
let raw;
|
|
13035
|
+
try {
|
|
13036
|
+
verifyOwnerOnly(path, await handle.stat(), expectedUid);
|
|
13037
|
+
raw = await handle.readFile({ encoding: "utf8" });
|
|
13038
|
+
} finally {
|
|
13039
|
+
await handle.close();
|
|
13040
|
+
}
|
|
12916
13041
|
let parsed;
|
|
12917
13042
|
try {
|
|
12918
13043
|
parsed = JSON.parse(raw);
|
|
@@ -12946,7 +13071,7 @@ async function readConnectionFile(path) {
|
|
|
12946
13071
|
return info;
|
|
12947
13072
|
}
|
|
12948
13073
|
|
|
12949
|
-
// ../../node_modules/.bun/@cortexkit+subc-client@0.
|
|
13074
|
+
// ../../node_modules/.bun/@cortexkit+subc-client@0.21.2/node_modules/@cortexkit/subc-client/dist/launch-nonce.js
|
|
12950
13075
|
import { closeSync as closeSync4, fstatSync as fstatSync2, readSync as readSync3 } from "node:fs";
|
|
12951
13076
|
var SUBC_LAUNCH_NONCE_ENV = "SUBC_LAUNCH_NONCE";
|
|
12952
13077
|
var SUBC_LAUNCH_NONCE_FD_ENV = "SUBC_LAUNCH_NONCE_FD";
|
|
@@ -13135,7 +13260,7 @@ function closeQuietly(fd) {
|
|
|
13135
13260
|
} catch {}
|
|
13136
13261
|
}
|
|
13137
13262
|
|
|
13138
|
-
// ../../node_modules/.bun/@cortexkit+subc-client@0.
|
|
13263
|
+
// ../../node_modules/.bun/@cortexkit+subc-client@0.21.2/node_modules/@cortexkit/subc-client/dist/route-handle.js
|
|
13139
13264
|
var connectionToken = new WeakMap;
|
|
13140
13265
|
var reverseRequests = new WeakMap;
|
|
13141
13266
|
|
|
@@ -13228,7 +13353,7 @@ function sameRouteHandle(left, right) {
|
|
|
13228
13353
|
return left === right;
|
|
13229
13354
|
}
|
|
13230
13355
|
|
|
13231
|
-
// ../../node_modules/.bun/@cortexkit+subc-client@0.
|
|
13356
|
+
// ../../node_modules/.bun/@cortexkit+subc-client@0.21.2/node_modules/@cortexkit/subc-client/dist/client.js
|
|
13232
13357
|
var debug = debuglog("subc-client");
|
|
13233
13358
|
var DEFAULT_HANDSHAKE_TIMEOUT_MS = 1e4;
|
|
13234
13359
|
var DEFAULT_REQUEST_TIMEOUT_MS = 30000;
|
|
@@ -13285,6 +13410,12 @@ function requireBinaryBody(body) {
|
|
|
13285
13410
|
const type = body === null ? "null" : Array.isArray(body) ? "array" : typeof body;
|
|
13286
13411
|
throw new SubcError(`binary request body must be a Uint8Array; got ${type}`, "binary_body_required");
|
|
13287
13412
|
}
|
|
13413
|
+
var SCOPE_ROUTE_CLOSE_REASONS = new Set([
|
|
13414
|
+
"scope_ended",
|
|
13415
|
+
"scope_carrier_removed",
|
|
13416
|
+
"scope_delegation_changed",
|
|
13417
|
+
"scope_parent_ended"
|
|
13418
|
+
]);
|
|
13288
13419
|
function parseRouteCloseReason(reason) {
|
|
13289
13420
|
switch (reason) {
|
|
13290
13421
|
case "reload":
|
|
@@ -13427,14 +13558,16 @@ class SubcClient {
|
|
|
13427
13558
|
return modules.filter((module) => module.capabilities?.provides.includes(capability) ?? false).map((module) => module.module_id).sort();
|
|
13428
13559
|
}
|
|
13429
13560
|
async routeOpen(target, identity, opts = {}) {
|
|
13561
|
+
const scope = validatedRouteScope(opts.scope);
|
|
13430
13562
|
const release = await this.routeOpenGate.acquire();
|
|
13431
13563
|
try {
|
|
13432
|
-
return await this.sendRouteOpen(target, identity, opts);
|
|
13564
|
+
return await this.sendRouteOpen(target, identity, { ...opts, scope });
|
|
13433
13565
|
} finally {
|
|
13434
13566
|
release();
|
|
13435
13567
|
}
|
|
13436
13568
|
}
|
|
13437
13569
|
async sendRouteOpen(target, identity, opts = {}) {
|
|
13570
|
+
const scope = validatedRouteScope(opts.scope);
|
|
13438
13571
|
const consumerIdentity = routeOpenConsumerIdentity(opts);
|
|
13439
13572
|
const reverseRequests = opts.reverseRequests ?? new ReverseRequestRegistry;
|
|
13440
13573
|
reverseRequests.seal();
|
|
@@ -13444,7 +13577,8 @@ class SubcClient {
|
|
|
13444
13577
|
target,
|
|
13445
13578
|
identity,
|
|
13446
13579
|
...consumerIdentity ? { consumer_identity: consumerIdentity } : {},
|
|
13447
|
-
...consumerCapabilities.length > 0 ? { consumer_capabilities: consumerCapabilities } : {}
|
|
13580
|
+
...consumerCapabilities.length > 0 ? { consumer_capabilities: consumerCapabilities } : {},
|
|
13581
|
+
...scope ? { scope: { owner: scope.owner, ref: scope.ref, scope_epoch: scope.scopeEpoch } } : {}
|
|
13448
13582
|
});
|
|
13449
13583
|
let installed = null;
|
|
13450
13584
|
const install = (frame) => {
|
|
@@ -13647,7 +13781,7 @@ class SubcClient {
|
|
|
13647
13781
|
this.sendRouteGoodbye(handle);
|
|
13648
13782
|
}
|
|
13649
13783
|
async closeManagedRoute(target, identity, opts = {}) {
|
|
13650
|
-
const key = routeCacheKey(target, identity, routeOpenConsumerIdentity(opts), (opts.reverseRequests ?? new ReverseRequestRegistry).capabilities());
|
|
13784
|
+
const key = routeCacheKey(target, identity, routeOpenConsumerIdentity(opts), (opts.reverseRequests ?? new ReverseRequestRegistry).capabilities(), validatedRouteScope(opts.scope));
|
|
13651
13785
|
const cached = this.routes.get(key);
|
|
13652
13786
|
if (!cached)
|
|
13653
13787
|
return;
|
|
@@ -13743,13 +13877,13 @@ class SubcClient {
|
|
|
13743
13877
|
};
|
|
13744
13878
|
pending.timer = setTimeout(() => this.arbitrateTimeout(key, pending, channel, corr, ms), ms);
|
|
13745
13879
|
this.pending.set(key, pending);
|
|
13746
|
-
if (signal)
|
|
13747
|
-
this.attachCancelSignal(signal, key, handle, corr, priority);
|
|
13748
13880
|
writeBorrowed(this.sock, encodeFrame(frame), Date.now() + ms).catch((error) => {
|
|
13749
13881
|
const current = this.pending.get(key);
|
|
13750
13882
|
if (current)
|
|
13751
13883
|
this.rejectPending(key, current, error instanceof Error ? error : new SubcError(String(error)));
|
|
13752
13884
|
});
|
|
13885
|
+
if (signal)
|
|
13886
|
+
this.attachCancelSignal(signal, key, handle, corr, priority);
|
|
13753
13887
|
});
|
|
13754
13888
|
}
|
|
13755
13889
|
arbitrateTimeout(key, pending, channel, corr, ms) {
|
|
@@ -13823,8 +13957,6 @@ class SubcClient {
|
|
|
13823
13957
|
};
|
|
13824
13958
|
pending.timer = setTimeout(() => this.arbitrateTimeout(key, pending, handle.channel, corr, ms), ms);
|
|
13825
13959
|
this.pending.set(key, pending);
|
|
13826
|
-
if (signal)
|
|
13827
|
-
this.attachCancelSignal(signal, key, handle, corr, priority);
|
|
13828
13960
|
const write = writeTrackedBorrowed(this.sock, encodeFrame(frame), Date.now() + ms);
|
|
13829
13961
|
handedToSocket = write.queued;
|
|
13830
13962
|
write.completed.catch((error) => {
|
|
@@ -13832,9 +13964,12 @@ class SubcClient {
|
|
|
13832
13964
|
if (current)
|
|
13833
13965
|
this.rejectPending(key, current, error instanceof Error ? error : new SubcError(String(error)));
|
|
13834
13966
|
});
|
|
13967
|
+
if (signal && write.queued)
|
|
13968
|
+
this.attachCancelSignal(signal, key, handle, corr, priority);
|
|
13835
13969
|
});
|
|
13836
13970
|
}
|
|
13837
13971
|
async cachedRouteHandle(moduleId, opts) {
|
|
13972
|
+
const scope = validatedRouteScope(opts.scope);
|
|
13838
13973
|
const identity = opts.identity ?? this.opts.identity;
|
|
13839
13974
|
if (!identity) {
|
|
13840
13975
|
throw new SubcCallError("terminal", "managed call requires a BindIdentity in SubcClient.connect({ identity }) or call(..., { identity })", "missing_identity");
|
|
@@ -13843,7 +13978,7 @@ class SubcClient {
|
|
|
13843
13978
|
const consumerIdentity = routeOpenConsumerIdentity(opts);
|
|
13844
13979
|
const reverseRequests = opts.reverseRequests ?? new ReverseRequestRegistry;
|
|
13845
13980
|
reverseRequests.seal();
|
|
13846
|
-
const key = routeCacheKey(target, identity, consumerIdentity, reverseRequests.capabilities());
|
|
13981
|
+
const key = routeCacheKey(target, identity, consumerIdentity, reverseRequests.capabilities(), scope);
|
|
13847
13982
|
let cached = this.routes.get(key);
|
|
13848
13983
|
if (!cached) {
|
|
13849
13984
|
cached = {
|
|
@@ -13851,6 +13986,7 @@ class SubcClient {
|
|
|
13851
13986
|
moduleId,
|
|
13852
13987
|
target,
|
|
13853
13988
|
identity,
|
|
13989
|
+
scope,
|
|
13854
13990
|
consumerIdentity,
|
|
13855
13991
|
reverseRequests,
|
|
13856
13992
|
handle: null,
|
|
@@ -13858,6 +13994,8 @@ class SubcClient {
|
|
|
13858
13994
|
};
|
|
13859
13995
|
this.routes.set(key, cached);
|
|
13860
13996
|
}
|
|
13997
|
+
if (cached.endReason !== undefined)
|
|
13998
|
+
throw this.endedCachedRoute(cached.endReason);
|
|
13861
13999
|
if (cached.handle && this.isLiveHandle(cached.handle))
|
|
13862
14000
|
return cached.handle;
|
|
13863
14001
|
const startedAt = this.opts.now();
|
|
@@ -13893,6 +14031,8 @@ class SubcClient {
|
|
|
13893
14031
|
for (;; ) {
|
|
13894
14032
|
if (cached.closed)
|
|
13895
14033
|
throw this.routeClosedDuringOpen();
|
|
14034
|
+
if (cached.endReason !== undefined)
|
|
14035
|
+
throw this.endedCachedRoute(cached.endReason);
|
|
13896
14036
|
try {
|
|
13897
14037
|
await this.ensureConnectedForManaged();
|
|
13898
14038
|
} catch (error) {
|
|
@@ -13907,6 +14047,7 @@ class SubcClient {
|
|
|
13907
14047
|
try {
|
|
13908
14048
|
outcome = {
|
|
13909
14049
|
handle: await this.sendRouteOpen(cached.target, cached.identity, {
|
|
14050
|
+
scope: cached.scope,
|
|
13910
14051
|
consumerIdentity: cached.consumerIdentity ?? null,
|
|
13911
14052
|
reverseRequests: cached.reverseRequests
|
|
13912
14053
|
})
|
|
@@ -14074,10 +14215,11 @@ class SubcClient {
|
|
|
14074
14215
|
}
|
|
14075
14216
|
for (const key of routeKeys) {
|
|
14076
14217
|
const cached = this.routes.get(key);
|
|
14077
|
-
if (!cached || cached.closed)
|
|
14218
|
+
if (!cached || cached.closed || cached.endReason !== undefined)
|
|
14078
14219
|
continue;
|
|
14079
14220
|
try {
|
|
14080
14221
|
const handle = await this.routeOpen(cached.target, cached.identity, {
|
|
14222
|
+
scope: cached.scope,
|
|
14081
14223
|
consumerIdentity: cached.consumerIdentity ?? null,
|
|
14082
14224
|
reverseRequests: cached.reverseRequests
|
|
14083
14225
|
});
|
|
@@ -14105,11 +14247,15 @@ class SubcClient {
|
|
|
14105
14247
|
const cause = new SubcError("route was closed before route.open completed", "route_closed", undefined, "closed_by_caller");
|
|
14106
14248
|
return new SubcCallError("not_sent", cause.message, cause.code, cause);
|
|
14107
14249
|
}
|
|
14250
|
+
endedCachedRoute(reason) {
|
|
14251
|
+
const cause = new SubcError(`cached route closed by subc (${reason}); must not reopen`, "route_closed", undefined, reason);
|
|
14252
|
+
return this.terminalCallError(cause.message, cause);
|
|
14253
|
+
}
|
|
14108
14254
|
async readLoop(sock, generation) {
|
|
14109
14255
|
try {
|
|
14110
14256
|
for (;; ) {
|
|
14111
14257
|
this.readerActive = false;
|
|
14112
|
-
const frame = await sock.readFrame(Number.POSITIVE_INFINITY, { afterHeaderMs: BODY_READ_TIMEOUT_MS }, () => {
|
|
14258
|
+
const frame = await sock.readFrame(Number.POSITIVE_INFINITY, { afterHeaderMs: this.opts.bodyReadTimeoutMs ?? BODY_READ_TIMEOUT_MS }, () => {
|
|
14113
14259
|
this.readerActive = true;
|
|
14114
14260
|
});
|
|
14115
14261
|
try {
|
|
@@ -14188,6 +14334,7 @@ class SubcClient {
|
|
|
14188
14334
|
}
|
|
14189
14335
|
if (frame.header.ty === FrameType.Goodbye && handle) {
|
|
14190
14336
|
this.failHandle(handle, new SubcError("route closed by subc (GOODBYE)", "route_closed", undefined, this.routeEndReason(handle.channel)));
|
|
14337
|
+
this.handleCachedRouteClose(handle.channel, this.routeEndReason(handle.channel));
|
|
14191
14338
|
if (this.liveRoutes.get(handle.channel) === handle)
|
|
14192
14339
|
this.liveRoutes.delete(handle.channel);
|
|
14193
14340
|
this.evictRouteHandle(handle);
|
|
@@ -14277,13 +14424,29 @@ class SubcClient {
|
|
|
14277
14424
|
const final = push.op === "route.closed";
|
|
14278
14425
|
if (final || !this.routeEndReasons.get(channel)?.final) {
|
|
14279
14426
|
this.routeEndReasons.set(channel, { reason, final });
|
|
14427
|
+
if (final)
|
|
14428
|
+
this.handleCachedRouteClose(channel, reason);
|
|
14280
14429
|
}
|
|
14281
14430
|
}
|
|
14282
14431
|
}
|
|
14283
14432
|
} else if (typeof push.body.module_id === "string") {
|
|
14284
14433
|
for (const [channel, module] of this.routeModules) {
|
|
14285
|
-
if (module === push.body.module_id)
|
|
14434
|
+
if (module === push.body.module_id) {
|
|
14286
14435
|
this.legacyChannelReasons.set(channel, reason);
|
|
14436
|
+
if (push.op === "route.closed")
|
|
14437
|
+
this.handleCachedRouteClose(channel, reason);
|
|
14438
|
+
}
|
|
14439
|
+
}
|
|
14440
|
+
}
|
|
14441
|
+
}
|
|
14442
|
+
handleCachedRouteClose(channel, reason) {
|
|
14443
|
+
for (const [key, cached] of this.routes) {
|
|
14444
|
+
if (cached.handle?.channel !== channel)
|
|
14445
|
+
continue;
|
|
14446
|
+
if (SCOPE_ROUTE_CLOSE_REASONS.has(reason)) {
|
|
14447
|
+
cached.endReason = reason;
|
|
14448
|
+
} else {
|
|
14449
|
+
this.routes.delete(key);
|
|
14287
14450
|
}
|
|
14288
14451
|
}
|
|
14289
14452
|
}
|
|
@@ -14306,6 +14469,10 @@ class SubcClient {
|
|
|
14306
14469
|
this.routeEndReasons.clear();
|
|
14307
14470
|
this.legacyChannelReasons.clear();
|
|
14308
14471
|
this.routeModules.clear();
|
|
14472
|
+
for (const [key, cached] of this.routes) {
|
|
14473
|
+
if (cached.endReason === undefined)
|
|
14474
|
+
this.routes.delete(key);
|
|
14475
|
+
}
|
|
14309
14476
|
for (const [key, pending] of this.pending) {
|
|
14310
14477
|
this.rejectPending(key, pending, pending.handle ? routeError : err);
|
|
14311
14478
|
}
|
|
@@ -14400,6 +14567,10 @@ function isRouteOpenRefusal(err) {
|
|
|
14400
14567
|
function isRetryableRouteOpenCode(code) {
|
|
14401
14568
|
if (code === "capability_forbidden")
|
|
14402
14569
|
return false;
|
|
14570
|
+
if (code === "target_flow_unsupported")
|
|
14571
|
+
return false;
|
|
14572
|
+
if (code === "target_agent_run_unsupported")
|
|
14573
|
+
return false;
|
|
14403
14574
|
return code === "module_reloading" || code === "module_warming" || code === "target_unavailable" || code === "module_timeout" || code === "scope_not_synced" || code === "scope_changed";
|
|
14404
14575
|
}
|
|
14405
14576
|
function equalJitter(delayMs, random) {
|
|
@@ -14457,6 +14628,7 @@ function normalizeConnectOptions(opts) {
|
|
|
14457
14628
|
random: opts.random ?? Math.random,
|
|
14458
14629
|
routeOpenRetryDeadlineMs: opts.routeOpenRetryDeadlineMs ?? ROUTE_OPEN_RETRY_DEADLINE_MS,
|
|
14459
14630
|
timeoutArbitrationGraceMs: opts.timeoutArbitrationGraceMs ?? TIMEOUT_ARBITRATION_GRACE_MS,
|
|
14631
|
+
bodyReadTimeoutMs: opts.bodyReadTimeoutMs,
|
|
14460
14632
|
livenessProbeWindowMs: opts.livenessProbeWindowMs ?? LIVENESS_PROBE_WINDOW_MS,
|
|
14461
14633
|
onControlPush: opts.onControlPush
|
|
14462
14634
|
};
|
|
@@ -14480,10 +14652,28 @@ function isValidCapabilityIdentifier(identifier) {
|
|
|
14480
14652
|
const numericVersion = Number(version);
|
|
14481
14653
|
return Number.isSafeInteger(numericVersion) && numericVersion >= 1 && numericVersion <= 4294967295;
|
|
14482
14654
|
}
|
|
14483
|
-
function routeCacheKey(target, identity, consumerIdentity, consumerCapabilities) {
|
|
14655
|
+
function routeCacheKey(target, identity, consumerIdentity, consumerCapabilities, scope) {
|
|
14484
14656
|
const consumerPart = consumerIdentity ? `${consumerIdentity.module_id}\x00${consumerIdentity.launch_nonce}` : "";
|
|
14485
14657
|
const capabilitiesPart = [...consumerCapabilities].sort().join("\x00");
|
|
14486
|
-
|
|
14658
|
+
const scopePart = scope ? JSON.stringify([scope.owner.kind, scope.owner.kind === "reserved" ? scope.owner.module_id : null, scope.ref, scope.scopeEpoch]) : "";
|
|
14659
|
+
return `${target.kind}\x00${target.module_id}\x00${identity.project_root}\x00${identity.harness}\x00${identity.session}\x00${consumerPart}\x00${capabilitiesPart}\x00${scopePart}`;
|
|
14660
|
+
}
|
|
14661
|
+
function validatedRouteScope(scope) {
|
|
14662
|
+
if (scope === undefined)
|
|
14663
|
+
return;
|
|
14664
|
+
if (!scope || typeof scope.owner !== "object" || !scope.owner || !["reserved", "direct", "unverified"].includes(scope.owner.kind) || scope.owner.kind === "reserved" && (typeof scope.owner.module_id !== "string" || scope.owner.module_id.length === 0)) {
|
|
14665
|
+
throw new SubcError("scope.owner must be a Principal with a non-empty reserved module_id", "invalid_scope");
|
|
14666
|
+
}
|
|
14667
|
+
if (typeof scope.ref !== "string" || scope.ref.length === 0) {
|
|
14668
|
+
throw new SubcError("scope.ref must be a non-empty string", "invalid_scope");
|
|
14669
|
+
}
|
|
14670
|
+
if (scope.scopeEpoch === undefined) {
|
|
14671
|
+
throw new SubcError("scope.scopeEpoch is required", "scope_epoch_required");
|
|
14672
|
+
}
|
|
14673
|
+
if (!Number.isSafeInteger(scope.scopeEpoch) || scope.scopeEpoch < 0) {
|
|
14674
|
+
throw new SubcError("scope.scopeEpoch must be a safe non-negative integer", "invalid_scope");
|
|
14675
|
+
}
|
|
14676
|
+
return { owner: { ...scope.owner }, ref: scope.ref, scopeEpoch: scope.scopeEpoch };
|
|
14487
14677
|
}
|
|
14488
14678
|
function routeOpenConsumerIdentity(opts = {}) {
|
|
14489
14679
|
if (opts.consumerIdentity !== undefined)
|
|
@@ -15004,6 +15194,41 @@ class SubcRouteClosedMidCallError extends SubcCallError {
|
|
|
15004
15194
|
this.reason = reason;
|
|
15005
15195
|
}
|
|
15006
15196
|
}
|
|
15197
|
+
|
|
15198
|
+
class SubcFrameReadTimeoutError extends SubcCallError {
|
|
15199
|
+
constructor(cause) {
|
|
15200
|
+
super("outcome_unknown", "the command may have run; timed out waiting for frame body bytes from the AFT daemon", "frame_read_timeout", cause);
|
|
15201
|
+
this.name = "SubcFrameReadTimeoutError";
|
|
15202
|
+
}
|
|
15203
|
+
}
|
|
15204
|
+
function isFrameBodyReadTimeoutMessage(message) {
|
|
15205
|
+
return typeof message === "string" && (/timed out waiting for \d+ bytes/i.test(message) || message.includes("timed out waiting for frame body bytes"));
|
|
15206
|
+
}
|
|
15207
|
+
function isSubcFrameReadTimeoutError(error) {
|
|
15208
|
+
if (error instanceof SubcFrameReadTimeoutError)
|
|
15209
|
+
return true;
|
|
15210
|
+
if (error instanceof SocketTimeoutError && isFrameBodyReadTimeoutMessage(error.message)) {
|
|
15211
|
+
return true;
|
|
15212
|
+
}
|
|
15213
|
+
if (error instanceof SubcError && isFrameBodyReadTimeoutMessage(error.message)) {
|
|
15214
|
+
return true;
|
|
15215
|
+
}
|
|
15216
|
+
if (error instanceof SubcCallError) {
|
|
15217
|
+
if (error.code === "frame_read_timeout")
|
|
15218
|
+
return true;
|
|
15219
|
+
if (error.cause !== undefined && error.cause !== error && isSubcFrameReadTimeoutError(error.cause)) {
|
|
15220
|
+
return true;
|
|
15221
|
+
}
|
|
15222
|
+
if (isFrameBodyReadTimeoutMessage(error.message))
|
|
15223
|
+
return true;
|
|
15224
|
+
}
|
|
15225
|
+
return false;
|
|
15226
|
+
}
|
|
15227
|
+
function frameReadTimeoutError(error) {
|
|
15228
|
+
if (!isSubcFrameReadTimeoutError(error))
|
|
15229
|
+
return error;
|
|
15230
|
+
return error instanceof SubcFrameReadTimeoutError ? error : new SubcFrameReadTimeoutError(error);
|
|
15231
|
+
}
|
|
15007
15232
|
function isLocalRouteCloseRejection(error) {
|
|
15008
15233
|
return error instanceof SubcError && error.closeReason === "closed_by_caller";
|
|
15009
15234
|
}
|
|
@@ -15030,6 +15255,8 @@ function isSubcClientClosedError(error) {
|
|
|
15030
15255
|
return error instanceof SubcError && error.message === "client closed";
|
|
15031
15256
|
}
|
|
15032
15257
|
function isSocketDeathError(error, depth = 0) {
|
|
15258
|
+
if (isSubcFrameReadTimeoutError(error))
|
|
15259
|
+
return true;
|
|
15033
15260
|
if (error instanceof SubcError && error.closeReason === "connection_lost")
|
|
15034
15261
|
return true;
|
|
15035
15262
|
if (error instanceof SubcCallError) {
|
|
@@ -15072,6 +15299,12 @@ class SubcLivenessProbeTimeoutError extends Error {
|
|
|
15072
15299
|
}
|
|
15073
15300
|
}
|
|
15074
15301
|
var AFT_MODULE_ID = "aft";
|
|
15302
|
+
var HOST_SCOPE_LOOKUP_TIMEOUT_MS = 2000;
|
|
15303
|
+
var HOST_SCOPE_RETRY_FLOOR_MS = 5000;
|
|
15304
|
+
var HOST_SCOPE_RETRY_CAP_MS = 300000;
|
|
15305
|
+
function isHostScopeSyncRefusal(code) {
|
|
15306
|
+
return code === "scope_not_live" || code === "scope_not_synced" || code === "scope_changed";
|
|
15307
|
+
}
|
|
15075
15308
|
var MAX_CONSECUTIVE_TRANSPORT_FAILURES = 3;
|
|
15076
15309
|
var LIVENESS_PROBE_TIMEOUT_MS = 15000;
|
|
15077
15310
|
var REQUEST_DEADLINE_CODE = "request_deadline";
|
|
@@ -15150,7 +15383,7 @@ class BgSubscription {
|
|
|
15150
15383
|
identity;
|
|
15151
15384
|
acquireClient;
|
|
15152
15385
|
dropClient;
|
|
15153
|
-
|
|
15386
|
+
openRoute;
|
|
15154
15387
|
onNudge;
|
|
15155
15388
|
sleep;
|
|
15156
15389
|
canAttach;
|
|
@@ -15161,21 +15394,25 @@ class BgSubscription {
|
|
|
15161
15394
|
isCurrent;
|
|
15162
15395
|
noteClientAlive;
|
|
15163
15396
|
noteUnansweredCall;
|
|
15397
|
+
onRouteAttached;
|
|
15164
15398
|
stopped = false;
|
|
15165
15399
|
current = null;
|
|
15166
15400
|
attached = null;
|
|
15401
|
+
route = null;
|
|
15167
15402
|
loop;
|
|
15168
15403
|
lifecycleLogState = new Map;
|
|
15169
15404
|
nudgeReceiptLogState = null;
|
|
15170
|
-
constructor(identity, acquireClient, dropClient,
|
|
15405
|
+
constructor(identity, acquireClient, dropClient, openRoute, onNudge, sleep, canAttach, onRootAttachFailure, onDormant, dispatchProbeIntervalMs, nudgeRef, isCurrent = () => true, noteClientAlive = () => {
|
|
15171
15406
|
return;
|
|
15172
15407
|
}, noteUnansweredCall = () => {
|
|
15173
15408
|
return;
|
|
15409
|
+
}, onRouteAttached = () => {
|
|
15410
|
+
return;
|
|
15174
15411
|
}) {
|
|
15175
15412
|
this.identity = identity;
|
|
15176
15413
|
this.acquireClient = acquireClient;
|
|
15177
15414
|
this.dropClient = dropClient;
|
|
15178
|
-
this.
|
|
15415
|
+
this.openRoute = openRoute;
|
|
15179
15416
|
this.onNudge = onNudge;
|
|
15180
15417
|
this.sleep = sleep;
|
|
15181
15418
|
this.canAttach = canAttach;
|
|
@@ -15186,11 +15423,15 @@ class BgSubscription {
|
|
|
15186
15423
|
this.isCurrent = isCurrent;
|
|
15187
15424
|
this.noteClientAlive = noteClientAlive;
|
|
15188
15425
|
this.noteUnansweredCall = noteUnansweredCall;
|
|
15426
|
+
this.onRouteAttached = onRouteAttached;
|
|
15189
15427
|
this.loop = this.run();
|
|
15190
15428
|
}
|
|
15191
15429
|
get attachedClient() {
|
|
15192
15430
|
return this.attached;
|
|
15193
15431
|
}
|
|
15432
|
+
get attachedRoute() {
|
|
15433
|
+
return this.route;
|
|
15434
|
+
}
|
|
15194
15435
|
async stop() {
|
|
15195
15436
|
this.stopped = true;
|
|
15196
15437
|
const sub = this.current;
|
|
@@ -15315,7 +15556,7 @@ class BgSubscription {
|
|
|
15315
15556
|
}
|
|
15316
15557
|
let route;
|
|
15317
15558
|
try {
|
|
15318
|
-
route = await
|
|
15559
|
+
route = await this.openRoute(client);
|
|
15319
15560
|
} catch (err) {
|
|
15320
15561
|
if (this.isCurrent() && this.onRootAttachFailure(err)) {
|
|
15321
15562
|
if (reconnecting)
|
|
@@ -15373,6 +15614,8 @@ class BgSubscription {
|
|
|
15373
15614
|
});
|
|
15374
15615
|
this.current = sub;
|
|
15375
15616
|
this.attached = client;
|
|
15617
|
+
this.route = route;
|
|
15618
|
+
this.onRouteAttached();
|
|
15376
15619
|
stopDispatchProbe = this.startDispatchProbe(client, routeId);
|
|
15377
15620
|
this.info("subscription-open", `subscription open channel=${routeId}`);
|
|
15378
15621
|
if (reconnecting) {
|
|
@@ -15415,6 +15658,7 @@ class BgSubscription {
|
|
|
15415
15658
|
stopDispatchProbe();
|
|
15416
15659
|
this.current = null;
|
|
15417
15660
|
this.attached = null;
|
|
15661
|
+
this.route = null;
|
|
15418
15662
|
safeCloseRoute(client, route);
|
|
15419
15663
|
}
|
|
15420
15664
|
await this.backoff(backoffAttempt++);
|
|
@@ -15430,7 +15674,7 @@ function isRecord(value) {
|
|
|
15430
15674
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15431
15675
|
}
|
|
15432
15676
|
function isRouteProvenAbsentError(err) {
|
|
15433
|
-
return err instanceof SubcError && err.code === "unknown_channel" || err instanceof StaleRouteHandleError;
|
|
15677
|
+
return err instanceof SubcError && (err.code === "unknown_channel" || err.code === "stale_route_epoch") || err instanceof StaleRouteHandleError;
|
|
15434
15678
|
}
|
|
15435
15679
|
function isAbsentRootRouteError(err) {
|
|
15436
15680
|
return isRecord(err) && err.code === "config_divergence" && typeof err.message === "string" && err.message.includes("project root does not exist");
|
|
@@ -15557,6 +15801,8 @@ class SubcTransport {
|
|
|
15557
15801
|
body.preview = true;
|
|
15558
15802
|
if (options?.workerSession === true)
|
|
15559
15803
|
body[WORKER_SESSION_FIELD] = true;
|
|
15804
|
+
if (options?.hostToolNames)
|
|
15805
|
+
body[HOST_TOOL_NAMES_FIELD] = options.hostToolNames;
|
|
15560
15806
|
body[PRESET_FIELD] = callPresetFor(options?.workerSession === true);
|
|
15561
15807
|
const reply = await this.pool.routeRequest(this.identityFor(sessionId), body, timeoutMs, onProgress, this.generation, abortSignal);
|
|
15562
15808
|
return reliftReply(reply);
|
|
@@ -15568,10 +15814,12 @@ class SubcTransport {
|
|
|
15568
15814
|
}
|
|
15569
15815
|
const { timeoutMs, onProgress, abortSignal } = this.splitOptions(options);
|
|
15570
15816
|
const session = typeof params.session_id === "string" ? params.session_id : undefined;
|
|
15571
|
-
const { [WORKER_SESSION_FIELD]: workerSession, ...args } = params;
|
|
15817
|
+
const { [WORKER_SESSION_FIELD]: workerSession, [HOST_TOOL_NAMES_FIELD]: hostToolNames, ...args } = params;
|
|
15572
15818
|
const body = { name: command, arguments: args };
|
|
15573
15819
|
if (workerSession === true)
|
|
15574
15820
|
body[WORKER_SESSION_FIELD] = true;
|
|
15821
|
+
if (hostToolNames !== undefined)
|
|
15822
|
+
body[HOST_TOOL_NAMES_FIELD] = hostToolNames;
|
|
15575
15823
|
body[PRESET_FIELD] = callPresetFor(workerSession === true);
|
|
15576
15824
|
const editSlotSurvives = this.pool.getEditSlotSurvives();
|
|
15577
15825
|
if (editSlotSurvives !== undefined)
|
|
@@ -15594,6 +15842,10 @@ class SubcTransportPool {
|
|
|
15594
15842
|
connectionFile;
|
|
15595
15843
|
handshakeTimeoutMs;
|
|
15596
15844
|
consumerIdentity;
|
|
15845
|
+
hostScopes;
|
|
15846
|
+
hostScopeCache = new Map;
|
|
15847
|
+
hostScopeRefusals = new Set;
|
|
15848
|
+
hostScopeOutcomes = new Map;
|
|
15597
15849
|
connectFn;
|
|
15598
15850
|
onBgEventsNudge;
|
|
15599
15851
|
onBgEventsNudgeRef;
|
|
@@ -15615,6 +15867,7 @@ class SubcTransportPool {
|
|
|
15615
15867
|
routeReopenRetry = null;
|
|
15616
15868
|
routeReopenRetryMs = null;
|
|
15617
15869
|
sessions = new Map;
|
|
15870
|
+
sessionIndex = new Map;
|
|
15618
15871
|
observedStarts = new Set;
|
|
15619
15872
|
sessionSweep = null;
|
|
15620
15873
|
rootIndex = new Map;
|
|
@@ -15634,6 +15887,7 @@ class SubcTransportPool {
|
|
|
15634
15887
|
this.harness = options.harness;
|
|
15635
15888
|
this.handshakeTimeoutMs = options.handshakeTimeoutMs;
|
|
15636
15889
|
this.consumerIdentity = options.consumerIdentity;
|
|
15890
|
+
this.hostScopes = options.hostScopes ?? false;
|
|
15637
15891
|
this.connectFn = options.connect ?? ((opts) => SubcClient.connect(opts));
|
|
15638
15892
|
this.onBgEventsNudge = options.onBgEventsNudge;
|
|
15639
15893
|
this.onBgEventsNudgeRef = options.onBgEventsNudgeRef;
|
|
@@ -15853,7 +16107,9 @@ class SubcTransportPool {
|
|
|
15853
16107
|
return transport;
|
|
15854
16108
|
}
|
|
15855
16109
|
getBridge(projectRoot) {
|
|
15856
|
-
|
|
16110
|
+
return this.getBridgeForCanonicalRoot(this.canonicalRoot(projectRoot));
|
|
16111
|
+
}
|
|
16112
|
+
getBridgeForCanonicalRoot(root) {
|
|
15857
16113
|
if (this.shuttingDown && this.lifecycleEnabled()) {
|
|
15858
16114
|
throw new SubcTransportShuttingDownError;
|
|
15859
16115
|
}
|
|
@@ -15950,6 +16206,12 @@ class SubcTransportPool {
|
|
|
15950
16206
|
pendingDetach: false
|
|
15951
16207
|
};
|
|
15952
16208
|
this.sessions.set(key, record);
|
|
16209
|
+
let records = this.sessionIndex.get(identity.session);
|
|
16210
|
+
if (!records) {
|
|
16211
|
+
records = new Set;
|
|
16212
|
+
this.sessionIndex.set(identity.session, records);
|
|
16213
|
+
}
|
|
16214
|
+
records.add(record);
|
|
15953
16215
|
let keys = this.rootIndex.get(root);
|
|
15954
16216
|
if (!keys) {
|
|
15955
16217
|
keys = new Set;
|
|
@@ -16127,6 +16389,10 @@ class SubcTransportPool {
|
|
|
16127
16389
|
log(`subc bg_events: ${message}${suffix}`, { sessionId: record.identity.session });
|
|
16128
16390
|
}
|
|
16129
16391
|
removeIndexMembership(record) {
|
|
16392
|
+
const records = this.sessionIndex.get(record.identity.session);
|
|
16393
|
+
records?.delete(record);
|
|
16394
|
+
if (records?.size === 0)
|
|
16395
|
+
this.sessionIndex.delete(record.identity.session);
|
|
16130
16396
|
const keys = this.rootIndex.get(record.canonicalRoot);
|
|
16131
16397
|
if (!keys)
|
|
16132
16398
|
return;
|
|
@@ -16139,6 +16405,7 @@ class SubcTransportPool {
|
|
|
16139
16405
|
this.sessions.delete(record.identityKey);
|
|
16140
16406
|
this.removeIndexMembership(record);
|
|
16141
16407
|
}
|
|
16408
|
+
this.stopUnusedHostScopeRetry(record.identity.session);
|
|
16142
16409
|
}
|
|
16143
16410
|
detachSession(key, reason) {
|
|
16144
16411
|
const record = this.sessions.get(key);
|
|
@@ -16158,6 +16425,7 @@ class SubcTransportPool {
|
|
|
16158
16425
|
record.routeEntry = null;
|
|
16159
16426
|
if (routeEntry)
|
|
16160
16427
|
routeEntry.closed = true;
|
|
16428
|
+
this.stopUnusedHostScopeRetry(record.identity.session);
|
|
16161
16429
|
return { record, bgSub, routeEntry };
|
|
16162
16430
|
}
|
|
16163
16431
|
async cleanupDetached(detached) {
|
|
@@ -16214,6 +16482,17 @@ class SubcTransportPool {
|
|
|
16214
16482
|
entry.inflight -= 1;
|
|
16215
16483
|
this.closeRetiredRoute(record, entry);
|
|
16216
16484
|
}
|
|
16485
|
+
retireHostToolRoute(record) {
|
|
16486
|
+
const entry = record.routeEntry;
|
|
16487
|
+
if (!entry)
|
|
16488
|
+
return;
|
|
16489
|
+
entry.closed = true;
|
|
16490
|
+
entry.closedBy ??= "route replaced";
|
|
16491
|
+
record.routeEntry = null;
|
|
16492
|
+
record.retiredRoutes.add(entry);
|
|
16493
|
+
this.closeRetiredRoute(record, entry);
|
|
16494
|
+
this.stopUnusedHostScopeRetry(record.identity.session);
|
|
16495
|
+
}
|
|
16217
16496
|
async routeRequest(identity, body, timeoutMs, onProgress, expectedGeneration, abortSignal) {
|
|
16218
16497
|
const callStartedAt = performance.now();
|
|
16219
16498
|
const callDeadlineMs = timeoutMs ?? SUBC_DEFAULT_REQUEST_TIMEOUT_MS;
|
|
@@ -16352,15 +16631,14 @@ class SubcTransportPool {
|
|
|
16352
16631
|
const clearRouteEntry = (entry) => {
|
|
16353
16632
|
if (record.routeEntry !== entry)
|
|
16354
16633
|
return;
|
|
16355
|
-
|
|
16356
|
-
entry.closedBy ??= "route replaced";
|
|
16357
|
-
record.routeEntry = null;
|
|
16358
|
-
record.retiredRoutes.add(entry);
|
|
16359
|
-
this.closeRetiredRoute(record, entry);
|
|
16634
|
+
this.retireHostToolRoute(record);
|
|
16360
16635
|
};
|
|
16361
16636
|
const handleRequestFailure = (error, entry) => {
|
|
16362
16637
|
const aborted = error instanceof Error && error.name === "AbortError";
|
|
16363
16638
|
const ownsClient = this.isCurrentSession(key, record) && this.client === client && !this.isReapInduced(record);
|
|
16639
|
+
if (ownsClient && error instanceof SubcError && error.closeReason?.startsWith("scope_")) {
|
|
16640
|
+
this.invalidateHostScope(identity.session);
|
|
16641
|
+
}
|
|
16364
16642
|
if (!aborted && ownsClient && isSocketDeathError(error)) {
|
|
16365
16643
|
this.dropClient(client, {
|
|
16366
16644
|
error,
|
|
@@ -16369,7 +16647,7 @@ class SubcTransportPool {
|
|
|
16369
16647
|
route: routeLabel(entry.handle)
|
|
16370
16648
|
});
|
|
16371
16649
|
}
|
|
16372
|
-
if (aborted || isRouteProvenAbsentError(error) || error instanceof SubcError && error.closeReason !== undefined) {
|
|
16650
|
+
if (aborted || isRouteProvenAbsentError(error) || error instanceof SubcError && error.closeReason !== undefined || isSubcFrameReadTimeoutError(error)) {
|
|
16373
16651
|
clearRouteEntry(entry);
|
|
16374
16652
|
}
|
|
16375
16653
|
if (aborted || !ownsClient || this.client !== client)
|
|
@@ -16448,10 +16726,13 @@ class SubcTransportPool {
|
|
|
16448
16726
|
this.resetRouteReopenBackoff();
|
|
16449
16727
|
return reply;
|
|
16450
16728
|
} catch (requestError) {
|
|
16451
|
-
const error = routeClosedMidCallError(requestError, routeAndEntry.entry, record);
|
|
16729
|
+
const error = frameReadTimeoutError(routeClosedMidCallError(requestError, routeAndEntry.entry, record));
|
|
16452
16730
|
if (this.isReapInduced(record))
|
|
16453
16731
|
throw this.annotateReapError(error, record);
|
|
16454
16732
|
const ownsRoute = this.isCurrentSession(key, record) && this.client === client;
|
|
16733
|
+
if (ownsRoute && (error instanceof SubcError && error.code === "stale_route_epoch" || error instanceof StaleRouteHandleError)) {
|
|
16734
|
+
this.invalidateHostScope(identity.session);
|
|
16735
|
+
}
|
|
16455
16736
|
if (ownsRoute && isRouteRequestReloadRefusal(error)) {
|
|
16456
16737
|
clearRouteEntry(routeAndEntry.entry);
|
|
16457
16738
|
lastRefusal = error;
|
|
@@ -16528,9 +16809,14 @@ class SubcTransportPool {
|
|
|
16528
16809
|
return this.client;
|
|
16529
16810
|
if (this.connecting)
|
|
16530
16811
|
return this.connecting;
|
|
16812
|
+
let owner;
|
|
16531
16813
|
this.connecting = this.connectFn({
|
|
16532
16814
|
connectionFile: this.connectionFile,
|
|
16533
|
-
handshakeTimeoutMs: this.handshakeTimeoutMs
|
|
16815
|
+
handshakeTimeoutMs: this.handshakeTimeoutMs,
|
|
16816
|
+
onControlPush: (push) => {
|
|
16817
|
+
if (owner && this.client === owner)
|
|
16818
|
+
this.onScopeRouteClose(push);
|
|
16819
|
+
}
|
|
16534
16820
|
}).then((client) => {
|
|
16535
16821
|
this.connecting = null;
|
|
16536
16822
|
if (this.shuttingDown) {
|
|
@@ -16540,6 +16826,7 @@ class SubcTransportPool {
|
|
|
16540
16826
|
throw new SubcTransportShuttingDownError;
|
|
16541
16827
|
}
|
|
16542
16828
|
this.client = client;
|
|
16829
|
+
owner = client;
|
|
16543
16830
|
this.hadWorkingClient = true;
|
|
16544
16831
|
this.transportFailures = 0;
|
|
16545
16832
|
return client;
|
|
@@ -16569,9 +16856,7 @@ class SubcTransportPool {
|
|
|
16569
16856
|
closedBy: null
|
|
16570
16857
|
};
|
|
16571
16858
|
this.assertRootCanAttach(record.canonicalRoot);
|
|
16572
|
-
const opening =
|
|
16573
|
-
consumerIdentity: this.consumerIdentity
|
|
16574
|
-
}).then((route) => {
|
|
16859
|
+
const opening = this.openHostToolRoute(client, identity, record, entry).then((route) => {
|
|
16575
16860
|
if (!this.isCurrentSession(record.identityKey, record) || record.routeEntry !== entry || entry.closed || this.client !== client || this.lifecycleEnabled() && !this.isCurrentLiveGeneration(record.canonicalRoot, record.generation)) {
|
|
16576
16861
|
safeCloseRoute(client, route);
|
|
16577
16862
|
if (record.routeEntry === entry)
|
|
@@ -16581,6 +16866,12 @@ class SubcTransportPool {
|
|
|
16581
16866
|
entry.handle = route;
|
|
16582
16867
|
entry.opening = null;
|
|
16583
16868
|
this.noteClientAlive(client);
|
|
16869
|
+
const state = this.hostScopeCache.get(identity.session);
|
|
16870
|
+
if (state)
|
|
16871
|
+
this.scheduleHostScopeRetry(identity.session, state);
|
|
16872
|
+
if (state?.answer?.result === "scope" && !state.scopeBindPending && !entry.hostScope && !this.hostScopeRefusals.has(identity.session)) {
|
|
16873
|
+
this.promoteHostScope(identity.session);
|
|
16874
|
+
}
|
|
16584
16875
|
return route;
|
|
16585
16876
|
}).catch((error) => {
|
|
16586
16877
|
const currentEntry = this.isCurrentSession(record.identityKey, record) && record.routeEntry === entry;
|
|
@@ -16588,6 +16879,7 @@ class SubcTransportPool {
|
|
|
16588
16879
|
entry.closed = true;
|
|
16589
16880
|
record.routeEntry = null;
|
|
16590
16881
|
}
|
|
16882
|
+
this.stopUnusedHostScopeRetry(identity.session);
|
|
16591
16883
|
if (!this.isCurrentSession(record.identityKey, record) && !(error instanceof RouteTornDownError)) {
|
|
16592
16884
|
throw new RouteTornDownError("subc route opened after session closed");
|
|
16593
16885
|
}
|
|
@@ -16600,6 +16892,327 @@ class SubcTransportPool {
|
|
|
16600
16892
|
record.routeEntry = entry;
|
|
16601
16893
|
return { route: await opening, entry };
|
|
16602
16894
|
}
|
|
16895
|
+
hostScope(client, identity) {
|
|
16896
|
+
if (this.hostScopeRefusals.has(identity.session))
|
|
16897
|
+
return Promise.resolve(undefined);
|
|
16898
|
+
let state = this.hostScopeCache.get(identity.session);
|
|
16899
|
+
if (!state) {
|
|
16900
|
+
state = { retryAt: 0, retryMs: HOST_SCOPE_RETRY_FLOOR_MS };
|
|
16901
|
+
this.hostScopeCache.set(identity.session, state);
|
|
16902
|
+
}
|
|
16903
|
+
if (state.answer) {
|
|
16904
|
+
return Promise.resolve(state.answer.result === "scope" && !state.scopeBindPending ? state.answer.scope : undefined);
|
|
16905
|
+
}
|
|
16906
|
+
if (state.pending)
|
|
16907
|
+
return state.pending;
|
|
16908
|
+
if (Date.now() < state.retryAt)
|
|
16909
|
+
return Promise.resolve(undefined);
|
|
16910
|
+
const current = state;
|
|
16911
|
+
clearTimeout(current.retryTimer);
|
|
16912
|
+
current.retryTimer = undefined;
|
|
16913
|
+
const abort = new AbortController;
|
|
16914
|
+
current.lookupAbort = abort;
|
|
16915
|
+
const lookup = this.readHostScope(client, identity, abort.signal).then((outcome) => {
|
|
16916
|
+
if (this.hostScopeCache.get(identity.session) !== current)
|
|
16917
|
+
return;
|
|
16918
|
+
current.pending = undefined;
|
|
16919
|
+
current.lookupAbort = undefined;
|
|
16920
|
+
if (abort.signal.aborted)
|
|
16921
|
+
return;
|
|
16922
|
+
this.logHostScopeOutcome(identity.session, outcome);
|
|
16923
|
+
if (outcome.result === "scope" || outcome.result === "no_scope") {
|
|
16924
|
+
current.answer = outcome;
|
|
16925
|
+
current.retryAt = 0;
|
|
16926
|
+
current.retryMs = HOST_SCOPE_RETRY_FLOOR_MS;
|
|
16927
|
+
if (outcome.result === "scope")
|
|
16928
|
+
this.promoteHostScope(identity.session);
|
|
16929
|
+
} else {
|
|
16930
|
+
current.retryAt = Date.now() + current.retryMs;
|
|
16931
|
+
current.retryMs = Math.min(current.retryMs * 2, HOST_SCOPE_RETRY_CAP_MS);
|
|
16932
|
+
this.scheduleHostScopeRetry(identity.session, current);
|
|
16933
|
+
}
|
|
16934
|
+
return outcome.result === "scope" ? outcome.scope : undefined;
|
|
16935
|
+
});
|
|
16936
|
+
current.pending = lookup;
|
|
16937
|
+
return lookup;
|
|
16938
|
+
}
|
|
16939
|
+
unscopedHostRecord(session) {
|
|
16940
|
+
for (const record of this.sessionIndex.get(session) ?? []) {
|
|
16941
|
+
const entry = record.routeEntry;
|
|
16942
|
+
if (record.identity.session === session && !record.closed && (entry && !entry.closed && !entry.hostScope && entry.client === this.client && (entry.handle || entry.opening) || record.bgSub?.attachedClient === this.client && record.bgSub?.attachedRoute))
|
|
16943
|
+
return record;
|
|
16944
|
+
}
|
|
16945
|
+
return;
|
|
16946
|
+
}
|
|
16947
|
+
cancelHostScopeRetry(state) {
|
|
16948
|
+
clearTimeout(state.retryTimer);
|
|
16949
|
+
state.retryTimer = undefined;
|
|
16950
|
+
state.lookupAbort?.abort();
|
|
16951
|
+
}
|
|
16952
|
+
stopUnusedHostScopeRetry(session) {
|
|
16953
|
+
const state = this.hostScopeCache.get(session);
|
|
16954
|
+
if (state && !this.unscopedHostRecord(session)) {
|
|
16955
|
+
if (state.retryTimer || state.scopeBindPending && state.bindPending)
|
|
16956
|
+
log(`subc head scope: session=${session} retry stopped reason=route_closed`);
|
|
16957
|
+
this.cancelHostScopeRetry(state);
|
|
16958
|
+
state.scopeBindPending = false;
|
|
16959
|
+
}
|
|
16960
|
+
}
|
|
16961
|
+
scheduleHostScopeRetry(session, state) {
|
|
16962
|
+
if (this.shuttingDown || this.hostScopeCache.get(session) !== state || state.answer && !state.scopeBindPending || state.pending || state.bindPending || state.retryTimer || this.hostScopeRefusals.has(session) || !this.unscopedHostRecord(session))
|
|
16963
|
+
return;
|
|
16964
|
+
state.retryTimer = setTimeout(() => {
|
|
16965
|
+
state.retryTimer = undefined;
|
|
16966
|
+
const record = this.unscopedHostRecord(session);
|
|
16967
|
+
const client = record?.routeEntry?.client ?? record?.bgSub?.attachedClient;
|
|
16968
|
+
if (this.shuttingDown || this.hostScopeCache.get(session) !== state || !record || !client)
|
|
16969
|
+
return;
|
|
16970
|
+
let retry;
|
|
16971
|
+
if (state.scopeBindPending && state.answer?.result === "scope") {
|
|
16972
|
+
state.bindPending = this.retryHostScopeBind(client, record.identity, state, state.answer.scope).finally(() => {
|
|
16973
|
+
state.bindPending = undefined;
|
|
16974
|
+
this.scheduleHostScopeRetry(session, state);
|
|
16975
|
+
});
|
|
16976
|
+
retry = state.bindPending;
|
|
16977
|
+
} else {
|
|
16978
|
+
retry = this.hostScope(client, record.identity);
|
|
16979
|
+
}
|
|
16980
|
+
retry.catch((error) => {
|
|
16981
|
+
warn(`subc head scope: background retry failed code=${error instanceof Error ? error.name : "Error"}`);
|
|
16982
|
+
});
|
|
16983
|
+
}, Math.max(0, state.retryAt - Date.now()));
|
|
16984
|
+
state.retryTimer.unref();
|
|
16985
|
+
}
|
|
16986
|
+
deferHostScopeBind(session, state, code) {
|
|
16987
|
+
state.scopeBindPending = true;
|
|
16988
|
+
if (Date.now() >= state.retryAt) {
|
|
16989
|
+
state.retryAt = Date.now() + state.retryMs;
|
|
16990
|
+
state.retryMs = Math.min(state.retryMs * 2, HOST_SCOPE_RETRY_CAP_MS);
|
|
16991
|
+
}
|
|
16992
|
+
warn(`subc head scope: session=${session} scoped bind refused code=${code}; binding unscoped; retry in=${Math.max(0, state.retryAt - Date.now())}ms`);
|
|
16993
|
+
this.scheduleHostScopeRetry(session, state);
|
|
16994
|
+
}
|
|
16995
|
+
refuseHostScopeBind(session, state, code) {
|
|
16996
|
+
if (!this.hostScopeRefusals.has(session)) {
|
|
16997
|
+
this.hostScopeRefusals.add(session);
|
|
16998
|
+
warn(`subc head scope: session=${session} scoped bind refused code=${code}; binding unscoped; retry stopped`);
|
|
16999
|
+
}
|
|
17000
|
+
if (state) {
|
|
17001
|
+
state.scopeBindPending = false;
|
|
17002
|
+
this.cancelHostScopeRetry(state);
|
|
17003
|
+
}
|
|
17004
|
+
}
|
|
17005
|
+
async retryHostScopeBind(client, identity, state, scope) {
|
|
17006
|
+
try {
|
|
17007
|
+
const route = await client.routeOpen({ kind: "tool_provider", module_id: AFT_MODULE_ID }, identity, { consumerIdentity: this.consumerIdentity, scope });
|
|
17008
|
+
safeCloseRoute(client, route);
|
|
17009
|
+
if (this.hostScopeCache.get(identity.session) !== state || !state.scopeBindPending || this.shuttingDown || client !== this.client || !this.unscopedHostRecord(identity.session))
|
|
17010
|
+
return;
|
|
17011
|
+
state.scopeBindPending = false;
|
|
17012
|
+
state.retryAt = 0;
|
|
17013
|
+
state.retryMs = HOST_SCOPE_RETRY_FLOOR_MS;
|
|
17014
|
+
log(`subc head scope: session=${identity.session} scoped bind recovered ref=${scope.ref.slice(0, 8)} epoch=${scope.scopeEpoch}`);
|
|
17015
|
+
this.promoteHostScope(identity.session);
|
|
17016
|
+
} catch (error) {
|
|
17017
|
+
if (this.hostScopeCache.get(identity.session) !== state || !state.scopeBindPending || this.shuttingDown || !this.unscopedHostRecord(identity.session))
|
|
17018
|
+
return;
|
|
17019
|
+
if (error instanceof SubcError && error.code && !isSocketDeathError(error) && !isRouteOpenReloadWindowError(error) && !isHostScopeSyncRefusal(error.code)) {
|
|
17020
|
+
this.refuseHostScopeBind(identity.session, state, error.code);
|
|
17021
|
+
} else {
|
|
17022
|
+
this.deferHostScopeBind(identity.session, state, error instanceof SubcError ? error.code ?? error.name : "Error");
|
|
17023
|
+
}
|
|
17024
|
+
}
|
|
17025
|
+
}
|
|
17026
|
+
promoteHostScope(session) {
|
|
17027
|
+
for (const record of this.sessions.values()) {
|
|
17028
|
+
const entry = record.routeEntry;
|
|
17029
|
+
if (record.identity.session !== session)
|
|
17030
|
+
continue;
|
|
17031
|
+
if (entry?.handle && !entry.hostScope)
|
|
17032
|
+
this.retireHostToolRoute(record);
|
|
17033
|
+
const sub = record.bgSub;
|
|
17034
|
+
if (!sub?.attachedRoute)
|
|
17035
|
+
continue;
|
|
17036
|
+
sub.stop().then(() => {
|
|
17037
|
+
if (record.bgSub !== sub)
|
|
17038
|
+
return;
|
|
17039
|
+
record.bgSub = null;
|
|
17040
|
+
this.ensureBgSubscription(record.identity, record);
|
|
17041
|
+
});
|
|
17042
|
+
}
|
|
17043
|
+
}
|
|
17044
|
+
logHostScopeOutcome(session, outcome) {
|
|
17045
|
+
let key;
|
|
17046
|
+
let detail;
|
|
17047
|
+
switch (outcome.result) {
|
|
17048
|
+
case "scope":
|
|
17049
|
+
key = JSON.stringify([outcome.result, outcome.scope.ref, outcome.scope.scopeEpoch]);
|
|
17050
|
+
detail = `scope ref=${outcome.scope.ref.slice(0, 8)} epoch=${outcome.scope.scopeEpoch}`;
|
|
17051
|
+
break;
|
|
17052
|
+
case "error":
|
|
17053
|
+
key = JSON.stringify([outcome.result, outcome.code]);
|
|
17054
|
+
detail = `error code=${outcome.code} after=${outcome.afterMs}`;
|
|
17055
|
+
break;
|
|
17056
|
+
case "timeout":
|
|
17057
|
+
key = outcome.result;
|
|
17058
|
+
detail = `timeout after=${outcome.afterMs}`;
|
|
17059
|
+
break;
|
|
17060
|
+
default:
|
|
17061
|
+
key = outcome.result;
|
|
17062
|
+
detail = outcome.result;
|
|
17063
|
+
}
|
|
17064
|
+
if (this.hostScopeOutcomes.get(session) === key)
|
|
17065
|
+
return;
|
|
17066
|
+
this.hostScopeOutcomes.set(session, key);
|
|
17067
|
+
log(`subc head scope: session=${session} result=${detail}`);
|
|
17068
|
+
}
|
|
17069
|
+
async readHostScope(client, identity, signal) {
|
|
17070
|
+
const startedAt = Date.now();
|
|
17071
|
+
const timeout = () => ({ result: "timeout", afterMs: Date.now() - startedAt });
|
|
17072
|
+
let expired = false;
|
|
17073
|
+
let timer;
|
|
17074
|
+
const abort = new AbortController;
|
|
17075
|
+
let cancel;
|
|
17076
|
+
const cancelled = new Promise((resolve) => {
|
|
17077
|
+
cancel = () => {
|
|
17078
|
+
expired = true;
|
|
17079
|
+
abort.abort();
|
|
17080
|
+
resolve({ result: "error", code: "AbortError", afterMs: Date.now() - startedAt });
|
|
17081
|
+
};
|
|
17082
|
+
signal.addEventListener("abort", cancel, { once: true });
|
|
17083
|
+
});
|
|
17084
|
+
const read = async () => {
|
|
17085
|
+
let route;
|
|
17086
|
+
try {
|
|
17087
|
+
route = await client.routeOpen({ kind: "management_surface", module_id: "prefrontal-core" }, identity, { consumerIdentity: this.consumerIdentity });
|
|
17088
|
+
if (expired)
|
|
17089
|
+
return timeout();
|
|
17090
|
+
const envelope = await client.request(route, {
|
|
17091
|
+
method: "session.scope_identity",
|
|
17092
|
+
params: { harness: identity.harness, session: identity.session }
|
|
17093
|
+
}, { timeoutMs: HOST_SCOPE_LOOKUP_TIMEOUT_MS, signal: abort.signal });
|
|
17094
|
+
const reply = isRecord(envelope) ? envelope.result : undefined;
|
|
17095
|
+
if (isRecord(reply) && reply.answer === "no_scope")
|
|
17096
|
+
return { result: "no_scope" };
|
|
17097
|
+
if (!isRecord(reply) || !isRecord(reply.owner) || reply.owner.kind !== "reserved" || reply.owner.module_id !== "prefrontal-core" || typeof reply.ref !== "string" || reply.ref.length === 0 || !Number.isSafeInteger(reply.scope_epoch) || reply.scope_epoch < 0)
|
|
17098
|
+
return { result: "malformed" };
|
|
17099
|
+
return {
|
|
17100
|
+
result: "scope",
|
|
17101
|
+
scope: {
|
|
17102
|
+
owner: { kind: "reserved", module_id: "prefrontal-core" },
|
|
17103
|
+
ref: reply.ref,
|
|
17104
|
+
scopeEpoch: reply.scope_epoch
|
|
17105
|
+
}
|
|
17106
|
+
};
|
|
17107
|
+
} catch (error) {
|
|
17108
|
+
if (expired)
|
|
17109
|
+
return timeout();
|
|
17110
|
+
return {
|
|
17111
|
+
result: "error",
|
|
17112
|
+
code: error instanceof SubcError && error.code ? error.code : error instanceof Error ? error.name : "Error",
|
|
17113
|
+
afterMs: Date.now() - startedAt
|
|
17114
|
+
};
|
|
17115
|
+
} finally {
|
|
17116
|
+
if (route)
|
|
17117
|
+
safeCloseRoute(client, route);
|
|
17118
|
+
}
|
|
17119
|
+
};
|
|
17120
|
+
try {
|
|
17121
|
+
return await Promise.race([
|
|
17122
|
+
read(),
|
|
17123
|
+
cancelled,
|
|
17124
|
+
new Promise((resolve) => {
|
|
17125
|
+
timer = setTimeout(() => {
|
|
17126
|
+
expired = true;
|
|
17127
|
+
abort.abort();
|
|
17128
|
+
resolve(timeout());
|
|
17129
|
+
}, HOST_SCOPE_LOOKUP_TIMEOUT_MS);
|
|
17130
|
+
})
|
|
17131
|
+
]);
|
|
17132
|
+
} finally {
|
|
17133
|
+
clearTimeout(timer);
|
|
17134
|
+
signal.removeEventListener("abort", cancel);
|
|
17135
|
+
}
|
|
17136
|
+
}
|
|
17137
|
+
openHostToolRoute(client, identity, record, entry) {
|
|
17138
|
+
if (!this.hostScopes || !["opencode", "pi"].includes(identity.harness) || identity.session === DEFAULT_SESSION_ID) {
|
|
17139
|
+
return client.routeOpen({ kind: "tool_provider", module_id: AFT_MODULE_ID }, identity, {
|
|
17140
|
+
consumerIdentity: this.consumerIdentity
|
|
17141
|
+
});
|
|
17142
|
+
}
|
|
17143
|
+
return this.openScopedHostToolRoute(client, identity, record, entry);
|
|
17144
|
+
}
|
|
17145
|
+
async openScopedHostToolRoute(client, identity, record, entry) {
|
|
17146
|
+
const scope = await this.hostScope(client, identity);
|
|
17147
|
+
this.assertRecordLive(record);
|
|
17148
|
+
if (scope) {
|
|
17149
|
+
try {
|
|
17150
|
+
const route = await client.routeOpen({ kind: "tool_provider", module_id: AFT_MODULE_ID }, identity, { consumerIdentity: this.consumerIdentity, scope });
|
|
17151
|
+
if (entry)
|
|
17152
|
+
entry.hostScope = scope;
|
|
17153
|
+
return route;
|
|
17154
|
+
} catch (error) {
|
|
17155
|
+
if (!(error instanceof SubcError) || !error.code || isSocketDeathError(error))
|
|
17156
|
+
throw error;
|
|
17157
|
+
if (isRouteOpenReloadWindowError(error))
|
|
17158
|
+
throw error;
|
|
17159
|
+
this.assertRecordLive(record);
|
|
17160
|
+
const state = this.hostScopeCache.get(identity.session);
|
|
17161
|
+
if (state && isHostScopeSyncRefusal(error.code)) {
|
|
17162
|
+
this.deferHostScopeBind(identity.session, state, error.code);
|
|
17163
|
+
} else {
|
|
17164
|
+
this.refuseHostScopeBind(identity.session, state, error.code);
|
|
17165
|
+
}
|
|
17166
|
+
}
|
|
17167
|
+
}
|
|
17168
|
+
this.assertRecordLive(record);
|
|
17169
|
+
const route = await client.routeOpen({ kind: "tool_provider", module_id: AFT_MODULE_ID }, identity, {
|
|
17170
|
+
consumerIdentity: this.consumerIdentity
|
|
17171
|
+
});
|
|
17172
|
+
if (this.hostScopeCache.get(identity.session)?.answer?.result === "scope" && !this.hostScopeCache.get(identity.session)?.scopeBindPending && !this.hostScopeRefusals.has(identity.session)) {
|
|
17173
|
+
safeCloseRoute(client, route);
|
|
17174
|
+
return this.openScopedHostToolRoute(client, identity, record, entry);
|
|
17175
|
+
}
|
|
17176
|
+
return route;
|
|
17177
|
+
}
|
|
17178
|
+
invalidateHostScope(session) {
|
|
17179
|
+
const state = this.hostScopeCache.get(session);
|
|
17180
|
+
if (state) {
|
|
17181
|
+
if (state.scopeBindPending)
|
|
17182
|
+
log(`subc head scope: session=${session} retry stopped reason=scope_route_closed`);
|
|
17183
|
+
this.cancelHostScopeRetry(state);
|
|
17184
|
+
}
|
|
17185
|
+
this.hostScopeCache.delete(session);
|
|
17186
|
+
this.hostScopeRefusals.delete(session);
|
|
17187
|
+
for (const record of this.sessions.values()) {
|
|
17188
|
+
if (record.identity.session !== session || !record.routeEntry)
|
|
17189
|
+
continue;
|
|
17190
|
+
this.retireHostToolRoute(record);
|
|
17191
|
+
}
|
|
17192
|
+
}
|
|
17193
|
+
onScopeRouteClose(push) {
|
|
17194
|
+
if (push.op !== "route.closed")
|
|
17195
|
+
return;
|
|
17196
|
+
const reason = routeCloseReason(push);
|
|
17197
|
+
if (!reason?.startsWith("scope_")) {
|
|
17198
|
+
for (const record of this.sessions.values()) {
|
|
17199
|
+
const route = record.routeEntry?.handle;
|
|
17200
|
+
if (!route)
|
|
17201
|
+
continue;
|
|
17202
|
+
if (Array.isArray(push.body.channels) && push.body.channels.includes(route.channel) || !Array.isArray(push.body.channels) && push.body.module_id === AFT_MODULE_ID)
|
|
17203
|
+
this.retireHostToolRoute(record);
|
|
17204
|
+
}
|
|
17205
|
+
return;
|
|
17206
|
+
}
|
|
17207
|
+
const sessions = new Set;
|
|
17208
|
+
for (const record of this.sessions.values()) {
|
|
17209
|
+
const routes = [record.routeEntry?.handle, record.bgSub?.attachedRoute].filter((route) => route != null);
|
|
17210
|
+
if (Array.isArray(push.body.channels) && routes.some((route) => push.body.channels.includes(route.channel)) || !Array.isArray(push.body.channels) && push.body.module_id === AFT_MODULE_ID)
|
|
17211
|
+
sessions.add(record.identity.session);
|
|
17212
|
+
}
|
|
17213
|
+
for (const session of sessions)
|
|
17214
|
+
this.invalidateHostScope(session);
|
|
17215
|
+
}
|
|
16603
17216
|
ensureBgSubscription(identity, record) {
|
|
16604
17217
|
if (this.shuttingDown || !this.onBgEventsNudge && !this.onBgEventsNudgeRef)
|
|
16605
17218
|
return;
|
|
@@ -16639,17 +17252,28 @@ class SubcTransportPool {
|
|
|
16639
17252
|
return;
|
|
16640
17253
|
this.logNudgeDelivery("drop-no-compatible-handler", currentRecord, `nudge dropped cause=generation-provenance-unavailable-and-root-session-handler-unwired root=${currentRecord.canonicalRoot}`);
|
|
16641
17254
|
};
|
|
17255
|
+
const binding = {};
|
|
16642
17256
|
let sub = null;
|
|
16643
17257
|
const clearDormantSubscription = () => {
|
|
16644
17258
|
if (sub && record.bgSub === sub)
|
|
16645
17259
|
record.bgSub = null;
|
|
16646
17260
|
};
|
|
16647
|
-
sub = new BgSubscription(identity, () => this.ensureClient(), (client, cause) => this.dropClient(client, cause),
|
|
17261
|
+
sub = new BgSubscription(identity, () => this.ensureClient(), (client, cause) => this.dropClient(client, cause), (client) => {
|
|
17262
|
+
binding.hostScope = undefined;
|
|
17263
|
+
return this.openHostToolRoute(client, identity, record, binding);
|
|
17264
|
+
}, onNudge, this.bgBackoffSleep, () => this.rootCanAttach(record.canonicalRoot), (error) => {
|
|
16648
17265
|
if (!isAbsentRootRouteError(error))
|
|
16649
17266
|
return false;
|
|
16650
17267
|
this.markRootDormant(record.canonicalRoot, error);
|
|
16651
17268
|
return true;
|
|
16652
|
-
}, clearDormantSubscription, this.bgDispatchProbeIntervalMs, nudgeRef, () => this.isCurrentSession(record.identityKey, record) && this.isCurrentLiveGeneration(record.canonicalRoot, record.generation), (client) => this.noteClientAlive(client), (client, cause) => this.noteUnansweredCall(client, cause))
|
|
17269
|
+
}, clearDormantSubscription, this.bgDispatchProbeIntervalMs, nudgeRef, () => this.isCurrentSession(record.identityKey, record) && this.isCurrentLiveGeneration(record.canonicalRoot, record.generation), (client) => this.noteClientAlive(client), (client, cause) => this.noteUnansweredCall(client, cause), () => {
|
|
17270
|
+
const state = this.hostScopeCache.get(identity.session);
|
|
17271
|
+
if (state)
|
|
17272
|
+
this.scheduleHostScopeRetry(identity.session, state);
|
|
17273
|
+
if (state?.answer?.result === "scope" && !state.scopeBindPending && !binding.hostScope && !this.hostScopeRefusals.has(identity.session)) {
|
|
17274
|
+
this.promoteHostScope(identity.session);
|
|
17275
|
+
}
|
|
17276
|
+
});
|
|
16653
17277
|
record.bgSub = sub;
|
|
16654
17278
|
if (!this.rootCanAttach(record.canonicalRoot)) {
|
|
16655
17279
|
record.bgSub = null;
|
|
@@ -16685,6 +17309,8 @@ class SubcTransportPool {
|
|
|
16685
17309
|
record.routeEntry = null;
|
|
16686
17310
|
this.deleteSessionIfEmpty(record.identityKey, record);
|
|
16687
17311
|
}
|
|
17312
|
+
for (const session of this.hostScopeCache.keys())
|
|
17313
|
+
this.stopUnusedHostScopeRetry(session);
|
|
16688
17314
|
this.transportFailures = 0;
|
|
16689
17315
|
try {
|
|
16690
17316
|
client.close();
|
|
@@ -16822,6 +17448,13 @@ class SubcTransportPool {
|
|
|
16822
17448
|
this.generationRejections.clear();
|
|
16823
17449
|
this.observedStarts.clear();
|
|
16824
17450
|
this.dormantRoots.clear();
|
|
17451
|
+
const scopeLookups = [...this.hostScopeCache.values()].flatMap((state) => {
|
|
17452
|
+
this.cancelHostScopeRetry(state);
|
|
17453
|
+
return state.pending ? [state.pending] : [];
|
|
17454
|
+
});
|
|
17455
|
+
this.hostScopeCache.clear();
|
|
17456
|
+
this.hostScopeRefusals.clear();
|
|
17457
|
+
this.hostScopeOutcomes.clear();
|
|
16825
17458
|
const registration = this.lifecycleRegistration;
|
|
16826
17459
|
registration?.deregister();
|
|
16827
17460
|
this.lifecycleRegistration = null;
|
|
@@ -16832,11 +17465,16 @@ class SubcTransportPool {
|
|
|
16832
17465
|
detached.push(owner);
|
|
16833
17466
|
}
|
|
16834
17467
|
this.rootIndex.clear();
|
|
17468
|
+
this.sessionIndex.clear();
|
|
16835
17469
|
this.transports.clear();
|
|
16836
17470
|
const client = this.client;
|
|
16837
17471
|
this.client = null;
|
|
16838
17472
|
const pending = [...this.pendingRootCleanups];
|
|
16839
|
-
await Promise.allSettled([
|
|
17473
|
+
await Promise.allSettled([
|
|
17474
|
+
...detached.map((owner) => this.cleanupDetached(owner)),
|
|
17475
|
+
...pending,
|
|
17476
|
+
...scopeLookups
|
|
17477
|
+
]);
|
|
16840
17478
|
if (client) {
|
|
16841
17479
|
try {
|
|
16842
17480
|
client.close();
|
|
@@ -16850,6 +17488,13 @@ class SubcTransportPool {
|
|
|
16850
17488
|
session: session && session.length > 0 ? session : DEFAULT_SESSION_ID
|
|
16851
17489
|
};
|
|
16852
17490
|
const owner = this.detachSession(identityKey(identity), "session_closed");
|
|
17491
|
+
if (!this.sessionIndex.has(identity.session)) {
|
|
17492
|
+
const state = this.hostScopeCache.get(identity.session);
|
|
17493
|
+
if (state)
|
|
17494
|
+
this.cancelHostScopeRetry(state);
|
|
17495
|
+
this.hostScopeCache.delete(identity.session);
|
|
17496
|
+
this.hostScopeRefusals.delete(identity.session);
|
|
17497
|
+
}
|
|
16853
17498
|
if (owner)
|
|
16854
17499
|
await this.cleanupDetached(owner);
|
|
16855
17500
|
}
|
|
@@ -16890,6 +17535,8 @@ var BASH_TRANSPORT_DISPOSITION = "The transport to the AFT daemon was interrupte
|
|
|
16890
17535
|
var SUBC_MODULE_RESTART_DISPOSITION = "The AFT daemon module restarted while this call was in flight, so its outcome is UNKNOWN: it may or may not have executed. Verify actual state before re-running, and never blind-retry a mutation.";
|
|
16891
17536
|
var BRIDGE_TRANSPORT_UNKNOWN_OUTCOME_DISPOSITION = "The standalone AFT transport failed after this call may have been sent, so its outcome is UNKNOWN: it may or may not have executed. Verify actual state before re-running, and never blind-retry a mutation.";
|
|
16892
17537
|
var SUBC_ROUTE_CLOSED_MID_CALL_DISPOSITION = "The plugin closed this call's route while the call was in flight, so its outcome is UNKNOWN: it may or may not have executed. Verify actual state before re-running, and never blind-retry a mutation.";
|
|
17538
|
+
var BASH_FRAME_READ_TIMEOUT_DISPOSITION = "The command's state is UNKNOWN: the transport timed out while reading the response frame, so it may or may not have executed. Check bash_status if a task ID could have been assigned, and do not re-run side-effecting commands blindly.";
|
|
17539
|
+
var SUBC_FRAME_READ_TIMEOUT_DISPOSITION = "The transport timed out while reading the response frame for this call, so its outcome is UNKNOWN: it may or may not have executed. Verify actual state before re-running, and never blind-retry a mutation.";
|
|
16893
17540
|
function isRouteClosedMidCallError(error) {
|
|
16894
17541
|
return error instanceof SubcRouteClosedMidCallError || isLocalRouteCloseRejection(error);
|
|
16895
17542
|
}
|
|
@@ -16951,6 +17598,8 @@ function classifyBashHostFallbackError(error) {
|
|
|
16951
17598
|
return;
|
|
16952
17599
|
if (isRouteClosedMidCallError(error))
|
|
16953
17600
|
return;
|
|
17601
|
+
if (isSubcFrameReadTimeoutError(error))
|
|
17602
|
+
return;
|
|
16954
17603
|
if (error instanceof SubcTransportShuttingDownError)
|
|
16955
17604
|
return "transport down";
|
|
16956
17605
|
if (isSubcRouteTornDownError(error))
|
|
@@ -16974,6 +17623,14 @@ function isTransportClassError(error) {
|
|
|
16974
17623
|
function adaptToolError(command, error) {
|
|
16975
17624
|
if (!(error instanceof Error))
|
|
16976
17625
|
return error;
|
|
17626
|
+
if (isSubcFrameReadTimeoutError(error)) {
|
|
17627
|
+
const typedError = error instanceof SubcFrameReadTimeoutError ? error : new SubcFrameReadTimeoutError(error);
|
|
17628
|
+
const disposition = command === "bash" ? BASH_FRAME_READ_TIMEOUT_DISPOSITION : SUBC_FRAME_READ_TIMEOUT_DISPOSITION;
|
|
17629
|
+
if (typedError.message.includes(disposition))
|
|
17630
|
+
return typedError;
|
|
17631
|
+
typedError.message = typedError.message ? `${typedError.message} ${disposition}` : disposition;
|
|
17632
|
+
return typedError;
|
|
17633
|
+
}
|
|
16977
17634
|
if (error instanceof BridgeTransportUnknownOutcomeError) {
|
|
16978
17635
|
if (error.message.includes(BRIDGE_TRANSPORT_UNKNOWN_OUTCOME_DISPOSITION))
|
|
16979
17636
|
return error;
|
|
@@ -17055,7 +17712,12 @@ var RETIRED_PATHS = [
|
|
|
17055
17712
|
["semantic_search", "indexes.semantic"],
|
|
17056
17713
|
["experimental_semantic_search", "indexes.semantic"],
|
|
17057
17714
|
["callgraph_store", "indexes.callgraph"],
|
|
17058
|
-
["github.enabled", "github.read,github.write,github.shim"]
|
|
17715
|
+
["github.enabled", "github.read,github.write,github.shim"],
|
|
17716
|
+
["experimental_lsp_ty", "experimental.lsp_ty"],
|
|
17717
|
+
["experimental_bash_rewrite", "bash.rewrite"],
|
|
17718
|
+
["experimental_bash_compress", "bash.compress"],
|
|
17719
|
+
["experimental_bash_background", "bash.background"],
|
|
17720
|
+
["experimental.bash", "bash"]
|
|
17059
17721
|
];
|
|
17060
17722
|
var INDEX_INPUTS = [
|
|
17061
17723
|
["trigram", "search_index", "experimental_search_index"],
|
|
@@ -17187,10 +17849,68 @@ function translateInspectLspPaths(map, prefix, out) {
|
|
|
17187
17849
|
recordRetired(out, prefix, `inspect.${leaf}`);
|
|
17188
17850
|
}
|
|
17189
17851
|
}
|
|
17852
|
+
function translateExperimentalPaths(map, prefix, out) {
|
|
17853
|
+
for (const [oldKey, leaf] of [
|
|
17854
|
+
["experimental_lsp_ty", "lsp_ty"],
|
|
17855
|
+
["experimental_bash_rewrite", "rewrite"],
|
|
17856
|
+
["experimental_bash_compress", "compress"],
|
|
17857
|
+
["experimental_bash_background", "background"]
|
|
17858
|
+
]) {
|
|
17859
|
+
if (!Object.hasOwn(map, oldKey))
|
|
17860
|
+
continue;
|
|
17861
|
+
recordRetired(out, prefix, oldKey);
|
|
17862
|
+
const value = map[oldKey];
|
|
17863
|
+
delete map[oldKey];
|
|
17864
|
+
if (!isRecord2(map.experimental))
|
|
17865
|
+
map.experimental = {};
|
|
17866
|
+
const experimental = map.experimental;
|
|
17867
|
+
let destination = experimental;
|
|
17868
|
+
if (leaf !== "lsp_ty") {
|
|
17869
|
+
if (!isRecord2(experimental.bash))
|
|
17870
|
+
experimental.bash = {};
|
|
17871
|
+
destination = experimental.bash;
|
|
17872
|
+
}
|
|
17873
|
+
if (Object.hasOwn(destination, leaf)) {
|
|
17874
|
+
superseded(out, oldKey, `${oldKey} is ignored because experimental.${leaf === "lsp_ty" ? leaf : `bash.${leaf}`} is already set`);
|
|
17875
|
+
} else {
|
|
17876
|
+
destination[leaf] = value;
|
|
17877
|
+
}
|
|
17878
|
+
}
|
|
17879
|
+
if (!isRecord2(map.experimental) || !Object.hasOwn(map.experimental, "bash"))
|
|
17880
|
+
return;
|
|
17881
|
+
const experimental = map.experimental;
|
|
17882
|
+
const legacy = experimental.bash;
|
|
17883
|
+
if (isRecord2(legacy)) {
|
|
17884
|
+
if (!["rewrite", "compress", "background"].some((leaf) => Object.hasOwn(legacy, leaf)))
|
|
17885
|
+
return;
|
|
17886
|
+
for (const leaf of Object.keys(legacy))
|
|
17887
|
+
recordRetired(out, prefix, `experimental.bash.${leaf}`);
|
|
17888
|
+
if (Object.hasOwn(map, "bash")) {
|
|
17889
|
+
superseded(out, "experimental.bash", 'experimental.bash is ignored because top-level "bash" is already set');
|
|
17890
|
+
} else {
|
|
17891
|
+
const bash = {
|
|
17892
|
+
rewrite: legacy.rewrite === true,
|
|
17893
|
+
compress: legacy.compress === true,
|
|
17894
|
+
background: legacy.background === true
|
|
17895
|
+
};
|
|
17896
|
+
for (const leaf of ["long_running_reminder_enabled", "long_running_reminder_interval_ms"]) {
|
|
17897
|
+
if (Object.hasOwn(legacy, leaf))
|
|
17898
|
+
bash[leaf] = legacy[leaf];
|
|
17899
|
+
}
|
|
17900
|
+
map.bash = bash;
|
|
17901
|
+
}
|
|
17902
|
+
} else {
|
|
17903
|
+
recordRetired(out, prefix, "experimental.bash");
|
|
17904
|
+
}
|
|
17905
|
+
delete experimental.bash;
|
|
17906
|
+
if (Object.keys(experimental).length === 0)
|
|
17907
|
+
delete map.experimental;
|
|
17908
|
+
}
|
|
17190
17909
|
function translateBlock(map, userBase, blockLabel, out) {
|
|
17191
17910
|
const prefix = blockLabel === "base" ? "" : `${blockLabel}.`;
|
|
17192
17911
|
translateGithubAliases(map, prefix, out);
|
|
17193
17912
|
translateInspectLspPaths(map, prefix, out);
|
|
17913
|
+
translateExperimentalPaths(map, prefix, out);
|
|
17194
17914
|
let explicitList;
|
|
17195
17915
|
const rawList = map.disabled_tools;
|
|
17196
17916
|
if (Array.isArray(rawList) && rawList.every((entry) => typeof entry === "string")) {
|
|
@@ -17203,6 +17923,8 @@ function translateBlock(map, userBase, blockLabel, out) {
|
|
|
17203
17923
|
});
|
|
17204
17924
|
}
|
|
17205
17925
|
for (const [path] of RETIRED_PATHS) {
|
|
17926
|
+
if (path === "experimental.bash")
|
|
17927
|
+
continue;
|
|
17206
17928
|
if (hasPath(map, path))
|
|
17207
17929
|
recordRetired(out, prefix, path);
|
|
17208
17930
|
}
|
|
@@ -17636,11 +18358,13 @@ class BridgePool {
|
|
|
17636
18358
|
return alive;
|
|
17637
18359
|
}
|
|
17638
18360
|
getBridge(projectRoot) {
|
|
18361
|
+
return this.getBridgeForCanonicalRoot(asCanonicalRootPath(normalizeKey(projectRoot)));
|
|
18362
|
+
}
|
|
18363
|
+
getBridgeForCanonicalRoot(key) {
|
|
17639
18364
|
if (this.shutdownCalled) {
|
|
17640
18365
|
this.shutdownCalled = false;
|
|
17641
18366
|
this.startCleanupTimer();
|
|
17642
18367
|
}
|
|
17643
|
-
const key = normalizeKey(projectRoot);
|
|
17644
18368
|
const existing = this.bridges.get(key);
|
|
17645
18369
|
if (existing) {
|
|
17646
18370
|
existing.lastUsed = Date.now();
|
|
@@ -17846,7 +18570,9 @@ class RevivableTransportPool {
|
|
|
17846
18570
|
this.activePool = initialPool;
|
|
17847
18571
|
}
|
|
17848
18572
|
getBridge(projectRoot) {
|
|
17849
|
-
|
|
18573
|
+
return this.getBridgeForCanonicalRoot(asCanonicalRootPath(canonicalizeProjectRoot(projectRoot)));
|
|
18574
|
+
}
|
|
18575
|
+
getBridgeForCanonicalRoot(key) {
|
|
17850
18576
|
let transport = this.transports.get(key);
|
|
17851
18577
|
if (!transport) {
|
|
17852
18578
|
transport = new RevivableProjectTransport(this, key);
|
|
@@ -17881,7 +18607,7 @@ class RevivableTransportPool {
|
|
|
17881
18607
|
return transport;
|
|
17882
18608
|
}
|
|
17883
18609
|
async toolCall(projectRoot, runtime, name, rawArgs = {}, options) {
|
|
17884
|
-
return this.
|
|
18610
|
+
return this.toolCallForCanonicalRoot(asCanonicalRootPath(canonicalizeProjectRoot(projectRoot)), runtime.sessionID, name, rawArgs, options);
|
|
17885
18611
|
}
|
|
17886
18612
|
setConfigureOverride(key, value) {
|
|
17887
18613
|
if (key === "edit_slot_survives") {
|
|
@@ -18021,14 +18747,22 @@ class RevivableTransportPool {
|
|
|
18021
18747
|
return this.activePool?.getActiveBridgeForRoot(projectRoot) ?? null;
|
|
18022
18748
|
}
|
|
18023
18749
|
async send(projectRoot, command, params, options) {
|
|
18750
|
+
return this.sendForCanonicalRoot(asCanonicalRootPath(canonicalizeProjectRoot(projectRoot)), command, params, options);
|
|
18751
|
+
}
|
|
18752
|
+
async sendForCanonicalRoot(projectRoot, command, params, options) {
|
|
18753
|
+
this.getBridgeForCanonicalRoot(projectRoot);
|
|
18024
18754
|
const pool = await this.ensureActivePool();
|
|
18025
|
-
const bridge = pool.getBridge(projectRoot);
|
|
18755
|
+
const bridge = pool.getBridgeForCanonicalRoot?.(projectRoot) ?? pool.getBridge(projectRoot);
|
|
18026
18756
|
this.transports.get(projectRoot)?.refreshStatusSubscription(bridge);
|
|
18027
18757
|
return bridge.send(command, params, options);
|
|
18028
18758
|
}
|
|
18029
18759
|
async toolCallOnProject(projectRoot, sessionId, name, rawArgs, options) {
|
|
18760
|
+
return this.toolCallForCanonicalRoot(asCanonicalRootPath(canonicalizeProjectRoot(projectRoot)), sessionId, name, rawArgs, options);
|
|
18761
|
+
}
|
|
18762
|
+
async toolCallForCanonicalRoot(projectRoot, sessionId, name, rawArgs, options) {
|
|
18763
|
+
this.getBridgeForCanonicalRoot(projectRoot);
|
|
18030
18764
|
const pool = await this.ensureActivePool();
|
|
18031
|
-
const bridge = pool.getBridge(projectRoot);
|
|
18765
|
+
const bridge = pool.getBridgeForCanonicalRoot?.(projectRoot) ?? pool.getBridge(projectRoot);
|
|
18032
18766
|
this.transports.get(projectRoot)?.refreshStatusSubscription(bridge);
|
|
18033
18767
|
return bridge.toolCall(sessionId, name, rawArgs, options);
|
|
18034
18768
|
}
|
|
@@ -18106,7 +18840,7 @@ function satisfyLifecycleDemand(wrapper, projectRoot) {
|
|
|
18106
18840
|
const activePool = wrapper.activePool;
|
|
18107
18841
|
if (activePool.isShutdown() || !(activePool instanceof SubcTransportPool))
|
|
18108
18842
|
return;
|
|
18109
|
-
activePool.
|
|
18843
|
+
activePool.getBridgeForCanonicalRoot(asCanonicalRootPath(projectRoot));
|
|
18110
18844
|
}
|
|
18111
18845
|
async function createAftTransportPool(opts) {
|
|
18112
18846
|
let binaryPath = opts.binaryPath;
|
|
@@ -18141,8 +18875,8 @@ async function createAftTransportPool(opts) {
|
|
|
18141
18875
|
});
|
|
18142
18876
|
if (reapingEnabled) {
|
|
18143
18877
|
const owner = wrapper;
|
|
18144
|
-
const getBridge = owner.
|
|
18145
|
-
owner.
|
|
18878
|
+
const getBridge = owner.getBridgeForCanonicalRoot.bind(owner);
|
|
18879
|
+
owner.getBridgeForCanonicalRoot = (projectRoot) => {
|
|
18146
18880
|
satisfyLifecycleDemand(owner, projectRoot);
|
|
18147
18881
|
return getBridge(projectRoot);
|
|
18148
18882
|
};
|
|
@@ -18168,6 +18902,7 @@ async function createConcreteAftTransportPool(opts) {
|
|
|
18168
18902
|
throw new Error(missing);
|
|
18169
18903
|
}
|
|
18170
18904
|
return new SubcTransportPool({
|
|
18905
|
+
hostScopes: opts.harness === "opencode" || opts.harness === "pi",
|
|
18171
18906
|
connectionFile,
|
|
18172
18907
|
harness: opts.harness,
|
|
18173
18908
|
consumerIdentity: opts.subcConsumerIdentity,
|
|
@@ -18429,7 +19164,7 @@ import { homedir as homedir9, tmpdir as tmpdir3 } from "node:os";
|
|
|
18429
19164
|
import { basename as basename2, dirname as dirname6, join as join12, resolve as resolve8 } from "node:path";
|
|
18430
19165
|
|
|
18431
19166
|
// ../aft-bridge/dist/paths.js
|
|
18432
|
-
import { existsSync as existsSync5, readFileSync as readFileSync5, renameSync as renameSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
19167
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5, realpathSync as realpathSync3, renameSync as renameSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
18433
19168
|
import { homedir as homedir7 } from "node:os";
|
|
18434
19169
|
import { dirname as dirname4, isAbsolute as isAbsolute5, join as join10, resolve as resolve7 } from "node:path";
|
|
18435
19170
|
function homeDir3() {
|
|
@@ -18464,6 +19199,23 @@ function resolveCortexKitUserConfigPath() {
|
|
|
18464
19199
|
function resolveCortexKitProjectConfigPath(projectDirectory) {
|
|
18465
19200
|
return join10(projectDirectory, ".cortexkit", "aft.jsonc");
|
|
18466
19201
|
}
|
|
19202
|
+
function resolveProjectConfigReadPath(projectDirectory, operatingHarness) {
|
|
19203
|
+
const canonical = resolveCortexKitProjectConfigPath(projectDirectory);
|
|
19204
|
+
if (existsSync5(canonical))
|
|
19205
|
+
return canonical;
|
|
19206
|
+
const sources = resolveLegacyAftConfigSources(projectDirectory).project;
|
|
19207
|
+
const ordered = operatingHarness ? [
|
|
19208
|
+
...sources.filter((source) => source.harness === operatingHarness),
|
|
19209
|
+
...sources.filter((source) => source.harness !== operatingHarness)
|
|
19210
|
+
] : sources;
|
|
19211
|
+
return ordered.find((source) => existsSync5(source.path))?.path ?? canonical;
|
|
19212
|
+
}
|
|
19213
|
+
function legacyProjectConfigLocationNotice(projectDirectory, path) {
|
|
19214
|
+
const canonical = resolveCortexKitProjectConfigPath(projectDirectory);
|
|
19215
|
+
if (path === canonical)
|
|
19216
|
+
return null;
|
|
19217
|
+
return `Legacy project config ${path} is read in place; move it to ${canonical} yourself, and use aft doctor --fix to update retired keys.`;
|
|
19218
|
+
}
|
|
18467
19219
|
function resolveCortexKitConfigPaths(projectDirectory) {
|
|
18468
19220
|
return {
|
|
18469
19221
|
userConfigPath: resolveCortexKitUserConfigPath(),
|
|
@@ -18471,15 +19223,27 @@ function resolveCortexKitConfigPaths(projectDirectory) {
|
|
|
18471
19223
|
};
|
|
18472
19224
|
}
|
|
18473
19225
|
function resolveLegacyAftConfigSources(projectDirectory) {
|
|
19226
|
+
const project = [
|
|
19227
|
+
...legacySources(join10(projectDirectory, ".opencode", "aft"), "OpenCode project", "opencode"),
|
|
19228
|
+
...legacySources(join10(projectDirectory, ".pi", "aft"), "Pi project", "pi")
|
|
19229
|
+
];
|
|
19230
|
+
const identity = (path) => {
|
|
19231
|
+
try {
|
|
19232
|
+
return realpathSync3(path);
|
|
19233
|
+
} catch {
|
|
19234
|
+
return resolve7(path);
|
|
19235
|
+
}
|
|
19236
|
+
};
|
|
19237
|
+
const protectedPaths = new Set([
|
|
19238
|
+
resolveCortexKitProjectConfigPath(projectDirectory),
|
|
19239
|
+
...project.map((source) => source.path)
|
|
19240
|
+
].map(identity));
|
|
18474
19241
|
return {
|
|
18475
19242
|
user: [
|
|
18476
19243
|
...legacySources(join10(legacyOpenCodeConfigDir(), "aft"), "OpenCode user", "opencode"),
|
|
18477
19244
|
...legacySources(join10(legacyPiAgentDir(), "aft"), "Pi user", "pi")
|
|
18478
|
-
],
|
|
18479
|
-
project
|
|
18480
|
-
...legacySources(join10(projectDirectory, ".opencode", "aft"), "OpenCode project", "opencode"),
|
|
18481
|
-
...legacySources(join10(projectDirectory, ".pi", "aft"), "Pi project", "pi")
|
|
18482
|
-
]
|
|
19245
|
+
].filter((source) => !protectedPaths.has(identity(source.path))),
|
|
19246
|
+
project
|
|
18483
19247
|
};
|
|
18484
19248
|
}
|
|
18485
19249
|
function resolveHarnessStoragePath(storageRoot, harness, ...segments) {
|
|
@@ -19667,7 +20431,7 @@ function isNpmAvailable(deps = defaultDeps()) {
|
|
|
19667
20431
|
return resolveNpm(deps) !== null;
|
|
19668
20432
|
}
|
|
19669
20433
|
// ../aft-bridge/dist/onnx-probe.js
|
|
19670
|
-
import { closeSync as closeSync7, existsSync as existsSync8, openSync as openSync6, readSync as readSync5, realpathSync as
|
|
20434
|
+
import { closeSync as closeSync7, existsSync as existsSync8, openSync as openSync6, readSync as readSync5, realpathSync as realpathSync4 } from "node:fs";
|
|
19671
20435
|
var MH_MAGIC = 4277009102;
|
|
19672
20436
|
var MH_MAGIC_64 = 4277009103;
|
|
19673
20437
|
var FAT_MAGIC = 3405691582;
|
|
@@ -19754,7 +20518,7 @@ function isUncheckableDependency(path) {
|
|
|
19754
20518
|
function probeOnnxRuntimeLoadable(libPath, arch = process.arch) {
|
|
19755
20519
|
let fd;
|
|
19756
20520
|
try {
|
|
19757
|
-
fd = openSync6(
|
|
20521
|
+
fd = openSync6(realpathSync4(libPath), "r");
|
|
19758
20522
|
} catch (err) {
|
|
19759
20523
|
return {
|
|
19760
20524
|
loadable: false,
|
|
@@ -19793,7 +20557,7 @@ function probeOnnxRuntimeLoadable(libPath, arch = process.arch) {
|
|
|
19793
20557
|
}
|
|
19794
20558
|
// ../aft-bridge/dist/onnx-runtime.js
|
|
19795
20559
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
19796
|
-
import { chmodSync as chmodSync2, closeSync as closeSync8, copyFileSync as copyFileSync3, createWriteStream as createWriteStream2, existsSync as existsSync10, lstatSync as lstatSync2, openSync as openSync7, readdirSync as readdirSync3, readFileSync as readFileSync9, readlinkSync, realpathSync as
|
|
20560
|
+
import { chmodSync as chmodSync2, closeSync as closeSync8, copyFileSync as copyFileSync3, createWriteStream as createWriteStream2, existsSync as existsSync10, lstatSync as lstatSync2, openSync as openSync7, readdirSync as readdirSync3, readFileSync as readFileSync9, readlinkSync, realpathSync as realpathSync5, renameSync as renameSync6, rmSync as rmSync3, statSync as statSync9, symlinkSync, unlinkSync as unlinkSync4, writeFileSync as writeFileSync5 } from "node:fs";
|
|
19797
20561
|
import { basename as basename3, dirname as dirname9, isAbsolute as isAbsolute8, join as join16, relative as relative4, resolve as resolve10, win32 as win322 } from "node:path";
|
|
19798
20562
|
import { Readable as Readable2 } from "node:stream";
|
|
19799
20563
|
import { pipeline as pipeline2 } from "node:stream/promises";
|
|
@@ -20077,7 +20841,7 @@ function detectOnnxVersion(libDir, libName) {
|
|
|
20077
20841
|
try {
|
|
20078
20842
|
const entries = readdirSync3(libDir);
|
|
20079
20843
|
try {
|
|
20080
|
-
const resolved = parseOnnxVersionFromPath(
|
|
20844
|
+
const resolved = parseOnnxVersionFromPath(realpathSync5(join16(libDir, libName)));
|
|
20081
20845
|
if (resolved && resolved !== INVALID_ORT_VERSION)
|
|
20082
20846
|
return resolved;
|
|
20083
20847
|
} catch {}
|
|
@@ -20094,7 +20858,7 @@ function detectOnnxVersion(libDir, libName) {
|
|
|
20094
20858
|
const base = join16(libDir, libName);
|
|
20095
20859
|
if (existsSync10(base)) {
|
|
20096
20860
|
try {
|
|
20097
|
-
const real =
|
|
20861
|
+
const real = realpathSync5(base);
|
|
20098
20862
|
const version = parseOnnxVersionFromPath(real) ?? parseOnnxVersionFromDirectoryPath(real);
|
|
20099
20863
|
if (version)
|
|
20100
20864
|
return version;
|
|
@@ -20286,7 +21050,7 @@ async function downloadFileWithCap(url, destPath) {
|
|
|
20286
21050
|
}
|
|
20287
21051
|
}
|
|
20288
21052
|
function validateExtractedTree(stagingRoot) {
|
|
20289
|
-
const realRoot =
|
|
21053
|
+
const realRoot = realpathSync5(stagingRoot);
|
|
20290
21054
|
let totalBytes = 0;
|
|
20291
21055
|
const walk = (dir) => {
|
|
20292
21056
|
const entries = readdirSync3(dir);
|
|
@@ -20435,7 +21199,7 @@ function copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks, c
|
|
|
20435
21199
|
log(`ORT extract: failed to copy optional ${libFile}: ${copyErr}`);
|
|
20436
21200
|
}
|
|
20437
21201
|
}
|
|
20438
|
-
const targetRoot =
|
|
21202
|
+
const targetRoot = realpathSync5(targetDir);
|
|
20439
21203
|
for (const link of symlinks) {
|
|
20440
21204
|
const dst = join16(targetDir, link.name);
|
|
20441
21205
|
try {
|
|
@@ -21255,7 +22019,7 @@ function formatSemanticIndexStatus(status, stage, error, backend) {
|
|
|
21255
22019
|
return status;
|
|
21256
22020
|
}
|
|
21257
22021
|
// src/config.ts
|
|
21258
|
-
import { existsSync as existsSync11, readFileSync as readFileSync10,
|
|
22022
|
+
import { existsSync as existsSync11, readFileSync as readFileSync10, statSync as statSync10 } from "node:fs";
|
|
21259
22023
|
import { homedir as homedir12 } from "node:os";
|
|
21260
22024
|
import { isAbsolute as isAbsolute9, parse as parsePath, resolve as resolvePath } from "node:path";
|
|
21261
22025
|
var import_comment_json = __toESM(require_src2(), 1);
|
|
@@ -26477,155 +27241,6 @@ function resolveBashConfig(config) {
|
|
|
26477
27241
|
}
|
|
26478
27242
|
return { ...base, enabled: true, rewrite: true, compress: true, background: true };
|
|
26479
27243
|
}
|
|
26480
|
-
var CONFIG_MIGRATIONS = [
|
|
26481
|
-
{ oldKey: "experimental_lsp_ty", newPath: ["experimental", "lsp_ty"] },
|
|
26482
|
-
{ oldKey: "experimental_bash_rewrite", newPath: ["experimental", "bash", "rewrite"] },
|
|
26483
|
-
{ oldKey: "experimental_bash_compress", newPath: ["experimental", "bash", "compress"] },
|
|
26484
|
-
{ oldKey: "experimental_bash_background", newPath: ["experimental", "bash", "background"] }
|
|
26485
|
-
];
|
|
26486
|
-
function isWritableMigrationError(errorValue) {
|
|
26487
|
-
const code = errorValue?.code;
|
|
26488
|
-
return code === "EROFS" || code === "EACCES" || code === "EPERM";
|
|
26489
|
-
}
|
|
26490
|
-
function extractCommentsForPreservation(content) {
|
|
26491
|
-
const comments = [];
|
|
26492
|
-
const linePattern = /\/\/[^\n]*/g;
|
|
26493
|
-
for (const match of content.match(linePattern) ?? []) {
|
|
26494
|
-
comments.push(match.trim());
|
|
26495
|
-
}
|
|
26496
|
-
const blockPattern = /\/\*[\s\S]*?\*\//g;
|
|
26497
|
-
for (const match of content.match(blockPattern) ?? []) {
|
|
26498
|
-
comments.push(match.replace(/\s+/g, " ").trim());
|
|
26499
|
-
}
|
|
26500
|
-
return comments;
|
|
26501
|
-
}
|
|
26502
|
-
function ensureRecordAtPath(root, path) {
|
|
26503
|
-
let current = root;
|
|
26504
|
-
for (const segment of path) {
|
|
26505
|
-
const existing = current[segment];
|
|
26506
|
-
if (!existing || typeof existing !== "object" || Array.isArray(existing)) {
|
|
26507
|
-
current[segment] = {};
|
|
26508
|
-
}
|
|
26509
|
-
current = current[segment];
|
|
26510
|
-
}
|
|
26511
|
-
return current;
|
|
26512
|
-
}
|
|
26513
|
-
function hasPath2(root, path) {
|
|
26514
|
-
let current = root;
|
|
26515
|
-
for (const segment of path) {
|
|
26516
|
-
if (!current || typeof current !== "object" || Array.isArray(current))
|
|
26517
|
-
return false;
|
|
26518
|
-
const record2 = current;
|
|
26519
|
-
if (!Object.hasOwn(record2, segment))
|
|
26520
|
-
return false;
|
|
26521
|
-
current = record2[segment];
|
|
26522
|
-
}
|
|
26523
|
-
return true;
|
|
26524
|
-
}
|
|
26525
|
-
function setPath(root, path, value) {
|
|
26526
|
-
const parent = ensureRecordAtPath(root, path.slice(0, -1));
|
|
26527
|
-
parent[path[path.length - 1]] = value;
|
|
26528
|
-
}
|
|
26529
|
-
function migrateRawConfig(rawConfig, configPath, logger) {
|
|
26530
|
-
const oldKeys = [];
|
|
26531
|
-
for (const migration of CONFIG_MIGRATIONS) {
|
|
26532
|
-
if (!Object.hasOwn(rawConfig, migration.oldKey))
|
|
26533
|
-
continue;
|
|
26534
|
-
if (hasPath2(rawConfig, migration.newPath)) {
|
|
26535
|
-
logger?.warn(`Config migration conflict at ${configPath}: ${migration.oldKey} ignored because ${migration.newPath.join(".")} is already set`);
|
|
26536
|
-
} else {
|
|
26537
|
-
setPath(rawConfig, migration.newPath, rawConfig[migration.oldKey]);
|
|
26538
|
-
}
|
|
26539
|
-
delete rawConfig[migration.oldKey];
|
|
26540
|
-
oldKeys.push(migration.oldKey);
|
|
26541
|
-
}
|
|
26542
|
-
oldKeys.push(...migrateExperimentalBashBlock(rawConfig, configPath, logger));
|
|
26543
|
-
return oldKeys;
|
|
26544
|
-
}
|
|
26545
|
-
function migrateExperimentalBashBlock(rawConfig, configPath, logger) {
|
|
26546
|
-
const experimental = rawConfig.experimental;
|
|
26547
|
-
if (typeof experimental !== "object" || experimental === null || Array.isArray(experimental)) {
|
|
26548
|
-
return [];
|
|
26549
|
-
}
|
|
26550
|
-
const expRecord = experimental;
|
|
26551
|
-
if (!Object.hasOwn(expRecord, "bash"))
|
|
26552
|
-
return [];
|
|
26553
|
-
const legacyBash = expRecord.bash;
|
|
26554
|
-
if (typeof legacyBash !== "object" || legacyBash === null || Array.isArray(legacyBash)) {
|
|
26555
|
-
delete expRecord.bash;
|
|
26556
|
-
if (Object.keys(expRecord).length === 0)
|
|
26557
|
-
delete rawConfig.experimental;
|
|
26558
|
-
return ["experimental.bash"];
|
|
26559
|
-
}
|
|
26560
|
-
const bashRecord = legacyBash;
|
|
26561
|
-
const hasFeatureFlag = "rewrite" in bashRecord || "compress" in bashRecord || "background" in bashRecord;
|
|
26562
|
-
if (!hasFeatureFlag)
|
|
26563
|
-
return [];
|
|
26564
|
-
const movedKeys = Object.keys(bashRecord).map((k) => `experimental.bash.${k}`);
|
|
26565
|
-
if (Object.hasOwn(rawConfig, "bash")) {
|
|
26566
|
-
logger?.warn(`Config migration conflict at ${configPath}: experimental.bash dropped because top-level "bash" is already set`);
|
|
26567
|
-
} else {
|
|
26568
|
-
const migrated = {
|
|
26569
|
-
rewrite: bashRecord.rewrite === true,
|
|
26570
|
-
compress: bashRecord.compress === true,
|
|
26571
|
-
background: bashRecord.background === true
|
|
26572
|
-
};
|
|
26573
|
-
if (bashRecord.long_running_reminder_enabled !== undefined) {
|
|
26574
|
-
migrated.long_running_reminder_enabled = bashRecord.long_running_reminder_enabled;
|
|
26575
|
-
}
|
|
26576
|
-
if (bashRecord.long_running_reminder_interval_ms !== undefined) {
|
|
26577
|
-
migrated.long_running_reminder_interval_ms = bashRecord.long_running_reminder_interval_ms;
|
|
26578
|
-
}
|
|
26579
|
-
rawConfig.bash = migrated;
|
|
26580
|
-
}
|
|
26581
|
-
delete expRecord.bash;
|
|
26582
|
-
if (Object.keys(expRecord).length === 0) {
|
|
26583
|
-
delete rawConfig.experimental;
|
|
26584
|
-
}
|
|
26585
|
-
return movedKeys;
|
|
26586
|
-
}
|
|
26587
|
-
function migrateAftConfigFile2(configPath, logger = { log: log2, warn: warn2 }) {
|
|
26588
|
-
if (!existsSync11(configPath)) {
|
|
26589
|
-
return { migrated: false, oldKeys: [] };
|
|
26590
|
-
}
|
|
26591
|
-
let tmpPath = null;
|
|
26592
|
-
let oldKeys = [];
|
|
26593
|
-
try {
|
|
26594
|
-
const content = readFileSync10(configPath, "utf-8");
|
|
26595
|
-
const rawConfig = import_comment_json.parse(content);
|
|
26596
|
-
if (!rawConfig || typeof rawConfig !== "object" || Array.isArray(rawConfig)) {
|
|
26597
|
-
return { migrated: false, oldKeys: [] };
|
|
26598
|
-
}
|
|
26599
|
-
oldKeys = migrateRawConfig(rawConfig, configPath, logger);
|
|
26600
|
-
if (oldKeys.length === 0) {
|
|
26601
|
-
return { migrated: false, oldKeys: [] };
|
|
26602
|
-
}
|
|
26603
|
-
const serialized = `${import_comment_json.stringify(rawConfig, null, 2)}
|
|
26604
|
-
`;
|
|
26605
|
-
const originalComments = extractCommentsForPreservation(content);
|
|
26606
|
-
const droppedComments = originalComments.filter((comment) => !serialized.includes(comment.trim()));
|
|
26607
|
-
const nextContent = droppedComments.length > 0 ? `${droppedComments.join(`
|
|
26608
|
-
`)}
|
|
26609
|
-
${serialized}` : serialized;
|
|
26610
|
-
tmpPath = `${configPath}.tmp.${process.pid}`;
|
|
26611
|
-
writeFileSync6(tmpPath, nextContent, "utf-8");
|
|
26612
|
-
renameSync7(tmpPath, configPath);
|
|
26613
|
-
logger.log(`Migrated config at ${configPath}: removed ${oldKeys.join(", ")}`);
|
|
26614
|
-
return { migrated: true, oldKeys };
|
|
26615
|
-
} catch (err) {
|
|
26616
|
-
if (tmpPath) {
|
|
26617
|
-
try {
|
|
26618
|
-
unlinkSync5(tmpPath);
|
|
26619
|
-
} catch {}
|
|
26620
|
-
}
|
|
26621
|
-
if (isWritableMigrationError(err)) {
|
|
26622
|
-
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
26623
|
-
logger.warn(`Config migration could not write ${configPath} (${errorMsg}); using migrated config in memory`);
|
|
26624
|
-
return { migrated: oldKeys.length > 0, oldKeys };
|
|
26625
|
-
}
|
|
26626
|
-
return { migrated: false, oldKeys: [] };
|
|
26627
|
-
}
|
|
26628
|
-
}
|
|
26629
27244
|
function parseConfigPartially(rawConfig) {
|
|
26630
27245
|
let configForParsing = rawConfig;
|
|
26631
27246
|
if (Object.hasOwn(rawConfig, "edit_mode") && rawConfig.edit_mode !== "default" && rawConfig.edit_mode !== "hashline") {
|
|
@@ -26700,7 +27315,6 @@ function loadConfigFromPath(configPath, tier) {
|
|
|
26700
27315
|
configLoadSources.push(configPath);
|
|
26701
27316
|
configLoadTexts.set(configPath, content);
|
|
26702
27317
|
const rawConfig = import_comment_json.parse(content);
|
|
26703
|
-
migrateRawConfig(rawConfig, configPath, { log: log2, warn: warn2 });
|
|
26704
27318
|
cleanConfig = stripJsoncSymbols(rawConfig);
|
|
26705
27319
|
} catch (err) {
|
|
26706
27320
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
@@ -27060,21 +27674,14 @@ function migrateAftConfigLocations(projectDirectory, logger = { log: log2, warn:
|
|
|
27060
27674
|
legacySources: legacy.user,
|
|
27061
27675
|
operatingHarness: "opencode",
|
|
27062
27676
|
logger
|
|
27063
|
-
}),
|
|
27064
|
-
migrateAftConfigFile({
|
|
27065
|
-
scope: "project",
|
|
27066
|
-
targetPath: paths.projectConfigPath,
|
|
27067
|
-
legacySources: legacy.project,
|
|
27068
|
-
operatingHarness: "opencode",
|
|
27069
|
-
logger
|
|
27070
27677
|
})
|
|
27071
27678
|
];
|
|
27072
27679
|
}
|
|
27073
27680
|
function resolveAftConfigPaths(projectDirectory) {
|
|
27074
|
-
|
|
27075
|
-
|
|
27076
|
-
|
|
27077
|
-
|
|
27681
|
+
return {
|
|
27682
|
+
...resolveCortexKitConfigPaths(projectDirectory),
|
|
27683
|
+
projectConfigPath: resolveProjectConfigReadPath(projectDirectory, ACTIVE_HARNESS)
|
|
27684
|
+
};
|
|
27078
27685
|
}
|
|
27079
27686
|
function resolveOpenCodeRegistrationRoot(directory, worktree) {
|
|
27080
27687
|
if (!worktree)
|
|
@@ -27102,6 +27709,15 @@ function loadAftConfig(projectDirectory) {
|
|
|
27102
27709
|
configLoadNotices = [];
|
|
27103
27710
|
semanticInputSupplied = false;
|
|
27104
27711
|
const { userConfigPath, projectConfigPath } = resolveAftConfigPaths(projectDirectory);
|
|
27712
|
+
const locationNotice = legacyProjectConfigLocationNotice(projectDirectory, projectConfigPath);
|
|
27713
|
+
if (locationNotice) {
|
|
27714
|
+
log2(locationNotice);
|
|
27715
|
+
configLoadNotices.push({
|
|
27716
|
+
configPath: projectConfigPath,
|
|
27717
|
+
digest: "legacy-project-config-location",
|
|
27718
|
+
message: locationNotice
|
|
27719
|
+
});
|
|
27720
|
+
}
|
|
27105
27721
|
let config = loadConfigFromPath(userConfigPath, "user") ?? {
|
|
27106
27722
|
disabled_tools: [...DEFAULT_DISABLED_TOOLS]
|
|
27107
27723
|
};
|
|
@@ -39030,6 +39646,24 @@ function markExplicitControl(sessionID, taskId, trackOutstanding = true) {
|
|
|
39030
39646
|
function unmarkExplicitControl(sessionID, taskId) {
|
|
39031
39647
|
stateFor(sessionID).explicitControlTasks.delete(taskId);
|
|
39032
39648
|
}
|
|
39649
|
+
function holdBgCompletion(sessionID, completion, stopped = false) {
|
|
39650
|
+
const state = stateFor(sessionID);
|
|
39651
|
+
if (!stopped && state.consumedTaskIds.has(completion.task_id))
|
|
39652
|
+
return;
|
|
39653
|
+
state.heldWorkerCompletions = true;
|
|
39654
|
+
state.explicitControlTasks.delete(completion.task_id);
|
|
39655
|
+
state.outstandingTaskIds.delete(completion.task_id);
|
|
39656
|
+
if (!state.pendingCompletions.some((pending) => pending.task_id === completion.task_id)) {
|
|
39657
|
+
state.pendingCompletions.push(completion);
|
|
39658
|
+
}
|
|
39659
|
+
}
|
|
39660
|
+
function hasHeldBgCompletions(sessionID) {
|
|
39661
|
+
const state = stateFor(sessionID);
|
|
39662
|
+
return state.heldWorkerCompletions === true && (state.pendingCompletions.length > 0 || state.pendingPatternMatches.length > 0);
|
|
39663
|
+
}
|
|
39664
|
+
function markHeldBgCompletions(sessionID) {
|
|
39665
|
+
stateFor(sessionID).heldWorkerCompletions = true;
|
|
39666
|
+
}
|
|
39033
39667
|
function queuePendingPatternMatch(state, entry) {
|
|
39034
39668
|
const normalized = entry.reason ? entry : { ...entry, reason: "pattern_match" };
|
|
39035
39669
|
const existingIdx = state.pendingPatternMatches.findIndex((match) => match.task_id === normalized.task_id);
|
|
@@ -39115,7 +39749,7 @@ async function appendInTurnBgCompletions(drainContext, output) {
|
|
|
39115
39749
|
return;
|
|
39116
39750
|
const state = stateFor(drainContext.sessionID);
|
|
39117
39751
|
if (state.outstandingTaskIds.size === 0 && state.pendingCompletions.length === 0 && state.pendingLongRunning.length === 0 && state.pendingPatternMatches.length === 0) {
|
|
39118
|
-
if (!idleDrainMayFindWork(state))
|
|
39752
|
+
if (!idleDrainMayFindWork(state, drainContext))
|
|
39119
39753
|
return;
|
|
39120
39754
|
await drainCompletions(drainContext);
|
|
39121
39755
|
if (state.outstandingTaskIds.size === 0 && state.pendingCompletions.length === 0 && state.pendingLongRunning.length === 0 && state.pendingPatternMatches.length === 0) {
|
|
@@ -39141,6 +39775,7 @@ async function appendInTurnBgCompletions(drainContext, output) {
|
|
|
39141
39775
|
reminder_chars: reminder.length
|
|
39142
39776
|
});
|
|
39143
39777
|
state.pendingCompletions = [];
|
|
39778
|
+
state.heldWorkerCompletions = false;
|
|
39144
39779
|
for (const completion of deliveredCompletions) {
|
|
39145
39780
|
state.wakeDeferredTaskIds.delete(completion.task_id);
|
|
39146
39781
|
}
|
|
@@ -39494,7 +40129,8 @@ function observeOpenCodeBgNotificationEvent(event) {
|
|
|
39494
40129
|
}
|
|
39495
40130
|
async function triggerWakeIfPending(drainContext, skipDrain, includeDeferredCompletions = true, forceDrain = false, deferWhileBusy = false) {
|
|
39496
40131
|
const state = stateFor(drainContext.sessionID);
|
|
39497
|
-
|
|
40132
|
+
const hasTrackedOrPendingWork = state.outstandingTaskIds.size > 0 || state.pendingCompletions.length > 0 || state.pendingLongRunning.length > 0 || state.pendingPatternMatches.length > 0;
|
|
40133
|
+
if (!skipDrain && (forceDrain || state.outstandingTaskIds.size > 0 || !state.forcedDrainCompleted) && (forceDrain || hasTrackedOrPendingWork || idleDrainMayFindWork(state, drainContext))) {
|
|
39498
40134
|
await drainCompletions(drainContext);
|
|
39499
40135
|
}
|
|
39500
40136
|
routeExplicitControlCompletions(state);
|
|
@@ -39729,8 +40365,13 @@ function bridgeForDrain(drainContext) {
|
|
|
39729
40365
|
function isTerminalNudgeError(error) {
|
|
39730
40366
|
return error instanceof Error && error.code === "root_generation_expired";
|
|
39731
40367
|
}
|
|
39732
|
-
function idleDrainMayFindWork(state) {
|
|
39733
|
-
|
|
40368
|
+
function idleDrainMayFindWork(state, drainContext) {
|
|
40369
|
+
if (state.untrackedCompletionSinceDrain || state.deliveredAwaitingAckTaskIds.size > 0) {
|
|
40370
|
+
return true;
|
|
40371
|
+
}
|
|
40372
|
+
if (state.forcedDrainCompleted)
|
|
40373
|
+
return false;
|
|
40374
|
+
return Boolean(drainContext.resolvedBridge ?? drainContext.ctx.pool.getActiveBridgeForRoot(drainContext.directory));
|
|
39734
40375
|
}
|
|
39735
40376
|
async function drainCompletions(drainContext) {
|
|
39736
40377
|
const state = stateFor(drainContext.sessionID);
|
|
@@ -40530,7 +41171,7 @@ function resolvePathFromProjectRoot(projectRoot, target) {
|
|
|
40530
41171
|
async function resolvePathArg(ctx, runtime, target) {
|
|
40531
41172
|
return resolvePathFromProjectRoot(await resolveProjectRoot(ctx, runtime), target);
|
|
40532
41173
|
}
|
|
40533
|
-
function
|
|
41174
|
+
function checkedProjectRoot(ctx, runtime) {
|
|
40534
41175
|
const projectRoot = projectRootFor(runtime);
|
|
40535
41176
|
if (ctx.isProjectEnabled?.(projectRoot) === false) {
|
|
40536
41177
|
throw new Error(`AFT disabled by config for ${projectRoot}`);
|
|
@@ -40538,7 +41179,7 @@ function bridgeFor(ctx, runtime) {
|
|
|
40538
41179
|
if (!existsSync12(projectRoot)) {
|
|
40539
41180
|
throw new Error(`project directory no longer exists: ${projectRoot} (stale restored session?)`);
|
|
40540
41181
|
}
|
|
40541
|
-
return
|
|
41182
|
+
return projectRoot;
|
|
40542
41183
|
}
|
|
40543
41184
|
async function isWorkerRuntime(ctx, runtime) {
|
|
40544
41185
|
if (!runtime.sessionID)
|
|
@@ -40557,6 +41198,9 @@ async function callBridge(ctx, runtime, command, params = {}, options) {
|
|
|
40557
41198
|
if (workerSession) {
|
|
40558
41199
|
merged[WORKER_SESSION_FIELD] = true;
|
|
40559
41200
|
}
|
|
41201
|
+
if (ctx.hostToolNames) {
|
|
41202
|
+
merged[HOST_TOOL_NAMES_FIELD] = ctx.hostToolNames;
|
|
41203
|
+
}
|
|
40560
41204
|
const timeoutMs = timeoutForCommand(command);
|
|
40561
41205
|
const sendOptions = {
|
|
40562
41206
|
...timeoutMs !== undefined ? { timeoutMs } : {},
|
|
@@ -40567,7 +41211,9 @@ async function callBridge(ctx, runtime, command, params = {}, options) {
|
|
|
40567
41211
|
markBridgeStart();
|
|
40568
41212
|
let response;
|
|
40569
41213
|
try {
|
|
40570
|
-
|
|
41214
|
+
const root = asCanonicalRootPath(checkedProjectRoot(ctx, runtime));
|
|
41215
|
+
const opts = Object.keys(sendOptions).length > 0 ? sendOptions : undefined;
|
|
41216
|
+
response = ctx.pool.sendForCanonicalRoot ? await ctx.pool.sendForCanonicalRoot(root, command, merged, opts) : await ctx.pool.getBridge(root).send(command, merged, opts);
|
|
40571
41217
|
} catch (error) {
|
|
40572
41218
|
if (command === "bash" && isBashTransportDeadError(error))
|
|
40573
41219
|
throw error;
|
|
@@ -40589,12 +41235,15 @@ async function callToolCall(ctx, runtime, name, rawArgs = {}, options) {
|
|
|
40589
41235
|
configureWarningClient: ctx.client,
|
|
40590
41236
|
...runtime.effectAbort ? { abortSignal: runtime.effectAbort } : {},
|
|
40591
41237
|
...workerSession ? { workerSession: true } : {},
|
|
41238
|
+
...ctx.hostToolNames ? { hostToolNames: ctx.hostToolNames } : {},
|
|
40592
41239
|
...options
|
|
40593
41240
|
};
|
|
40594
41241
|
markBridgeStart();
|
|
40595
41242
|
let response;
|
|
40596
41243
|
try {
|
|
40597
|
-
|
|
41244
|
+
const root = asCanonicalRootPath(checkedProjectRoot(ctx, runtime));
|
|
41245
|
+
const opts = Object.keys(sendOptions).length > 0 ? sendOptions : undefined;
|
|
41246
|
+
response = ctx.pool.toolCallForCanonicalRoot ? await ctx.pool.toolCallForCanonicalRoot(root, runtime.sessionID, name, rawArgs, opts) : await ctx.pool.getBridge(root).toolCall(runtime.sessionID, name, rawArgs, opts);
|
|
40598
41247
|
} catch (error) {
|
|
40599
41248
|
throw adaptToolError(name, error);
|
|
40600
41249
|
} finally {
|
|
@@ -40702,7 +41351,7 @@ async function signalBashWaitDetachForProject(pool, projectRoot, sessionID) {
|
|
|
40702
41351
|
}
|
|
40703
41352
|
|
|
40704
41353
|
// src/lsp-auto-install.ts
|
|
40705
|
-
import { existsSync as existsSync14, mkdirSync as mkdirSync6, renameSync as
|
|
41354
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync6, renameSync as renameSync8, rmSync as rmSync5, statSync as statSync12, writeFileSync as writeFileSync7 } from "node:fs";
|
|
40706
41355
|
import { join as join19 } from "node:path";
|
|
40707
41356
|
|
|
40708
41357
|
// src/lsp-cache.ts
|
|
@@ -40712,11 +41361,11 @@ import {
|
|
|
40712
41361
|
mkdirSync as mkdirSync5,
|
|
40713
41362
|
openSync as openSync8,
|
|
40714
41363
|
readFileSync as readFileSync11,
|
|
40715
|
-
renameSync as
|
|
41364
|
+
renameSync as renameSync7,
|
|
40716
41365
|
rmSync as rmSync4,
|
|
40717
41366
|
statSync as statSync11,
|
|
40718
|
-
unlinkSync as
|
|
40719
|
-
writeFileSync as
|
|
41367
|
+
unlinkSync as unlinkSync5,
|
|
41368
|
+
writeFileSync as writeFileSync6
|
|
40720
41369
|
} from "node:fs";
|
|
40721
41370
|
import { join as join17 } from "node:path";
|
|
40722
41371
|
function aftCacheBase() {
|
|
@@ -40757,7 +41406,7 @@ function writeInstalledMetaIn(installDir, version, sha256) {
|
|
|
40757
41406
|
installedAt: new Date().toISOString(),
|
|
40758
41407
|
...sha256 ? { sha256 } : {}
|
|
40759
41408
|
};
|
|
40760
|
-
|
|
41409
|
+
writeFileSync6(join17(installDir, INSTALLED_META_FILE), JSON.stringify(meta), "utf8");
|
|
40761
41410
|
} catch (err) {
|
|
40762
41411
|
log2(`[lsp-cache] failed to write installed-meta in ${installDir}: ${err}`);
|
|
40763
41412
|
}
|
|
@@ -40798,7 +41447,7 @@ function acquireInstallLock(lockKey) {
|
|
|
40798
41447
|
try {
|
|
40799
41448
|
const fd = openSync8(lock, "wx");
|
|
40800
41449
|
try {
|
|
40801
|
-
|
|
41450
|
+
writeFileSync6(fd, `${process.pid}
|
|
40802
41451
|
${new Date().toISOString()}
|
|
40803
41452
|
${token}
|
|
40804
41453
|
`);
|
|
@@ -40854,7 +41503,7 @@ ${token}
|
|
|
40854
41503
|
}
|
|
40855
41504
|
log2(`[lsp] reclaiming install lock for ${lockKey} (owner_pid=${owningPid ?? "unknown"}, alive=${ownerAlive}, age_ms=${age})`);
|
|
40856
41505
|
try {
|
|
40857
|
-
|
|
41506
|
+
unlinkSync5(lock);
|
|
40858
41507
|
} catch {}
|
|
40859
41508
|
return tryClaim();
|
|
40860
41509
|
}
|
|
@@ -40893,7 +41542,7 @@ function releaseInstallLock(lockKey, acquisitionToken) {
|
|
|
40893
41542
|
return;
|
|
40894
41543
|
}
|
|
40895
41544
|
try {
|
|
40896
|
-
|
|
41545
|
+
unlinkSync5(lock);
|
|
40897
41546
|
} catch (unlinkErr) {
|
|
40898
41547
|
const code = unlinkErr.code;
|
|
40899
41548
|
if (code !== "ENOENT") {
|
|
@@ -40968,7 +41617,7 @@ function claimLspAutoInstallPass() {
|
|
|
40968
41617
|
try {
|
|
40969
41618
|
const fd = openSync8(lock, "wx");
|
|
40970
41619
|
try {
|
|
40971
|
-
|
|
41620
|
+
writeFileSync6(fd, JSON.stringify(owner));
|
|
40972
41621
|
} finally {
|
|
40973
41622
|
closeSync9(fd);
|
|
40974
41623
|
}
|
|
@@ -40999,12 +41648,12 @@ function claimLspAutoInstallPass() {
|
|
|
40999
41648
|
return null;
|
|
41000
41649
|
}
|
|
41001
41650
|
const tempPath = `${stampPath}.tmp-${process.pid}-${owner.token}`;
|
|
41002
|
-
|
|
41651
|
+
writeFileSync6(tempPath, `${JSON.stringify({ lastRunMs: Date.now() })}
|
|
41003
41652
|
`, {
|
|
41004
41653
|
encoding: "utf8",
|
|
41005
41654
|
mode: 384
|
|
41006
41655
|
});
|
|
41007
|
-
|
|
41656
|
+
renameSync7(tempPath, stampPath);
|
|
41008
41657
|
return { release };
|
|
41009
41658
|
} catch (err) {
|
|
41010
41659
|
if (claimed) {
|
|
@@ -41041,7 +41690,7 @@ function writeVersionCheck(npmPackage, latest) {
|
|
|
41041
41690
|
last_checked: new Date().toISOString(),
|
|
41042
41691
|
latest_eligible: latest
|
|
41043
41692
|
};
|
|
41044
|
-
|
|
41693
|
+
writeFileSync6(file, JSON.stringify(record, null, 2));
|
|
41045
41694
|
}
|
|
41046
41695
|
function shouldRecheckVersion(record, weeklyCheckIntervalMs = 7 * 24 * 60 * 60 * 1000) {
|
|
41047
41696
|
if (!record)
|
|
@@ -41422,7 +42071,7 @@ function ensureInstallAnchor(cwd) {
|
|
|
41422
42071
|
try {
|
|
41423
42072
|
const stub = join19(cwd, "package.json");
|
|
41424
42073
|
if (!existsSync14(stub)) {
|
|
41425
|
-
|
|
42074
|
+
writeFileSync7(stub, `${JSON.stringify({ name: "aft-lsp-cache", version: "0.0.0", private: true })}
|
|
41426
42075
|
`);
|
|
41427
42076
|
}
|
|
41428
42077
|
} catch (err) {
|
|
@@ -41635,7 +42284,7 @@ function quarantineCachedNpmInstall(spec, reason) {
|
|
|
41635
42284
|
try {
|
|
41636
42285
|
mkdirSync6(join19(dest, ".."), { recursive: true });
|
|
41637
42286
|
rmSync5(dest, { recursive: true, force: true });
|
|
41638
|
-
|
|
42287
|
+
renameSync8(packageDir, dest);
|
|
41639
42288
|
} catch (err) {
|
|
41640
42289
|
warn2(`[lsp] tofu_mismatch ${spec.npm}: failed to quarantine cache entry: ${err}`);
|
|
41641
42290
|
}
|
|
@@ -41739,12 +42388,12 @@ import {
|
|
|
41739
42388
|
readFileSync as readFileSync13,
|
|
41740
42389
|
readlinkSync as readlinkSync2,
|
|
41741
42390
|
readSync as readSync6,
|
|
41742
|
-
realpathSync as
|
|
41743
|
-
renameSync as
|
|
42391
|
+
realpathSync as realpathSync6,
|
|
42392
|
+
renameSync as renameSync9,
|
|
41744
42393
|
rmSync as rmSync6,
|
|
41745
42394
|
statSync as statSync13,
|
|
41746
|
-
unlinkSync as
|
|
41747
|
-
writeFileSync as
|
|
42395
|
+
unlinkSync as unlinkSync6,
|
|
42396
|
+
writeFileSync as writeFileSync8
|
|
41748
42397
|
} from "node:fs";
|
|
41749
42398
|
import { dirname as dirname10, join as join20, relative as relative5, resolve as resolve12 } from "node:path";
|
|
41750
42399
|
import { Readable as Readable3 } from "node:stream";
|
|
@@ -41870,9 +42519,9 @@ function writeInstallFailureMarker(spec, release, sha256, installError) {
|
|
|
41870
42519
|
const tempPath = `${path}.tmp-${process.pid}-${randomBytes4(6).toString("hex")}`;
|
|
41871
42520
|
try {
|
|
41872
42521
|
mkdirSync7(dirname10(path), { recursive: true });
|
|
41873
|
-
|
|
42522
|
+
writeFileSync8(tempPath, `${JSON.stringify({ release, sha256, error: markerError, at: new Date().toISOString() })}
|
|
41874
42523
|
`, { encoding: "utf8", mode: 384 });
|
|
41875
|
-
|
|
42524
|
+
renameSync9(tempPath, path);
|
|
41876
42525
|
} catch (err) {
|
|
41877
42526
|
try {
|
|
41878
42527
|
rmSync6(tempPath, { force: true });
|
|
@@ -41938,7 +42587,7 @@ function writeGithubInstalledMetaIn(installDir, version, binarySha256, archiveSh
|
|
|
41938
42587
|
binarySha256,
|
|
41939
42588
|
...archiveSha256 ? { archiveSha256 } : {}
|
|
41940
42589
|
};
|
|
41941
|
-
|
|
42590
|
+
writeFileSync8(join20(installDir, INSTALLED_META_FILE2), JSON.stringify(meta), "utf8");
|
|
41942
42591
|
} catch (err) {
|
|
41943
42592
|
warn2(`[lsp] failed to write github installed metadata in ${installDir}: ${err}`);
|
|
41944
42593
|
}
|
|
@@ -42178,7 +42827,7 @@ async function downloadFile(url, destPath, fetchImpl, assetSize, signal) {
|
|
|
42178
42827
|
await pipeline3(nodeStream, createWriteStream3(destPath), { signal: timeout.signal });
|
|
42179
42828
|
} catch (err) {
|
|
42180
42829
|
try {
|
|
42181
|
-
|
|
42830
|
+
unlinkSync6(destPath);
|
|
42182
42831
|
} catch {}
|
|
42183
42832
|
throw err;
|
|
42184
42833
|
} finally {
|
|
@@ -42207,7 +42856,7 @@ async function fetchFollowingRedirects(url, fetchImpl, signal) {
|
|
|
42207
42856
|
throw new Error(`too many redirects (>${maxRedirects})`);
|
|
42208
42857
|
}
|
|
42209
42858
|
function validateExtraction(stagingRoot) {
|
|
42210
|
-
const realStagingRoot =
|
|
42859
|
+
const realStagingRoot = realpathSync6(stagingRoot);
|
|
42211
42860
|
let totalBytes = 0;
|
|
42212
42861
|
const walk = (dir) => {
|
|
42213
42862
|
let entries;
|
|
@@ -42233,7 +42882,7 @@ function validateExtraction(stagingRoot) {
|
|
|
42233
42882
|
}
|
|
42234
42883
|
let realFull;
|
|
42235
42884
|
try {
|
|
42236
|
-
realFull =
|
|
42885
|
+
realFull = realpathSync6(full);
|
|
42237
42886
|
} catch (err) {
|
|
42238
42887
|
throw new Error(`failed to realpath ${full}: ${err}`);
|
|
42239
42888
|
}
|
|
@@ -42261,14 +42910,30 @@ function assertArchiveEntryPath(entry) {
|
|
|
42261
42910
|
throw new Error(`archive entry escapes archive root: ${entry}`);
|
|
42262
42911
|
}
|
|
42263
42912
|
}
|
|
42913
|
+
var ARCHIVE_LIST_TIMEOUT_MS = 30000;
|
|
42264
42914
|
function runArchiveTool(command, args, timeout) {
|
|
42265
42915
|
return new Promise((resolvePromise, reject) => {
|
|
42266
|
-
|
|
42267
|
-
|
|
42268
|
-
|
|
42269
|
-
|
|
42270
|
-
|
|
42271
|
-
|
|
42916
|
+
let child;
|
|
42917
|
+
const deadline = setTimeout(() => {
|
|
42918
|
+
reject(new Error(`${command} ${args[0]} timed out after ${timeout} ms`));
|
|
42919
|
+
child?.kill("SIGKILL");
|
|
42920
|
+
child?.stdin?.destroy();
|
|
42921
|
+
child?.stdout?.destroy();
|
|
42922
|
+
child?.stderr?.destroy();
|
|
42923
|
+
}, timeout);
|
|
42924
|
+
try {
|
|
42925
|
+
child = execFile(command, args, { encoding: "utf8", timeout, killSignal: "SIGKILL" }, (err, stdout) => {
|
|
42926
|
+
clearTimeout(deadline);
|
|
42927
|
+
if (err)
|
|
42928
|
+
reject(err);
|
|
42929
|
+
else
|
|
42930
|
+
resolvePromise(stdout);
|
|
42931
|
+
});
|
|
42932
|
+
child.stdin?.end();
|
|
42933
|
+
} catch (error) {
|
|
42934
|
+
clearTimeout(deadline);
|
|
42935
|
+
reject(error);
|
|
42936
|
+
}
|
|
42272
42937
|
});
|
|
42273
42938
|
}
|
|
42274
42939
|
async function runTarExtraction(args, timeout) {
|
|
@@ -42282,7 +42947,7 @@ async function runTarExtraction(args, timeout) {
|
|
|
42282
42947
|
}
|
|
42283
42948
|
async function precheckWithTar(archivePath, command) {
|
|
42284
42949
|
let totalBytes = 0;
|
|
42285
|
-
const verbose = await runArchiveTool(command, ["-tvf", archivePath]);
|
|
42950
|
+
const verbose = await runArchiveTool(command, ["-tvf", archivePath], ARCHIVE_LIST_TIMEOUT_MS);
|
|
42286
42951
|
for (const line of verbose.split(`
|
|
42287
42952
|
`)) {
|
|
42288
42953
|
if (line.startsWith("h")) {
|
|
@@ -42295,7 +42960,7 @@ async function precheckWithTar(archivePath, command) {
|
|
|
42295
42960
|
totalBytes += Math.max(...numeric);
|
|
42296
42961
|
}
|
|
42297
42962
|
}
|
|
42298
|
-
const names = await runArchiveTool(command, ["-tf", archivePath]);
|
|
42963
|
+
const names = await runArchiveTool(command, ["-tf", archivePath], ARCHIVE_LIST_TIMEOUT_MS);
|
|
42299
42964
|
for (const entry of names.split(`
|
|
42300
42965
|
`)) {
|
|
42301
42966
|
if (entry.length > 0)
|
|
@@ -42306,11 +42971,11 @@ async function precheckWithTar(archivePath, command) {
|
|
|
42306
42971
|
async function precheckArchiveContents(archivePath, archiveType, zipListingMode = "platform") {
|
|
42307
42972
|
let totalBytes = 0;
|
|
42308
42973
|
if (archiveType === "zip" && zipListingMode === "platform" && process.platform !== "win32") {
|
|
42309
|
-
const out = await runArchiveTool("unzip", ["-l", archivePath]);
|
|
42974
|
+
const out = await runArchiveTool("unzip", ["-l", archivePath], ARCHIVE_LIST_TIMEOUT_MS);
|
|
42310
42975
|
const match = out.match(/^\s*(\d+)\s+\d+\s+files?\s*$/m);
|
|
42311
42976
|
if (match)
|
|
42312
42977
|
totalBytes = Number.parseInt(match[1] ?? "0", 10);
|
|
42313
|
-
const names = await runArchiveTool("unzip", ["-Z1", archivePath]);
|
|
42978
|
+
const names = await runArchiveTool("unzip", ["-Z1", archivePath], ARCHIVE_LIST_TIMEOUT_MS);
|
|
42314
42979
|
for (const entry of names.split(`
|
|
42315
42980
|
`)) {
|
|
42316
42981
|
if (entry.length > 0)
|
|
@@ -42337,7 +43002,7 @@ async function extractArchiveSafely(archivePath, destDir, archiveType) {
|
|
|
42337
43002
|
try {
|
|
42338
43003
|
rmSync6(destDir, { recursive: true, force: true });
|
|
42339
43004
|
} catch {}
|
|
42340
|
-
|
|
43005
|
+
renameSync9(stagingDir, destDir);
|
|
42341
43006
|
} catch (err) {
|
|
42342
43007
|
try {
|
|
42343
43008
|
rmSync6(stagingDir, { recursive: true, force: true });
|
|
@@ -42352,7 +43017,7 @@ function quarantineCachedGithubInstall(spec, reason) {
|
|
|
42352
43017
|
try {
|
|
42353
43018
|
mkdirSync7(dirname10(dest), { recursive: true });
|
|
42354
43019
|
rmSync6(dest, { recursive: true, force: true });
|
|
42355
|
-
|
|
43020
|
+
renameSync9(packageDir, dest);
|
|
42356
43021
|
} catch (err) {
|
|
42357
43022
|
warn2(`[lsp] tofu_mismatch ${spec.id}: failed to quarantine cache entry: ${err}`);
|
|
42358
43023
|
}
|
|
@@ -42444,7 +43109,7 @@ async function downloadAndInstall(spec, tag, assets, platform, arch, fetchImpl,
|
|
|
42444
43109
|
const message = `hash failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
42445
43110
|
error2(`[lsp] hash ${spec.id} failed: ${err}`);
|
|
42446
43111
|
try {
|
|
42447
|
-
|
|
43112
|
+
unlinkSync6(archivePath);
|
|
42448
43113
|
} catch {}
|
|
42449
43114
|
return { ok: false, sha256: "", error: message };
|
|
42450
43115
|
}
|
|
@@ -42455,7 +43120,7 @@ async function downloadAndInstall(spec, tag, assets, platform, arch, fetchImpl,
|
|
|
42455
43120
|
const message = `TOFU sha256 mismatch: previously installed archive sha256=${previousArchiveSha256}, ` + `downloaded sha256=${archiveSha256}`;
|
|
42456
43121
|
error2(`[lsp] ${spec.id} ${tag}: ${message}. ` + `This means the published release for tag ${tag} changed. Investigate before proceeding. ` + `Run \`npx @cortexkit/aft doctor --clear\` to wipe the cache and force a fresh install if you've verified the change.`);
|
|
42457
43122
|
try {
|
|
42458
|
-
|
|
43123
|
+
unlinkSync6(archivePath);
|
|
42459
43124
|
} catch {}
|
|
42460
43125
|
return { ok: false, sha256: archiveSha256, error: message };
|
|
42461
43126
|
}
|
|
@@ -42467,7 +43132,7 @@ async function downloadAndInstall(spec, tag, assets, platform, arch, fetchImpl,
|
|
|
42467
43132
|
return { ok: false, sha256: archiveSha256, error: message };
|
|
42468
43133
|
} finally {
|
|
42469
43134
|
try {
|
|
42470
|
-
|
|
43135
|
+
unlinkSync6(archivePath);
|
|
42471
43136
|
} catch {}
|
|
42472
43137
|
}
|
|
42473
43138
|
const innerBinaryPath = join20(extractDir, spec.binaryPathInArchive(platform, arch, version));
|
|
@@ -43364,6 +44029,190 @@ function astTools(ctx) {
|
|
|
43364
44029
|
};
|
|
43365
44030
|
}
|
|
43366
44031
|
|
|
44032
|
+
// src/v2-session-lifecycle.ts
|
|
44033
|
+
import { Effect as Effect4, Stream } from "effect";
|
|
44034
|
+
function noteV2ToolAbort(client, sessionID) {
|
|
44035
|
+
const state = stateFor2(client, sessionID);
|
|
44036
|
+
if (!state || state.settling)
|
|
44037
|
+
return;
|
|
44038
|
+
let resolve = () => {};
|
|
44039
|
+
const promise = new Promise((done) => {
|
|
44040
|
+
resolve = done;
|
|
44041
|
+
});
|
|
44042
|
+
state.settling = { promise, resolve };
|
|
44043
|
+
}
|
|
44044
|
+
async function awaitV2ExecutionSettlement(client, sessionID) {
|
|
44045
|
+
await stateFor2(client, sessionID)?.settling?.promise;
|
|
44046
|
+
}
|
|
44047
|
+
var locations = new WeakMap;
|
|
44048
|
+
function stateFor2(client, sessionID) {
|
|
44049
|
+
if (!client || typeof client !== "object")
|
|
44050
|
+
return;
|
|
44051
|
+
const sessions = locations.get(client);
|
|
44052
|
+
if (!sessions)
|
|
44053
|
+
return;
|
|
44054
|
+
let state = sessions.get(sessionID);
|
|
44055
|
+
if (!state) {
|
|
44056
|
+
state = {
|
|
44057
|
+
stopped: false,
|
|
44058
|
+
stopEpoch: 0,
|
|
44059
|
+
tasks: new Map,
|
|
44060
|
+
wakes: new Set,
|
|
44061
|
+
watches: new Map
|
|
44062
|
+
};
|
|
44063
|
+
sessions.set(sessionID, state);
|
|
44064
|
+
}
|
|
44065
|
+
return state;
|
|
44066
|
+
}
|
|
44067
|
+
function v2SubagentStopped(client, sessionID) {
|
|
44068
|
+
return stateFor2(client, sessionID)?.stopped === true;
|
|
44069
|
+
}
|
|
44070
|
+
function v2StopEpoch(client, sessionID) {
|
|
44071
|
+
return stateFor2(client, sessionID)?.stopEpoch ?? 0;
|
|
44072
|
+
}
|
|
44073
|
+
async function killBackgroundTask(ctx, runtime, taskId) {
|
|
44074
|
+
const result = await callBashBridge(ctx, runtime, "bash_kill", { task_id: taskId }, { abortSignal: undefined });
|
|
44075
|
+
if (result.success === false)
|
|
44076
|
+
throw new Error(String(result.message ?? "bash_kill failed"));
|
|
44077
|
+
markHeldBgCompletions(runtime.sessionID);
|
|
44078
|
+
}
|
|
44079
|
+
async function trackV2BackgroundTask(ctx, runtime, taskId, startedEpoch) {
|
|
44080
|
+
const state = stateFor2(ctx.client, runtime.sessionID);
|
|
44081
|
+
if (!state)
|
|
44082
|
+
return;
|
|
44083
|
+
const kill = () => killBackgroundTask(ctx, runtime, taskId);
|
|
44084
|
+
if (state.stopped || state.stopEpoch !== startedEpoch) {
|
|
44085
|
+
await kill();
|
|
44086
|
+
return;
|
|
44087
|
+
}
|
|
44088
|
+
state.tasks.set(taskId, kill);
|
|
44089
|
+
}
|
|
44090
|
+
async function killStoppedBackgroundTasks(client, sessionID, ctx, startedBeforeMs) {
|
|
44091
|
+
const kills = [];
|
|
44092
|
+
const visited = new Set;
|
|
44093
|
+
const dispatch = (taskId, kill) => {
|
|
44094
|
+
if (visited.has(taskId))
|
|
44095
|
+
return;
|
|
44096
|
+
visited.add(taskId);
|
|
44097
|
+
kills.push(kill().catch((error) => {
|
|
44098
|
+
warn2(`[bash] failed to kill stopped session ${sessionID}'s task ${taskId}: ${String(error)}`);
|
|
44099
|
+
}));
|
|
44100
|
+
};
|
|
44101
|
+
for (const [owner, state] of locations.get(client) ?? []) {
|
|
44102
|
+
if (owner !== sessionID)
|
|
44103
|
+
continue;
|
|
44104
|
+
const tasks = [...state.tasks];
|
|
44105
|
+
state.tasks.clear();
|
|
44106
|
+
for (const [taskId, kill] of tasks) {
|
|
44107
|
+
dispatch(taskId, kill);
|
|
44108
|
+
}
|
|
44109
|
+
}
|
|
44110
|
+
if (ctx) {
|
|
44111
|
+
const runtime = {
|
|
44112
|
+
sessionID,
|
|
44113
|
+
directory: client.location.directory,
|
|
44114
|
+
directoryIsSessionRoot: true
|
|
44115
|
+
};
|
|
44116
|
+
try {
|
|
44117
|
+
const result = await callBashBridge(ctx, runtime, "bash_owned_tasks", { started_before_ms: startedBeforeMs }, { abortSignal: undefined });
|
|
44118
|
+
if (result.success === false)
|
|
44119
|
+
throw new Error(String(result.message ?? "bash_owned_tasks failed"));
|
|
44120
|
+
for (const taskId of Array.isArray(result.task_ids) ? result.task_ids : []) {
|
|
44121
|
+
if (typeof taskId === "string")
|
|
44122
|
+
dispatch(taskId, () => killBackgroundTask(ctx, runtime, taskId));
|
|
44123
|
+
}
|
|
44124
|
+
} catch (error) {
|
|
44125
|
+
warn2(`[bash] failed to enumerate stopped session ${sessionID}'s tasks: ${String(error)}`);
|
|
44126
|
+
}
|
|
44127
|
+
}
|
|
44128
|
+
await Promise.all(kills);
|
|
44129
|
+
}
|
|
44130
|
+
function allowV2WatchWake(client, sessionID, taskId) {
|
|
44131
|
+
const state = stateFor2(client, sessionID);
|
|
44132
|
+
if (state && !state.stopped)
|
|
44133
|
+
state.wakes.add(taskId);
|
|
44134
|
+
}
|
|
44135
|
+
function v2WatchWakeAllowed(client, sessionID, taskId) {
|
|
44136
|
+
const state = stateFor2(client, sessionID);
|
|
44137
|
+
return state !== undefined && !state.stopped && state.wakes.has(taskId);
|
|
44138
|
+
}
|
|
44139
|
+
function forgetV2Watch(client, sessionID, taskId) {
|
|
44140
|
+
const state = stateFor2(client, sessionID);
|
|
44141
|
+
if (!state)
|
|
44142
|
+
return;
|
|
44143
|
+
state.wakes.delete(taskId);
|
|
44144
|
+
state.tasks.delete(taskId);
|
|
44145
|
+
for (const [watchId, watch] of state.watches) {
|
|
44146
|
+
if (watch.taskId === taskId)
|
|
44147
|
+
state.watches.delete(watchId);
|
|
44148
|
+
}
|
|
44149
|
+
}
|
|
44150
|
+
async function trackV2Watch(ctx, runtime, taskId, watchId) {
|
|
44151
|
+
const state = stateFor2(ctx.client, runtime.sessionID);
|
|
44152
|
+
if (!state || typeof watchId !== "string")
|
|
44153
|
+
return;
|
|
44154
|
+
const cancel = () => callBashBridge(ctx, runtime, "bash_unnotify", { task_id: taskId, watch_id: watchId }, { abortSignal: undefined });
|
|
44155
|
+
if (state.stopped) {
|
|
44156
|
+
await cancel();
|
|
44157
|
+
return;
|
|
44158
|
+
}
|
|
44159
|
+
state.wakes.add(taskId);
|
|
44160
|
+
state.watches.set(watchId, { taskId, cancel });
|
|
44161
|
+
}
|
|
44162
|
+
function registerV2SessionLifecycle(context, ctx) {
|
|
44163
|
+
const host = context;
|
|
44164
|
+
return Effect4.gen(function* () {
|
|
44165
|
+
if (!host.event?.subscribe || !context || typeof context !== "object")
|
|
44166
|
+
return;
|
|
44167
|
+
const sessions = new Map;
|
|
44168
|
+
locations.set(context, sessions);
|
|
44169
|
+
yield* Effect4.addFinalizer(() => Effect4.sync(() => {
|
|
44170
|
+
for (const state of sessions.values())
|
|
44171
|
+
state.settling?.resolve();
|
|
44172
|
+
locations.delete(context);
|
|
44173
|
+
}));
|
|
44174
|
+
const pull = yield* Stream.toPull(host.event.subscribe());
|
|
44175
|
+
yield* Effect4.forkScoped(Effect4.forever(pull.pipe(Effect4.flatMap((events) => Effect4.forEach(events, (event) => Effect4.promise(async () => {
|
|
44176
|
+
const sessionID = event.data.sessionID;
|
|
44177
|
+
if (!sessionID)
|
|
44178
|
+
return;
|
|
44179
|
+
const state = stateFor2(context, sessionID);
|
|
44180
|
+
if (!state)
|
|
44181
|
+
return;
|
|
44182
|
+
if (event.type === "session.execution.started") {
|
|
44183
|
+
state.stopped = false;
|
|
44184
|
+
return;
|
|
44185
|
+
}
|
|
44186
|
+
const deliberateStop = event.type === "session.execution.interrupted" && (event.data.reason === "user" || event.data.reason === "inactivity");
|
|
44187
|
+
if (!deliberateStop) {
|
|
44188
|
+
if (event.type === "session.execution.failed" || event.type === "session.execution.succeeded" || event.type === "session.execution.interrupted") {
|
|
44189
|
+
state.settling?.resolve();
|
|
44190
|
+
state.settling = undefined;
|
|
44191
|
+
}
|
|
44192
|
+
return;
|
|
44193
|
+
}
|
|
44194
|
+
if (!await resolveIsSubagent(context, sessionID)) {
|
|
44195
|
+
state.settling?.resolve();
|
|
44196
|
+
state.settling = undefined;
|
|
44197
|
+
return;
|
|
44198
|
+
}
|
|
44199
|
+
state.stopped = true;
|
|
44200
|
+
state.stopEpoch += 1;
|
|
44201
|
+
state.wakes.clear();
|
|
44202
|
+
state.settling?.resolve();
|
|
44203
|
+
state.settling = undefined;
|
|
44204
|
+
const watches = [...state.watches.values()];
|
|
44205
|
+
state.watches.clear();
|
|
44206
|
+
Promise.all([
|
|
44207
|
+
killStoppedBackgroundTasks(context, sessionID, ctx, event.created),
|
|
44208
|
+
...watches.map((watch) => watch.cancel().catch((error) => {
|
|
44209
|
+
warn2(`[bash_watch] failed to cancel stopped session ${sessionID}'s watch on ${watch.taskId}: ${String(error)}`);
|
|
44210
|
+
}))
|
|
44211
|
+
]);
|
|
44212
|
+
}), { discard: true })))), { startImmediately: true });
|
|
44213
|
+
});
|
|
44214
|
+
}
|
|
44215
|
+
|
|
43367
44216
|
// src/tools/bash.ts
|
|
43368
44217
|
var z3 = tool.schema;
|
|
43369
44218
|
var METADATA_PREVIEW_LIMIT = 30 * 1024;
|
|
@@ -43499,7 +44348,7 @@ function bashToolDescription(aftSearchRegistered, compressionOn, backgroundOn, d
|
|
|
43499
44348
|
].join(", ");
|
|
43500
44349
|
const searchSteer = `use ${steerTools}${zoomEnabled ? ", or aft_zoom" : ""} instead`;
|
|
43501
44350
|
const compression = compressionOn ? " Output is compressed by default; pass compressed: false for raw output. Piped commands run verbatim and show the pipeline's output; for AFT's test/build summary, run the runner without | head, | tail, or | grep. Pipeline-failure notes cover single top-level pipelines only; multi-statement commands (`a; b | c; d`) are not instrumented, so masked failures inside them still need explicit exit-code checks." : "";
|
|
43502
|
-
const tasks = backgroundOn ? ` Commands run in the foreground and return inline; wait: true blocks until a long command finishes instead of auto-promoting (
|
|
44351
|
+
const tasks = backgroundOn ? ` Commands run in the foreground and return inline; wait: true blocks until a long command finishes instead of auto-promoting (it blocks up to bash.worker_wait_max_ms, 30 minutes by default, then reports the command is still running; watch again to keep waiting); ${userMessageDetachDescription(detachOnUserMessage)} Use it when you need the result before doing anything else; ${autoPromote}. Use background: true yourself ONLY when you have other useful work to do while it runs; ${backgroundWaitDescription(watchToolRegistered, status, role)} A \`nohup … &\` launch still holds the call if the child keeps stdout/stderr; redirect both or use background:true. pty: true runs interactive programs (REPLs, TUIs), implies background${ptyDriveClause(status, write)}.` : " Commands run in the foreground to completion; timeout is the hard kill cap (default 30 minutes).";
|
|
43503
44352
|
const outputRules = backgroundOn ? " Finished output expires after the task is 24 hours old and its completion has been delivered; under project-root restrictions, only the starting session can read output outside the project—copy cited lines into your report." : "";
|
|
43504
44353
|
const timeoutRules = " `timeout` starts after spawn and kills the Unix process group (exit 124; Windows uses taskkill /T /F); processes that leave the group survive.";
|
|
43505
44354
|
const remoteRuns = surface.remoteRuns ? ` ${BASH_RUNON_GUIDANCE}` : "";
|
|
@@ -43597,7 +44446,7 @@ function bashTimeoutDescription(backgroundOn, role = "primary") {
|
|
|
43597
44446
|
if (!backgroundOn) {
|
|
43598
44447
|
return "Hard kill cap in milliseconds (positive integer). When omitted, the foreground command can run up to 30 minutes and returns inline when it finishes.";
|
|
43599
44448
|
}
|
|
43600
|
-
const promoted = role === "worker" ? "moves to the background as a task that won't wake you, so wait on it with bash_watch; wait:true disables promotion and remains inline until completion, the timeout, or the worker wait limit" : "is promoted to background and gets a completion reminder when it exits; wait:true disables promotion and remains inline until completion or
|
|
44449
|
+
const promoted = role === "worker" ? "moves to the background as a task that won't wake you, so wait on it with bash_watch; wait:true disables promotion and remains inline until completion, the timeout, or the worker wait limit" : "is promoted to background and gets a completion reminder when it exits; wait:true disables promotion and remains inline until completion, timeout, or bash.worker_wait_max_ms";
|
|
43601
44450
|
return `Hard kill cap in milliseconds (positive integer). In the default foreground mode when wait is false, a command that exceeds the configured wait window ${promoted}. A background task with no timeout is killed after 30 minutes; pass a longer timeout for long jobs.`;
|
|
43602
44451
|
}
|
|
43603
44452
|
function createBashTool(ctx, aftSearchRegisteredOverride) {
|
|
@@ -43609,7 +44458,7 @@ function createBashTool(ctx, aftSearchRegisteredOverride) {
|
|
|
43609
44458
|
...bashCompanionRegistered(ctx.config, "bash_kill") ? ["bash_kill"] : []
|
|
43610
44459
|
];
|
|
43611
44460
|
const waitArg = initialBashCfg.background ? {
|
|
43612
|
-
wait: z3.boolean().optional().describe(`When true, run in the foreground without auto-promoting and wait until the command finishes or reaches its timeout (
|
|
44461
|
+
wait: z3.boolean().optional().describe(`When true, run in the foreground without auto-promoting and wait until the command finishes or reaches its timeout (at most bash.worker_wait_max_ms, 30 minutes by default; the command then keeps running in the background and the reply says how to keep waiting); ${userMessageDetachDescription(initialBashCfg.detach_on_user_message)} Use only when you know the result is required before doing anything else.`)
|
|
43613
44462
|
} : {};
|
|
43614
44463
|
const sandboxArg = nativeSandboxEnabled(ctx.config) ? {
|
|
43615
44464
|
sandbox: z3.literal("host").optional().describe("Request one-command approval to run unsandboxed on the host; use only when native sandboxing blocks required work, and note that it is a no-op when sandboxing is disabled.")
|
|
@@ -43664,6 +44513,7 @@ ${BASH_RUNON_GUIDANCE}`, "");
|
|
|
43664
44513
|
},
|
|
43665
44514
|
execute: async (args, context) => {
|
|
43666
44515
|
const bashCfg = resolveBashConfig(ctx.config);
|
|
44516
|
+
const startedEpoch = v2StopEpoch(ctx.client, context.sessionID);
|
|
43667
44517
|
const ctxAftSearchRegistered = ctx.aftSearchRegistered === true;
|
|
43668
44518
|
const aftSearchRegistered = aftSearchRegisteredOverride ?? ctxAftSearchRegistered;
|
|
43669
44519
|
let accumulatedOutput = "";
|
|
@@ -43693,7 +44543,7 @@ ${BASH_RUNON_GUIDANCE}`, "");
|
|
|
43693
44543
|
const blockToCompletion = subagentForcedForeground || backgroundDisabled || requestedWait;
|
|
43694
44544
|
const effectiveBackground = blockToCompletion ? false : requestedBackground;
|
|
43695
44545
|
const rawTimeout = coerceOptionalInt(args.timeout, "timeout", 1, Number.MAX_SAFE_INTEGER);
|
|
43696
|
-
const workerWaitMaxMs =
|
|
44546
|
+
const workerWaitMaxMs = bashCfg.worker_wait_max_ms;
|
|
43697
44547
|
const ptyRows = coerceOptionalInt(args.ptyRows, "ptyRows", 1, 60);
|
|
43698
44548
|
const ptyCols = coerceOptionalInt(args.ptyCols, "ptyCols", 1, 140);
|
|
43699
44549
|
const compressed = coerceBoolean(args.compressed, true);
|
|
@@ -43777,6 +44627,7 @@ ${BASH_RUNON_GUIDANCE}`, "");
|
|
|
43777
44627
|
if (data.status === "running" && typeof data.task_id === "string") {
|
|
43778
44628
|
const taskId = data.task_id;
|
|
43779
44629
|
trackBgTask(context.sessionID, taskId);
|
|
44630
|
+
await trackV2BackgroundTask(ctx, context, taskId, startedEpoch);
|
|
43780
44631
|
let rendered = data.output ?? "";
|
|
43781
44632
|
if (isSubagent) {
|
|
43782
44633
|
rendered += workerBackgroundTaskNote(taskId);
|
|
@@ -43849,7 +44700,10 @@ async function formatBashStatusText(runtime, taskId, data, requestedOutputMode,
|
|
|
43849
44700
|
if (typeof data.live_descendants_summary === "string") {
|
|
43850
44701
|
text += ` · ${data.live_descendants_summary}`;
|
|
43851
44702
|
}
|
|
43852
|
-
if (data.
|
|
44703
|
+
if (typeof data.remote_phase_note === "string") {
|
|
44704
|
+
text += ` · ${data.remote_phase_note}`;
|
|
44705
|
+
}
|
|
44706
|
+
if (data.output_incomplete === true || status === "failed" && data.status_reason) {
|
|
43853
44707
|
const reason = typeof data.status_reason === "string" && data.status_reason ? data.status_reason : "PTY output may be incomplete";
|
|
43854
44708
|
text += `
|
|
43855
44709
|
[${reason}]`;
|
|
@@ -43946,7 +44800,25 @@ function conflictTools(ctx) {
|
|
|
43946
44800
|
}
|
|
43947
44801
|
|
|
43948
44802
|
// src/tools/definitions/v2.ts
|
|
43949
|
-
import { Effect as
|
|
44803
|
+
import { Effect as Effect5 } from "effect";
|
|
44804
|
+
|
|
44805
|
+
// src/v2-host-tool-names.ts
|
|
44806
|
+
var V2_HOST_TOOL_NAMES = Object.freeze({
|
|
44807
|
+
bash: "shell",
|
|
44808
|
+
apply_patch: "patch"
|
|
44809
|
+
});
|
|
44810
|
+
var AFT_TOOL_FOR_V2_HOST = Object.freeze(Object.fromEntries(Object.entries(V2_HOST_TOOL_NAMES).map(([aft, host]) => [host, aft])));
|
|
44811
|
+
function v2HostToolName(aftName) {
|
|
44812
|
+
return V2_HOST_TOOL_NAMES[aftName] ?? aftName;
|
|
44813
|
+
}
|
|
44814
|
+
function aftToolNameForV2Host(hostName) {
|
|
44815
|
+
return AFT_TOOL_FOR_V2_HOST[hostName] ?? hostName;
|
|
44816
|
+
}
|
|
44817
|
+
function renameV2ToolReferences(text) {
|
|
44818
|
+
return text.replace(/(?<![\w./-])bash(?![\w-]|\.\w|\s+(?:itself|REPLs|syntax)\b)/g, "shell").replace(/(?<![\w./-])apply_patch(?![\w-]|\.\w)/g, "patch");
|
|
44819
|
+
}
|
|
44820
|
+
|
|
44821
|
+
// src/tools/definitions/v2.ts
|
|
43950
44822
|
var V2_TOOL_NAME = /^[A-Za-z][A-Za-z0-9_-]{0,63}$/;
|
|
43951
44823
|
var V2_PATH_HEADER_TOOLS = new Set(["read", "write", "edit"]);
|
|
43952
44824
|
var V2_BASH_TOOLS = new Set(["bash"]);
|
|
@@ -44031,12 +44903,23 @@ function bareToolName3(name) {
|
|
|
44031
44903
|
return name.startsWith("aft_") ? name.slice(4) : name;
|
|
44032
44904
|
}
|
|
44033
44905
|
function projectArguments(name, definition) {
|
|
44034
|
-
|
|
44035
|
-
|
|
44906
|
+
const args = renameArgumentDescriptions(definition.args);
|
|
44907
|
+
if (!V2_PATH_HEADER_TOOLS.has(bareToolName3(name)) || !("filePath" in args)) {
|
|
44908
|
+
return args;
|
|
44036
44909
|
}
|
|
44037
|
-
const { filePath, ...rest } =
|
|
44910
|
+
const { filePath, ...rest } = args;
|
|
44038
44911
|
return { path: filePath, ...rest };
|
|
44039
44912
|
}
|
|
44913
|
+
function renameArgumentDescriptions(args) {
|
|
44914
|
+
const renamed = {};
|
|
44915
|
+
for (const [key, schema] of Object.entries(args)) {
|
|
44916
|
+
const described = schema;
|
|
44917
|
+
const description = described.description;
|
|
44918
|
+
const next = description === undefined ? undefined : renameV2ToolReferences(description);
|
|
44919
|
+
renamed[key] = next === undefined || next === description ? schema : described.describe(next);
|
|
44920
|
+
}
|
|
44921
|
+
return renamed;
|
|
44922
|
+
}
|
|
44040
44923
|
function executionArguments(name, input) {
|
|
44041
44924
|
if (!V2_PATH_HEADER_TOOLS.has(bareToolName3(name)) || !("path" in input))
|
|
44042
44925
|
return input;
|
|
@@ -44095,7 +44978,7 @@ function runtimeFor(name, location, context, signal, consumers) {
|
|
|
44095
44978
|
abort: signal,
|
|
44096
44979
|
effectAbort: signal,
|
|
44097
44980
|
metadata: (update) => {
|
|
44098
|
-
|
|
44981
|
+
Effect5.runPromise(context.progress(jsonSafeMetadata({
|
|
44099
44982
|
...update.metadata ?? {},
|
|
44100
44983
|
...update.title ? { title: update.title } : {}
|
|
44101
44984
|
})));
|
|
@@ -44110,36 +44993,46 @@ function runtimeFor(name, location, context, signal, consumers) {
|
|
|
44110
44993
|
};
|
|
44111
44994
|
}
|
|
44112
44995
|
function projectV2Tool(name, definition, location, consumers = {}) {
|
|
44996
|
+
const hostName = v2HostToolName(name);
|
|
44113
44997
|
const input = tool.schema.object(projectArguments(name, definition));
|
|
44114
|
-
assertV2Contract(
|
|
44998
|
+
assertV2Contract(hostName, input);
|
|
44115
44999
|
const sharedOptions = definition.options;
|
|
44116
45000
|
const permission = hostPermission(name);
|
|
44117
45001
|
return {
|
|
44118
|
-
name,
|
|
44119
|
-
description: definition.description,
|
|
45002
|
+
name: hostName,
|
|
45003
|
+
description: renameV2ToolReferences(definition.description),
|
|
44120
45004
|
input,
|
|
44121
45005
|
options: {
|
|
44122
45006
|
...sharedOptions,
|
|
44123
45007
|
...permission ? { permission } : {},
|
|
44124
45008
|
codemode: false
|
|
44125
45009
|
},
|
|
44126
|
-
execute: (rawInput, context) =>
|
|
45010
|
+
execute: (rawInput, context) => Effect5.tryPromise({
|
|
44127
45011
|
try: async (signal) => {
|
|
44128
|
-
|
|
44129
|
-
|
|
45012
|
+
const onAbort = () => {
|
|
45013
|
+
if (context.sessionID)
|
|
45014
|
+
consumers.onToolAbort?.(context.sessionID);
|
|
45015
|
+
};
|
|
45016
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
45017
|
+
try {
|
|
45018
|
+
if (name === "bash_status" || name === "bash_watch") {
|
|
45019
|
+
consumers.registerSession?.(context.sessionID);
|
|
45020
|
+
}
|
|
45021
|
+
const runtime = runtimeFor(name, location, context, signal, consumers);
|
|
45022
|
+
const input = executionArguments(name, rawInput);
|
|
45023
|
+
const result = consumers.executeBash && V2_BASH_TOOLS.has(name) ? await consumers.executeBash({
|
|
45024
|
+
name,
|
|
45025
|
+
input,
|
|
45026
|
+
context: runtime,
|
|
45027
|
+
definition
|
|
45028
|
+
}) : await definition.execute(input, runtime);
|
|
45029
|
+
const refusal = failureEnvelopeMessage(result);
|
|
45030
|
+
if (refusal)
|
|
45031
|
+
throw new Error(refusal);
|
|
45032
|
+
return resultContent(result);
|
|
45033
|
+
} finally {
|
|
45034
|
+
signal.removeEventListener("abort", onAbort);
|
|
44130
45035
|
}
|
|
44131
|
-
const runtime = runtimeFor(name, location, context, signal, consumers);
|
|
44132
|
-
const input = executionArguments(name, rawInput);
|
|
44133
|
-
const result = consumers.executeBash && V2_BASH_TOOLS.has(name) ? await consumers.executeBash({
|
|
44134
|
-
name,
|
|
44135
|
-
input,
|
|
44136
|
-
context: runtime,
|
|
44137
|
-
definition
|
|
44138
|
-
}) : await definition.execute(input, runtime);
|
|
44139
|
-
const refusal = failureEnvelopeMessage(result);
|
|
44140
|
-
if (refusal)
|
|
44141
|
-
throw new Error(refusal);
|
|
44142
|
-
return resultContent(result);
|
|
44143
45036
|
},
|
|
44144
45037
|
catch: failure
|
|
44145
45038
|
})
|
|
@@ -44168,10 +45061,10 @@ function joinList(items) {
|
|
|
44168
45061
|
}
|
|
44169
45062
|
|
|
44170
45063
|
// src/shared/read-vision.ts
|
|
44171
|
-
import { Effect as
|
|
45064
|
+
import { Effect as Effect6 } from "effect";
|
|
44172
45065
|
var VISION_HOST_TIMEOUT_MS = 1500;
|
|
44173
45066
|
var states = new WeakMap;
|
|
44174
|
-
function
|
|
45067
|
+
function stateFor3(client) {
|
|
44175
45068
|
let state = states.get(client);
|
|
44176
45069
|
if (!state) {
|
|
44177
45070
|
state = { models: new Map, history: new Map, capabilities: new Map };
|
|
@@ -44180,7 +45073,7 @@ function stateFor2(client) {
|
|
|
44180
45073
|
return state;
|
|
44181
45074
|
}
|
|
44182
45075
|
function rememberReadModel(client, sessionID, model) {
|
|
44183
|
-
|
|
45076
|
+
stateFor3(client).models.set(sessionID, model);
|
|
44184
45077
|
}
|
|
44185
45078
|
async function bounded(call) {
|
|
44186
45079
|
let timer;
|
|
@@ -44203,7 +45096,7 @@ function record3(value) {
|
|
|
44203
45096
|
async function currentSessionVisionCapability(client, sessionID) {
|
|
44204
45097
|
if (!sessionID)
|
|
44205
45098
|
return;
|
|
44206
|
-
const state =
|
|
45099
|
+
const state = stateFor3(client);
|
|
44207
45100
|
let model = state.models.get(sessionID);
|
|
44208
45101
|
if (!model) {
|
|
44209
45102
|
let pending = state.history.get(sessionID);
|
|
@@ -44227,7 +45120,7 @@ async function currentSessionVisionCapability(client, sessionID) {
|
|
|
44227
45120
|
return;
|
|
44228
45121
|
const list = async () => {
|
|
44229
45122
|
const result = api.list?.();
|
|
44230
|
-
return isV2PluginContext3(client) ?
|
|
45123
|
+
return isV2PluginContext3(client) ? Effect6.runPromise(result) : await result;
|
|
44231
45124
|
};
|
|
44232
45125
|
const listed = record3(await bounded(list));
|
|
44233
45126
|
const catalog = record3(listed?.data) ?? listed;
|
|
@@ -44318,6 +45211,7 @@ function createBashWatchTool(ctx) {
|
|
|
44318
45211
|
throw new Error(`invalid_request: ${message}`);
|
|
44319
45212
|
throw new Error(`${code}: ${message}`);
|
|
44320
45213
|
}
|
|
45214
|
+
await trackV2Watch(ctx, context, taskId, registered.watch_id);
|
|
44321
45215
|
const metadata = context.metadata;
|
|
44322
45216
|
metadata?.({ taskId, registered: true, watchId: registered.watch_id });
|
|
44323
45217
|
return `Watch registered: ${registered.watch_id} on task ${taskId}
|
|
@@ -44327,6 +45221,11 @@ A notification will fire when the pattern matches or the task exits.`;
|
|
|
44327
45221
|
const role = isSubagent ? "worker" : "primary";
|
|
44328
45222
|
const effectiveWaitMs = subagentForcedSync ? bashCfg.worker_wait_max_ms : resolveWatchTimeoutMs(coerceConfiguredWatchTimeout(args.timeoutMs, role, syncWaitCap), role, syncWaitCap, bashCfg.worker_wait_max_ms);
|
|
44329
45223
|
const data = await waitForBashStatus(ctx, context, taskId, undefined, waitFor, effectiveWaitMs, role);
|
|
45224
|
+
if (isSubagent && context.abort.aborted)
|
|
45225
|
+
await awaitV2ExecutionSettlement(ctx.client, context.sessionID);
|
|
45226
|
+
const stopped = isSubagent && v2SubagentStopped(ctx.client, context.sessionID);
|
|
45227
|
+
if (stopped && data.waited?.reason === "user_message")
|
|
45228
|
+
data.waited.reason = "aborted";
|
|
44330
45229
|
const waited = data.waited;
|
|
44331
45230
|
if (waited?.reason === "user_message") {
|
|
44332
45231
|
const convertedText = await convertToAsyncWatchOnAbort(ctx, context, taskId, waitFor, coerceBoolean(args.once, true), waited.elapsed_ms, role);
|
|
@@ -44345,6 +45244,7 @@ async function convertToAsyncWatchOnAbort(ctx, context, taskId, waitFor, once, e
|
|
|
44345
45244
|
const interrupted = `Sync watch for task ${taskId} was interrupted because you sent a message after ${elapsedMs}ms of waiting. `;
|
|
44346
45245
|
const reminderFallback = interruptedWatchTail(role, `The task is still running in the background. A completion reminder will be ` + `delivered automatically when the task exits.`);
|
|
44347
45246
|
if (!waitFor) {
|
|
45247
|
+
allowV2WatchWake(ctx.client, context.sessionID, taskId);
|
|
44348
45248
|
return interrupted + interruptedWatchTail(role, `The task is still running in the background. A completion reminder will be ` + `delivered automatically when the task exits; don't poll bash_status.`);
|
|
44349
45249
|
}
|
|
44350
45250
|
const notifyParams = {
|
|
@@ -44362,6 +45262,7 @@ async function convertToAsyncWatchOnAbort(ctx, context, taskId, waitFor, once, e
|
|
|
44362
45262
|
unmarkExplicitControl(context.sessionID, taskId);
|
|
44363
45263
|
return interrupted + `Auto-registering an async watch failed (${String(registered.message ?? "unknown error")}). ` + reminderFallback;
|
|
44364
45264
|
}
|
|
45265
|
+
await trackV2Watch(ctx, context, taskId, registered.watch_id);
|
|
44365
45266
|
return interrupted + `The wait has been converted to an async watch (${registered.watch_id}). ` + interruptedWatchTail(role, `A notification will fire when the pattern matches or the task exits.`);
|
|
44366
45267
|
} catch (err) {
|
|
44367
45268
|
unmarkExplicitControl(context.sessionID, taskId);
|
|
@@ -44478,6 +45379,9 @@ async function waitForBashStatus(ctx, runtime, taskId, outputMode, waitFor, effe
|
|
|
44478
45379
|
}
|
|
44479
45380
|
busySince = undefined;
|
|
44480
45381
|
lastData = data;
|
|
45382
|
+
if (role === "worker" && abortSignal?.aborted) {
|
|
45383
|
+
return withWaited(data, waited("aborted"));
|
|
45384
|
+
}
|
|
44481
45385
|
const terminal = isTerminalStatus(data.status);
|
|
44482
45386
|
if (waitFor) {
|
|
44483
45387
|
const scan = await readNewTaskOutput(data, spillCursor);
|
|
@@ -45166,9 +46070,6 @@ function createHashlineEditTool(ctx) {
|
|
|
45166
46070
|
});
|
|
45167
46071
|
if (denial)
|
|
45168
46072
|
return permissionDeniedResponse(denial);
|
|
45169
|
-
const preview = await callToolCall(ctx, context, "edit", rawArgs, { preview: true });
|
|
45170
|
-
if (preview.success === false)
|
|
45171
|
-
throw toolErrorFromResponse("edit", preview);
|
|
45172
46073
|
const data = await callToolCall(ctx, context, "edit", rawArgs);
|
|
45173
46074
|
if (data.success === false)
|
|
45174
46075
|
throw toolErrorFromResponse("edit", data);
|
|
@@ -45797,12 +46698,13 @@ function createInspectTier2IdleScheduler(options) {
|
|
|
45797
46698
|
}
|
|
45798
46699
|
function inspectTools(ctx) {
|
|
45799
46700
|
const inspectTool = {
|
|
45800
|
-
description: "Codebase health inspection that waits for current analysis. FRESH means the reported analysis is current. PARTIAL means some diagnostics are unknown; the header names the analyzer, reason, and retry guidance. INTERRUPTED means the request stopped without a fresh snapshot; retry aft_inspect. PHASE-FAILED means inspection could not finish; address the reported reason and retry, or narrow the scope. `sections` selects drill-down detail
|
|
46701
|
+
description: "Codebase health inspection that waits for current analysis. FRESH means the reported analysis is current. PARTIAL means some diagnostics are unknown; the header names the analyzer, reason, and retry guidance. INTERRUPTED means the request stopped without a fresh snapshot; retry aft_inspect. PHASE-FAILED means inspection could not finish; address the reported reason and retry, or narrow the scope. `sections` selects drill-down detail. Scoped requests collect diagnostics when sections is omitted or includes 'diagnostics' or 'all'; sections that leave diagnostics out skip diagnostics work. Unscoped requests keep warm diagnostics. Categories switched off in `inspect.categories` are not computed or refreshed, render as off, and never make the header PARTIAL.\n\n" + "Use `scope=` to narrow all findings, counts, and examples to those paths. Cross-boundary duplicates are labeled as groups touching the scope. Scope also limits Rust analyzer startup to the Cargo workspaces owning those paths when diagnostics are requested. Files without an authoritative diagnostic report remain named gaps (complete: false), not a clean result.\n\n" + `Use when: starting work on unfamiliar code, after multi-edit batches to check diagnostics, before a refactor, before review, or to verify cleanup completeness.
|
|
45801
46702
|
|
|
45802
46703
|
` + "Treat `dead_code` as a hint, not proof: reachability is call-based, so symbols reached only via method dispatch or referenced only in type position may be false positives — verify before deleting.\n\n" + "When a list is cut, the reply ends with `shown N of M <unit> (<reason>) · narrow: <knobs>`; absence of that line means the list is complete.",
|
|
45803
46704
|
args: {
|
|
45804
|
-
sections: arg(z9.union([z9.string(), z9.array(z9.string())]).optional().describe("Categories to include in detailed drill-down (e.g. 'todos' or ['todos', 'dead_code', 'cycles']). Use 'all' for every active category. Omit for summary-only mode.
|
|
45805
|
-
scope: arg(z9.union([z9.string(), z9.array(z9.string())]).optional().describe("Restrict returned results to paths under this scope (one path, or an array of paths — not a space-separated list; file or directory; absolute or relative to project root). `scope=` narrows results
|
|
46705
|
+
sections: arg(z9.union([z9.string(), z9.array(z9.string())]).optional().describe("Categories to include in detailed drill-down (e.g. 'todos' or ['todos', 'dead_code', 'cycles']). Use 'all' for every active category. Omit for summary-only mode. With scope, listing sections without 'diagnostics' (or 'all') skips diagnostics work; omitting sections keeps diagnostics. Other categories are verified regardless of sections.")),
|
|
46706
|
+
scope: arg(z9.union([z9.string(), z9.array(z9.string())]).optional().describe("Restrict returned results to paths under this scope (one path, or an array of paths — not a space-separated list; file or directory; absolute or relative to project root). `scope=` narrows results. Scoped requests collect diagnostics for the scoped files and report coverage gaps, unless sections lists other categories and leaves out 'diagnostics'.")),
|
|
46707
|
+
offset: arg(z9.number().int().nonnegative().optional().describe("Rows to skip independently in each drill-down list. Default 0; follow next_offset in each list envelope.")),
|
|
45806
46708
|
topK: arg(z9.number().int().positive().max(100).optional().describe("Max drill-down items per category. Default 20, max 100."))
|
|
45807
46709
|
},
|
|
45808
46710
|
execute: async (args, context) => {
|
|
@@ -45817,6 +46719,8 @@ function inspectTools(ctx) {
|
|
|
45817
46719
|
rawArgs.scope = scoped.scope;
|
|
45818
46720
|
if (args.topK !== undefined && args.topK !== null)
|
|
45819
46721
|
rawArgs.topK = args.topK;
|
|
46722
|
+
if (args.offset !== undefined && args.offset !== null)
|
|
46723
|
+
rawArgs.offset = args.offset;
|
|
45820
46724
|
const response = await callToolCall(ctx, context, "inspect", rawArgs, {
|
|
45821
46725
|
transportTimeoutMs: resolveInspectDiagnosticsTimeoutMs(ctx.config) + INSPECT_TRANSPORT_HEADROOM_MS
|
|
45822
46726
|
});
|
|
@@ -46696,11 +47600,11 @@ function describeBashForRegisteredSurface(allTools, config) {
|
|
|
46696
47600
|
}
|
|
46697
47601
|
function registerAftTools(context, location, definitions, consumers = {}) {
|
|
46698
47602
|
return context.tool.transform((editor) => {
|
|
46699
|
-
const
|
|
46700
|
-
|
|
46701
|
-
if (V2_BUILTIN_REPLACEMENTS.has(
|
|
46702
|
-
editor.remove(
|
|
46703
|
-
editor.add(
|
|
47603
|
+
for (const [name, definition] of Object.entries(definitions)) {
|
|
47604
|
+
const projected = projectV2Tool(name, definition, location, consumers);
|
|
47605
|
+
if (V2_BUILTIN_REPLACEMENTS.has(name))
|
|
47606
|
+
editor.remove(projected.name);
|
|
47607
|
+
editor.add(projected);
|
|
46704
47608
|
}
|
|
46705
47609
|
});
|
|
46706
47610
|
}
|
|
@@ -47101,6 +48005,7 @@ function startOpenCodeLiveConfigReload(options) {
|
|
|
47101
48005
|
const { userConfigPath, projectConfigPath } = resolveCortexKitConfigPaths(options.directory);
|
|
47102
48006
|
return startLiveConfigReload({
|
|
47103
48007
|
paths: [userConfigPath, projectConfigPath],
|
|
48008
|
+
projectPath: projectConfigPath,
|
|
47104
48009
|
load: () => loadOpenCodeConfigForLiveReload(options.directory),
|
|
47105
48010
|
initialSources: options.initialSources,
|
|
47106
48011
|
initialProjectText: options.initialSourceTexts?.[projectConfigPath] ?? null,
|
|
@@ -47667,21 +48572,21 @@ import {
|
|
|
47667
48572
|
mkdirSync as mkdirSync8,
|
|
47668
48573
|
openSync as openSync11,
|
|
47669
48574
|
readFileSync as readFileSync17,
|
|
47670
|
-
renameSync as
|
|
48575
|
+
renameSync as renameSync10,
|
|
47671
48576
|
rmSync as rmSync8,
|
|
47672
48577
|
statSync as statSync16,
|
|
47673
|
-
writeFileSync as
|
|
48578
|
+
writeFileSync as writeFileSync11
|
|
47674
48579
|
} from "node:fs";
|
|
47675
48580
|
import { dirname as dirname16 } from "node:path";
|
|
47676
48581
|
|
|
47677
48582
|
// src/hooks/auto-update-checker/cache.ts
|
|
47678
|
-
import { cpSync, existsSync as existsSync19, mkdtempSync, readFileSync as readFileSync16, rmSync as rmSync7, writeFileSync as
|
|
48583
|
+
import { cpSync, existsSync as existsSync19, mkdtempSync, readFileSync as readFileSync16, rmSync as rmSync7, writeFileSync as writeFileSync10 } from "node:fs";
|
|
47679
48584
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
47680
48585
|
import { basename as basename6, dirname as dirname15, join as join25 } from "node:path";
|
|
47681
48586
|
var import_comment_json3 = __toESM(require_src2(), 1);
|
|
47682
48587
|
|
|
47683
48588
|
// src/hooks/auto-update-checker/checker.ts
|
|
47684
|
-
import { existsSync as existsSync18, readFileSync as readFileSync15, statSync as statSync15, writeFileSync as
|
|
48589
|
+
import { existsSync as existsSync18, readFileSync as readFileSync15, statSync as statSync15, writeFileSync as writeFileSync9 } from "node:fs";
|
|
47685
48590
|
import { readFile } from "node:fs/promises";
|
|
47686
48591
|
import { homedir as homedir15 } from "node:os";
|
|
47687
48592
|
import { dirname as dirname14, isAbsolute as isAbsolute13, join as join24, resolve as resolve15 } from "node:path";
|
|
@@ -48069,11 +48974,11 @@ function restoreAutoUpdateSnapshot(snapshot) {
|
|
|
48069
48974
|
if (snapshot.packageJson === null)
|
|
48070
48975
|
rmSync7(snapshot.packageJsonPath, { force: true });
|
|
48071
48976
|
else
|
|
48072
|
-
|
|
48977
|
+
writeFileSync10(snapshot.packageJsonPath, snapshot.packageJson);
|
|
48073
48978
|
if (snapshot.lockfile === null)
|
|
48074
48979
|
rmSync7(snapshot.lockfilePath, { force: true });
|
|
48075
48980
|
else
|
|
48076
|
-
|
|
48981
|
+
writeFileSync10(snapshot.lockfilePath, snapshot.lockfile);
|
|
48077
48982
|
rmSync7(snapshot.packageDir, { recursive: true, force: true });
|
|
48078
48983
|
if (snapshot.stagedPackageDir) {
|
|
48079
48984
|
cpSync(snapshot.stagedPackageDir, snapshot.packageDir, { recursive: true });
|
|
@@ -48110,7 +49015,7 @@ function removeFromPackageLock(installDir, packageName) {
|
|
|
48110
49015
|
modified = true;
|
|
48111
49016
|
}
|
|
48112
49017
|
if (modified) {
|
|
48113
|
-
|
|
49018
|
+
writeFileSync10(lockPath, JSON.stringify(lock, null, 2));
|
|
48114
49019
|
log2(`[auto-update-checker] Removed from package-lock.json: ${packageName}`);
|
|
48115
49020
|
}
|
|
48116
49021
|
return modified;
|
|
@@ -48132,7 +49037,7 @@ function ensureDependencyVersion(packageJsonPath, packageName, version) {
|
|
|
48132
49037
|
return true;
|
|
48133
49038
|
dependencies[packageName] = version;
|
|
48134
49039
|
nextPackageJson.dependencies = dependencies;
|
|
48135
|
-
|
|
49040
|
+
writeFileSync10(packageJsonPath, JSON.stringify(nextPackageJson, null, 2));
|
|
48136
49041
|
log2(`[auto-update-checker] Updated dependency in package.json: ${packageName} → ${version}`);
|
|
48137
49042
|
return true;
|
|
48138
49043
|
} catch (err) {
|
|
@@ -48376,7 +49281,7 @@ function claimCheckSlot(storageDir, intervalMs) {
|
|
|
48376
49281
|
let lockFd = null;
|
|
48377
49282
|
try {
|
|
48378
49283
|
lockFd = openSync11(lockPath, "wx");
|
|
48379
|
-
|
|
49284
|
+
writeFileSync11(lockFd, JSON.stringify(owner));
|
|
48380
49285
|
closeSync12(lockFd);
|
|
48381
49286
|
lockFd = null;
|
|
48382
49287
|
} catch (err) {
|
|
@@ -48390,9 +49295,9 @@ function claimCheckSlot(storageDir, intervalMs) {
|
|
|
48390
49295
|
if (!isOrphanedCheckLock(lockPath))
|
|
48391
49296
|
return null;
|
|
48392
49297
|
const replacementPath = `${lockPath}.tmp.${process.pid}`;
|
|
48393
|
-
|
|
49298
|
+
writeFileSync11(replacementPath, JSON.stringify(owner), { encoding: "utf-8", mode: 384 });
|
|
48394
49299
|
try {
|
|
48395
|
-
|
|
49300
|
+
renameSync10(replacementPath, lockPath);
|
|
48396
49301
|
} catch (replaceError) {
|
|
48397
49302
|
rmSync8(replacementPath, { force: true });
|
|
48398
49303
|
throw replaceError;
|
|
@@ -48472,8 +49377,8 @@ function hasRecentCheckTimestamp(file, intervalMs) {
|
|
|
48472
49377
|
}
|
|
48473
49378
|
function writeCheckTimestamp(file) {
|
|
48474
49379
|
const tmp = `${file}.tmp.${process.pid}`;
|
|
48475
|
-
|
|
48476
|
-
|
|
49380
|
+
writeFileSync11(tmp, JSON.stringify({ lastCheckedMs: Date.now() }), "utf-8");
|
|
49381
|
+
renameSync10(tmp, file);
|
|
48477
49382
|
}
|
|
48478
49383
|
function isNewerVersion(candidate, installed) {
|
|
48479
49384
|
const semver = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
@@ -48677,9 +49582,9 @@ import {
|
|
|
48677
49582
|
mkdirSync as mkdirSync9,
|
|
48678
49583
|
readdirSync as readdirSync6,
|
|
48679
49584
|
readFileSync as readFileSync18,
|
|
48680
|
-
renameSync as
|
|
48681
|
-
unlinkSync as
|
|
48682
|
-
writeFileSync as
|
|
49585
|
+
renameSync as renameSync11,
|
|
49586
|
+
unlinkSync as unlinkSync7,
|
|
49587
|
+
writeFileSync as writeFileSync12
|
|
48683
49588
|
} from "node:fs";
|
|
48684
49589
|
import { createServer } from "node:http";
|
|
48685
49590
|
import { dirname as dirname17, join as join27 } from "node:path";
|
|
@@ -48835,13 +49740,13 @@ class AftRpcServer {
|
|
|
48835
49740
|
const dir = dirname17(this.portFilePath);
|
|
48836
49741
|
mkdirSync9(dir, { recursive: true, mode: 448 });
|
|
48837
49742
|
const tmpPath = `${this.portFilePath}.tmp`;
|
|
48838
|
-
|
|
49743
|
+
writeFileSync12(tmpPath, JSON.stringify({
|
|
48839
49744
|
port: this.port,
|
|
48840
49745
|
token: this.token,
|
|
48841
49746
|
pid: process.pid,
|
|
48842
49747
|
started_at: Date.now()
|
|
48843
49748
|
}), { encoding: "utf-8", mode: 384 });
|
|
48844
|
-
|
|
49749
|
+
renameSync11(tmpPath, this.portFilePath);
|
|
48845
49750
|
}
|
|
48846
49751
|
sweepDeadPortFiles() {
|
|
48847
49752
|
let entries;
|
|
@@ -48859,11 +49764,11 @@ class AftRpcServer {
|
|
|
48859
49764
|
try {
|
|
48860
49765
|
const record = parseRpcPortRecord(readFileSync18(filePath, "utf-8"));
|
|
48861
49766
|
if (record === null) {
|
|
48862
|
-
|
|
49767
|
+
unlinkSync7(filePath);
|
|
48863
49768
|
continue;
|
|
48864
49769
|
}
|
|
48865
49770
|
if (record.pid !== undefined && !isPidAlive(record.pid)) {
|
|
48866
|
-
|
|
49771
|
+
unlinkSync7(filePath);
|
|
48867
49772
|
}
|
|
48868
49773
|
} catch {}
|
|
48869
49774
|
}
|
|
@@ -48899,7 +49804,7 @@ class AftRpcServer {
|
|
|
48899
49804
|
this.bunServer = null;
|
|
48900
49805
|
}
|
|
48901
49806
|
try {
|
|
48902
|
-
|
|
49807
|
+
unlinkSync7(this.portFilePath);
|
|
48903
49808
|
} catch {}
|
|
48904
49809
|
}
|
|
48905
49810
|
async handleFetch(req, srv) {
|
|
@@ -49128,7 +50033,13 @@ function readStatusBar(value) {
|
|
|
49128
50033
|
const duplicates = readOptionalNumber(bar.duplicates);
|
|
49129
50034
|
const todos = readOptionalNumber(bar.todos);
|
|
49130
50035
|
const noLanguageServer = bar.diagnostics === "no_language_server";
|
|
49131
|
-
|
|
50036
|
+
const producerMissing = bar.diagnostics === "producer_missing";
|
|
50037
|
+
const diagnosticsGaps = Array.isArray(bar.diagnostics_gaps) ? bar.diagnostics_gaps.map(asRecord3).filter((gap) => typeof gap.language === "string" && typeof gap.producer === "string" && typeof gap.root === "string").map((gap) => ({
|
|
50038
|
+
language: gap.language,
|
|
50039
|
+
producer: gap.producer,
|
|
50040
|
+
root: gap.root
|
|
50041
|
+
})) : [];
|
|
50042
|
+
if (errors === null && warnings === null && deadCode === null && unusedExports === null && duplicates === null && todos === null && !noLanguageServer && !producerMissing && disabled.length === 0) {
|
|
49132
50043
|
return;
|
|
49133
50044
|
}
|
|
49134
50045
|
return {
|
|
@@ -49140,7 +50051,8 @@ function readStatusBar(value) {
|
|
|
49140
50051
|
...duplicates !== null ? { duplicates } : {},
|
|
49141
50052
|
...todos !== null ? { todos } : {},
|
|
49142
50053
|
...bar.tier2_stale === true ? { tier2_stale: true } : {},
|
|
49143
|
-
...noLanguageServer ? { diagnostics: "no_language_server" } : {}
|
|
50054
|
+
...noLanguageServer ? { diagnostics: "no_language_server" } : {},
|
|
50055
|
+
...producerMissing ? { diagnostics: "producer_missing", diagnostics_gaps: diagnosticsGaps } : {}
|
|
49144
50056
|
};
|
|
49145
50057
|
}
|
|
49146
50058
|
function formatFlag(enabled) {
|
|
@@ -49592,11 +50504,11 @@ function throwSentinel(command) {
|
|
|
49592
50504
|
throw sentinel;
|
|
49593
50505
|
}
|
|
49594
50506
|
var PLUGIN_VERSION = resolvePluginVersion(import.meta.url);
|
|
49595
|
-
var ANNOUNCEMENT_VERSION = "0.
|
|
50507
|
+
var ANNOUNCEMENT_VERSION = "0.60.0";
|
|
49596
50508
|
var ANNOUNCEMENT_FEATURES = [
|
|
49597
|
-
"
|
|
49598
|
-
"
|
|
49599
|
-
"
|
|
50509
|
+
"Call graphs stay current cheaply: a one-file edit updates a project's graph in seconds instead of minutes, and linked worktrees reuse their main checkout's graph until they change.",
|
|
50510
|
+
"`aft_inspect` dead-code and unused-export results are more accurate on Rust, Go and TypeScript, and inspect uses far less memory on large projects.",
|
|
50511
|
+
"OpenCode 2 gets native `shell` and `patch` tools, and stopped subagents are no longer woken by their own background commands."
|
|
49600
50512
|
];
|
|
49601
50513
|
var ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/DSa65w8wuf";
|
|
49602
50514
|
var plugin = async (input) => initializePluginForDirectory(input);
|