@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/entry/server.js
CHANGED
|
@@ -10290,11 +10290,11 @@ var DEFAULT_WORKER_WAIT_MAX_MS = 1800000;
|
|
|
10290
10290
|
var MIN_WORKER_WAIT_MAX_MS = 60000;
|
|
10291
10291
|
var LONGEST_TIMER_DELAY_MS = 2147483647;
|
|
10292
10292
|
var WORKER_WAIT_LIMIT_PHRASE = "the worker wait limit (`bash.worker_wait_max_ms`, 30 minutes by default)";
|
|
10293
|
-
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";
|
|
10294
|
-
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
|
|
10293
|
+
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";
|
|
10294
|
+
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.";
|
|
10295
10295
|
function resolveWatchTimeoutMs(requestedMs, role, capMs, workerWaitMaxMs) {
|
|
10296
10296
|
if (role === "worker")
|
|
10297
|
-
return requestedMs ?? workerWaitMaxMs;
|
|
10297
|
+
return Math.min(requestedMs ?? workerWaitMaxMs, workerWaitMaxMs);
|
|
10298
10298
|
return Math.min(requestedMs ?? DEFAULT_PRIMARY_WATCH_TIMEOUT_MS, capMs);
|
|
10299
10299
|
}
|
|
10300
10300
|
function maxWatchTimeoutMs(role, capMs) {
|
|
@@ -10357,6 +10357,10 @@ function taskKillDeadlineText(data, role, nowMs = Date.now()) {
|
|
|
10357
10357
|
return "";
|
|
10358
10358
|
if (data.status === "unknown")
|
|
10359
10359
|
return "";
|
|
10360
|
+
if (typeof data.remote_phase_note === "string" && data.remote_phase_note !== "") {
|
|
10361
|
+
const note = data.remote_phase_note;
|
|
10362
|
+
return `${note.charAt(0).toUpperCase()}${note.slice(1)}.`;
|
|
10363
|
+
}
|
|
10360
10364
|
const hardKill = data.hard_kill;
|
|
10361
10365
|
if (!hardKill || typeof hardKill.limit_ms !== "number") {
|
|
10362
10366
|
return "This task has no kill deadline.";
|
|
@@ -11091,8 +11095,8 @@ var BASH_HOST_FALLBACK_BANNER = "[AFT host fallback - module transport down; no
|
|
|
11091
11095
|
var BASH_HOST_FALLBACK_MAX_OUTPUT_BYTES = 100 * 1024;
|
|
11092
11096
|
var BASH_HOST_FALLBACK_MAX_TIMEOUT_MS = 10 * 60 * 1000;
|
|
11093
11097
|
var BASH_HOST_FALLBACK_REFUSAL = "AFT transport is down; only foreground execution is available in host fallback";
|
|
11094
|
-
var BASH_RUNON_DESCRIPTION = 'Run
|
|
11095
|
-
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
|
|
11098
|
+
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).';
|
|
11099
|
+
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.';
|
|
11096
11100
|
function pathKeyForPlatform(env, platform) {
|
|
11097
11101
|
return platform === "win32" ? Object.keys(env).find((key) => key.toLowerCase() === "path") : "PATH";
|
|
11098
11102
|
}
|
|
@@ -11536,6 +11540,7 @@ function isPassiveCommand(command) {
|
|
|
11536
11540
|
|
|
11537
11541
|
// ../aft-bridge/dist/transport.js
|
|
11538
11542
|
var WORKER_SESSION_FIELD = "worker_session";
|
|
11543
|
+
var HOST_TOOL_NAMES_FIELD = "host_tool_names";
|
|
11539
11544
|
var PRESET_FIELD = "preset";
|
|
11540
11545
|
function callPresetFor(workerSession) {
|
|
11541
11546
|
return workerSession ? "worker" : "head";
|
|
@@ -11700,6 +11705,8 @@ class BinaryBridge {
|
|
|
11700
11705
|
lastBinaryFingerprintCheckAt = 0;
|
|
11701
11706
|
_retiringDueToBinaryChange = false;
|
|
11702
11707
|
pending = new Map;
|
|
11708
|
+
activeSendCalls = 0;
|
|
11709
|
+
processLoopReferenced = true;
|
|
11703
11710
|
outstandingBackgroundTaskIds = new Set;
|
|
11704
11711
|
nextId = 1;
|
|
11705
11712
|
processGeneration = 0;
|
|
@@ -11931,6 +11938,32 @@ class BinaryBridge {
|
|
|
11931
11938
|
this.sessionLogVia(sessionId, `hashline registration carrier transport=ndjson phase=${phase} edit_slot_survives=${editSlotSurvives}${repeated}`);
|
|
11932
11939
|
}
|
|
11933
11940
|
async send(command, params = {}, options) {
|
|
11941
|
+
this.activeSendCalls += 1;
|
|
11942
|
+
this.syncEventLoopHold();
|
|
11943
|
+
try {
|
|
11944
|
+
return await this.dispatchSend(command, params, options);
|
|
11945
|
+
} finally {
|
|
11946
|
+
this.activeSendCalls -= 1;
|
|
11947
|
+
this.syncEventLoopHold();
|
|
11948
|
+
}
|
|
11949
|
+
}
|
|
11950
|
+
syncEventLoopHold() {
|
|
11951
|
+
const child = this.process;
|
|
11952
|
+
if (!child)
|
|
11953
|
+
return;
|
|
11954
|
+
const hold = this.activeSendCalls > 0 || this.outstandingBackgroundTaskIds.size > 0;
|
|
11955
|
+
if (hold === this.processLoopReferenced)
|
|
11956
|
+
return;
|
|
11957
|
+
this.processLoopReferenced = hold;
|
|
11958
|
+
const handles = [child, child.stdin, child.stdout, child.stderr];
|
|
11959
|
+
for (const handle of handles) {
|
|
11960
|
+
if (hold)
|
|
11961
|
+
handle?.ref?.();
|
|
11962
|
+
else
|
|
11963
|
+
handle?.unref?.();
|
|
11964
|
+
}
|
|
11965
|
+
}
|
|
11966
|
+
async dispatchSend(command, params, options) {
|
|
11934
11967
|
let dispatchParams = params;
|
|
11935
11968
|
if (command === "configure") {
|
|
11936
11969
|
dispatchParams = { ...params };
|
|
@@ -11968,11 +12001,13 @@ class BinaryBridge {
|
|
|
11968
12001
|
params.edit_slot_survives = editSlotSurvives;
|
|
11969
12002
|
this.logHashlineRegistrationCarrier("tool_call", sessionId, editSlotSurvives);
|
|
11970
12003
|
}
|
|
11971
|
-
const { preview, workerSession, ...sendOptions } = options ?? {};
|
|
12004
|
+
const { preview, workerSession, hostToolNames, ...sendOptions } = options ?? {};
|
|
11972
12005
|
if (preview === true)
|
|
11973
12006
|
params.preview = true;
|
|
11974
12007
|
if (workerSession === true)
|
|
11975
12008
|
params[WORKER_SESSION_FIELD] = true;
|
|
12009
|
+
if (hostToolNames)
|
|
12010
|
+
params[HOST_TOOL_NAMES_FIELD] = hostToolNames;
|
|
11976
12011
|
return await this.send("tool_call", params, Object.keys(sendOptions).length > 0 ? sendOptions : undefined);
|
|
11977
12012
|
}
|
|
11978
12013
|
async sendWithVersionMismatchRetry(command, params, options, canRetryAfterVersionSwap) {
|
|
@@ -12041,7 +12076,12 @@ class BinaryBridge {
|
|
|
12041
12076
|
if (command === "cancel_request" || Object.hasOwn(params, "command") || Object.hasOwn(params, "method")) {
|
|
12042
12077
|
const nested = { ...params };
|
|
12043
12078
|
const reserved = {};
|
|
12044
|
-
for (const key of [
|
|
12079
|
+
for (const key of [
|
|
12080
|
+
"session_id",
|
|
12081
|
+
"lsp_hints",
|
|
12082
|
+
WORKER_SESSION_FIELD,
|
|
12083
|
+
HOST_TOOL_NAMES_FIELD
|
|
12084
|
+
]) {
|
|
12045
12085
|
if (Object.hasOwn(nested, key)) {
|
|
12046
12086
|
reserved[key] = nested[key];
|
|
12047
12087
|
delete nested[key];
|
|
@@ -12416,6 +12456,8 @@ class BinaryBridge {
|
|
|
12416
12456
|
this.lastChildActivityAt = 0;
|
|
12417
12457
|
this.consecutiveRequestTimeouts = 0;
|
|
12418
12458
|
this.stderrTail = [];
|
|
12459
|
+
this.processLoopReferenced = true;
|
|
12460
|
+
this.syncEventLoopHold();
|
|
12419
12461
|
}
|
|
12420
12462
|
pushStderrLine(line) {
|
|
12421
12463
|
this.stderrTail.push(line);
|
|
@@ -12542,6 +12584,7 @@ class BinaryBridge {
|
|
|
12542
12584
|
const taskId = bashTaskIdFrom(response);
|
|
12543
12585
|
if (taskId)
|
|
12544
12586
|
this.outstandingBackgroundTaskIds.delete(taskId);
|
|
12587
|
+
this.syncEventLoopHold();
|
|
12545
12588
|
this.onBashCompletion?.(response, this);
|
|
12546
12589
|
return;
|
|
12547
12590
|
}
|
|
@@ -12655,7 +12698,7 @@ class BinaryBridge {
|
|
|
12655
12698
|
const delay = 100 * 2 ** this._restartCount;
|
|
12656
12699
|
this._restartCount++;
|
|
12657
12700
|
this.logVia(`Auto-restart #${this._restartCount} in ${delay}ms`);
|
|
12658
|
-
setTimeout(() => {
|
|
12701
|
+
const restartTimer = setTimeout(() => {
|
|
12659
12702
|
if (!this._shuttingDown && !this.isAlive()) {
|
|
12660
12703
|
try {
|
|
12661
12704
|
this.spawnProcess();
|
|
@@ -12664,6 +12707,7 @@ class BinaryBridge {
|
|
|
12664
12707
|
}
|
|
12665
12708
|
}
|
|
12666
12709
|
}, delay);
|
|
12710
|
+
restartTimer.unref?.();
|
|
12667
12711
|
this.scheduleRestartCountReset();
|
|
12668
12712
|
} else {
|
|
12669
12713
|
this.errorVia(`Max restarts (${this.maxRestarts}) reached, giving up. Logs: ${this.getLogFilePathVia()}${tail}`);
|
|
@@ -12684,6 +12728,7 @@ class BinaryBridge {
|
|
|
12684
12728
|
this._restartCount = 0;
|
|
12685
12729
|
this.restartResetTimer = null;
|
|
12686
12730
|
}, BinaryBridge.RESTART_RESET_MS);
|
|
12731
|
+
this.restartResetTimer.unref?.();
|
|
12687
12732
|
}
|
|
12688
12733
|
clearRestartResetTimer() {
|
|
12689
12734
|
if (this.restartResetTimer) {
|
|
@@ -13300,10 +13345,12 @@ function startLiveConfigReload(options) {
|
|
|
13300
13345
|
let acceptedTexts;
|
|
13301
13346
|
const reload = () => {
|
|
13302
13347
|
let loaded = options.load();
|
|
13348
|
+
let deletedProjectConfig = false;
|
|
13303
13349
|
if (loaded.ok) {
|
|
13304
13350
|
const read = new Set(loaded.sources);
|
|
13305
13351
|
const deleted = [...acceptedSources].find((path) => !read.has(path));
|
|
13306
|
-
|
|
13352
|
+
deletedProjectConfig = deleted === options.projectPath && loaded.projectText === null;
|
|
13353
|
+
if (deleted !== undefined && !deletedProjectConfig) {
|
|
13307
13354
|
loaded = { ok: false, message: `AFT config at ${deleted} was deleted` };
|
|
13308
13355
|
} else {
|
|
13309
13356
|
acceptedSources = read;
|
|
@@ -14075,17 +14122,17 @@ async function renameIfPresent(from, to) {
|
|
|
14075
14122
|
throw error;
|
|
14076
14123
|
}
|
|
14077
14124
|
}
|
|
14078
|
-
// ../../node_modules/.bun/@cortexkit+subc-client@0.
|
|
14125
|
+
// ../../node_modules/.bun/@cortexkit+subc-client@0.21.2/node_modules/@cortexkit/subc-client/dist/client.js
|
|
14079
14126
|
import { promises as fs3 } from "node:fs";
|
|
14080
14127
|
import { debuglog } from "node:util";
|
|
14081
14128
|
|
|
14082
|
-
// ../../node_modules/.bun/@cortexkit+subc-client@0.
|
|
14129
|
+
// ../../node_modules/.bun/@cortexkit+subc-client@0.21.2/node_modules/@cortexkit/subc-client/dist/auth.js
|
|
14083
14130
|
import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
|
|
14084
14131
|
|
|
14085
|
-
// ../../node_modules/.bun/@cortexkit+subc-client@0.
|
|
14132
|
+
// ../../node_modules/.bun/@cortexkit+subc-client@0.21.2/node_modules/@cortexkit/subc-client/dist/socket.js
|
|
14086
14133
|
import net from "node:net";
|
|
14087
14134
|
|
|
14088
|
-
// ../../node_modules/.bun/@cortexkit+subc-client@0.
|
|
14135
|
+
// ../../node_modules/.bun/@cortexkit+subc-client@0.21.2/node_modules/@cortexkit/subc-client/dist/envelope.js
|
|
14089
14136
|
var PROTOCOL_VERSION = 2;
|
|
14090
14137
|
var HEADER_LEN = 21;
|
|
14091
14138
|
var FROZEN_PREFIX_LEN = 5;
|
|
@@ -14188,12 +14235,12 @@ function validateHeaderFields(header) {
|
|
|
14188
14235
|
if (admission === AdmissionClass.Sheddable && ty !== FrameType.Push && ty !== FrameType.StreamData) {
|
|
14189
14236
|
throw new DecodeError(`SHEDDABLE admission class is illegal on ${FrameType[ty]} in flags 0b${flags.toString(2).padStart(8, "0")}`, "sheddable_illegal_frame_type");
|
|
14190
14237
|
}
|
|
14191
|
-
if (channel === 0 && epoch !== 0) {
|
|
14192
|
-
throw new DecodeError(`control channel carried nonzero epoch ${epoch}`, "nonzero_epoch_on_control_channel");
|
|
14193
|
-
}
|
|
14194
14238
|
if (isPureHeader(ty) && len !== 0) {
|
|
14195
14239
|
throw new DecodeError(`pure-header frame ${FrameType[ty]} declared non-zero body length ${len}`, "pure_header_frame_with_body");
|
|
14196
14240
|
}
|
|
14241
|
+
if (channel === 0 && epoch !== 0) {
|
|
14242
|
+
throw new DecodeError(`control channel carried nonzero epoch ${epoch}`, "nonzero_epoch_on_control_channel");
|
|
14243
|
+
}
|
|
14197
14244
|
}
|
|
14198
14245
|
function decodeHeader(bytes) {
|
|
14199
14246
|
if (bytes.length < FROZEN_PREFIX_LEN) {
|
|
@@ -14227,12 +14274,12 @@ function decodeHeader(bytes) {
|
|
|
14227
14274
|
}
|
|
14228
14275
|
const channel = view.getUint16(7, true);
|
|
14229
14276
|
const epoch = view.getUint32(9, true);
|
|
14230
|
-
if (channel === 0 && epoch !== 0) {
|
|
14231
|
-
throw new DecodeError(`control channel carried nonzero epoch ${epoch}`, "nonzero_epoch_on_control_channel");
|
|
14232
|
-
}
|
|
14233
14277
|
if (isPureHeader(ty) && len !== 0) {
|
|
14234
14278
|
throw new DecodeError(`pure-header frame ${FrameType[ty]} declared non-zero body length ${len}`, "pure_header_frame_with_body");
|
|
14235
14279
|
}
|
|
14280
|
+
if (channel === 0 && epoch !== 0) {
|
|
14281
|
+
throw new DecodeError(`control channel carried nonzero epoch ${epoch}`, "nonzero_epoch_on_control_channel");
|
|
14282
|
+
}
|
|
14236
14283
|
return { len, ver, ty, flags, channel, epoch, corr: view.getBigUint64(13, true) };
|
|
14237
14284
|
}
|
|
14238
14285
|
function buildFrame(ty, flags, channel, epoch, corr, body) {
|
|
@@ -14250,14 +14297,18 @@ function encodeFrame(frame) {
|
|
|
14250
14297
|
if (frame.header.len !== frame.body.length) {
|
|
14251
14298
|
throw new DecodeError(`frame header length ${frame.header.len} does not match body length ${frame.body.length}`, "frame_length_mismatch");
|
|
14252
14299
|
}
|
|
14300
|
+
if (frame.body.length > MAX_FRAME_BODY_LEN) {
|
|
14301
|
+
throw new DecodeError(`frame body ${frame.body.length} exceeds max ${MAX_FRAME_BODY_LEN}`, "frame_body_too_large");
|
|
14302
|
+
}
|
|
14253
14303
|
const header = encodeHeader(frame.header);
|
|
14304
|
+
decodeHeader(header);
|
|
14254
14305
|
const output = new Uint8Array(header.length + frame.body.length);
|
|
14255
14306
|
output.set(header, 0);
|
|
14256
14307
|
output.set(frame.body, header.length);
|
|
14257
14308
|
return output;
|
|
14258
14309
|
}
|
|
14259
14310
|
|
|
14260
|
-
// ../../node_modules/.bun/@cortexkit+subc-client@0.
|
|
14311
|
+
// ../../node_modules/.bun/@cortexkit+subc-client@0.21.2/node_modules/@cortexkit/subc-client/dist/socket.js
|
|
14261
14312
|
class SocketClosedError extends Error {
|
|
14262
14313
|
}
|
|
14263
14314
|
|
|
@@ -14307,6 +14358,15 @@ class SubcSocket {
|
|
|
14307
14358
|
sock.on("data", (chunk) => {
|
|
14308
14359
|
this.chunks.push(chunk);
|
|
14309
14360
|
this.buffered += chunk.length;
|
|
14361
|
+
if (this.waiter) {
|
|
14362
|
+
if (this.waiter.immediate) {
|
|
14363
|
+
clearImmediate(this.waiter.immediate);
|
|
14364
|
+
this.waiter.immediate = null;
|
|
14365
|
+
}
|
|
14366
|
+
if (this.waiter.extendOnProgress && this.buffered < this.waiter.need) {
|
|
14367
|
+
this.armWaiterTimer(this.waiter);
|
|
14368
|
+
}
|
|
14369
|
+
}
|
|
14310
14370
|
this.tryServe();
|
|
14311
14371
|
});
|
|
14312
14372
|
const fail = (err) => {
|
|
@@ -14353,30 +14413,80 @@ class SubcSocket {
|
|
|
14353
14413
|
throw new DecodeError(`frame body ${header.len} exceeds max ${MAX_FRAME_BODY_LEN}`, "frame_body_too_large");
|
|
14354
14414
|
}
|
|
14355
14415
|
onHeader?.();
|
|
14356
|
-
const
|
|
14357
|
-
const body = header.len === 0 ? new Uint8Array(0) : await this.readExact(header.len,
|
|
14416
|
+
const bodyDeadlineArg = typeof bodyDeadline === "number" ? bodyDeadline : { idleTimeoutMs: bodyDeadline.afterHeaderMs };
|
|
14417
|
+
const body = header.len === 0 ? new Uint8Array(0) : await this.readExact(header.len, bodyDeadlineArg);
|
|
14358
14418
|
return { header, body };
|
|
14359
14419
|
}
|
|
14360
|
-
|
|
14420
|
+
armWaiterTimer(w) {
|
|
14421
|
+
if (!Number.isFinite(w.budget))
|
|
14422
|
+
return;
|
|
14423
|
+
if (w.timer) {
|
|
14424
|
+
clearTimeout(w.timer);
|
|
14425
|
+
w.timer = null;
|
|
14426
|
+
}
|
|
14427
|
+
w.timer = setTimeout(() => {
|
|
14428
|
+
w.timer = null;
|
|
14429
|
+
const bufferedAtTimeout = this.buffered;
|
|
14430
|
+
const verifyTimeout = () => {
|
|
14431
|
+
if (this.waiter !== w)
|
|
14432
|
+
return;
|
|
14433
|
+
if (w.extendOnProgress && this.buffered > bufferedAtTimeout) {
|
|
14434
|
+
this.armWaiterTimer(w);
|
|
14435
|
+
return;
|
|
14436
|
+
}
|
|
14437
|
+
this.waiter = null;
|
|
14438
|
+
w.reject(new SocketTimeoutError(`timed out waiting for ${w.need} bytes`));
|
|
14439
|
+
};
|
|
14440
|
+
w.immediate = setImmediate(() => {
|
|
14441
|
+
if (this.waiter !== w)
|
|
14442
|
+
return;
|
|
14443
|
+
if (this.buffered > bufferedAtTimeout) {
|
|
14444
|
+
verifyTimeout();
|
|
14445
|
+
} else {
|
|
14446
|
+
w.immediate = setImmediate(() => {
|
|
14447
|
+
w.immediate = null;
|
|
14448
|
+
verifyTimeout();
|
|
14449
|
+
});
|
|
14450
|
+
}
|
|
14451
|
+
});
|
|
14452
|
+
}, w.budget);
|
|
14453
|
+
}
|
|
14454
|
+
readExact(n, deadline) {
|
|
14361
14455
|
if (this.waiter) {
|
|
14362
14456
|
return Promise.reject(new Error("concurrent readExact is not supported"));
|
|
14363
14457
|
}
|
|
14364
14458
|
if (n === 0)
|
|
14365
14459
|
return Promise.resolve(new Uint8Array(0));
|
|
14366
14460
|
return new Promise((resolve, reject) => {
|
|
14367
|
-
let
|
|
14368
|
-
|
|
14369
|
-
|
|
14370
|
-
|
|
14461
|
+
let budget;
|
|
14462
|
+
const extendOnProgress = typeof deadline === "object";
|
|
14463
|
+
if (typeof deadline === "object") {
|
|
14464
|
+
budget = deadline.idleTimeoutMs;
|
|
14465
|
+
if (budget <= 0) {
|
|
14371
14466
|
reject(new SocketTimeoutError(`timed out waiting for ${n} bytes`));
|
|
14372
14467
|
return;
|
|
14373
14468
|
}
|
|
14374
|
-
|
|
14375
|
-
|
|
14469
|
+
} else if (Number.isFinite(deadline)) {
|
|
14470
|
+
const remaining = deadline - Date.now();
|
|
14471
|
+
if (remaining <= 0) {
|
|
14376
14472
|
reject(new SocketTimeoutError(`timed out waiting for ${n} bytes`));
|
|
14377
|
-
|
|
14473
|
+
return;
|
|
14474
|
+
}
|
|
14475
|
+
budget = remaining;
|
|
14476
|
+
} else {
|
|
14477
|
+
budget = Number.POSITIVE_INFINITY;
|
|
14378
14478
|
}
|
|
14379
|
-
|
|
14479
|
+
const waiter = {
|
|
14480
|
+
need: n,
|
|
14481
|
+
resolve,
|
|
14482
|
+
reject,
|
|
14483
|
+
timer: null,
|
|
14484
|
+
immediate: null,
|
|
14485
|
+
budget,
|
|
14486
|
+
extendOnProgress
|
|
14487
|
+
};
|
|
14488
|
+
this.waiter = waiter;
|
|
14489
|
+
this.armWaiterTimer(waiter);
|
|
14380
14490
|
this.tryServe();
|
|
14381
14491
|
});
|
|
14382
14492
|
}
|
|
@@ -14459,6 +14569,8 @@ class SubcSocket {
|
|
|
14459
14569
|
this.waiter = null;
|
|
14460
14570
|
if (w.timer)
|
|
14461
14571
|
clearTimeout(w.timer);
|
|
14572
|
+
if (w.immediate)
|
|
14573
|
+
clearImmediate(w.immediate);
|
|
14462
14574
|
w.resolve(out);
|
|
14463
14575
|
return;
|
|
14464
14576
|
}
|
|
@@ -14466,6 +14578,8 @@ class SubcSocket {
|
|
|
14466
14578
|
this.waiter = null;
|
|
14467
14579
|
if (w.timer)
|
|
14468
14580
|
clearTimeout(w.timer);
|
|
14581
|
+
if (w.immediate)
|
|
14582
|
+
clearImmediate(w.immediate);
|
|
14469
14583
|
w.reject(this.closedErr);
|
|
14470
14584
|
}
|
|
14471
14585
|
}
|
|
@@ -14490,7 +14604,7 @@ class SubcSocket {
|
|
|
14490
14604
|
}
|
|
14491
14605
|
}
|
|
14492
14606
|
|
|
14493
|
-
// ../../node_modules/.bun/@cortexkit+subc-client@0.
|
|
14607
|
+
// ../../node_modules/.bun/@cortexkit+subc-client@0.21.2/node_modules/@cortexkit/subc-client/dist/auth.js
|
|
14494
14608
|
var NONCE_LEN = 32;
|
|
14495
14609
|
var MAX_AUTH_MESSAGE_LEN = 4096;
|
|
14496
14610
|
var SERVER_PROOF_DOMAIN = "subc-server-v1";
|
|
@@ -14564,7 +14678,7 @@ async function authenticateClient(sock, conn, deadlineMs) {
|
|
|
14564
14678
|
await writeMessage(sock, { client_auth: Array.from(clientAuth) }, deadlineMs);
|
|
14565
14679
|
}
|
|
14566
14680
|
|
|
14567
|
-
// ../../node_modules/.bun/@cortexkit+subc-client@0.
|
|
14681
|
+
// ../../node_modules/.bun/@cortexkit+subc-client@0.21.2/node_modules/@cortexkit/subc-client/dist/connection-file.js
|
|
14568
14682
|
import { promises as fs2 } from "node:fs";
|
|
14569
14683
|
var SCHEMA_VERSION = 1;
|
|
14570
14684
|
var MIN_KEY_LEN = 32;
|
|
@@ -14597,18 +14711,29 @@ function validate(info) {
|
|
|
14597
14711
|
throw new ConnectionFileError(`connection file daemon_id must be ${DAEMON_ID_LEN} bytes, got ${info.daemonId.length}`);
|
|
14598
14712
|
}
|
|
14599
14713
|
}
|
|
14600
|
-
|
|
14714
|
+
function verifyOwnerOnly(path, stat, expectedUid) {
|
|
14601
14715
|
if (process.platform === "win32")
|
|
14602
14716
|
return;
|
|
14603
|
-
|
|
14717
|
+
if (expectedUid !== undefined && stat.uid !== expectedUid) {
|
|
14718
|
+
throw new ConnectionFileError(`connection file ${path} is owned by uid ${stat.uid}, expected effective uid ${expectedUid}`);
|
|
14719
|
+
}
|
|
14604
14720
|
const mode = stat.mode & 511;
|
|
14605
14721
|
if ((mode & 63) !== 0) {
|
|
14606
14722
|
throw new ConnectionFileError(`connection file ${path} has insecure permissions 0o${mode.toString(8)}; expected owner-only 0600`);
|
|
14607
14723
|
}
|
|
14608
14724
|
}
|
|
14609
14725
|
async function readConnectionFile(path) {
|
|
14610
|
-
|
|
14611
|
-
|
|
14726
|
+
return readConnectionFileForUid(path, process.geteuid?.());
|
|
14727
|
+
}
|
|
14728
|
+
async function readConnectionFileForUid(path, expectedUid) {
|
|
14729
|
+
const handle = await fs2.open(path, "r");
|
|
14730
|
+
let raw;
|
|
14731
|
+
try {
|
|
14732
|
+
verifyOwnerOnly(path, await handle.stat(), expectedUid);
|
|
14733
|
+
raw = await handle.readFile({ encoding: "utf8" });
|
|
14734
|
+
} finally {
|
|
14735
|
+
await handle.close();
|
|
14736
|
+
}
|
|
14612
14737
|
let parsed;
|
|
14613
14738
|
try {
|
|
14614
14739
|
parsed = JSON.parse(raw);
|
|
@@ -14642,7 +14767,7 @@ async function readConnectionFile(path) {
|
|
|
14642
14767
|
return info;
|
|
14643
14768
|
}
|
|
14644
14769
|
|
|
14645
|
-
// ../../node_modules/.bun/@cortexkit+subc-client@0.
|
|
14770
|
+
// ../../node_modules/.bun/@cortexkit+subc-client@0.21.2/node_modules/@cortexkit/subc-client/dist/launch-nonce.js
|
|
14646
14771
|
import { closeSync as closeSync4, fstatSync as fstatSync2, readSync as readSync3 } from "node:fs";
|
|
14647
14772
|
var SUBC_LAUNCH_NONCE_ENV = "SUBC_LAUNCH_NONCE";
|
|
14648
14773
|
var SUBC_LAUNCH_NONCE_FD_ENV = "SUBC_LAUNCH_NONCE_FD";
|
|
@@ -14831,7 +14956,7 @@ function closeQuietly(fd) {
|
|
|
14831
14956
|
} catch {}
|
|
14832
14957
|
}
|
|
14833
14958
|
|
|
14834
|
-
// ../../node_modules/.bun/@cortexkit+subc-client@0.
|
|
14959
|
+
// ../../node_modules/.bun/@cortexkit+subc-client@0.21.2/node_modules/@cortexkit/subc-client/dist/route-handle.js
|
|
14835
14960
|
var connectionToken = new WeakMap;
|
|
14836
14961
|
var reverseRequests = new WeakMap;
|
|
14837
14962
|
|
|
@@ -14924,7 +15049,7 @@ function sameRouteHandle(left, right) {
|
|
|
14924
15049
|
return left === right;
|
|
14925
15050
|
}
|
|
14926
15051
|
|
|
14927
|
-
// ../../node_modules/.bun/@cortexkit+subc-client@0.
|
|
15052
|
+
// ../../node_modules/.bun/@cortexkit+subc-client@0.21.2/node_modules/@cortexkit/subc-client/dist/client.js
|
|
14928
15053
|
var debug = debuglog("subc-client");
|
|
14929
15054
|
var DEFAULT_HANDSHAKE_TIMEOUT_MS = 1e4;
|
|
14930
15055
|
var DEFAULT_REQUEST_TIMEOUT_MS = 30000;
|
|
@@ -14981,6 +15106,12 @@ function requireBinaryBody(body) {
|
|
|
14981
15106
|
const type = body === null ? "null" : Array.isArray(body) ? "array" : typeof body;
|
|
14982
15107
|
throw new SubcError(`binary request body must be a Uint8Array; got ${type}`, "binary_body_required");
|
|
14983
15108
|
}
|
|
15109
|
+
var SCOPE_ROUTE_CLOSE_REASONS = new Set([
|
|
15110
|
+
"scope_ended",
|
|
15111
|
+
"scope_carrier_removed",
|
|
15112
|
+
"scope_delegation_changed",
|
|
15113
|
+
"scope_parent_ended"
|
|
15114
|
+
]);
|
|
14984
15115
|
function parseRouteCloseReason(reason) {
|
|
14985
15116
|
switch (reason) {
|
|
14986
15117
|
case "reload":
|
|
@@ -15123,14 +15254,16 @@ class SubcClient {
|
|
|
15123
15254
|
return modules.filter((module) => module.capabilities?.provides.includes(capability) ?? false).map((module) => module.module_id).sort();
|
|
15124
15255
|
}
|
|
15125
15256
|
async routeOpen(target, identity, opts = {}) {
|
|
15257
|
+
const scope = validatedRouteScope(opts.scope);
|
|
15126
15258
|
const release = await this.routeOpenGate.acquire();
|
|
15127
15259
|
try {
|
|
15128
|
-
return await this.sendRouteOpen(target, identity, opts);
|
|
15260
|
+
return await this.sendRouteOpen(target, identity, { ...opts, scope });
|
|
15129
15261
|
} finally {
|
|
15130
15262
|
release();
|
|
15131
15263
|
}
|
|
15132
15264
|
}
|
|
15133
15265
|
async sendRouteOpen(target, identity, opts = {}) {
|
|
15266
|
+
const scope = validatedRouteScope(opts.scope);
|
|
15134
15267
|
const consumerIdentity = routeOpenConsumerIdentity(opts);
|
|
15135
15268
|
const reverseRequests = opts.reverseRequests ?? new ReverseRequestRegistry;
|
|
15136
15269
|
reverseRequests.seal();
|
|
@@ -15140,7 +15273,8 @@ class SubcClient {
|
|
|
15140
15273
|
target,
|
|
15141
15274
|
identity,
|
|
15142
15275
|
...consumerIdentity ? { consumer_identity: consumerIdentity } : {},
|
|
15143
|
-
...consumerCapabilities.length > 0 ? { consumer_capabilities: consumerCapabilities } : {}
|
|
15276
|
+
...consumerCapabilities.length > 0 ? { consumer_capabilities: consumerCapabilities } : {},
|
|
15277
|
+
...scope ? { scope: { owner: scope.owner, ref: scope.ref, scope_epoch: scope.scopeEpoch } } : {}
|
|
15144
15278
|
});
|
|
15145
15279
|
let installed = null;
|
|
15146
15280
|
const install = (frame) => {
|
|
@@ -15343,7 +15477,7 @@ class SubcClient {
|
|
|
15343
15477
|
this.sendRouteGoodbye(handle);
|
|
15344
15478
|
}
|
|
15345
15479
|
async closeManagedRoute(target, identity, opts = {}) {
|
|
15346
|
-
const key = routeCacheKey(target, identity, routeOpenConsumerIdentity(opts), (opts.reverseRequests ?? new ReverseRequestRegistry).capabilities());
|
|
15480
|
+
const key = routeCacheKey(target, identity, routeOpenConsumerIdentity(opts), (opts.reverseRequests ?? new ReverseRequestRegistry).capabilities(), validatedRouteScope(opts.scope));
|
|
15347
15481
|
const cached = this.routes.get(key);
|
|
15348
15482
|
if (!cached)
|
|
15349
15483
|
return;
|
|
@@ -15439,13 +15573,13 @@ class SubcClient {
|
|
|
15439
15573
|
};
|
|
15440
15574
|
pending.timer = setTimeout(() => this.arbitrateTimeout(key, pending, channel, corr, ms), ms);
|
|
15441
15575
|
this.pending.set(key, pending);
|
|
15442
|
-
if (signal)
|
|
15443
|
-
this.attachCancelSignal(signal, key, handle, corr, priority);
|
|
15444
15576
|
writeBorrowed(this.sock, encodeFrame(frame), Date.now() + ms).catch((error) => {
|
|
15445
15577
|
const current = this.pending.get(key);
|
|
15446
15578
|
if (current)
|
|
15447
15579
|
this.rejectPending(key, current, error instanceof Error ? error : new SubcError(String(error)));
|
|
15448
15580
|
});
|
|
15581
|
+
if (signal)
|
|
15582
|
+
this.attachCancelSignal(signal, key, handle, corr, priority);
|
|
15449
15583
|
});
|
|
15450
15584
|
}
|
|
15451
15585
|
arbitrateTimeout(key, pending, channel, corr, ms) {
|
|
@@ -15519,8 +15653,6 @@ class SubcClient {
|
|
|
15519
15653
|
};
|
|
15520
15654
|
pending.timer = setTimeout(() => this.arbitrateTimeout(key, pending, handle.channel, corr, ms), ms);
|
|
15521
15655
|
this.pending.set(key, pending);
|
|
15522
|
-
if (signal)
|
|
15523
|
-
this.attachCancelSignal(signal, key, handle, corr, priority);
|
|
15524
15656
|
const write = writeTrackedBorrowed(this.sock, encodeFrame(frame), Date.now() + ms);
|
|
15525
15657
|
handedToSocket = write.queued;
|
|
15526
15658
|
write.completed.catch((error) => {
|
|
@@ -15528,9 +15660,12 @@ class SubcClient {
|
|
|
15528
15660
|
if (current)
|
|
15529
15661
|
this.rejectPending(key, current, error instanceof Error ? error : new SubcError(String(error)));
|
|
15530
15662
|
});
|
|
15663
|
+
if (signal && write.queued)
|
|
15664
|
+
this.attachCancelSignal(signal, key, handle, corr, priority);
|
|
15531
15665
|
});
|
|
15532
15666
|
}
|
|
15533
15667
|
async cachedRouteHandle(moduleId, opts) {
|
|
15668
|
+
const scope = validatedRouteScope(opts.scope);
|
|
15534
15669
|
const identity = opts.identity ?? this.opts.identity;
|
|
15535
15670
|
if (!identity) {
|
|
15536
15671
|
throw new SubcCallError("terminal", "managed call requires a BindIdentity in SubcClient.connect({ identity }) or call(..., { identity })", "missing_identity");
|
|
@@ -15539,7 +15674,7 @@ class SubcClient {
|
|
|
15539
15674
|
const consumerIdentity = routeOpenConsumerIdentity(opts);
|
|
15540
15675
|
const reverseRequests = opts.reverseRequests ?? new ReverseRequestRegistry;
|
|
15541
15676
|
reverseRequests.seal();
|
|
15542
|
-
const key = routeCacheKey(target, identity, consumerIdentity, reverseRequests.capabilities());
|
|
15677
|
+
const key = routeCacheKey(target, identity, consumerIdentity, reverseRequests.capabilities(), scope);
|
|
15543
15678
|
let cached = this.routes.get(key);
|
|
15544
15679
|
if (!cached) {
|
|
15545
15680
|
cached = {
|
|
@@ -15547,6 +15682,7 @@ class SubcClient {
|
|
|
15547
15682
|
moduleId,
|
|
15548
15683
|
target,
|
|
15549
15684
|
identity,
|
|
15685
|
+
scope,
|
|
15550
15686
|
consumerIdentity,
|
|
15551
15687
|
reverseRequests,
|
|
15552
15688
|
handle: null,
|
|
@@ -15554,6 +15690,8 @@ class SubcClient {
|
|
|
15554
15690
|
};
|
|
15555
15691
|
this.routes.set(key, cached);
|
|
15556
15692
|
}
|
|
15693
|
+
if (cached.endReason !== undefined)
|
|
15694
|
+
throw this.endedCachedRoute(cached.endReason);
|
|
15557
15695
|
if (cached.handle && this.isLiveHandle(cached.handle))
|
|
15558
15696
|
return cached.handle;
|
|
15559
15697
|
const startedAt = this.opts.now();
|
|
@@ -15589,6 +15727,8 @@ class SubcClient {
|
|
|
15589
15727
|
for (;; ) {
|
|
15590
15728
|
if (cached.closed)
|
|
15591
15729
|
throw this.routeClosedDuringOpen();
|
|
15730
|
+
if (cached.endReason !== undefined)
|
|
15731
|
+
throw this.endedCachedRoute(cached.endReason);
|
|
15592
15732
|
try {
|
|
15593
15733
|
await this.ensureConnectedForManaged();
|
|
15594
15734
|
} catch (error) {
|
|
@@ -15603,6 +15743,7 @@ class SubcClient {
|
|
|
15603
15743
|
try {
|
|
15604
15744
|
outcome = {
|
|
15605
15745
|
handle: await this.sendRouteOpen(cached.target, cached.identity, {
|
|
15746
|
+
scope: cached.scope,
|
|
15606
15747
|
consumerIdentity: cached.consumerIdentity ?? null,
|
|
15607
15748
|
reverseRequests: cached.reverseRequests
|
|
15608
15749
|
})
|
|
@@ -15770,10 +15911,11 @@ class SubcClient {
|
|
|
15770
15911
|
}
|
|
15771
15912
|
for (const key of routeKeys) {
|
|
15772
15913
|
const cached = this.routes.get(key);
|
|
15773
|
-
if (!cached || cached.closed)
|
|
15914
|
+
if (!cached || cached.closed || cached.endReason !== undefined)
|
|
15774
15915
|
continue;
|
|
15775
15916
|
try {
|
|
15776
15917
|
const handle = await this.routeOpen(cached.target, cached.identity, {
|
|
15918
|
+
scope: cached.scope,
|
|
15777
15919
|
consumerIdentity: cached.consumerIdentity ?? null,
|
|
15778
15920
|
reverseRequests: cached.reverseRequests
|
|
15779
15921
|
});
|
|
@@ -15801,11 +15943,15 @@ class SubcClient {
|
|
|
15801
15943
|
const cause = new SubcError("route was closed before route.open completed", "route_closed", undefined, "closed_by_caller");
|
|
15802
15944
|
return new SubcCallError("not_sent", cause.message, cause.code, cause);
|
|
15803
15945
|
}
|
|
15946
|
+
endedCachedRoute(reason) {
|
|
15947
|
+
const cause = new SubcError(`cached route closed by subc (${reason}); must not reopen`, "route_closed", undefined, reason);
|
|
15948
|
+
return this.terminalCallError(cause.message, cause);
|
|
15949
|
+
}
|
|
15804
15950
|
async readLoop(sock, generation) {
|
|
15805
15951
|
try {
|
|
15806
15952
|
for (;; ) {
|
|
15807
15953
|
this.readerActive = false;
|
|
15808
|
-
const frame = await sock.readFrame(Number.POSITIVE_INFINITY, { afterHeaderMs: BODY_READ_TIMEOUT_MS }, () => {
|
|
15954
|
+
const frame = await sock.readFrame(Number.POSITIVE_INFINITY, { afterHeaderMs: this.opts.bodyReadTimeoutMs ?? BODY_READ_TIMEOUT_MS }, () => {
|
|
15809
15955
|
this.readerActive = true;
|
|
15810
15956
|
});
|
|
15811
15957
|
try {
|
|
@@ -15884,6 +16030,7 @@ class SubcClient {
|
|
|
15884
16030
|
}
|
|
15885
16031
|
if (frame.header.ty === FrameType.Goodbye && handle) {
|
|
15886
16032
|
this.failHandle(handle, new SubcError("route closed by subc (GOODBYE)", "route_closed", undefined, this.routeEndReason(handle.channel)));
|
|
16033
|
+
this.handleCachedRouteClose(handle.channel, this.routeEndReason(handle.channel));
|
|
15887
16034
|
if (this.liveRoutes.get(handle.channel) === handle)
|
|
15888
16035
|
this.liveRoutes.delete(handle.channel);
|
|
15889
16036
|
this.evictRouteHandle(handle);
|
|
@@ -15973,13 +16120,29 @@ class SubcClient {
|
|
|
15973
16120
|
const final = push.op === "route.closed";
|
|
15974
16121
|
if (final || !this.routeEndReasons.get(channel)?.final) {
|
|
15975
16122
|
this.routeEndReasons.set(channel, { reason, final });
|
|
16123
|
+
if (final)
|
|
16124
|
+
this.handleCachedRouteClose(channel, reason);
|
|
15976
16125
|
}
|
|
15977
16126
|
}
|
|
15978
16127
|
}
|
|
15979
16128
|
} else if (typeof push.body.module_id === "string") {
|
|
15980
16129
|
for (const [channel, module] of this.routeModules) {
|
|
15981
|
-
if (module === push.body.module_id)
|
|
16130
|
+
if (module === push.body.module_id) {
|
|
15982
16131
|
this.legacyChannelReasons.set(channel, reason);
|
|
16132
|
+
if (push.op === "route.closed")
|
|
16133
|
+
this.handleCachedRouteClose(channel, reason);
|
|
16134
|
+
}
|
|
16135
|
+
}
|
|
16136
|
+
}
|
|
16137
|
+
}
|
|
16138
|
+
handleCachedRouteClose(channel, reason) {
|
|
16139
|
+
for (const [key, cached] of this.routes) {
|
|
16140
|
+
if (cached.handle?.channel !== channel)
|
|
16141
|
+
continue;
|
|
16142
|
+
if (SCOPE_ROUTE_CLOSE_REASONS.has(reason)) {
|
|
16143
|
+
cached.endReason = reason;
|
|
16144
|
+
} else {
|
|
16145
|
+
this.routes.delete(key);
|
|
15983
16146
|
}
|
|
15984
16147
|
}
|
|
15985
16148
|
}
|
|
@@ -16002,6 +16165,10 @@ class SubcClient {
|
|
|
16002
16165
|
this.routeEndReasons.clear();
|
|
16003
16166
|
this.legacyChannelReasons.clear();
|
|
16004
16167
|
this.routeModules.clear();
|
|
16168
|
+
for (const [key, cached] of this.routes) {
|
|
16169
|
+
if (cached.endReason === undefined)
|
|
16170
|
+
this.routes.delete(key);
|
|
16171
|
+
}
|
|
16005
16172
|
for (const [key, pending] of this.pending) {
|
|
16006
16173
|
this.rejectPending(key, pending, pending.handle ? routeError : err);
|
|
16007
16174
|
}
|
|
@@ -16096,6 +16263,10 @@ function isRouteOpenRefusal(err) {
|
|
|
16096
16263
|
function isRetryableRouteOpenCode(code) {
|
|
16097
16264
|
if (code === "capability_forbidden")
|
|
16098
16265
|
return false;
|
|
16266
|
+
if (code === "target_flow_unsupported")
|
|
16267
|
+
return false;
|
|
16268
|
+
if (code === "target_agent_run_unsupported")
|
|
16269
|
+
return false;
|
|
16099
16270
|
return code === "module_reloading" || code === "module_warming" || code === "target_unavailable" || code === "module_timeout" || code === "scope_not_synced" || code === "scope_changed";
|
|
16100
16271
|
}
|
|
16101
16272
|
function equalJitter(delayMs, random) {
|
|
@@ -16153,6 +16324,7 @@ function normalizeConnectOptions(opts) {
|
|
|
16153
16324
|
random: opts.random ?? Math.random,
|
|
16154
16325
|
routeOpenRetryDeadlineMs: opts.routeOpenRetryDeadlineMs ?? ROUTE_OPEN_RETRY_DEADLINE_MS,
|
|
16155
16326
|
timeoutArbitrationGraceMs: opts.timeoutArbitrationGraceMs ?? TIMEOUT_ARBITRATION_GRACE_MS,
|
|
16327
|
+
bodyReadTimeoutMs: opts.bodyReadTimeoutMs,
|
|
16156
16328
|
livenessProbeWindowMs: opts.livenessProbeWindowMs ?? LIVENESS_PROBE_WINDOW_MS,
|
|
16157
16329
|
onControlPush: opts.onControlPush
|
|
16158
16330
|
};
|
|
@@ -16176,10 +16348,28 @@ function isValidCapabilityIdentifier(identifier) {
|
|
|
16176
16348
|
const numericVersion = Number(version);
|
|
16177
16349
|
return Number.isSafeInteger(numericVersion) && numericVersion >= 1 && numericVersion <= 4294967295;
|
|
16178
16350
|
}
|
|
16179
|
-
function routeCacheKey(target, identity, consumerIdentity, consumerCapabilities) {
|
|
16351
|
+
function routeCacheKey(target, identity, consumerIdentity, consumerCapabilities, scope) {
|
|
16180
16352
|
const consumerPart = consumerIdentity ? `${consumerIdentity.module_id}\x00${consumerIdentity.launch_nonce}` : "";
|
|
16181
16353
|
const capabilitiesPart = [...consumerCapabilities].sort().join("\x00");
|
|
16182
|
-
|
|
16354
|
+
const scopePart = scope ? JSON.stringify([scope.owner.kind, scope.owner.kind === "reserved" ? scope.owner.module_id : null, scope.ref, scope.scopeEpoch]) : "";
|
|
16355
|
+
return `${target.kind}\x00${target.module_id}\x00${identity.project_root}\x00${identity.harness}\x00${identity.session}\x00${consumerPart}\x00${capabilitiesPart}\x00${scopePart}`;
|
|
16356
|
+
}
|
|
16357
|
+
function validatedRouteScope(scope) {
|
|
16358
|
+
if (scope === undefined)
|
|
16359
|
+
return;
|
|
16360
|
+
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)) {
|
|
16361
|
+
throw new SubcError("scope.owner must be a Principal with a non-empty reserved module_id", "invalid_scope");
|
|
16362
|
+
}
|
|
16363
|
+
if (typeof scope.ref !== "string" || scope.ref.length === 0) {
|
|
16364
|
+
throw new SubcError("scope.ref must be a non-empty string", "invalid_scope");
|
|
16365
|
+
}
|
|
16366
|
+
if (scope.scopeEpoch === undefined) {
|
|
16367
|
+
throw new SubcError("scope.scopeEpoch is required", "scope_epoch_required");
|
|
16368
|
+
}
|
|
16369
|
+
if (!Number.isSafeInteger(scope.scopeEpoch) || scope.scopeEpoch < 0) {
|
|
16370
|
+
throw new SubcError("scope.scopeEpoch must be a safe non-negative integer", "invalid_scope");
|
|
16371
|
+
}
|
|
16372
|
+
return { owner: { ...scope.owner }, ref: scope.ref, scopeEpoch: scope.scopeEpoch };
|
|
16183
16373
|
}
|
|
16184
16374
|
function routeOpenConsumerIdentity(opts = {}) {
|
|
16185
16375
|
if (opts.consumerIdentity !== undefined)
|
|
@@ -16700,6 +16890,41 @@ class SubcRouteClosedMidCallError extends SubcCallError {
|
|
|
16700
16890
|
this.reason = reason;
|
|
16701
16891
|
}
|
|
16702
16892
|
}
|
|
16893
|
+
|
|
16894
|
+
class SubcFrameReadTimeoutError extends SubcCallError {
|
|
16895
|
+
constructor(cause) {
|
|
16896
|
+
super("outcome_unknown", "the command may have run; timed out waiting for frame body bytes from the AFT daemon", "frame_read_timeout", cause);
|
|
16897
|
+
this.name = "SubcFrameReadTimeoutError";
|
|
16898
|
+
}
|
|
16899
|
+
}
|
|
16900
|
+
function isFrameBodyReadTimeoutMessage(message) {
|
|
16901
|
+
return typeof message === "string" && (/timed out waiting for \d+ bytes/i.test(message) || message.includes("timed out waiting for frame body bytes"));
|
|
16902
|
+
}
|
|
16903
|
+
function isSubcFrameReadTimeoutError(error) {
|
|
16904
|
+
if (error instanceof SubcFrameReadTimeoutError)
|
|
16905
|
+
return true;
|
|
16906
|
+
if (error instanceof SocketTimeoutError && isFrameBodyReadTimeoutMessage(error.message)) {
|
|
16907
|
+
return true;
|
|
16908
|
+
}
|
|
16909
|
+
if (error instanceof SubcError && isFrameBodyReadTimeoutMessage(error.message)) {
|
|
16910
|
+
return true;
|
|
16911
|
+
}
|
|
16912
|
+
if (error instanceof SubcCallError) {
|
|
16913
|
+
if (error.code === "frame_read_timeout")
|
|
16914
|
+
return true;
|
|
16915
|
+
if (error.cause !== undefined && error.cause !== error && isSubcFrameReadTimeoutError(error.cause)) {
|
|
16916
|
+
return true;
|
|
16917
|
+
}
|
|
16918
|
+
if (isFrameBodyReadTimeoutMessage(error.message))
|
|
16919
|
+
return true;
|
|
16920
|
+
}
|
|
16921
|
+
return false;
|
|
16922
|
+
}
|
|
16923
|
+
function frameReadTimeoutError(error) {
|
|
16924
|
+
if (!isSubcFrameReadTimeoutError(error))
|
|
16925
|
+
return error;
|
|
16926
|
+
return error instanceof SubcFrameReadTimeoutError ? error : new SubcFrameReadTimeoutError(error);
|
|
16927
|
+
}
|
|
16703
16928
|
function isLocalRouteCloseRejection(error) {
|
|
16704
16929
|
return error instanceof SubcError && error.closeReason === "closed_by_caller";
|
|
16705
16930
|
}
|
|
@@ -16726,6 +16951,8 @@ function isSubcClientClosedError(error) {
|
|
|
16726
16951
|
return error instanceof SubcError && error.message === "client closed";
|
|
16727
16952
|
}
|
|
16728
16953
|
function isSocketDeathError(error, depth = 0) {
|
|
16954
|
+
if (isSubcFrameReadTimeoutError(error))
|
|
16955
|
+
return true;
|
|
16729
16956
|
if (error instanceof SubcError && error.closeReason === "connection_lost")
|
|
16730
16957
|
return true;
|
|
16731
16958
|
if (error instanceof SubcCallError) {
|
|
@@ -16768,6 +16995,12 @@ class SubcLivenessProbeTimeoutError extends Error {
|
|
|
16768
16995
|
}
|
|
16769
16996
|
}
|
|
16770
16997
|
var AFT_MODULE_ID = "aft";
|
|
16998
|
+
var HOST_SCOPE_LOOKUP_TIMEOUT_MS = 2000;
|
|
16999
|
+
var HOST_SCOPE_RETRY_FLOOR_MS = 5000;
|
|
17000
|
+
var HOST_SCOPE_RETRY_CAP_MS = 300000;
|
|
17001
|
+
function isHostScopeSyncRefusal(code) {
|
|
17002
|
+
return code === "scope_not_live" || code === "scope_not_synced" || code === "scope_changed";
|
|
17003
|
+
}
|
|
16771
17004
|
var MAX_CONSECUTIVE_TRANSPORT_FAILURES = 3;
|
|
16772
17005
|
var LIVENESS_PROBE_TIMEOUT_MS = 15000;
|
|
16773
17006
|
var REQUEST_DEADLINE_CODE = "request_deadline";
|
|
@@ -16846,7 +17079,7 @@ class BgSubscription {
|
|
|
16846
17079
|
identity;
|
|
16847
17080
|
acquireClient;
|
|
16848
17081
|
dropClient;
|
|
16849
|
-
|
|
17082
|
+
openRoute;
|
|
16850
17083
|
onNudge;
|
|
16851
17084
|
sleep;
|
|
16852
17085
|
canAttach;
|
|
@@ -16857,21 +17090,25 @@ class BgSubscription {
|
|
|
16857
17090
|
isCurrent;
|
|
16858
17091
|
noteClientAlive;
|
|
16859
17092
|
noteUnansweredCall;
|
|
17093
|
+
onRouteAttached;
|
|
16860
17094
|
stopped = false;
|
|
16861
17095
|
current = null;
|
|
16862
17096
|
attached = null;
|
|
17097
|
+
route = null;
|
|
16863
17098
|
loop;
|
|
16864
17099
|
lifecycleLogState = new Map;
|
|
16865
17100
|
nudgeReceiptLogState = null;
|
|
16866
|
-
constructor(identity, acquireClient, dropClient,
|
|
17101
|
+
constructor(identity, acquireClient, dropClient, openRoute, onNudge, sleep, canAttach, onRootAttachFailure, onDormant, dispatchProbeIntervalMs, nudgeRef, isCurrent = () => true, noteClientAlive = () => {
|
|
16867
17102
|
return;
|
|
16868
17103
|
}, noteUnansweredCall = () => {
|
|
16869
17104
|
return;
|
|
17105
|
+
}, onRouteAttached = () => {
|
|
17106
|
+
return;
|
|
16870
17107
|
}) {
|
|
16871
17108
|
this.identity = identity;
|
|
16872
17109
|
this.acquireClient = acquireClient;
|
|
16873
17110
|
this.dropClient = dropClient;
|
|
16874
|
-
this.
|
|
17111
|
+
this.openRoute = openRoute;
|
|
16875
17112
|
this.onNudge = onNudge;
|
|
16876
17113
|
this.sleep = sleep;
|
|
16877
17114
|
this.canAttach = canAttach;
|
|
@@ -16882,11 +17119,15 @@ class BgSubscription {
|
|
|
16882
17119
|
this.isCurrent = isCurrent;
|
|
16883
17120
|
this.noteClientAlive = noteClientAlive;
|
|
16884
17121
|
this.noteUnansweredCall = noteUnansweredCall;
|
|
17122
|
+
this.onRouteAttached = onRouteAttached;
|
|
16885
17123
|
this.loop = this.run();
|
|
16886
17124
|
}
|
|
16887
17125
|
get attachedClient() {
|
|
16888
17126
|
return this.attached;
|
|
16889
17127
|
}
|
|
17128
|
+
get attachedRoute() {
|
|
17129
|
+
return this.route;
|
|
17130
|
+
}
|
|
16890
17131
|
async stop() {
|
|
16891
17132
|
this.stopped = true;
|
|
16892
17133
|
const sub = this.current;
|
|
@@ -17011,7 +17252,7 @@ class BgSubscription {
|
|
|
17011
17252
|
}
|
|
17012
17253
|
let route;
|
|
17013
17254
|
try {
|
|
17014
|
-
route = await
|
|
17255
|
+
route = await this.openRoute(client);
|
|
17015
17256
|
} catch (err) {
|
|
17016
17257
|
if (this.isCurrent() && this.onRootAttachFailure(err)) {
|
|
17017
17258
|
if (reconnecting)
|
|
@@ -17069,6 +17310,8 @@ class BgSubscription {
|
|
|
17069
17310
|
});
|
|
17070
17311
|
this.current = sub;
|
|
17071
17312
|
this.attached = client;
|
|
17313
|
+
this.route = route;
|
|
17314
|
+
this.onRouteAttached();
|
|
17072
17315
|
stopDispatchProbe = this.startDispatchProbe(client, routeId);
|
|
17073
17316
|
this.info("subscription-open", `subscription open channel=${routeId}`);
|
|
17074
17317
|
if (reconnecting) {
|
|
@@ -17111,6 +17354,7 @@ class BgSubscription {
|
|
|
17111
17354
|
stopDispatchProbe();
|
|
17112
17355
|
this.current = null;
|
|
17113
17356
|
this.attached = null;
|
|
17357
|
+
this.route = null;
|
|
17114
17358
|
safeCloseRoute(client, route);
|
|
17115
17359
|
}
|
|
17116
17360
|
await this.backoff(backoffAttempt++);
|
|
@@ -17126,7 +17370,7 @@ function isRecord(value) {
|
|
|
17126
17370
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
17127
17371
|
}
|
|
17128
17372
|
function isRouteProvenAbsentError(err) {
|
|
17129
|
-
return err instanceof SubcError && err.code === "unknown_channel" || err instanceof StaleRouteHandleError;
|
|
17373
|
+
return err instanceof SubcError && (err.code === "unknown_channel" || err.code === "stale_route_epoch") || err instanceof StaleRouteHandleError;
|
|
17130
17374
|
}
|
|
17131
17375
|
function isAbsentRootRouteError(err) {
|
|
17132
17376
|
return isRecord(err) && err.code === "config_divergence" && typeof err.message === "string" && err.message.includes("project root does not exist");
|
|
@@ -17253,6 +17497,8 @@ class SubcTransport {
|
|
|
17253
17497
|
body.preview = true;
|
|
17254
17498
|
if (options?.workerSession === true)
|
|
17255
17499
|
body[WORKER_SESSION_FIELD] = true;
|
|
17500
|
+
if (options?.hostToolNames)
|
|
17501
|
+
body[HOST_TOOL_NAMES_FIELD] = options.hostToolNames;
|
|
17256
17502
|
body[PRESET_FIELD] = callPresetFor(options?.workerSession === true);
|
|
17257
17503
|
const reply = await this.pool.routeRequest(this.identityFor(sessionId), body, timeoutMs, onProgress, this.generation, abortSignal);
|
|
17258
17504
|
return reliftReply(reply);
|
|
@@ -17264,10 +17510,12 @@ class SubcTransport {
|
|
|
17264
17510
|
}
|
|
17265
17511
|
const { timeoutMs, onProgress, abortSignal } = this.splitOptions(options);
|
|
17266
17512
|
const session = typeof params.session_id === "string" ? params.session_id : undefined;
|
|
17267
|
-
const { [WORKER_SESSION_FIELD]: workerSession, ...args } = params;
|
|
17513
|
+
const { [WORKER_SESSION_FIELD]: workerSession, [HOST_TOOL_NAMES_FIELD]: hostToolNames, ...args } = params;
|
|
17268
17514
|
const body = { name: command, arguments: args };
|
|
17269
17515
|
if (workerSession === true)
|
|
17270
17516
|
body[WORKER_SESSION_FIELD] = true;
|
|
17517
|
+
if (hostToolNames !== undefined)
|
|
17518
|
+
body[HOST_TOOL_NAMES_FIELD] = hostToolNames;
|
|
17271
17519
|
body[PRESET_FIELD] = callPresetFor(workerSession === true);
|
|
17272
17520
|
const editSlotSurvives = this.pool.getEditSlotSurvives();
|
|
17273
17521
|
if (editSlotSurvives !== undefined)
|
|
@@ -17290,6 +17538,10 @@ class SubcTransportPool {
|
|
|
17290
17538
|
connectionFile;
|
|
17291
17539
|
handshakeTimeoutMs;
|
|
17292
17540
|
consumerIdentity;
|
|
17541
|
+
hostScopes;
|
|
17542
|
+
hostScopeCache = new Map;
|
|
17543
|
+
hostScopeRefusals = new Set;
|
|
17544
|
+
hostScopeOutcomes = new Map;
|
|
17293
17545
|
connectFn;
|
|
17294
17546
|
onBgEventsNudge;
|
|
17295
17547
|
onBgEventsNudgeRef;
|
|
@@ -17311,6 +17563,7 @@ class SubcTransportPool {
|
|
|
17311
17563
|
routeReopenRetry = null;
|
|
17312
17564
|
routeReopenRetryMs = null;
|
|
17313
17565
|
sessions = new Map;
|
|
17566
|
+
sessionIndex = new Map;
|
|
17314
17567
|
observedStarts = new Set;
|
|
17315
17568
|
sessionSweep = null;
|
|
17316
17569
|
rootIndex = new Map;
|
|
@@ -17330,6 +17583,7 @@ class SubcTransportPool {
|
|
|
17330
17583
|
this.harness = options.harness;
|
|
17331
17584
|
this.handshakeTimeoutMs = options.handshakeTimeoutMs;
|
|
17332
17585
|
this.consumerIdentity = options.consumerIdentity;
|
|
17586
|
+
this.hostScopes = options.hostScopes ?? false;
|
|
17333
17587
|
this.connectFn = options.connect ?? ((opts) => SubcClient.connect(opts));
|
|
17334
17588
|
this.onBgEventsNudge = options.onBgEventsNudge;
|
|
17335
17589
|
this.onBgEventsNudgeRef = options.onBgEventsNudgeRef;
|
|
@@ -17549,7 +17803,9 @@ class SubcTransportPool {
|
|
|
17549
17803
|
return transport;
|
|
17550
17804
|
}
|
|
17551
17805
|
getBridge(projectRoot) {
|
|
17552
|
-
|
|
17806
|
+
return this.getBridgeForCanonicalRoot(this.canonicalRoot(projectRoot));
|
|
17807
|
+
}
|
|
17808
|
+
getBridgeForCanonicalRoot(root) {
|
|
17553
17809
|
if (this.shuttingDown && this.lifecycleEnabled()) {
|
|
17554
17810
|
throw new SubcTransportShuttingDownError;
|
|
17555
17811
|
}
|
|
@@ -17646,6 +17902,12 @@ class SubcTransportPool {
|
|
|
17646
17902
|
pendingDetach: false
|
|
17647
17903
|
};
|
|
17648
17904
|
this.sessions.set(key, record);
|
|
17905
|
+
let records = this.sessionIndex.get(identity.session);
|
|
17906
|
+
if (!records) {
|
|
17907
|
+
records = new Set;
|
|
17908
|
+
this.sessionIndex.set(identity.session, records);
|
|
17909
|
+
}
|
|
17910
|
+
records.add(record);
|
|
17649
17911
|
let keys = this.rootIndex.get(root);
|
|
17650
17912
|
if (!keys) {
|
|
17651
17913
|
keys = new Set;
|
|
@@ -17823,6 +18085,10 @@ class SubcTransportPool {
|
|
|
17823
18085
|
log(`subc bg_events: ${message}${suffix}`, { sessionId: record.identity.session });
|
|
17824
18086
|
}
|
|
17825
18087
|
removeIndexMembership(record) {
|
|
18088
|
+
const records = this.sessionIndex.get(record.identity.session);
|
|
18089
|
+
records?.delete(record);
|
|
18090
|
+
if (records?.size === 0)
|
|
18091
|
+
this.sessionIndex.delete(record.identity.session);
|
|
17826
18092
|
const keys = this.rootIndex.get(record.canonicalRoot);
|
|
17827
18093
|
if (!keys)
|
|
17828
18094
|
return;
|
|
@@ -17835,6 +18101,7 @@ class SubcTransportPool {
|
|
|
17835
18101
|
this.sessions.delete(record.identityKey);
|
|
17836
18102
|
this.removeIndexMembership(record);
|
|
17837
18103
|
}
|
|
18104
|
+
this.stopUnusedHostScopeRetry(record.identity.session);
|
|
17838
18105
|
}
|
|
17839
18106
|
detachSession(key, reason) {
|
|
17840
18107
|
const record = this.sessions.get(key);
|
|
@@ -17854,6 +18121,7 @@ class SubcTransportPool {
|
|
|
17854
18121
|
record.routeEntry = null;
|
|
17855
18122
|
if (routeEntry)
|
|
17856
18123
|
routeEntry.closed = true;
|
|
18124
|
+
this.stopUnusedHostScopeRetry(record.identity.session);
|
|
17857
18125
|
return { record, bgSub, routeEntry };
|
|
17858
18126
|
}
|
|
17859
18127
|
async cleanupDetached(detached) {
|
|
@@ -17910,6 +18178,17 @@ class SubcTransportPool {
|
|
|
17910
18178
|
entry.inflight -= 1;
|
|
17911
18179
|
this.closeRetiredRoute(record, entry);
|
|
17912
18180
|
}
|
|
18181
|
+
retireHostToolRoute(record) {
|
|
18182
|
+
const entry = record.routeEntry;
|
|
18183
|
+
if (!entry)
|
|
18184
|
+
return;
|
|
18185
|
+
entry.closed = true;
|
|
18186
|
+
entry.closedBy ??= "route replaced";
|
|
18187
|
+
record.routeEntry = null;
|
|
18188
|
+
record.retiredRoutes.add(entry);
|
|
18189
|
+
this.closeRetiredRoute(record, entry);
|
|
18190
|
+
this.stopUnusedHostScopeRetry(record.identity.session);
|
|
18191
|
+
}
|
|
17913
18192
|
async routeRequest(identity, body, timeoutMs, onProgress, expectedGeneration, abortSignal) {
|
|
17914
18193
|
const callStartedAt = performance.now();
|
|
17915
18194
|
const callDeadlineMs = timeoutMs ?? SUBC_DEFAULT_REQUEST_TIMEOUT_MS;
|
|
@@ -18048,15 +18327,14 @@ class SubcTransportPool {
|
|
|
18048
18327
|
const clearRouteEntry = (entry) => {
|
|
18049
18328
|
if (record.routeEntry !== entry)
|
|
18050
18329
|
return;
|
|
18051
|
-
|
|
18052
|
-
entry.closedBy ??= "route replaced";
|
|
18053
|
-
record.routeEntry = null;
|
|
18054
|
-
record.retiredRoutes.add(entry);
|
|
18055
|
-
this.closeRetiredRoute(record, entry);
|
|
18330
|
+
this.retireHostToolRoute(record);
|
|
18056
18331
|
};
|
|
18057
18332
|
const handleRequestFailure = (error, entry) => {
|
|
18058
18333
|
const aborted = error instanceof Error && error.name === "AbortError";
|
|
18059
18334
|
const ownsClient = this.isCurrentSession(key, record) && this.client === client && !this.isReapInduced(record);
|
|
18335
|
+
if (ownsClient && error instanceof SubcError && error.closeReason?.startsWith("scope_")) {
|
|
18336
|
+
this.invalidateHostScope(identity.session);
|
|
18337
|
+
}
|
|
18060
18338
|
if (!aborted && ownsClient && isSocketDeathError(error)) {
|
|
18061
18339
|
this.dropClient(client, {
|
|
18062
18340
|
error,
|
|
@@ -18065,7 +18343,7 @@ class SubcTransportPool {
|
|
|
18065
18343
|
route: routeLabel(entry.handle)
|
|
18066
18344
|
});
|
|
18067
18345
|
}
|
|
18068
|
-
if (aborted || isRouteProvenAbsentError(error) || error instanceof SubcError && error.closeReason !== undefined) {
|
|
18346
|
+
if (aborted || isRouteProvenAbsentError(error) || error instanceof SubcError && error.closeReason !== undefined || isSubcFrameReadTimeoutError(error)) {
|
|
18069
18347
|
clearRouteEntry(entry);
|
|
18070
18348
|
}
|
|
18071
18349
|
if (aborted || !ownsClient || this.client !== client)
|
|
@@ -18144,10 +18422,13 @@ class SubcTransportPool {
|
|
|
18144
18422
|
this.resetRouteReopenBackoff();
|
|
18145
18423
|
return reply;
|
|
18146
18424
|
} catch (requestError) {
|
|
18147
|
-
const error = routeClosedMidCallError(requestError, routeAndEntry.entry, record);
|
|
18425
|
+
const error = frameReadTimeoutError(routeClosedMidCallError(requestError, routeAndEntry.entry, record));
|
|
18148
18426
|
if (this.isReapInduced(record))
|
|
18149
18427
|
throw this.annotateReapError(error, record);
|
|
18150
18428
|
const ownsRoute = this.isCurrentSession(key, record) && this.client === client;
|
|
18429
|
+
if (ownsRoute && (error instanceof SubcError && error.code === "stale_route_epoch" || error instanceof StaleRouteHandleError)) {
|
|
18430
|
+
this.invalidateHostScope(identity.session);
|
|
18431
|
+
}
|
|
18151
18432
|
if (ownsRoute && isRouteRequestReloadRefusal(error)) {
|
|
18152
18433
|
clearRouteEntry(routeAndEntry.entry);
|
|
18153
18434
|
lastRefusal = error;
|
|
@@ -18224,9 +18505,14 @@ class SubcTransportPool {
|
|
|
18224
18505
|
return this.client;
|
|
18225
18506
|
if (this.connecting)
|
|
18226
18507
|
return this.connecting;
|
|
18508
|
+
let owner;
|
|
18227
18509
|
this.connecting = this.connectFn({
|
|
18228
18510
|
connectionFile: this.connectionFile,
|
|
18229
|
-
handshakeTimeoutMs: this.handshakeTimeoutMs
|
|
18511
|
+
handshakeTimeoutMs: this.handshakeTimeoutMs,
|
|
18512
|
+
onControlPush: (push) => {
|
|
18513
|
+
if (owner && this.client === owner)
|
|
18514
|
+
this.onScopeRouteClose(push);
|
|
18515
|
+
}
|
|
18230
18516
|
}).then((client) => {
|
|
18231
18517
|
this.connecting = null;
|
|
18232
18518
|
if (this.shuttingDown) {
|
|
@@ -18236,6 +18522,7 @@ class SubcTransportPool {
|
|
|
18236
18522
|
throw new SubcTransportShuttingDownError;
|
|
18237
18523
|
}
|
|
18238
18524
|
this.client = client;
|
|
18525
|
+
owner = client;
|
|
18239
18526
|
this.hadWorkingClient = true;
|
|
18240
18527
|
this.transportFailures = 0;
|
|
18241
18528
|
return client;
|
|
@@ -18265,9 +18552,7 @@ class SubcTransportPool {
|
|
|
18265
18552
|
closedBy: null
|
|
18266
18553
|
};
|
|
18267
18554
|
this.assertRootCanAttach(record.canonicalRoot);
|
|
18268
|
-
const opening =
|
|
18269
|
-
consumerIdentity: this.consumerIdentity
|
|
18270
|
-
}).then((route) => {
|
|
18555
|
+
const opening = this.openHostToolRoute(client, identity, record, entry).then((route) => {
|
|
18271
18556
|
if (!this.isCurrentSession(record.identityKey, record) || record.routeEntry !== entry || entry.closed || this.client !== client || this.lifecycleEnabled() && !this.isCurrentLiveGeneration(record.canonicalRoot, record.generation)) {
|
|
18272
18557
|
safeCloseRoute(client, route);
|
|
18273
18558
|
if (record.routeEntry === entry)
|
|
@@ -18277,6 +18562,12 @@ class SubcTransportPool {
|
|
|
18277
18562
|
entry.handle = route;
|
|
18278
18563
|
entry.opening = null;
|
|
18279
18564
|
this.noteClientAlive(client);
|
|
18565
|
+
const state = this.hostScopeCache.get(identity.session);
|
|
18566
|
+
if (state)
|
|
18567
|
+
this.scheduleHostScopeRetry(identity.session, state);
|
|
18568
|
+
if (state?.answer?.result === "scope" && !state.scopeBindPending && !entry.hostScope && !this.hostScopeRefusals.has(identity.session)) {
|
|
18569
|
+
this.promoteHostScope(identity.session);
|
|
18570
|
+
}
|
|
18280
18571
|
return route;
|
|
18281
18572
|
}).catch((error) => {
|
|
18282
18573
|
const currentEntry = this.isCurrentSession(record.identityKey, record) && record.routeEntry === entry;
|
|
@@ -18284,6 +18575,7 @@ class SubcTransportPool {
|
|
|
18284
18575
|
entry.closed = true;
|
|
18285
18576
|
record.routeEntry = null;
|
|
18286
18577
|
}
|
|
18578
|
+
this.stopUnusedHostScopeRetry(identity.session);
|
|
18287
18579
|
if (!this.isCurrentSession(record.identityKey, record) && !(error instanceof RouteTornDownError)) {
|
|
18288
18580
|
throw new RouteTornDownError("subc route opened after session closed");
|
|
18289
18581
|
}
|
|
@@ -18296,6 +18588,327 @@ class SubcTransportPool {
|
|
|
18296
18588
|
record.routeEntry = entry;
|
|
18297
18589
|
return { route: await opening, entry };
|
|
18298
18590
|
}
|
|
18591
|
+
hostScope(client, identity) {
|
|
18592
|
+
if (this.hostScopeRefusals.has(identity.session))
|
|
18593
|
+
return Promise.resolve(undefined);
|
|
18594
|
+
let state = this.hostScopeCache.get(identity.session);
|
|
18595
|
+
if (!state) {
|
|
18596
|
+
state = { retryAt: 0, retryMs: HOST_SCOPE_RETRY_FLOOR_MS };
|
|
18597
|
+
this.hostScopeCache.set(identity.session, state);
|
|
18598
|
+
}
|
|
18599
|
+
if (state.answer) {
|
|
18600
|
+
return Promise.resolve(state.answer.result === "scope" && !state.scopeBindPending ? state.answer.scope : undefined);
|
|
18601
|
+
}
|
|
18602
|
+
if (state.pending)
|
|
18603
|
+
return state.pending;
|
|
18604
|
+
if (Date.now() < state.retryAt)
|
|
18605
|
+
return Promise.resolve(undefined);
|
|
18606
|
+
const current = state;
|
|
18607
|
+
clearTimeout(current.retryTimer);
|
|
18608
|
+
current.retryTimer = undefined;
|
|
18609
|
+
const abort = new AbortController;
|
|
18610
|
+
current.lookupAbort = abort;
|
|
18611
|
+
const lookup = this.readHostScope(client, identity, abort.signal).then((outcome) => {
|
|
18612
|
+
if (this.hostScopeCache.get(identity.session) !== current)
|
|
18613
|
+
return;
|
|
18614
|
+
current.pending = undefined;
|
|
18615
|
+
current.lookupAbort = undefined;
|
|
18616
|
+
if (abort.signal.aborted)
|
|
18617
|
+
return;
|
|
18618
|
+
this.logHostScopeOutcome(identity.session, outcome);
|
|
18619
|
+
if (outcome.result === "scope" || outcome.result === "no_scope") {
|
|
18620
|
+
current.answer = outcome;
|
|
18621
|
+
current.retryAt = 0;
|
|
18622
|
+
current.retryMs = HOST_SCOPE_RETRY_FLOOR_MS;
|
|
18623
|
+
if (outcome.result === "scope")
|
|
18624
|
+
this.promoteHostScope(identity.session);
|
|
18625
|
+
} else {
|
|
18626
|
+
current.retryAt = Date.now() + current.retryMs;
|
|
18627
|
+
current.retryMs = Math.min(current.retryMs * 2, HOST_SCOPE_RETRY_CAP_MS);
|
|
18628
|
+
this.scheduleHostScopeRetry(identity.session, current);
|
|
18629
|
+
}
|
|
18630
|
+
return outcome.result === "scope" ? outcome.scope : undefined;
|
|
18631
|
+
});
|
|
18632
|
+
current.pending = lookup;
|
|
18633
|
+
return lookup;
|
|
18634
|
+
}
|
|
18635
|
+
unscopedHostRecord(session) {
|
|
18636
|
+
for (const record of this.sessionIndex.get(session) ?? []) {
|
|
18637
|
+
const entry = record.routeEntry;
|
|
18638
|
+
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))
|
|
18639
|
+
return record;
|
|
18640
|
+
}
|
|
18641
|
+
return;
|
|
18642
|
+
}
|
|
18643
|
+
cancelHostScopeRetry(state) {
|
|
18644
|
+
clearTimeout(state.retryTimer);
|
|
18645
|
+
state.retryTimer = undefined;
|
|
18646
|
+
state.lookupAbort?.abort();
|
|
18647
|
+
}
|
|
18648
|
+
stopUnusedHostScopeRetry(session) {
|
|
18649
|
+
const state = this.hostScopeCache.get(session);
|
|
18650
|
+
if (state && !this.unscopedHostRecord(session)) {
|
|
18651
|
+
if (state.retryTimer || state.scopeBindPending && state.bindPending)
|
|
18652
|
+
log(`subc head scope: session=${session} retry stopped reason=route_closed`);
|
|
18653
|
+
this.cancelHostScopeRetry(state);
|
|
18654
|
+
state.scopeBindPending = false;
|
|
18655
|
+
}
|
|
18656
|
+
}
|
|
18657
|
+
scheduleHostScopeRetry(session, state) {
|
|
18658
|
+
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))
|
|
18659
|
+
return;
|
|
18660
|
+
state.retryTimer = setTimeout(() => {
|
|
18661
|
+
state.retryTimer = undefined;
|
|
18662
|
+
const record = this.unscopedHostRecord(session);
|
|
18663
|
+
const client = record?.routeEntry?.client ?? record?.bgSub?.attachedClient;
|
|
18664
|
+
if (this.shuttingDown || this.hostScopeCache.get(session) !== state || !record || !client)
|
|
18665
|
+
return;
|
|
18666
|
+
let retry;
|
|
18667
|
+
if (state.scopeBindPending && state.answer?.result === "scope") {
|
|
18668
|
+
state.bindPending = this.retryHostScopeBind(client, record.identity, state, state.answer.scope).finally(() => {
|
|
18669
|
+
state.bindPending = undefined;
|
|
18670
|
+
this.scheduleHostScopeRetry(session, state);
|
|
18671
|
+
});
|
|
18672
|
+
retry = state.bindPending;
|
|
18673
|
+
} else {
|
|
18674
|
+
retry = this.hostScope(client, record.identity);
|
|
18675
|
+
}
|
|
18676
|
+
retry.catch((error) => {
|
|
18677
|
+
warn(`subc head scope: background retry failed code=${error instanceof Error ? error.name : "Error"}`);
|
|
18678
|
+
});
|
|
18679
|
+
}, Math.max(0, state.retryAt - Date.now()));
|
|
18680
|
+
state.retryTimer.unref();
|
|
18681
|
+
}
|
|
18682
|
+
deferHostScopeBind(session, state, code) {
|
|
18683
|
+
state.scopeBindPending = true;
|
|
18684
|
+
if (Date.now() >= state.retryAt) {
|
|
18685
|
+
state.retryAt = Date.now() + state.retryMs;
|
|
18686
|
+
state.retryMs = Math.min(state.retryMs * 2, HOST_SCOPE_RETRY_CAP_MS);
|
|
18687
|
+
}
|
|
18688
|
+
warn(`subc head scope: session=${session} scoped bind refused code=${code}; binding unscoped; retry in=${Math.max(0, state.retryAt - Date.now())}ms`);
|
|
18689
|
+
this.scheduleHostScopeRetry(session, state);
|
|
18690
|
+
}
|
|
18691
|
+
refuseHostScopeBind(session, state, code) {
|
|
18692
|
+
if (!this.hostScopeRefusals.has(session)) {
|
|
18693
|
+
this.hostScopeRefusals.add(session);
|
|
18694
|
+
warn(`subc head scope: session=${session} scoped bind refused code=${code}; binding unscoped; retry stopped`);
|
|
18695
|
+
}
|
|
18696
|
+
if (state) {
|
|
18697
|
+
state.scopeBindPending = false;
|
|
18698
|
+
this.cancelHostScopeRetry(state);
|
|
18699
|
+
}
|
|
18700
|
+
}
|
|
18701
|
+
async retryHostScopeBind(client, identity, state, scope) {
|
|
18702
|
+
try {
|
|
18703
|
+
const route = await client.routeOpen({ kind: "tool_provider", module_id: AFT_MODULE_ID }, identity, { consumerIdentity: this.consumerIdentity, scope });
|
|
18704
|
+
safeCloseRoute(client, route);
|
|
18705
|
+
if (this.hostScopeCache.get(identity.session) !== state || !state.scopeBindPending || this.shuttingDown || client !== this.client || !this.unscopedHostRecord(identity.session))
|
|
18706
|
+
return;
|
|
18707
|
+
state.scopeBindPending = false;
|
|
18708
|
+
state.retryAt = 0;
|
|
18709
|
+
state.retryMs = HOST_SCOPE_RETRY_FLOOR_MS;
|
|
18710
|
+
log(`subc head scope: session=${identity.session} scoped bind recovered ref=${scope.ref.slice(0, 8)} epoch=${scope.scopeEpoch}`);
|
|
18711
|
+
this.promoteHostScope(identity.session);
|
|
18712
|
+
} catch (error) {
|
|
18713
|
+
if (this.hostScopeCache.get(identity.session) !== state || !state.scopeBindPending || this.shuttingDown || !this.unscopedHostRecord(identity.session))
|
|
18714
|
+
return;
|
|
18715
|
+
if (error instanceof SubcError && error.code && !isSocketDeathError(error) && !isRouteOpenReloadWindowError(error) && !isHostScopeSyncRefusal(error.code)) {
|
|
18716
|
+
this.refuseHostScopeBind(identity.session, state, error.code);
|
|
18717
|
+
} else {
|
|
18718
|
+
this.deferHostScopeBind(identity.session, state, error instanceof SubcError ? error.code ?? error.name : "Error");
|
|
18719
|
+
}
|
|
18720
|
+
}
|
|
18721
|
+
}
|
|
18722
|
+
promoteHostScope(session) {
|
|
18723
|
+
for (const record of this.sessions.values()) {
|
|
18724
|
+
const entry = record.routeEntry;
|
|
18725
|
+
if (record.identity.session !== session)
|
|
18726
|
+
continue;
|
|
18727
|
+
if (entry?.handle && !entry.hostScope)
|
|
18728
|
+
this.retireHostToolRoute(record);
|
|
18729
|
+
const sub = record.bgSub;
|
|
18730
|
+
if (!sub?.attachedRoute)
|
|
18731
|
+
continue;
|
|
18732
|
+
sub.stop().then(() => {
|
|
18733
|
+
if (record.bgSub !== sub)
|
|
18734
|
+
return;
|
|
18735
|
+
record.bgSub = null;
|
|
18736
|
+
this.ensureBgSubscription(record.identity, record);
|
|
18737
|
+
});
|
|
18738
|
+
}
|
|
18739
|
+
}
|
|
18740
|
+
logHostScopeOutcome(session, outcome) {
|
|
18741
|
+
let key;
|
|
18742
|
+
let detail;
|
|
18743
|
+
switch (outcome.result) {
|
|
18744
|
+
case "scope":
|
|
18745
|
+
key = JSON.stringify([outcome.result, outcome.scope.ref, outcome.scope.scopeEpoch]);
|
|
18746
|
+
detail = `scope ref=${outcome.scope.ref.slice(0, 8)} epoch=${outcome.scope.scopeEpoch}`;
|
|
18747
|
+
break;
|
|
18748
|
+
case "error":
|
|
18749
|
+
key = JSON.stringify([outcome.result, outcome.code]);
|
|
18750
|
+
detail = `error code=${outcome.code} after=${outcome.afterMs}`;
|
|
18751
|
+
break;
|
|
18752
|
+
case "timeout":
|
|
18753
|
+
key = outcome.result;
|
|
18754
|
+
detail = `timeout after=${outcome.afterMs}`;
|
|
18755
|
+
break;
|
|
18756
|
+
default:
|
|
18757
|
+
key = outcome.result;
|
|
18758
|
+
detail = outcome.result;
|
|
18759
|
+
}
|
|
18760
|
+
if (this.hostScopeOutcomes.get(session) === key)
|
|
18761
|
+
return;
|
|
18762
|
+
this.hostScopeOutcomes.set(session, key);
|
|
18763
|
+
log(`subc head scope: session=${session} result=${detail}`);
|
|
18764
|
+
}
|
|
18765
|
+
async readHostScope(client, identity, signal) {
|
|
18766
|
+
const startedAt = Date.now();
|
|
18767
|
+
const timeout = () => ({ result: "timeout", afterMs: Date.now() - startedAt });
|
|
18768
|
+
let expired = false;
|
|
18769
|
+
let timer;
|
|
18770
|
+
const abort = new AbortController;
|
|
18771
|
+
let cancel;
|
|
18772
|
+
const cancelled = new Promise((resolve) => {
|
|
18773
|
+
cancel = () => {
|
|
18774
|
+
expired = true;
|
|
18775
|
+
abort.abort();
|
|
18776
|
+
resolve({ result: "error", code: "AbortError", afterMs: Date.now() - startedAt });
|
|
18777
|
+
};
|
|
18778
|
+
signal.addEventListener("abort", cancel, { once: true });
|
|
18779
|
+
});
|
|
18780
|
+
const read = async () => {
|
|
18781
|
+
let route;
|
|
18782
|
+
try {
|
|
18783
|
+
route = await client.routeOpen({ kind: "management_surface", module_id: "prefrontal-core" }, identity, { consumerIdentity: this.consumerIdentity });
|
|
18784
|
+
if (expired)
|
|
18785
|
+
return timeout();
|
|
18786
|
+
const envelope = await client.request(route, {
|
|
18787
|
+
method: "session.scope_identity",
|
|
18788
|
+
params: { harness: identity.harness, session: identity.session }
|
|
18789
|
+
}, { timeoutMs: HOST_SCOPE_LOOKUP_TIMEOUT_MS, signal: abort.signal });
|
|
18790
|
+
const reply = isRecord(envelope) ? envelope.result : undefined;
|
|
18791
|
+
if (isRecord(reply) && reply.answer === "no_scope")
|
|
18792
|
+
return { result: "no_scope" };
|
|
18793
|
+
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)
|
|
18794
|
+
return { result: "malformed" };
|
|
18795
|
+
return {
|
|
18796
|
+
result: "scope",
|
|
18797
|
+
scope: {
|
|
18798
|
+
owner: { kind: "reserved", module_id: "prefrontal-core" },
|
|
18799
|
+
ref: reply.ref,
|
|
18800
|
+
scopeEpoch: reply.scope_epoch
|
|
18801
|
+
}
|
|
18802
|
+
};
|
|
18803
|
+
} catch (error) {
|
|
18804
|
+
if (expired)
|
|
18805
|
+
return timeout();
|
|
18806
|
+
return {
|
|
18807
|
+
result: "error",
|
|
18808
|
+
code: error instanceof SubcError && error.code ? error.code : error instanceof Error ? error.name : "Error",
|
|
18809
|
+
afterMs: Date.now() - startedAt
|
|
18810
|
+
};
|
|
18811
|
+
} finally {
|
|
18812
|
+
if (route)
|
|
18813
|
+
safeCloseRoute(client, route);
|
|
18814
|
+
}
|
|
18815
|
+
};
|
|
18816
|
+
try {
|
|
18817
|
+
return await Promise.race([
|
|
18818
|
+
read(),
|
|
18819
|
+
cancelled,
|
|
18820
|
+
new Promise((resolve) => {
|
|
18821
|
+
timer = setTimeout(() => {
|
|
18822
|
+
expired = true;
|
|
18823
|
+
abort.abort();
|
|
18824
|
+
resolve(timeout());
|
|
18825
|
+
}, HOST_SCOPE_LOOKUP_TIMEOUT_MS);
|
|
18826
|
+
})
|
|
18827
|
+
]);
|
|
18828
|
+
} finally {
|
|
18829
|
+
clearTimeout(timer);
|
|
18830
|
+
signal.removeEventListener("abort", cancel);
|
|
18831
|
+
}
|
|
18832
|
+
}
|
|
18833
|
+
openHostToolRoute(client, identity, record, entry) {
|
|
18834
|
+
if (!this.hostScopes || !["opencode", "pi"].includes(identity.harness) || identity.session === DEFAULT_SESSION_ID) {
|
|
18835
|
+
return client.routeOpen({ kind: "tool_provider", module_id: AFT_MODULE_ID }, identity, {
|
|
18836
|
+
consumerIdentity: this.consumerIdentity
|
|
18837
|
+
});
|
|
18838
|
+
}
|
|
18839
|
+
return this.openScopedHostToolRoute(client, identity, record, entry);
|
|
18840
|
+
}
|
|
18841
|
+
async openScopedHostToolRoute(client, identity, record, entry) {
|
|
18842
|
+
const scope = await this.hostScope(client, identity);
|
|
18843
|
+
this.assertRecordLive(record);
|
|
18844
|
+
if (scope) {
|
|
18845
|
+
try {
|
|
18846
|
+
const route = await client.routeOpen({ kind: "tool_provider", module_id: AFT_MODULE_ID }, identity, { consumerIdentity: this.consumerIdentity, scope });
|
|
18847
|
+
if (entry)
|
|
18848
|
+
entry.hostScope = scope;
|
|
18849
|
+
return route;
|
|
18850
|
+
} catch (error) {
|
|
18851
|
+
if (!(error instanceof SubcError) || !error.code || isSocketDeathError(error))
|
|
18852
|
+
throw error;
|
|
18853
|
+
if (isRouteOpenReloadWindowError(error))
|
|
18854
|
+
throw error;
|
|
18855
|
+
this.assertRecordLive(record);
|
|
18856
|
+
const state = this.hostScopeCache.get(identity.session);
|
|
18857
|
+
if (state && isHostScopeSyncRefusal(error.code)) {
|
|
18858
|
+
this.deferHostScopeBind(identity.session, state, error.code);
|
|
18859
|
+
} else {
|
|
18860
|
+
this.refuseHostScopeBind(identity.session, state, error.code);
|
|
18861
|
+
}
|
|
18862
|
+
}
|
|
18863
|
+
}
|
|
18864
|
+
this.assertRecordLive(record);
|
|
18865
|
+
const route = await client.routeOpen({ kind: "tool_provider", module_id: AFT_MODULE_ID }, identity, {
|
|
18866
|
+
consumerIdentity: this.consumerIdentity
|
|
18867
|
+
});
|
|
18868
|
+
if (this.hostScopeCache.get(identity.session)?.answer?.result === "scope" && !this.hostScopeCache.get(identity.session)?.scopeBindPending && !this.hostScopeRefusals.has(identity.session)) {
|
|
18869
|
+
safeCloseRoute(client, route);
|
|
18870
|
+
return this.openScopedHostToolRoute(client, identity, record, entry);
|
|
18871
|
+
}
|
|
18872
|
+
return route;
|
|
18873
|
+
}
|
|
18874
|
+
invalidateHostScope(session) {
|
|
18875
|
+
const state = this.hostScopeCache.get(session);
|
|
18876
|
+
if (state) {
|
|
18877
|
+
if (state.scopeBindPending)
|
|
18878
|
+
log(`subc head scope: session=${session} retry stopped reason=scope_route_closed`);
|
|
18879
|
+
this.cancelHostScopeRetry(state);
|
|
18880
|
+
}
|
|
18881
|
+
this.hostScopeCache.delete(session);
|
|
18882
|
+
this.hostScopeRefusals.delete(session);
|
|
18883
|
+
for (const record of this.sessions.values()) {
|
|
18884
|
+
if (record.identity.session !== session || !record.routeEntry)
|
|
18885
|
+
continue;
|
|
18886
|
+
this.retireHostToolRoute(record);
|
|
18887
|
+
}
|
|
18888
|
+
}
|
|
18889
|
+
onScopeRouteClose(push) {
|
|
18890
|
+
if (push.op !== "route.closed")
|
|
18891
|
+
return;
|
|
18892
|
+
const reason = routeCloseReason(push);
|
|
18893
|
+
if (!reason?.startsWith("scope_")) {
|
|
18894
|
+
for (const record of this.sessions.values()) {
|
|
18895
|
+
const route = record.routeEntry?.handle;
|
|
18896
|
+
if (!route)
|
|
18897
|
+
continue;
|
|
18898
|
+
if (Array.isArray(push.body.channels) && push.body.channels.includes(route.channel) || !Array.isArray(push.body.channels) && push.body.module_id === AFT_MODULE_ID)
|
|
18899
|
+
this.retireHostToolRoute(record);
|
|
18900
|
+
}
|
|
18901
|
+
return;
|
|
18902
|
+
}
|
|
18903
|
+
const sessions = new Set;
|
|
18904
|
+
for (const record of this.sessions.values()) {
|
|
18905
|
+
const routes = [record.routeEntry?.handle, record.bgSub?.attachedRoute].filter((route) => route != null);
|
|
18906
|
+
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)
|
|
18907
|
+
sessions.add(record.identity.session);
|
|
18908
|
+
}
|
|
18909
|
+
for (const session of sessions)
|
|
18910
|
+
this.invalidateHostScope(session);
|
|
18911
|
+
}
|
|
18299
18912
|
ensureBgSubscription(identity, record) {
|
|
18300
18913
|
if (this.shuttingDown || !this.onBgEventsNudge && !this.onBgEventsNudgeRef)
|
|
18301
18914
|
return;
|
|
@@ -18335,17 +18948,28 @@ class SubcTransportPool {
|
|
|
18335
18948
|
return;
|
|
18336
18949
|
this.logNudgeDelivery("drop-no-compatible-handler", currentRecord, `nudge dropped cause=generation-provenance-unavailable-and-root-session-handler-unwired root=${currentRecord.canonicalRoot}`);
|
|
18337
18950
|
};
|
|
18951
|
+
const binding = {};
|
|
18338
18952
|
let sub = null;
|
|
18339
18953
|
const clearDormantSubscription = () => {
|
|
18340
18954
|
if (sub && record.bgSub === sub)
|
|
18341
18955
|
record.bgSub = null;
|
|
18342
18956
|
};
|
|
18343
|
-
sub = new BgSubscription(identity, () => this.ensureClient(), (client, cause) => this.dropClient(client, cause),
|
|
18957
|
+
sub = new BgSubscription(identity, () => this.ensureClient(), (client, cause) => this.dropClient(client, cause), (client) => {
|
|
18958
|
+
binding.hostScope = undefined;
|
|
18959
|
+
return this.openHostToolRoute(client, identity, record, binding);
|
|
18960
|
+
}, onNudge, this.bgBackoffSleep, () => this.rootCanAttach(record.canonicalRoot), (error) => {
|
|
18344
18961
|
if (!isAbsentRootRouteError(error))
|
|
18345
18962
|
return false;
|
|
18346
18963
|
this.markRootDormant(record.canonicalRoot, error);
|
|
18347
18964
|
return true;
|
|
18348
|
-
}, 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))
|
|
18965
|
+
}, 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), () => {
|
|
18966
|
+
const state = this.hostScopeCache.get(identity.session);
|
|
18967
|
+
if (state)
|
|
18968
|
+
this.scheduleHostScopeRetry(identity.session, state);
|
|
18969
|
+
if (state?.answer?.result === "scope" && !state.scopeBindPending && !binding.hostScope && !this.hostScopeRefusals.has(identity.session)) {
|
|
18970
|
+
this.promoteHostScope(identity.session);
|
|
18971
|
+
}
|
|
18972
|
+
});
|
|
18349
18973
|
record.bgSub = sub;
|
|
18350
18974
|
if (!this.rootCanAttach(record.canonicalRoot)) {
|
|
18351
18975
|
record.bgSub = null;
|
|
@@ -18381,6 +19005,8 @@ class SubcTransportPool {
|
|
|
18381
19005
|
record.routeEntry = null;
|
|
18382
19006
|
this.deleteSessionIfEmpty(record.identityKey, record);
|
|
18383
19007
|
}
|
|
19008
|
+
for (const session of this.hostScopeCache.keys())
|
|
19009
|
+
this.stopUnusedHostScopeRetry(session);
|
|
18384
19010
|
this.transportFailures = 0;
|
|
18385
19011
|
try {
|
|
18386
19012
|
client.close();
|
|
@@ -18518,6 +19144,13 @@ class SubcTransportPool {
|
|
|
18518
19144
|
this.generationRejections.clear();
|
|
18519
19145
|
this.observedStarts.clear();
|
|
18520
19146
|
this.dormantRoots.clear();
|
|
19147
|
+
const scopeLookups = [...this.hostScopeCache.values()].flatMap((state) => {
|
|
19148
|
+
this.cancelHostScopeRetry(state);
|
|
19149
|
+
return state.pending ? [state.pending] : [];
|
|
19150
|
+
});
|
|
19151
|
+
this.hostScopeCache.clear();
|
|
19152
|
+
this.hostScopeRefusals.clear();
|
|
19153
|
+
this.hostScopeOutcomes.clear();
|
|
18521
19154
|
const registration = this.lifecycleRegistration;
|
|
18522
19155
|
registration?.deregister();
|
|
18523
19156
|
this.lifecycleRegistration = null;
|
|
@@ -18528,11 +19161,16 @@ class SubcTransportPool {
|
|
|
18528
19161
|
detached.push(owner);
|
|
18529
19162
|
}
|
|
18530
19163
|
this.rootIndex.clear();
|
|
19164
|
+
this.sessionIndex.clear();
|
|
18531
19165
|
this.transports.clear();
|
|
18532
19166
|
const client = this.client;
|
|
18533
19167
|
this.client = null;
|
|
18534
19168
|
const pending = [...this.pendingRootCleanups];
|
|
18535
|
-
await Promise.allSettled([
|
|
19169
|
+
await Promise.allSettled([
|
|
19170
|
+
...detached.map((owner) => this.cleanupDetached(owner)),
|
|
19171
|
+
...pending,
|
|
19172
|
+
...scopeLookups
|
|
19173
|
+
]);
|
|
18536
19174
|
if (client) {
|
|
18537
19175
|
try {
|
|
18538
19176
|
client.close();
|
|
@@ -18546,6 +19184,13 @@ class SubcTransportPool {
|
|
|
18546
19184
|
session: session && session.length > 0 ? session : DEFAULT_SESSION_ID
|
|
18547
19185
|
};
|
|
18548
19186
|
const owner = this.detachSession(identityKey(identity), "session_closed");
|
|
19187
|
+
if (!this.sessionIndex.has(identity.session)) {
|
|
19188
|
+
const state = this.hostScopeCache.get(identity.session);
|
|
19189
|
+
if (state)
|
|
19190
|
+
this.cancelHostScopeRetry(state);
|
|
19191
|
+
this.hostScopeCache.delete(identity.session);
|
|
19192
|
+
this.hostScopeRefusals.delete(identity.session);
|
|
19193
|
+
}
|
|
18549
19194
|
if (owner)
|
|
18550
19195
|
await this.cleanupDetached(owner);
|
|
18551
19196
|
}
|
|
@@ -18586,6 +19231,8 @@ var BASH_TRANSPORT_DISPOSITION = "The transport to the AFT daemon was interrupte
|
|
|
18586
19231
|
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.";
|
|
18587
19232
|
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.";
|
|
18588
19233
|
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.";
|
|
19234
|
+
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.";
|
|
19235
|
+
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.";
|
|
18589
19236
|
function isRouteClosedMidCallError(error) {
|
|
18590
19237
|
return error instanceof SubcRouteClosedMidCallError || isLocalRouteCloseRejection(error);
|
|
18591
19238
|
}
|
|
@@ -18647,6 +19294,8 @@ function classifyBashHostFallbackError(error) {
|
|
|
18647
19294
|
return;
|
|
18648
19295
|
if (isRouteClosedMidCallError(error))
|
|
18649
19296
|
return;
|
|
19297
|
+
if (isSubcFrameReadTimeoutError(error))
|
|
19298
|
+
return;
|
|
18650
19299
|
if (error instanceof SubcTransportShuttingDownError)
|
|
18651
19300
|
return "transport down";
|
|
18652
19301
|
if (isSubcRouteTornDownError(error))
|
|
@@ -18670,6 +19319,14 @@ function isTransportClassError(error) {
|
|
|
18670
19319
|
function adaptToolError(command, error) {
|
|
18671
19320
|
if (!(error instanceof Error))
|
|
18672
19321
|
return error;
|
|
19322
|
+
if (isSubcFrameReadTimeoutError(error)) {
|
|
19323
|
+
const typedError = error instanceof SubcFrameReadTimeoutError ? error : new SubcFrameReadTimeoutError(error);
|
|
19324
|
+
const disposition = command === "bash" ? BASH_FRAME_READ_TIMEOUT_DISPOSITION : SUBC_FRAME_READ_TIMEOUT_DISPOSITION;
|
|
19325
|
+
if (typedError.message.includes(disposition))
|
|
19326
|
+
return typedError;
|
|
19327
|
+
typedError.message = typedError.message ? `${typedError.message} ${disposition}` : disposition;
|
|
19328
|
+
return typedError;
|
|
19329
|
+
}
|
|
18673
19330
|
if (error instanceof BridgeTransportUnknownOutcomeError) {
|
|
18674
19331
|
if (error.message.includes(BRIDGE_TRANSPORT_UNKNOWN_OUTCOME_DISPOSITION))
|
|
18675
19332
|
return error;
|
|
@@ -18751,7 +19408,12 @@ var RETIRED_PATHS = [
|
|
|
18751
19408
|
["semantic_search", "indexes.semantic"],
|
|
18752
19409
|
["experimental_semantic_search", "indexes.semantic"],
|
|
18753
19410
|
["callgraph_store", "indexes.callgraph"],
|
|
18754
|
-
["github.enabled", "github.read,github.write,github.shim"]
|
|
19411
|
+
["github.enabled", "github.read,github.write,github.shim"],
|
|
19412
|
+
["experimental_lsp_ty", "experimental.lsp_ty"],
|
|
19413
|
+
["experimental_bash_rewrite", "bash.rewrite"],
|
|
19414
|
+
["experimental_bash_compress", "bash.compress"],
|
|
19415
|
+
["experimental_bash_background", "bash.background"],
|
|
19416
|
+
["experimental.bash", "bash"]
|
|
18755
19417
|
];
|
|
18756
19418
|
var INDEX_INPUTS = [
|
|
18757
19419
|
["trigram", "search_index", "experimental_search_index"],
|
|
@@ -18883,10 +19545,68 @@ function translateInspectLspPaths(map, prefix, out) {
|
|
|
18883
19545
|
recordRetired(out, prefix, `inspect.${leaf}`);
|
|
18884
19546
|
}
|
|
18885
19547
|
}
|
|
19548
|
+
function translateExperimentalPaths(map, prefix, out) {
|
|
19549
|
+
for (const [oldKey, leaf] of [
|
|
19550
|
+
["experimental_lsp_ty", "lsp_ty"],
|
|
19551
|
+
["experimental_bash_rewrite", "rewrite"],
|
|
19552
|
+
["experimental_bash_compress", "compress"],
|
|
19553
|
+
["experimental_bash_background", "background"]
|
|
19554
|
+
]) {
|
|
19555
|
+
if (!Object.hasOwn(map, oldKey))
|
|
19556
|
+
continue;
|
|
19557
|
+
recordRetired(out, prefix, oldKey);
|
|
19558
|
+
const value = map[oldKey];
|
|
19559
|
+
delete map[oldKey];
|
|
19560
|
+
if (!isRecord2(map.experimental))
|
|
19561
|
+
map.experimental = {};
|
|
19562
|
+
const experimental = map.experimental;
|
|
19563
|
+
let destination = experimental;
|
|
19564
|
+
if (leaf !== "lsp_ty") {
|
|
19565
|
+
if (!isRecord2(experimental.bash))
|
|
19566
|
+
experimental.bash = {};
|
|
19567
|
+
destination = experimental.bash;
|
|
19568
|
+
}
|
|
19569
|
+
if (Object.hasOwn(destination, leaf)) {
|
|
19570
|
+
superseded(out, oldKey, `${oldKey} is ignored because experimental.${leaf === "lsp_ty" ? leaf : `bash.${leaf}`} is already set`);
|
|
19571
|
+
} else {
|
|
19572
|
+
destination[leaf] = value;
|
|
19573
|
+
}
|
|
19574
|
+
}
|
|
19575
|
+
if (!isRecord2(map.experimental) || !Object.hasOwn(map.experimental, "bash"))
|
|
19576
|
+
return;
|
|
19577
|
+
const experimental = map.experimental;
|
|
19578
|
+
const legacy = experimental.bash;
|
|
19579
|
+
if (isRecord2(legacy)) {
|
|
19580
|
+
if (!["rewrite", "compress", "background"].some((leaf) => Object.hasOwn(legacy, leaf)))
|
|
19581
|
+
return;
|
|
19582
|
+
for (const leaf of Object.keys(legacy))
|
|
19583
|
+
recordRetired(out, prefix, `experimental.bash.${leaf}`);
|
|
19584
|
+
if (Object.hasOwn(map, "bash")) {
|
|
19585
|
+
superseded(out, "experimental.bash", 'experimental.bash is ignored because top-level "bash" is already set');
|
|
19586
|
+
} else {
|
|
19587
|
+
const bash = {
|
|
19588
|
+
rewrite: legacy.rewrite === true,
|
|
19589
|
+
compress: legacy.compress === true,
|
|
19590
|
+
background: legacy.background === true
|
|
19591
|
+
};
|
|
19592
|
+
for (const leaf of ["long_running_reminder_enabled", "long_running_reminder_interval_ms"]) {
|
|
19593
|
+
if (Object.hasOwn(legacy, leaf))
|
|
19594
|
+
bash[leaf] = legacy[leaf];
|
|
19595
|
+
}
|
|
19596
|
+
map.bash = bash;
|
|
19597
|
+
}
|
|
19598
|
+
} else {
|
|
19599
|
+
recordRetired(out, prefix, "experimental.bash");
|
|
19600
|
+
}
|
|
19601
|
+
delete experimental.bash;
|
|
19602
|
+
if (Object.keys(experimental).length === 0)
|
|
19603
|
+
delete map.experimental;
|
|
19604
|
+
}
|
|
18886
19605
|
function translateBlock(map, userBase, blockLabel, out) {
|
|
18887
19606
|
const prefix = blockLabel === "base" ? "" : `${blockLabel}.`;
|
|
18888
19607
|
translateGithubAliases(map, prefix, out);
|
|
18889
19608
|
translateInspectLspPaths(map, prefix, out);
|
|
19609
|
+
translateExperimentalPaths(map, prefix, out);
|
|
18890
19610
|
let explicitList;
|
|
18891
19611
|
const rawList = map.disabled_tools;
|
|
18892
19612
|
if (Array.isArray(rawList) && rawList.every((entry) => typeof entry === "string")) {
|
|
@@ -18899,6 +19619,8 @@ function translateBlock(map, userBase, blockLabel, out) {
|
|
|
18899
19619
|
});
|
|
18900
19620
|
}
|
|
18901
19621
|
for (const [path] of RETIRED_PATHS) {
|
|
19622
|
+
if (path === "experimental.bash")
|
|
19623
|
+
continue;
|
|
18902
19624
|
if (hasPath(map, path))
|
|
18903
19625
|
recordRetired(out, prefix, path);
|
|
18904
19626
|
}
|
|
@@ -19332,11 +20054,13 @@ class BridgePool {
|
|
|
19332
20054
|
return alive;
|
|
19333
20055
|
}
|
|
19334
20056
|
getBridge(projectRoot) {
|
|
20057
|
+
return this.getBridgeForCanonicalRoot(asCanonicalRootPath(normalizeKey(projectRoot)));
|
|
20058
|
+
}
|
|
20059
|
+
getBridgeForCanonicalRoot(key) {
|
|
19335
20060
|
if (this.shutdownCalled) {
|
|
19336
20061
|
this.shutdownCalled = false;
|
|
19337
20062
|
this.startCleanupTimer();
|
|
19338
20063
|
}
|
|
19339
|
-
const key = normalizeKey(projectRoot);
|
|
19340
20064
|
const existing = this.bridges.get(key);
|
|
19341
20065
|
if (existing) {
|
|
19342
20066
|
existing.lastUsed = Date.now();
|
|
@@ -19542,7 +20266,9 @@ class RevivableTransportPool {
|
|
|
19542
20266
|
this.activePool = initialPool;
|
|
19543
20267
|
}
|
|
19544
20268
|
getBridge(projectRoot) {
|
|
19545
|
-
|
|
20269
|
+
return this.getBridgeForCanonicalRoot(asCanonicalRootPath(canonicalizeProjectRoot(projectRoot)));
|
|
20270
|
+
}
|
|
20271
|
+
getBridgeForCanonicalRoot(key) {
|
|
19546
20272
|
let transport = this.transports.get(key);
|
|
19547
20273
|
if (!transport) {
|
|
19548
20274
|
transport = new RevivableProjectTransport(this, key);
|
|
@@ -19577,7 +20303,7 @@ class RevivableTransportPool {
|
|
|
19577
20303
|
return transport;
|
|
19578
20304
|
}
|
|
19579
20305
|
async toolCall(projectRoot, runtime, name, rawArgs = {}, options) {
|
|
19580
|
-
return this.
|
|
20306
|
+
return this.toolCallForCanonicalRoot(asCanonicalRootPath(canonicalizeProjectRoot(projectRoot)), runtime.sessionID, name, rawArgs, options);
|
|
19581
20307
|
}
|
|
19582
20308
|
setConfigureOverride(key, value) {
|
|
19583
20309
|
if (key === "edit_slot_survives") {
|
|
@@ -19717,14 +20443,22 @@ class RevivableTransportPool {
|
|
|
19717
20443
|
return this.activePool?.getActiveBridgeForRoot(projectRoot) ?? null;
|
|
19718
20444
|
}
|
|
19719
20445
|
async send(projectRoot, command, params, options) {
|
|
20446
|
+
return this.sendForCanonicalRoot(asCanonicalRootPath(canonicalizeProjectRoot(projectRoot)), command, params, options);
|
|
20447
|
+
}
|
|
20448
|
+
async sendForCanonicalRoot(projectRoot, command, params, options) {
|
|
20449
|
+
this.getBridgeForCanonicalRoot(projectRoot);
|
|
19720
20450
|
const pool = await this.ensureActivePool();
|
|
19721
|
-
const bridge = pool.getBridge(projectRoot);
|
|
20451
|
+
const bridge = pool.getBridgeForCanonicalRoot?.(projectRoot) ?? pool.getBridge(projectRoot);
|
|
19722
20452
|
this.transports.get(projectRoot)?.refreshStatusSubscription(bridge);
|
|
19723
20453
|
return bridge.send(command, params, options);
|
|
19724
20454
|
}
|
|
19725
20455
|
async toolCallOnProject(projectRoot, sessionId, name, rawArgs, options) {
|
|
20456
|
+
return this.toolCallForCanonicalRoot(asCanonicalRootPath(canonicalizeProjectRoot(projectRoot)), sessionId, name, rawArgs, options);
|
|
20457
|
+
}
|
|
20458
|
+
async toolCallForCanonicalRoot(projectRoot, sessionId, name, rawArgs, options) {
|
|
20459
|
+
this.getBridgeForCanonicalRoot(projectRoot);
|
|
19726
20460
|
const pool = await this.ensureActivePool();
|
|
19727
|
-
const bridge = pool.getBridge(projectRoot);
|
|
20461
|
+
const bridge = pool.getBridgeForCanonicalRoot?.(projectRoot) ?? pool.getBridge(projectRoot);
|
|
19728
20462
|
this.transports.get(projectRoot)?.refreshStatusSubscription(bridge);
|
|
19729
20463
|
return bridge.toolCall(sessionId, name, rawArgs, options);
|
|
19730
20464
|
}
|
|
@@ -19802,7 +20536,7 @@ function satisfyLifecycleDemand(wrapper, projectRoot) {
|
|
|
19802
20536
|
const activePool = wrapper.activePool;
|
|
19803
20537
|
if (activePool.isShutdown() || !(activePool instanceof SubcTransportPool))
|
|
19804
20538
|
return;
|
|
19805
|
-
activePool.
|
|
20539
|
+
activePool.getBridgeForCanonicalRoot(asCanonicalRootPath(projectRoot));
|
|
19806
20540
|
}
|
|
19807
20541
|
async function createAftTransportPool(opts) {
|
|
19808
20542
|
let binaryPath = opts.binaryPath;
|
|
@@ -19837,8 +20571,8 @@ async function createAftTransportPool(opts) {
|
|
|
19837
20571
|
});
|
|
19838
20572
|
if (reapingEnabled) {
|
|
19839
20573
|
const owner = wrapper;
|
|
19840
|
-
const getBridge = owner.
|
|
19841
|
-
owner.
|
|
20574
|
+
const getBridge = owner.getBridgeForCanonicalRoot.bind(owner);
|
|
20575
|
+
owner.getBridgeForCanonicalRoot = (projectRoot) => {
|
|
19842
20576
|
satisfyLifecycleDemand(owner, projectRoot);
|
|
19843
20577
|
return getBridge(projectRoot);
|
|
19844
20578
|
};
|
|
@@ -19864,6 +20598,7 @@ async function createConcreteAftTransportPool(opts) {
|
|
|
19864
20598
|
throw new Error(missing);
|
|
19865
20599
|
}
|
|
19866
20600
|
return new SubcTransportPool({
|
|
20601
|
+
hostScopes: opts.harness === "opencode" || opts.harness === "pi",
|
|
19867
20602
|
connectionFile,
|
|
19868
20603
|
harness: opts.harness,
|
|
19869
20604
|
consumerIdentity: opts.subcConsumerIdentity,
|
|
@@ -20125,7 +20860,7 @@ import { homedir as homedir9, tmpdir as tmpdir3 } from "node:os";
|
|
|
20125
20860
|
import { basename as basename2, dirname as dirname6, join as join12, resolve as resolve8 } from "node:path";
|
|
20126
20861
|
|
|
20127
20862
|
// ../aft-bridge/dist/paths.js
|
|
20128
|
-
import { existsSync as existsSync5, readFileSync as readFileSync5, renameSync as renameSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
20863
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5, realpathSync as realpathSync3, renameSync as renameSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
20129
20864
|
import { homedir as homedir7 } from "node:os";
|
|
20130
20865
|
import { dirname as dirname4, isAbsolute as isAbsolute5, join as join10, resolve as resolve7 } from "node:path";
|
|
20131
20866
|
function homeDir3() {
|
|
@@ -20160,6 +20895,23 @@ function resolveCortexKitUserConfigPath() {
|
|
|
20160
20895
|
function resolveCortexKitProjectConfigPath(projectDirectory) {
|
|
20161
20896
|
return join10(projectDirectory, ".cortexkit", "aft.jsonc");
|
|
20162
20897
|
}
|
|
20898
|
+
function resolveProjectConfigReadPath(projectDirectory, operatingHarness) {
|
|
20899
|
+
const canonical = resolveCortexKitProjectConfigPath(projectDirectory);
|
|
20900
|
+
if (existsSync5(canonical))
|
|
20901
|
+
return canonical;
|
|
20902
|
+
const sources = resolveLegacyAftConfigSources(projectDirectory).project;
|
|
20903
|
+
const ordered = operatingHarness ? [
|
|
20904
|
+
...sources.filter((source) => source.harness === operatingHarness),
|
|
20905
|
+
...sources.filter((source) => source.harness !== operatingHarness)
|
|
20906
|
+
] : sources;
|
|
20907
|
+
return ordered.find((source) => existsSync5(source.path))?.path ?? canonical;
|
|
20908
|
+
}
|
|
20909
|
+
function legacyProjectConfigLocationNotice(projectDirectory, path) {
|
|
20910
|
+
const canonical = resolveCortexKitProjectConfigPath(projectDirectory);
|
|
20911
|
+
if (path === canonical)
|
|
20912
|
+
return null;
|
|
20913
|
+
return `Legacy project config ${path} is read in place; move it to ${canonical} yourself, and use aft doctor --fix to update retired keys.`;
|
|
20914
|
+
}
|
|
20163
20915
|
function resolveCortexKitConfigPaths(projectDirectory) {
|
|
20164
20916
|
return {
|
|
20165
20917
|
userConfigPath: resolveCortexKitUserConfigPath(),
|
|
@@ -20167,15 +20919,27 @@ function resolveCortexKitConfigPaths(projectDirectory) {
|
|
|
20167
20919
|
};
|
|
20168
20920
|
}
|
|
20169
20921
|
function resolveLegacyAftConfigSources(projectDirectory) {
|
|
20922
|
+
const project = [
|
|
20923
|
+
...legacySources(join10(projectDirectory, ".opencode", "aft"), "OpenCode project", "opencode"),
|
|
20924
|
+
...legacySources(join10(projectDirectory, ".pi", "aft"), "Pi project", "pi")
|
|
20925
|
+
];
|
|
20926
|
+
const identity = (path) => {
|
|
20927
|
+
try {
|
|
20928
|
+
return realpathSync3(path);
|
|
20929
|
+
} catch {
|
|
20930
|
+
return resolve7(path);
|
|
20931
|
+
}
|
|
20932
|
+
};
|
|
20933
|
+
const protectedPaths = new Set([
|
|
20934
|
+
resolveCortexKitProjectConfigPath(projectDirectory),
|
|
20935
|
+
...project.map((source) => source.path)
|
|
20936
|
+
].map(identity));
|
|
20170
20937
|
return {
|
|
20171
20938
|
user: [
|
|
20172
20939
|
...legacySources(join10(legacyOpenCodeConfigDir(), "aft"), "OpenCode user", "opencode"),
|
|
20173
20940
|
...legacySources(join10(legacyPiAgentDir(), "aft"), "Pi user", "pi")
|
|
20174
|
-
],
|
|
20175
|
-
project
|
|
20176
|
-
...legacySources(join10(projectDirectory, ".opencode", "aft"), "OpenCode project", "opencode"),
|
|
20177
|
-
...legacySources(join10(projectDirectory, ".pi", "aft"), "Pi project", "pi")
|
|
20178
|
-
]
|
|
20941
|
+
].filter((source) => !protectedPaths.has(identity(source.path))),
|
|
20942
|
+
project
|
|
20179
20943
|
};
|
|
20180
20944
|
}
|
|
20181
20945
|
function resolveHarnessStoragePath(storageRoot, harness, ...segments) {
|
|
@@ -21363,7 +22127,7 @@ function isNpmAvailable(deps = defaultDeps()) {
|
|
|
21363
22127
|
return resolveNpm(deps) !== null;
|
|
21364
22128
|
}
|
|
21365
22129
|
// ../aft-bridge/dist/onnx-probe.js
|
|
21366
|
-
import { closeSync as closeSync7, existsSync as existsSync8, openSync as openSync6, readSync as readSync5, realpathSync as
|
|
22130
|
+
import { closeSync as closeSync7, existsSync as existsSync8, openSync as openSync6, readSync as readSync5, realpathSync as realpathSync4 } from "node:fs";
|
|
21367
22131
|
var MH_MAGIC = 4277009102;
|
|
21368
22132
|
var MH_MAGIC_64 = 4277009103;
|
|
21369
22133
|
var FAT_MAGIC = 3405691582;
|
|
@@ -21450,7 +22214,7 @@ function isUncheckableDependency(path) {
|
|
|
21450
22214
|
function probeOnnxRuntimeLoadable(libPath, arch = process.arch) {
|
|
21451
22215
|
let fd;
|
|
21452
22216
|
try {
|
|
21453
|
-
fd = openSync6(
|
|
22217
|
+
fd = openSync6(realpathSync4(libPath), "r");
|
|
21454
22218
|
} catch (err) {
|
|
21455
22219
|
return {
|
|
21456
22220
|
loadable: false,
|
|
@@ -21489,7 +22253,7 @@ function probeOnnxRuntimeLoadable(libPath, arch = process.arch) {
|
|
|
21489
22253
|
}
|
|
21490
22254
|
// ../aft-bridge/dist/onnx-runtime.js
|
|
21491
22255
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
21492
|
-
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
|
|
22256
|
+
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";
|
|
21493
22257
|
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";
|
|
21494
22258
|
import { Readable as Readable2 } from "node:stream";
|
|
21495
22259
|
import { pipeline as pipeline2 } from "node:stream/promises";
|
|
@@ -21773,7 +22537,7 @@ function detectOnnxVersion(libDir, libName) {
|
|
|
21773
22537
|
try {
|
|
21774
22538
|
const entries = readdirSync3(libDir);
|
|
21775
22539
|
try {
|
|
21776
|
-
const resolved = parseOnnxVersionFromPath(
|
|
22540
|
+
const resolved = parseOnnxVersionFromPath(realpathSync5(join16(libDir, libName)));
|
|
21777
22541
|
if (resolved && resolved !== INVALID_ORT_VERSION)
|
|
21778
22542
|
return resolved;
|
|
21779
22543
|
} catch {}
|
|
@@ -21790,7 +22554,7 @@ function detectOnnxVersion(libDir, libName) {
|
|
|
21790
22554
|
const base = join16(libDir, libName);
|
|
21791
22555
|
if (existsSync10(base)) {
|
|
21792
22556
|
try {
|
|
21793
|
-
const real =
|
|
22557
|
+
const real = realpathSync5(base);
|
|
21794
22558
|
const version = parseOnnxVersionFromPath(real) ?? parseOnnxVersionFromDirectoryPath(real);
|
|
21795
22559
|
if (version)
|
|
21796
22560
|
return version;
|
|
@@ -21982,7 +22746,7 @@ async function downloadFileWithCap(url, destPath) {
|
|
|
21982
22746
|
}
|
|
21983
22747
|
}
|
|
21984
22748
|
function validateExtractedTree(stagingRoot) {
|
|
21985
|
-
const realRoot =
|
|
22749
|
+
const realRoot = realpathSync5(stagingRoot);
|
|
21986
22750
|
let totalBytes = 0;
|
|
21987
22751
|
const walk = (dir) => {
|
|
21988
22752
|
const entries = readdirSync3(dir);
|
|
@@ -22131,7 +22895,7 @@ function copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks, c
|
|
|
22131
22895
|
log(`ORT extract: failed to copy optional ${libFile}: ${copyErr}`);
|
|
22132
22896
|
}
|
|
22133
22897
|
}
|
|
22134
|
-
const targetRoot =
|
|
22898
|
+
const targetRoot = realpathSync5(targetDir);
|
|
22135
22899
|
for (const link of symlinks) {
|
|
22136
22900
|
const dst = join16(targetDir, link.name);
|
|
22137
22901
|
try {
|
|
@@ -22951,7 +23715,7 @@ function formatSemanticIndexStatus(status, stage, error, backend) {
|
|
|
22951
23715
|
return status;
|
|
22952
23716
|
}
|
|
22953
23717
|
// src/config.ts
|
|
22954
|
-
import { existsSync as existsSync11, readFileSync as readFileSync10,
|
|
23718
|
+
import { existsSync as existsSync11, readFileSync as readFileSync10, statSync as statSync10 } from "node:fs";
|
|
22955
23719
|
import { homedir as homedir12 } from "node:os";
|
|
22956
23720
|
import { isAbsolute as isAbsolute9, parse as parsePath, resolve as resolvePath } from "node:path";
|
|
22957
23721
|
var import_comment_json = __toESM(require_src2(), 1);
|
|
@@ -28173,155 +28937,6 @@ function resolveBashConfig(config) {
|
|
|
28173
28937
|
}
|
|
28174
28938
|
return { ...base, enabled: true, rewrite: true, compress: true, background: true };
|
|
28175
28939
|
}
|
|
28176
|
-
var CONFIG_MIGRATIONS = [
|
|
28177
|
-
{ oldKey: "experimental_lsp_ty", newPath: ["experimental", "lsp_ty"] },
|
|
28178
|
-
{ oldKey: "experimental_bash_rewrite", newPath: ["experimental", "bash", "rewrite"] },
|
|
28179
|
-
{ oldKey: "experimental_bash_compress", newPath: ["experimental", "bash", "compress"] },
|
|
28180
|
-
{ oldKey: "experimental_bash_background", newPath: ["experimental", "bash", "background"] }
|
|
28181
|
-
];
|
|
28182
|
-
function isWritableMigrationError(errorValue) {
|
|
28183
|
-
const code = errorValue?.code;
|
|
28184
|
-
return code === "EROFS" || code === "EACCES" || code === "EPERM";
|
|
28185
|
-
}
|
|
28186
|
-
function extractCommentsForPreservation(content) {
|
|
28187
|
-
const comments = [];
|
|
28188
|
-
const linePattern = /\/\/[^\n]*/g;
|
|
28189
|
-
for (const match of content.match(linePattern) ?? []) {
|
|
28190
|
-
comments.push(match.trim());
|
|
28191
|
-
}
|
|
28192
|
-
const blockPattern = /\/\*[\s\S]*?\*\//g;
|
|
28193
|
-
for (const match of content.match(blockPattern) ?? []) {
|
|
28194
|
-
comments.push(match.replace(/\s+/g, " ").trim());
|
|
28195
|
-
}
|
|
28196
|
-
return comments;
|
|
28197
|
-
}
|
|
28198
|
-
function ensureRecordAtPath(root, path) {
|
|
28199
|
-
let current = root;
|
|
28200
|
-
for (const segment of path) {
|
|
28201
|
-
const existing = current[segment];
|
|
28202
|
-
if (!existing || typeof existing !== "object" || Array.isArray(existing)) {
|
|
28203
|
-
current[segment] = {};
|
|
28204
|
-
}
|
|
28205
|
-
current = current[segment];
|
|
28206
|
-
}
|
|
28207
|
-
return current;
|
|
28208
|
-
}
|
|
28209
|
-
function hasPath2(root, path) {
|
|
28210
|
-
let current = root;
|
|
28211
|
-
for (const segment of path) {
|
|
28212
|
-
if (!current || typeof current !== "object" || Array.isArray(current))
|
|
28213
|
-
return false;
|
|
28214
|
-
const record2 = current;
|
|
28215
|
-
if (!Object.hasOwn(record2, segment))
|
|
28216
|
-
return false;
|
|
28217
|
-
current = record2[segment];
|
|
28218
|
-
}
|
|
28219
|
-
return true;
|
|
28220
|
-
}
|
|
28221
|
-
function setPath(root, path, value) {
|
|
28222
|
-
const parent = ensureRecordAtPath(root, path.slice(0, -1));
|
|
28223
|
-
parent[path[path.length - 1]] = value;
|
|
28224
|
-
}
|
|
28225
|
-
function migrateRawConfig(rawConfig, configPath, logger) {
|
|
28226
|
-
const oldKeys = [];
|
|
28227
|
-
for (const migration of CONFIG_MIGRATIONS) {
|
|
28228
|
-
if (!Object.hasOwn(rawConfig, migration.oldKey))
|
|
28229
|
-
continue;
|
|
28230
|
-
if (hasPath2(rawConfig, migration.newPath)) {
|
|
28231
|
-
logger?.warn(`Config migration conflict at ${configPath}: ${migration.oldKey} ignored because ${migration.newPath.join(".")} is already set`);
|
|
28232
|
-
} else {
|
|
28233
|
-
setPath(rawConfig, migration.newPath, rawConfig[migration.oldKey]);
|
|
28234
|
-
}
|
|
28235
|
-
delete rawConfig[migration.oldKey];
|
|
28236
|
-
oldKeys.push(migration.oldKey);
|
|
28237
|
-
}
|
|
28238
|
-
oldKeys.push(...migrateExperimentalBashBlock(rawConfig, configPath, logger));
|
|
28239
|
-
return oldKeys;
|
|
28240
|
-
}
|
|
28241
|
-
function migrateExperimentalBashBlock(rawConfig, configPath, logger) {
|
|
28242
|
-
const experimental = rawConfig.experimental;
|
|
28243
|
-
if (typeof experimental !== "object" || experimental === null || Array.isArray(experimental)) {
|
|
28244
|
-
return [];
|
|
28245
|
-
}
|
|
28246
|
-
const expRecord = experimental;
|
|
28247
|
-
if (!Object.hasOwn(expRecord, "bash"))
|
|
28248
|
-
return [];
|
|
28249
|
-
const legacyBash = expRecord.bash;
|
|
28250
|
-
if (typeof legacyBash !== "object" || legacyBash === null || Array.isArray(legacyBash)) {
|
|
28251
|
-
delete expRecord.bash;
|
|
28252
|
-
if (Object.keys(expRecord).length === 0)
|
|
28253
|
-
delete rawConfig.experimental;
|
|
28254
|
-
return ["experimental.bash"];
|
|
28255
|
-
}
|
|
28256
|
-
const bashRecord = legacyBash;
|
|
28257
|
-
const hasFeatureFlag = "rewrite" in bashRecord || "compress" in bashRecord || "background" in bashRecord;
|
|
28258
|
-
if (!hasFeatureFlag)
|
|
28259
|
-
return [];
|
|
28260
|
-
const movedKeys = Object.keys(bashRecord).map((k) => `experimental.bash.${k}`);
|
|
28261
|
-
if (Object.hasOwn(rawConfig, "bash")) {
|
|
28262
|
-
logger?.warn(`Config migration conflict at ${configPath}: experimental.bash dropped because top-level "bash" is already set`);
|
|
28263
|
-
} else {
|
|
28264
|
-
const migrated = {
|
|
28265
|
-
rewrite: bashRecord.rewrite === true,
|
|
28266
|
-
compress: bashRecord.compress === true,
|
|
28267
|
-
background: bashRecord.background === true
|
|
28268
|
-
};
|
|
28269
|
-
if (bashRecord.long_running_reminder_enabled !== undefined) {
|
|
28270
|
-
migrated.long_running_reminder_enabled = bashRecord.long_running_reminder_enabled;
|
|
28271
|
-
}
|
|
28272
|
-
if (bashRecord.long_running_reminder_interval_ms !== undefined) {
|
|
28273
|
-
migrated.long_running_reminder_interval_ms = bashRecord.long_running_reminder_interval_ms;
|
|
28274
|
-
}
|
|
28275
|
-
rawConfig.bash = migrated;
|
|
28276
|
-
}
|
|
28277
|
-
delete expRecord.bash;
|
|
28278
|
-
if (Object.keys(expRecord).length === 0) {
|
|
28279
|
-
delete rawConfig.experimental;
|
|
28280
|
-
}
|
|
28281
|
-
return movedKeys;
|
|
28282
|
-
}
|
|
28283
|
-
function migrateAftConfigFile2(configPath, logger = { log: log2, warn: warn2 }) {
|
|
28284
|
-
if (!existsSync11(configPath)) {
|
|
28285
|
-
return { migrated: false, oldKeys: [] };
|
|
28286
|
-
}
|
|
28287
|
-
let tmpPath = null;
|
|
28288
|
-
let oldKeys = [];
|
|
28289
|
-
try {
|
|
28290
|
-
const content = readFileSync10(configPath, "utf-8");
|
|
28291
|
-
const rawConfig = import_comment_json.parse(content);
|
|
28292
|
-
if (!rawConfig || typeof rawConfig !== "object" || Array.isArray(rawConfig)) {
|
|
28293
|
-
return { migrated: false, oldKeys: [] };
|
|
28294
|
-
}
|
|
28295
|
-
oldKeys = migrateRawConfig(rawConfig, configPath, logger);
|
|
28296
|
-
if (oldKeys.length === 0) {
|
|
28297
|
-
return { migrated: false, oldKeys: [] };
|
|
28298
|
-
}
|
|
28299
|
-
const serialized = `${import_comment_json.stringify(rawConfig, null, 2)}
|
|
28300
|
-
`;
|
|
28301
|
-
const originalComments = extractCommentsForPreservation(content);
|
|
28302
|
-
const droppedComments = originalComments.filter((comment) => !serialized.includes(comment.trim()));
|
|
28303
|
-
const nextContent = droppedComments.length > 0 ? `${droppedComments.join(`
|
|
28304
|
-
`)}
|
|
28305
|
-
${serialized}` : serialized;
|
|
28306
|
-
tmpPath = `${configPath}.tmp.${process.pid}`;
|
|
28307
|
-
writeFileSync6(tmpPath, nextContent, "utf-8");
|
|
28308
|
-
renameSync7(tmpPath, configPath);
|
|
28309
|
-
logger.log(`Migrated config at ${configPath}: removed ${oldKeys.join(", ")}`);
|
|
28310
|
-
return { migrated: true, oldKeys };
|
|
28311
|
-
} catch (err) {
|
|
28312
|
-
if (tmpPath) {
|
|
28313
|
-
try {
|
|
28314
|
-
unlinkSync5(tmpPath);
|
|
28315
|
-
} catch {}
|
|
28316
|
-
}
|
|
28317
|
-
if (isWritableMigrationError(err)) {
|
|
28318
|
-
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
28319
|
-
logger.warn(`Config migration could not write ${configPath} (${errorMsg}); using migrated config in memory`);
|
|
28320
|
-
return { migrated: oldKeys.length > 0, oldKeys };
|
|
28321
|
-
}
|
|
28322
|
-
return { migrated: false, oldKeys: [] };
|
|
28323
|
-
}
|
|
28324
|
-
}
|
|
28325
28940
|
function parseConfigPartially(rawConfig) {
|
|
28326
28941
|
let configForParsing = rawConfig;
|
|
28327
28942
|
if (Object.hasOwn(rawConfig, "edit_mode") && rawConfig.edit_mode !== "default" && rawConfig.edit_mode !== "hashline") {
|
|
@@ -28396,7 +29011,6 @@ function loadConfigFromPath(configPath, tier) {
|
|
|
28396
29011
|
configLoadSources.push(configPath);
|
|
28397
29012
|
configLoadTexts.set(configPath, content);
|
|
28398
29013
|
const rawConfig = import_comment_json.parse(content);
|
|
28399
|
-
migrateRawConfig(rawConfig, configPath, { log: log2, warn: warn2 });
|
|
28400
29014
|
cleanConfig = stripJsoncSymbols(rawConfig);
|
|
28401
29015
|
} catch (err) {
|
|
28402
29016
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
@@ -28756,21 +29370,14 @@ function migrateAftConfigLocations(projectDirectory, logger = { log: log2, warn:
|
|
|
28756
29370
|
legacySources: legacy.user,
|
|
28757
29371
|
operatingHarness: "opencode",
|
|
28758
29372
|
logger
|
|
28759
|
-
}),
|
|
28760
|
-
migrateAftConfigFile({
|
|
28761
|
-
scope: "project",
|
|
28762
|
-
targetPath: paths.projectConfigPath,
|
|
28763
|
-
legacySources: legacy.project,
|
|
28764
|
-
operatingHarness: "opencode",
|
|
28765
|
-
logger
|
|
28766
29373
|
})
|
|
28767
29374
|
];
|
|
28768
29375
|
}
|
|
28769
29376
|
function resolveAftConfigPaths(projectDirectory) {
|
|
28770
|
-
|
|
28771
|
-
|
|
28772
|
-
|
|
28773
|
-
|
|
29377
|
+
return {
|
|
29378
|
+
...resolveCortexKitConfigPaths(projectDirectory),
|
|
29379
|
+
projectConfigPath: resolveProjectConfigReadPath(projectDirectory, ACTIVE_HARNESS)
|
|
29380
|
+
};
|
|
28774
29381
|
}
|
|
28775
29382
|
function resolveOpenCodeRegistrationRoot(directory, worktree) {
|
|
28776
29383
|
if (!worktree)
|
|
@@ -28798,6 +29405,15 @@ function loadAftConfig(projectDirectory) {
|
|
|
28798
29405
|
configLoadNotices = [];
|
|
28799
29406
|
semanticInputSupplied = false;
|
|
28800
29407
|
const { userConfigPath, projectConfigPath } = resolveAftConfigPaths(projectDirectory);
|
|
29408
|
+
const locationNotice = legacyProjectConfigLocationNotice(projectDirectory, projectConfigPath);
|
|
29409
|
+
if (locationNotice) {
|
|
29410
|
+
log2(locationNotice);
|
|
29411
|
+
configLoadNotices.push({
|
|
29412
|
+
configPath: projectConfigPath,
|
|
29413
|
+
digest: "legacy-project-config-location",
|
|
29414
|
+
message: locationNotice
|
|
29415
|
+
});
|
|
29416
|
+
}
|
|
28801
29417
|
let config = loadConfigFromPath(userConfigPath, "user") ?? {
|
|
28802
29418
|
disabled_tools: [...DEFAULT_DISABLED_TOOLS]
|
|
28803
29419
|
};
|
|
@@ -40726,6 +41342,24 @@ function markExplicitControl(sessionID, taskId, trackOutstanding = true) {
|
|
|
40726
41342
|
function unmarkExplicitControl(sessionID, taskId) {
|
|
40727
41343
|
stateFor(sessionID).explicitControlTasks.delete(taskId);
|
|
40728
41344
|
}
|
|
41345
|
+
function holdBgCompletion(sessionID, completion, stopped = false) {
|
|
41346
|
+
const state = stateFor(sessionID);
|
|
41347
|
+
if (!stopped && state.consumedTaskIds.has(completion.task_id))
|
|
41348
|
+
return;
|
|
41349
|
+
state.heldWorkerCompletions = true;
|
|
41350
|
+
state.explicitControlTasks.delete(completion.task_id);
|
|
41351
|
+
state.outstandingTaskIds.delete(completion.task_id);
|
|
41352
|
+
if (!state.pendingCompletions.some((pending) => pending.task_id === completion.task_id)) {
|
|
41353
|
+
state.pendingCompletions.push(completion);
|
|
41354
|
+
}
|
|
41355
|
+
}
|
|
41356
|
+
function hasHeldBgCompletions(sessionID) {
|
|
41357
|
+
const state = stateFor(sessionID);
|
|
41358
|
+
return state.heldWorkerCompletions === true && (state.pendingCompletions.length > 0 || state.pendingPatternMatches.length > 0);
|
|
41359
|
+
}
|
|
41360
|
+
function markHeldBgCompletions(sessionID) {
|
|
41361
|
+
stateFor(sessionID).heldWorkerCompletions = true;
|
|
41362
|
+
}
|
|
40729
41363
|
function queuePendingPatternMatch(state, entry) {
|
|
40730
41364
|
const normalized = entry.reason ? entry : { ...entry, reason: "pattern_match" };
|
|
40731
41365
|
const existingIdx = state.pendingPatternMatches.findIndex((match) => match.task_id === normalized.task_id);
|
|
@@ -40811,7 +41445,7 @@ async function appendInTurnBgCompletions(drainContext, output) {
|
|
|
40811
41445
|
return;
|
|
40812
41446
|
const state = stateFor(drainContext.sessionID);
|
|
40813
41447
|
if (state.outstandingTaskIds.size === 0 && state.pendingCompletions.length === 0 && state.pendingLongRunning.length === 0 && state.pendingPatternMatches.length === 0) {
|
|
40814
|
-
if (!idleDrainMayFindWork(state))
|
|
41448
|
+
if (!idleDrainMayFindWork(state, drainContext))
|
|
40815
41449
|
return;
|
|
40816
41450
|
await drainCompletions(drainContext);
|
|
40817
41451
|
if (state.outstandingTaskIds.size === 0 && state.pendingCompletions.length === 0 && state.pendingLongRunning.length === 0 && state.pendingPatternMatches.length === 0) {
|
|
@@ -40837,6 +41471,7 @@ async function appendInTurnBgCompletions(drainContext, output) {
|
|
|
40837
41471
|
reminder_chars: reminder.length
|
|
40838
41472
|
});
|
|
40839
41473
|
state.pendingCompletions = [];
|
|
41474
|
+
state.heldWorkerCompletions = false;
|
|
40840
41475
|
for (const completion of deliveredCompletions) {
|
|
40841
41476
|
state.wakeDeferredTaskIds.delete(completion.task_id);
|
|
40842
41477
|
}
|
|
@@ -41190,7 +41825,8 @@ function observeOpenCodeBgNotificationEvent(event) {
|
|
|
41190
41825
|
}
|
|
41191
41826
|
async function triggerWakeIfPending(drainContext, skipDrain, includeDeferredCompletions = true, forceDrain = false, deferWhileBusy = false) {
|
|
41192
41827
|
const state = stateFor(drainContext.sessionID);
|
|
41193
|
-
|
|
41828
|
+
const hasTrackedOrPendingWork = state.outstandingTaskIds.size > 0 || state.pendingCompletions.length > 0 || state.pendingLongRunning.length > 0 || state.pendingPatternMatches.length > 0;
|
|
41829
|
+
if (!skipDrain && (forceDrain || state.outstandingTaskIds.size > 0 || !state.forcedDrainCompleted) && (forceDrain || hasTrackedOrPendingWork || idleDrainMayFindWork(state, drainContext))) {
|
|
41194
41830
|
await drainCompletions(drainContext);
|
|
41195
41831
|
}
|
|
41196
41832
|
routeExplicitControlCompletions(state);
|
|
@@ -41425,8 +42061,13 @@ function bridgeForDrain(drainContext) {
|
|
|
41425
42061
|
function isTerminalNudgeError(error) {
|
|
41426
42062
|
return error instanceof Error && error.code === "root_generation_expired";
|
|
41427
42063
|
}
|
|
41428
|
-
function idleDrainMayFindWork(state) {
|
|
41429
|
-
|
|
42064
|
+
function idleDrainMayFindWork(state, drainContext) {
|
|
42065
|
+
if (state.untrackedCompletionSinceDrain || state.deliveredAwaitingAckTaskIds.size > 0) {
|
|
42066
|
+
return true;
|
|
42067
|
+
}
|
|
42068
|
+
if (state.forcedDrainCompleted)
|
|
42069
|
+
return false;
|
|
42070
|
+
return Boolean(drainContext.resolvedBridge ?? drainContext.ctx.pool.getActiveBridgeForRoot(drainContext.directory));
|
|
41430
42071
|
}
|
|
41431
42072
|
async function drainCompletions(drainContext) {
|
|
41432
42073
|
const state = stateFor(drainContext.sessionID);
|
|
@@ -42226,7 +42867,7 @@ function resolvePathFromProjectRoot(projectRoot, target) {
|
|
|
42226
42867
|
async function resolvePathArg(ctx, runtime, target) {
|
|
42227
42868
|
return resolvePathFromProjectRoot(await resolveProjectRoot(ctx, runtime), target);
|
|
42228
42869
|
}
|
|
42229
|
-
function
|
|
42870
|
+
function checkedProjectRoot(ctx, runtime) {
|
|
42230
42871
|
const projectRoot = projectRootFor(runtime);
|
|
42231
42872
|
if (ctx.isProjectEnabled?.(projectRoot) === false) {
|
|
42232
42873
|
throw new Error(`AFT disabled by config for ${projectRoot}`);
|
|
@@ -42234,7 +42875,7 @@ function bridgeFor(ctx, runtime) {
|
|
|
42234
42875
|
if (!existsSync12(projectRoot)) {
|
|
42235
42876
|
throw new Error(`project directory no longer exists: ${projectRoot} (stale restored session?)`);
|
|
42236
42877
|
}
|
|
42237
|
-
return
|
|
42878
|
+
return projectRoot;
|
|
42238
42879
|
}
|
|
42239
42880
|
async function isWorkerRuntime(ctx, runtime) {
|
|
42240
42881
|
if (!runtime.sessionID)
|
|
@@ -42253,6 +42894,9 @@ async function callBridge(ctx, runtime, command, params = {}, options) {
|
|
|
42253
42894
|
if (workerSession) {
|
|
42254
42895
|
merged[WORKER_SESSION_FIELD] = true;
|
|
42255
42896
|
}
|
|
42897
|
+
if (ctx.hostToolNames) {
|
|
42898
|
+
merged[HOST_TOOL_NAMES_FIELD] = ctx.hostToolNames;
|
|
42899
|
+
}
|
|
42256
42900
|
const timeoutMs = timeoutForCommand(command);
|
|
42257
42901
|
const sendOptions = {
|
|
42258
42902
|
...timeoutMs !== undefined ? { timeoutMs } : {},
|
|
@@ -42263,7 +42907,9 @@ async function callBridge(ctx, runtime, command, params = {}, options) {
|
|
|
42263
42907
|
markBridgeStart();
|
|
42264
42908
|
let response;
|
|
42265
42909
|
try {
|
|
42266
|
-
|
|
42910
|
+
const root = asCanonicalRootPath(checkedProjectRoot(ctx, runtime));
|
|
42911
|
+
const opts = Object.keys(sendOptions).length > 0 ? sendOptions : undefined;
|
|
42912
|
+
response = ctx.pool.sendForCanonicalRoot ? await ctx.pool.sendForCanonicalRoot(root, command, merged, opts) : await ctx.pool.getBridge(root).send(command, merged, opts);
|
|
42267
42913
|
} catch (error) {
|
|
42268
42914
|
if (command === "bash" && isBashTransportDeadError(error))
|
|
42269
42915
|
throw error;
|
|
@@ -42285,12 +42931,15 @@ async function callToolCall(ctx, runtime, name, rawArgs = {}, options) {
|
|
|
42285
42931
|
configureWarningClient: ctx.client,
|
|
42286
42932
|
...runtime.effectAbort ? { abortSignal: runtime.effectAbort } : {},
|
|
42287
42933
|
...workerSession ? { workerSession: true } : {},
|
|
42934
|
+
...ctx.hostToolNames ? { hostToolNames: ctx.hostToolNames } : {},
|
|
42288
42935
|
...options
|
|
42289
42936
|
};
|
|
42290
42937
|
markBridgeStart();
|
|
42291
42938
|
let response;
|
|
42292
42939
|
try {
|
|
42293
|
-
|
|
42940
|
+
const root = asCanonicalRootPath(checkedProjectRoot(ctx, runtime));
|
|
42941
|
+
const opts = Object.keys(sendOptions).length > 0 ? sendOptions : undefined;
|
|
42942
|
+
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);
|
|
42294
42943
|
} catch (error) {
|
|
42295
42944
|
throw adaptToolError(name, error);
|
|
42296
42945
|
} finally {
|
|
@@ -42398,7 +43047,7 @@ async function signalBashWaitDetachForProject(pool, projectRoot, sessionID) {
|
|
|
42398
43047
|
}
|
|
42399
43048
|
|
|
42400
43049
|
// src/lsp-auto-install.ts
|
|
42401
|
-
import { existsSync as existsSync14, mkdirSync as mkdirSync6, renameSync as
|
|
43050
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync6, renameSync as renameSync8, rmSync as rmSync5, statSync as statSync12, writeFileSync as writeFileSync7 } from "node:fs";
|
|
42402
43051
|
import { join as join19 } from "node:path";
|
|
42403
43052
|
|
|
42404
43053
|
// src/lsp-cache.ts
|
|
@@ -42408,11 +43057,11 @@ import {
|
|
|
42408
43057
|
mkdirSync as mkdirSync5,
|
|
42409
43058
|
openSync as openSync8,
|
|
42410
43059
|
readFileSync as readFileSync11,
|
|
42411
|
-
renameSync as
|
|
43060
|
+
renameSync as renameSync7,
|
|
42412
43061
|
rmSync as rmSync4,
|
|
42413
43062
|
statSync as statSync11,
|
|
42414
|
-
unlinkSync as
|
|
42415
|
-
writeFileSync as
|
|
43063
|
+
unlinkSync as unlinkSync5,
|
|
43064
|
+
writeFileSync as writeFileSync6
|
|
42416
43065
|
} from "node:fs";
|
|
42417
43066
|
import { join as join17 } from "node:path";
|
|
42418
43067
|
function aftCacheBase() {
|
|
@@ -42453,7 +43102,7 @@ function writeInstalledMetaIn(installDir, version, sha256) {
|
|
|
42453
43102
|
installedAt: new Date().toISOString(),
|
|
42454
43103
|
...sha256 ? { sha256 } : {}
|
|
42455
43104
|
};
|
|
42456
|
-
|
|
43105
|
+
writeFileSync6(join17(installDir, INSTALLED_META_FILE), JSON.stringify(meta), "utf8");
|
|
42457
43106
|
} catch (err) {
|
|
42458
43107
|
log2(`[lsp-cache] failed to write installed-meta in ${installDir}: ${err}`);
|
|
42459
43108
|
}
|
|
@@ -42494,7 +43143,7 @@ function acquireInstallLock(lockKey) {
|
|
|
42494
43143
|
try {
|
|
42495
43144
|
const fd = openSync8(lock, "wx");
|
|
42496
43145
|
try {
|
|
42497
|
-
|
|
43146
|
+
writeFileSync6(fd, `${process.pid}
|
|
42498
43147
|
${new Date().toISOString()}
|
|
42499
43148
|
${token}
|
|
42500
43149
|
`);
|
|
@@ -42550,7 +43199,7 @@ ${token}
|
|
|
42550
43199
|
}
|
|
42551
43200
|
log2(`[lsp] reclaiming install lock for ${lockKey} (owner_pid=${owningPid ?? "unknown"}, alive=${ownerAlive}, age_ms=${age})`);
|
|
42552
43201
|
try {
|
|
42553
|
-
|
|
43202
|
+
unlinkSync5(lock);
|
|
42554
43203
|
} catch {}
|
|
42555
43204
|
return tryClaim();
|
|
42556
43205
|
}
|
|
@@ -42589,7 +43238,7 @@ function releaseInstallLock(lockKey, acquisitionToken) {
|
|
|
42589
43238
|
return;
|
|
42590
43239
|
}
|
|
42591
43240
|
try {
|
|
42592
|
-
|
|
43241
|
+
unlinkSync5(lock);
|
|
42593
43242
|
} catch (unlinkErr) {
|
|
42594
43243
|
const code = unlinkErr.code;
|
|
42595
43244
|
if (code !== "ENOENT") {
|
|
@@ -42664,7 +43313,7 @@ function claimLspAutoInstallPass() {
|
|
|
42664
43313
|
try {
|
|
42665
43314
|
const fd = openSync8(lock, "wx");
|
|
42666
43315
|
try {
|
|
42667
|
-
|
|
43316
|
+
writeFileSync6(fd, JSON.stringify(owner));
|
|
42668
43317
|
} finally {
|
|
42669
43318
|
closeSync9(fd);
|
|
42670
43319
|
}
|
|
@@ -42695,12 +43344,12 @@ function claimLspAutoInstallPass() {
|
|
|
42695
43344
|
return null;
|
|
42696
43345
|
}
|
|
42697
43346
|
const tempPath = `${stampPath}.tmp-${process.pid}-${owner.token}`;
|
|
42698
|
-
|
|
43347
|
+
writeFileSync6(tempPath, `${JSON.stringify({ lastRunMs: Date.now() })}
|
|
42699
43348
|
`, {
|
|
42700
43349
|
encoding: "utf8",
|
|
42701
43350
|
mode: 384
|
|
42702
43351
|
});
|
|
42703
|
-
|
|
43352
|
+
renameSync7(tempPath, stampPath);
|
|
42704
43353
|
return { release };
|
|
42705
43354
|
} catch (err) {
|
|
42706
43355
|
if (claimed) {
|
|
@@ -42737,7 +43386,7 @@ function writeVersionCheck(npmPackage, latest) {
|
|
|
42737
43386
|
last_checked: new Date().toISOString(),
|
|
42738
43387
|
latest_eligible: latest
|
|
42739
43388
|
};
|
|
42740
|
-
|
|
43389
|
+
writeFileSync6(file, JSON.stringify(record, null, 2));
|
|
42741
43390
|
}
|
|
42742
43391
|
function shouldRecheckVersion(record, weeklyCheckIntervalMs = 7 * 24 * 60 * 60 * 1000) {
|
|
42743
43392
|
if (!record)
|
|
@@ -43118,7 +43767,7 @@ function ensureInstallAnchor(cwd) {
|
|
|
43118
43767
|
try {
|
|
43119
43768
|
const stub = join19(cwd, "package.json");
|
|
43120
43769
|
if (!existsSync14(stub)) {
|
|
43121
|
-
|
|
43770
|
+
writeFileSync7(stub, `${JSON.stringify({ name: "aft-lsp-cache", version: "0.0.0", private: true })}
|
|
43122
43771
|
`);
|
|
43123
43772
|
}
|
|
43124
43773
|
} catch (err) {
|
|
@@ -43331,7 +43980,7 @@ function quarantineCachedNpmInstall(spec, reason) {
|
|
|
43331
43980
|
try {
|
|
43332
43981
|
mkdirSync6(join19(dest, ".."), { recursive: true });
|
|
43333
43982
|
rmSync5(dest, { recursive: true, force: true });
|
|
43334
|
-
|
|
43983
|
+
renameSync8(packageDir, dest);
|
|
43335
43984
|
} catch (err) {
|
|
43336
43985
|
warn2(`[lsp] tofu_mismatch ${spec.npm}: failed to quarantine cache entry: ${err}`);
|
|
43337
43986
|
}
|
|
@@ -43435,12 +44084,12 @@ import {
|
|
|
43435
44084
|
readFileSync as readFileSync13,
|
|
43436
44085
|
readlinkSync as readlinkSync2,
|
|
43437
44086
|
readSync as readSync6,
|
|
43438
|
-
realpathSync as
|
|
43439
|
-
renameSync as
|
|
44087
|
+
realpathSync as realpathSync6,
|
|
44088
|
+
renameSync as renameSync9,
|
|
43440
44089
|
rmSync as rmSync6,
|
|
43441
44090
|
statSync as statSync13,
|
|
43442
|
-
unlinkSync as
|
|
43443
|
-
writeFileSync as
|
|
44091
|
+
unlinkSync as unlinkSync6,
|
|
44092
|
+
writeFileSync as writeFileSync8
|
|
43444
44093
|
} from "node:fs";
|
|
43445
44094
|
import { dirname as dirname10, join as join20, relative as relative5, resolve as resolve12 } from "node:path";
|
|
43446
44095
|
import { Readable as Readable3 } from "node:stream";
|
|
@@ -43566,9 +44215,9 @@ function writeInstallFailureMarker(spec, release, sha256, installError) {
|
|
|
43566
44215
|
const tempPath = `${path}.tmp-${process.pid}-${randomBytes4(6).toString("hex")}`;
|
|
43567
44216
|
try {
|
|
43568
44217
|
mkdirSync7(dirname10(path), { recursive: true });
|
|
43569
|
-
|
|
44218
|
+
writeFileSync8(tempPath, `${JSON.stringify({ release, sha256, error: markerError, at: new Date().toISOString() })}
|
|
43570
44219
|
`, { encoding: "utf8", mode: 384 });
|
|
43571
|
-
|
|
44220
|
+
renameSync9(tempPath, path);
|
|
43572
44221
|
} catch (err) {
|
|
43573
44222
|
try {
|
|
43574
44223
|
rmSync6(tempPath, { force: true });
|
|
@@ -43634,7 +44283,7 @@ function writeGithubInstalledMetaIn(installDir, version, binarySha256, archiveSh
|
|
|
43634
44283
|
binarySha256,
|
|
43635
44284
|
...archiveSha256 ? { archiveSha256 } : {}
|
|
43636
44285
|
};
|
|
43637
|
-
|
|
44286
|
+
writeFileSync8(join20(installDir, INSTALLED_META_FILE2), JSON.stringify(meta), "utf8");
|
|
43638
44287
|
} catch (err) {
|
|
43639
44288
|
warn2(`[lsp] failed to write github installed metadata in ${installDir}: ${err}`);
|
|
43640
44289
|
}
|
|
@@ -43874,7 +44523,7 @@ async function downloadFile(url, destPath, fetchImpl, assetSize, signal) {
|
|
|
43874
44523
|
await pipeline3(nodeStream, createWriteStream3(destPath), { signal: timeout.signal });
|
|
43875
44524
|
} catch (err) {
|
|
43876
44525
|
try {
|
|
43877
|
-
|
|
44526
|
+
unlinkSync6(destPath);
|
|
43878
44527
|
} catch {}
|
|
43879
44528
|
throw err;
|
|
43880
44529
|
} finally {
|
|
@@ -43903,7 +44552,7 @@ async function fetchFollowingRedirects(url, fetchImpl, signal) {
|
|
|
43903
44552
|
throw new Error(`too many redirects (>${maxRedirects})`);
|
|
43904
44553
|
}
|
|
43905
44554
|
function validateExtraction(stagingRoot) {
|
|
43906
|
-
const realStagingRoot =
|
|
44555
|
+
const realStagingRoot = realpathSync6(stagingRoot);
|
|
43907
44556
|
let totalBytes = 0;
|
|
43908
44557
|
const walk = (dir) => {
|
|
43909
44558
|
let entries;
|
|
@@ -43929,7 +44578,7 @@ function validateExtraction(stagingRoot) {
|
|
|
43929
44578
|
}
|
|
43930
44579
|
let realFull;
|
|
43931
44580
|
try {
|
|
43932
|
-
realFull =
|
|
44581
|
+
realFull = realpathSync6(full);
|
|
43933
44582
|
} catch (err) {
|
|
43934
44583
|
throw new Error(`failed to realpath ${full}: ${err}`);
|
|
43935
44584
|
}
|
|
@@ -43957,14 +44606,30 @@ function assertArchiveEntryPath(entry) {
|
|
|
43957
44606
|
throw new Error(`archive entry escapes archive root: ${entry}`);
|
|
43958
44607
|
}
|
|
43959
44608
|
}
|
|
44609
|
+
var ARCHIVE_LIST_TIMEOUT_MS = 30000;
|
|
43960
44610
|
function runArchiveTool(command, args, timeout) {
|
|
43961
44611
|
return new Promise((resolvePromise, reject) => {
|
|
43962
|
-
|
|
43963
|
-
|
|
43964
|
-
|
|
43965
|
-
|
|
43966
|
-
|
|
43967
|
-
|
|
44612
|
+
let child;
|
|
44613
|
+
const deadline = setTimeout(() => {
|
|
44614
|
+
reject(new Error(`${command} ${args[0]} timed out after ${timeout} ms`));
|
|
44615
|
+
child?.kill("SIGKILL");
|
|
44616
|
+
child?.stdin?.destroy();
|
|
44617
|
+
child?.stdout?.destroy();
|
|
44618
|
+
child?.stderr?.destroy();
|
|
44619
|
+
}, timeout);
|
|
44620
|
+
try {
|
|
44621
|
+
child = execFile(command, args, { encoding: "utf8", timeout, killSignal: "SIGKILL" }, (err, stdout) => {
|
|
44622
|
+
clearTimeout(deadline);
|
|
44623
|
+
if (err)
|
|
44624
|
+
reject(err);
|
|
44625
|
+
else
|
|
44626
|
+
resolvePromise(stdout);
|
|
44627
|
+
});
|
|
44628
|
+
child.stdin?.end();
|
|
44629
|
+
} catch (error) {
|
|
44630
|
+
clearTimeout(deadline);
|
|
44631
|
+
reject(error);
|
|
44632
|
+
}
|
|
43968
44633
|
});
|
|
43969
44634
|
}
|
|
43970
44635
|
async function runTarExtraction(args, timeout) {
|
|
@@ -43978,7 +44643,7 @@ async function runTarExtraction(args, timeout) {
|
|
|
43978
44643
|
}
|
|
43979
44644
|
async function precheckWithTar(archivePath, command) {
|
|
43980
44645
|
let totalBytes = 0;
|
|
43981
|
-
const verbose = await runArchiveTool(command, ["-tvf", archivePath]);
|
|
44646
|
+
const verbose = await runArchiveTool(command, ["-tvf", archivePath], ARCHIVE_LIST_TIMEOUT_MS);
|
|
43982
44647
|
for (const line of verbose.split(`
|
|
43983
44648
|
`)) {
|
|
43984
44649
|
if (line.startsWith("h")) {
|
|
@@ -43991,7 +44656,7 @@ async function precheckWithTar(archivePath, command) {
|
|
|
43991
44656
|
totalBytes += Math.max(...numeric);
|
|
43992
44657
|
}
|
|
43993
44658
|
}
|
|
43994
|
-
const names = await runArchiveTool(command, ["-tf", archivePath]);
|
|
44659
|
+
const names = await runArchiveTool(command, ["-tf", archivePath], ARCHIVE_LIST_TIMEOUT_MS);
|
|
43995
44660
|
for (const entry of names.split(`
|
|
43996
44661
|
`)) {
|
|
43997
44662
|
if (entry.length > 0)
|
|
@@ -44002,11 +44667,11 @@ async function precheckWithTar(archivePath, command) {
|
|
|
44002
44667
|
async function precheckArchiveContents(archivePath, archiveType, zipListingMode = "platform") {
|
|
44003
44668
|
let totalBytes = 0;
|
|
44004
44669
|
if (archiveType === "zip" && zipListingMode === "platform" && process.platform !== "win32") {
|
|
44005
|
-
const out = await runArchiveTool("unzip", ["-l", archivePath]);
|
|
44670
|
+
const out = await runArchiveTool("unzip", ["-l", archivePath], ARCHIVE_LIST_TIMEOUT_MS);
|
|
44006
44671
|
const match = out.match(/^\s*(\d+)\s+\d+\s+files?\s*$/m);
|
|
44007
44672
|
if (match)
|
|
44008
44673
|
totalBytes = Number.parseInt(match[1] ?? "0", 10);
|
|
44009
|
-
const names = await runArchiveTool("unzip", ["-Z1", archivePath]);
|
|
44674
|
+
const names = await runArchiveTool("unzip", ["-Z1", archivePath], ARCHIVE_LIST_TIMEOUT_MS);
|
|
44010
44675
|
for (const entry of names.split(`
|
|
44011
44676
|
`)) {
|
|
44012
44677
|
if (entry.length > 0)
|
|
@@ -44033,7 +44698,7 @@ async function extractArchiveSafely(archivePath, destDir, archiveType) {
|
|
|
44033
44698
|
try {
|
|
44034
44699
|
rmSync6(destDir, { recursive: true, force: true });
|
|
44035
44700
|
} catch {}
|
|
44036
|
-
|
|
44701
|
+
renameSync9(stagingDir, destDir);
|
|
44037
44702
|
} catch (err) {
|
|
44038
44703
|
try {
|
|
44039
44704
|
rmSync6(stagingDir, { recursive: true, force: true });
|
|
@@ -44048,7 +44713,7 @@ function quarantineCachedGithubInstall(spec, reason) {
|
|
|
44048
44713
|
try {
|
|
44049
44714
|
mkdirSync7(dirname10(dest), { recursive: true });
|
|
44050
44715
|
rmSync6(dest, { recursive: true, force: true });
|
|
44051
|
-
|
|
44716
|
+
renameSync9(packageDir, dest);
|
|
44052
44717
|
} catch (err) {
|
|
44053
44718
|
warn2(`[lsp] tofu_mismatch ${spec.id}: failed to quarantine cache entry: ${err}`);
|
|
44054
44719
|
}
|
|
@@ -44140,7 +44805,7 @@ async function downloadAndInstall(spec, tag, assets, platform, arch, fetchImpl,
|
|
|
44140
44805
|
const message = `hash failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
44141
44806
|
error2(`[lsp] hash ${spec.id} failed: ${err}`);
|
|
44142
44807
|
try {
|
|
44143
|
-
|
|
44808
|
+
unlinkSync6(archivePath);
|
|
44144
44809
|
} catch {}
|
|
44145
44810
|
return { ok: false, sha256: "", error: message };
|
|
44146
44811
|
}
|
|
@@ -44151,7 +44816,7 @@ async function downloadAndInstall(spec, tag, assets, platform, arch, fetchImpl,
|
|
|
44151
44816
|
const message = `TOFU sha256 mismatch: previously installed archive sha256=${previousArchiveSha256}, ` + `downloaded sha256=${archiveSha256}`;
|
|
44152
44817
|
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.`);
|
|
44153
44818
|
try {
|
|
44154
|
-
|
|
44819
|
+
unlinkSync6(archivePath);
|
|
44155
44820
|
} catch {}
|
|
44156
44821
|
return { ok: false, sha256: archiveSha256, error: message };
|
|
44157
44822
|
}
|
|
@@ -44163,7 +44828,7 @@ async function downloadAndInstall(spec, tag, assets, platform, arch, fetchImpl,
|
|
|
44163
44828
|
return { ok: false, sha256: archiveSha256, error: message };
|
|
44164
44829
|
} finally {
|
|
44165
44830
|
try {
|
|
44166
|
-
|
|
44831
|
+
unlinkSync6(archivePath);
|
|
44167
44832
|
} catch {}
|
|
44168
44833
|
}
|
|
44169
44834
|
const innerBinaryPath = join20(extractDir, spec.binaryPathInArchive(platform, arch, version));
|
|
@@ -45060,6 +45725,190 @@ function astTools(ctx) {
|
|
|
45060
45725
|
};
|
|
45061
45726
|
}
|
|
45062
45727
|
|
|
45728
|
+
// src/v2-session-lifecycle.ts
|
|
45729
|
+
import { Effect as Effect4, Stream } from "effect";
|
|
45730
|
+
function noteV2ToolAbort(client, sessionID) {
|
|
45731
|
+
const state = stateFor2(client, sessionID);
|
|
45732
|
+
if (!state || state.settling)
|
|
45733
|
+
return;
|
|
45734
|
+
let resolve = () => {};
|
|
45735
|
+
const promise = new Promise((done) => {
|
|
45736
|
+
resolve = done;
|
|
45737
|
+
});
|
|
45738
|
+
state.settling = { promise, resolve };
|
|
45739
|
+
}
|
|
45740
|
+
async function awaitV2ExecutionSettlement(client, sessionID) {
|
|
45741
|
+
await stateFor2(client, sessionID)?.settling?.promise;
|
|
45742
|
+
}
|
|
45743
|
+
var locations = new WeakMap;
|
|
45744
|
+
function stateFor2(client, sessionID) {
|
|
45745
|
+
if (!client || typeof client !== "object")
|
|
45746
|
+
return;
|
|
45747
|
+
const sessions = locations.get(client);
|
|
45748
|
+
if (!sessions)
|
|
45749
|
+
return;
|
|
45750
|
+
let state = sessions.get(sessionID);
|
|
45751
|
+
if (!state) {
|
|
45752
|
+
state = {
|
|
45753
|
+
stopped: false,
|
|
45754
|
+
stopEpoch: 0,
|
|
45755
|
+
tasks: new Map,
|
|
45756
|
+
wakes: new Set,
|
|
45757
|
+
watches: new Map
|
|
45758
|
+
};
|
|
45759
|
+
sessions.set(sessionID, state);
|
|
45760
|
+
}
|
|
45761
|
+
return state;
|
|
45762
|
+
}
|
|
45763
|
+
function v2SubagentStopped(client, sessionID) {
|
|
45764
|
+
return stateFor2(client, sessionID)?.stopped === true;
|
|
45765
|
+
}
|
|
45766
|
+
function v2StopEpoch(client, sessionID) {
|
|
45767
|
+
return stateFor2(client, sessionID)?.stopEpoch ?? 0;
|
|
45768
|
+
}
|
|
45769
|
+
async function killBackgroundTask(ctx, runtime, taskId) {
|
|
45770
|
+
const result = await callBashBridge(ctx, runtime, "bash_kill", { task_id: taskId }, { abortSignal: undefined });
|
|
45771
|
+
if (result.success === false)
|
|
45772
|
+
throw new Error(String(result.message ?? "bash_kill failed"));
|
|
45773
|
+
markHeldBgCompletions(runtime.sessionID);
|
|
45774
|
+
}
|
|
45775
|
+
async function trackV2BackgroundTask(ctx, runtime, taskId, startedEpoch) {
|
|
45776
|
+
const state = stateFor2(ctx.client, runtime.sessionID);
|
|
45777
|
+
if (!state)
|
|
45778
|
+
return;
|
|
45779
|
+
const kill = () => killBackgroundTask(ctx, runtime, taskId);
|
|
45780
|
+
if (state.stopped || state.stopEpoch !== startedEpoch) {
|
|
45781
|
+
await kill();
|
|
45782
|
+
return;
|
|
45783
|
+
}
|
|
45784
|
+
state.tasks.set(taskId, kill);
|
|
45785
|
+
}
|
|
45786
|
+
async function killStoppedBackgroundTasks(client, sessionID, ctx, startedBeforeMs) {
|
|
45787
|
+
const kills = [];
|
|
45788
|
+
const visited = new Set;
|
|
45789
|
+
const dispatch = (taskId, kill) => {
|
|
45790
|
+
if (visited.has(taskId))
|
|
45791
|
+
return;
|
|
45792
|
+
visited.add(taskId);
|
|
45793
|
+
kills.push(kill().catch((error) => {
|
|
45794
|
+
warn2(`[bash] failed to kill stopped session ${sessionID}'s task ${taskId}: ${String(error)}`);
|
|
45795
|
+
}));
|
|
45796
|
+
};
|
|
45797
|
+
for (const [owner, state] of locations.get(client) ?? []) {
|
|
45798
|
+
if (owner !== sessionID)
|
|
45799
|
+
continue;
|
|
45800
|
+
const tasks = [...state.tasks];
|
|
45801
|
+
state.tasks.clear();
|
|
45802
|
+
for (const [taskId, kill] of tasks) {
|
|
45803
|
+
dispatch(taskId, kill);
|
|
45804
|
+
}
|
|
45805
|
+
}
|
|
45806
|
+
if (ctx) {
|
|
45807
|
+
const runtime = {
|
|
45808
|
+
sessionID,
|
|
45809
|
+
directory: client.location.directory,
|
|
45810
|
+
directoryIsSessionRoot: true
|
|
45811
|
+
};
|
|
45812
|
+
try {
|
|
45813
|
+
const result = await callBashBridge(ctx, runtime, "bash_owned_tasks", { started_before_ms: startedBeforeMs }, { abortSignal: undefined });
|
|
45814
|
+
if (result.success === false)
|
|
45815
|
+
throw new Error(String(result.message ?? "bash_owned_tasks failed"));
|
|
45816
|
+
for (const taskId of Array.isArray(result.task_ids) ? result.task_ids : []) {
|
|
45817
|
+
if (typeof taskId === "string")
|
|
45818
|
+
dispatch(taskId, () => killBackgroundTask(ctx, runtime, taskId));
|
|
45819
|
+
}
|
|
45820
|
+
} catch (error) {
|
|
45821
|
+
warn2(`[bash] failed to enumerate stopped session ${sessionID}'s tasks: ${String(error)}`);
|
|
45822
|
+
}
|
|
45823
|
+
}
|
|
45824
|
+
await Promise.all(kills);
|
|
45825
|
+
}
|
|
45826
|
+
function allowV2WatchWake(client, sessionID, taskId) {
|
|
45827
|
+
const state = stateFor2(client, sessionID);
|
|
45828
|
+
if (state && !state.stopped)
|
|
45829
|
+
state.wakes.add(taskId);
|
|
45830
|
+
}
|
|
45831
|
+
function v2WatchWakeAllowed(client, sessionID, taskId) {
|
|
45832
|
+
const state = stateFor2(client, sessionID);
|
|
45833
|
+
return state !== undefined && !state.stopped && state.wakes.has(taskId);
|
|
45834
|
+
}
|
|
45835
|
+
function forgetV2Watch(client, sessionID, taskId) {
|
|
45836
|
+
const state = stateFor2(client, sessionID);
|
|
45837
|
+
if (!state)
|
|
45838
|
+
return;
|
|
45839
|
+
state.wakes.delete(taskId);
|
|
45840
|
+
state.tasks.delete(taskId);
|
|
45841
|
+
for (const [watchId, watch] of state.watches) {
|
|
45842
|
+
if (watch.taskId === taskId)
|
|
45843
|
+
state.watches.delete(watchId);
|
|
45844
|
+
}
|
|
45845
|
+
}
|
|
45846
|
+
async function trackV2Watch(ctx, runtime, taskId, watchId) {
|
|
45847
|
+
const state = stateFor2(ctx.client, runtime.sessionID);
|
|
45848
|
+
if (!state || typeof watchId !== "string")
|
|
45849
|
+
return;
|
|
45850
|
+
const cancel = () => callBashBridge(ctx, runtime, "bash_unnotify", { task_id: taskId, watch_id: watchId }, { abortSignal: undefined });
|
|
45851
|
+
if (state.stopped) {
|
|
45852
|
+
await cancel();
|
|
45853
|
+
return;
|
|
45854
|
+
}
|
|
45855
|
+
state.wakes.add(taskId);
|
|
45856
|
+
state.watches.set(watchId, { taskId, cancel });
|
|
45857
|
+
}
|
|
45858
|
+
function registerV2SessionLifecycle(context, ctx) {
|
|
45859
|
+
const host = context;
|
|
45860
|
+
return Effect4.gen(function* () {
|
|
45861
|
+
if (!host.event?.subscribe || !context || typeof context !== "object")
|
|
45862
|
+
return;
|
|
45863
|
+
const sessions = new Map;
|
|
45864
|
+
locations.set(context, sessions);
|
|
45865
|
+
yield* Effect4.addFinalizer(() => Effect4.sync(() => {
|
|
45866
|
+
for (const state of sessions.values())
|
|
45867
|
+
state.settling?.resolve();
|
|
45868
|
+
locations.delete(context);
|
|
45869
|
+
}));
|
|
45870
|
+
const pull = yield* Stream.toPull(host.event.subscribe());
|
|
45871
|
+
yield* Effect4.forkScoped(Effect4.forever(pull.pipe(Effect4.flatMap((events) => Effect4.forEach(events, (event) => Effect4.promise(async () => {
|
|
45872
|
+
const sessionID = event.data.sessionID;
|
|
45873
|
+
if (!sessionID)
|
|
45874
|
+
return;
|
|
45875
|
+
const state = stateFor2(context, sessionID);
|
|
45876
|
+
if (!state)
|
|
45877
|
+
return;
|
|
45878
|
+
if (event.type === "session.execution.started") {
|
|
45879
|
+
state.stopped = false;
|
|
45880
|
+
return;
|
|
45881
|
+
}
|
|
45882
|
+
const deliberateStop = event.type === "session.execution.interrupted" && (event.data.reason === "user" || event.data.reason === "inactivity");
|
|
45883
|
+
if (!deliberateStop) {
|
|
45884
|
+
if (event.type === "session.execution.failed" || event.type === "session.execution.succeeded" || event.type === "session.execution.interrupted") {
|
|
45885
|
+
state.settling?.resolve();
|
|
45886
|
+
state.settling = undefined;
|
|
45887
|
+
}
|
|
45888
|
+
return;
|
|
45889
|
+
}
|
|
45890
|
+
if (!await resolveIsSubagent(context, sessionID)) {
|
|
45891
|
+
state.settling?.resolve();
|
|
45892
|
+
state.settling = undefined;
|
|
45893
|
+
return;
|
|
45894
|
+
}
|
|
45895
|
+
state.stopped = true;
|
|
45896
|
+
state.stopEpoch += 1;
|
|
45897
|
+
state.wakes.clear();
|
|
45898
|
+
state.settling?.resolve();
|
|
45899
|
+
state.settling = undefined;
|
|
45900
|
+
const watches = [...state.watches.values()];
|
|
45901
|
+
state.watches.clear();
|
|
45902
|
+
Promise.all([
|
|
45903
|
+
killStoppedBackgroundTasks(context, sessionID, ctx, event.created),
|
|
45904
|
+
...watches.map((watch) => watch.cancel().catch((error) => {
|
|
45905
|
+
warn2(`[bash_watch] failed to cancel stopped session ${sessionID}'s watch on ${watch.taskId}: ${String(error)}`);
|
|
45906
|
+
}))
|
|
45907
|
+
]);
|
|
45908
|
+
}), { discard: true })))), { startImmediately: true });
|
|
45909
|
+
});
|
|
45910
|
+
}
|
|
45911
|
+
|
|
45063
45912
|
// src/tools/bash.ts
|
|
45064
45913
|
var z3 = tool.schema;
|
|
45065
45914
|
var METADATA_PREVIEW_LIMIT = 30 * 1024;
|
|
@@ -45195,7 +46044,7 @@ function bashToolDescription(aftSearchRegistered, compressionOn, backgroundOn, d
|
|
|
45195
46044
|
].join(", ");
|
|
45196
46045
|
const searchSteer = `use ${steerTools}${zoomEnabled ? ", or aft_zoom" : ""} instead`;
|
|
45197
46046
|
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." : "";
|
|
45198
|
-
const tasks = backgroundOn ? ` Commands run in the foreground and return inline; wait: true blocks until a long command finishes instead of auto-promoting (
|
|
46047
|
+
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).";
|
|
45199
46048
|
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." : "";
|
|
45200
46049
|
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.";
|
|
45201
46050
|
const remoteRuns = surface.remoteRuns ? ` ${BASH_RUNON_GUIDANCE}` : "";
|
|
@@ -45293,7 +46142,7 @@ function bashTimeoutDescription(backgroundOn, role = "primary") {
|
|
|
45293
46142
|
if (!backgroundOn) {
|
|
45294
46143
|
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.";
|
|
45295
46144
|
}
|
|
45296
|
-
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
|
|
46145
|
+
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";
|
|
45297
46146
|
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.`;
|
|
45298
46147
|
}
|
|
45299
46148
|
function createBashTool(ctx, aftSearchRegisteredOverride) {
|
|
@@ -45305,7 +46154,7 @@ function createBashTool(ctx, aftSearchRegisteredOverride) {
|
|
|
45305
46154
|
...bashCompanionRegistered(ctx.config, "bash_kill") ? ["bash_kill"] : []
|
|
45306
46155
|
];
|
|
45307
46156
|
const waitArg = initialBashCfg.background ? {
|
|
45308
|
-
wait: z3.boolean().optional().describe(`When true, run in the foreground without auto-promoting and wait until the command finishes or reaches its timeout (
|
|
46157
|
+
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.`)
|
|
45309
46158
|
} : {};
|
|
45310
46159
|
const sandboxArg = nativeSandboxEnabled(ctx.config) ? {
|
|
45311
46160
|
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.")
|
|
@@ -45360,6 +46209,7 @@ ${BASH_RUNON_GUIDANCE}`, "");
|
|
|
45360
46209
|
},
|
|
45361
46210
|
execute: async (args, context) => {
|
|
45362
46211
|
const bashCfg = resolveBashConfig(ctx.config);
|
|
46212
|
+
const startedEpoch = v2StopEpoch(ctx.client, context.sessionID);
|
|
45363
46213
|
const ctxAftSearchRegistered = ctx.aftSearchRegistered === true;
|
|
45364
46214
|
const aftSearchRegistered = aftSearchRegisteredOverride ?? ctxAftSearchRegistered;
|
|
45365
46215
|
let accumulatedOutput = "";
|
|
@@ -45389,7 +46239,7 @@ ${BASH_RUNON_GUIDANCE}`, "");
|
|
|
45389
46239
|
const blockToCompletion = subagentForcedForeground || backgroundDisabled || requestedWait;
|
|
45390
46240
|
const effectiveBackground = blockToCompletion ? false : requestedBackground;
|
|
45391
46241
|
const rawTimeout = coerceOptionalInt(args.timeout, "timeout", 1, Number.MAX_SAFE_INTEGER);
|
|
45392
|
-
const workerWaitMaxMs =
|
|
46242
|
+
const workerWaitMaxMs = bashCfg.worker_wait_max_ms;
|
|
45393
46243
|
const ptyRows = coerceOptionalInt(args.ptyRows, "ptyRows", 1, 60);
|
|
45394
46244
|
const ptyCols = coerceOptionalInt(args.ptyCols, "ptyCols", 1, 140);
|
|
45395
46245
|
const compressed = coerceBoolean(args.compressed, true);
|
|
@@ -45473,6 +46323,7 @@ ${BASH_RUNON_GUIDANCE}`, "");
|
|
|
45473
46323
|
if (data.status === "running" && typeof data.task_id === "string") {
|
|
45474
46324
|
const taskId = data.task_id;
|
|
45475
46325
|
trackBgTask(context.sessionID, taskId);
|
|
46326
|
+
await trackV2BackgroundTask(ctx, context, taskId, startedEpoch);
|
|
45476
46327
|
let rendered = data.output ?? "";
|
|
45477
46328
|
if (isSubagent) {
|
|
45478
46329
|
rendered += workerBackgroundTaskNote(taskId);
|
|
@@ -45545,7 +46396,10 @@ async function formatBashStatusText(runtime, taskId, data, requestedOutputMode,
|
|
|
45545
46396
|
if (typeof data.live_descendants_summary === "string") {
|
|
45546
46397
|
text += ` · ${data.live_descendants_summary}`;
|
|
45547
46398
|
}
|
|
45548
|
-
if (data.
|
|
46399
|
+
if (typeof data.remote_phase_note === "string") {
|
|
46400
|
+
text += ` · ${data.remote_phase_note}`;
|
|
46401
|
+
}
|
|
46402
|
+
if (data.output_incomplete === true || status === "failed" && data.status_reason) {
|
|
45549
46403
|
const reason = typeof data.status_reason === "string" && data.status_reason ? data.status_reason : "PTY output may be incomplete";
|
|
45550
46404
|
text += `
|
|
45551
46405
|
[${reason}]`;
|
|
@@ -45642,7 +46496,25 @@ function conflictTools(ctx) {
|
|
|
45642
46496
|
}
|
|
45643
46497
|
|
|
45644
46498
|
// src/tools/definitions/v2.ts
|
|
45645
|
-
import { Effect as
|
|
46499
|
+
import { Effect as Effect5 } from "effect";
|
|
46500
|
+
|
|
46501
|
+
// src/v2-host-tool-names.ts
|
|
46502
|
+
var V2_HOST_TOOL_NAMES = Object.freeze({
|
|
46503
|
+
bash: "shell",
|
|
46504
|
+
apply_patch: "patch"
|
|
46505
|
+
});
|
|
46506
|
+
var AFT_TOOL_FOR_V2_HOST = Object.freeze(Object.fromEntries(Object.entries(V2_HOST_TOOL_NAMES).map(([aft, host]) => [host, aft])));
|
|
46507
|
+
function v2HostToolName(aftName) {
|
|
46508
|
+
return V2_HOST_TOOL_NAMES[aftName] ?? aftName;
|
|
46509
|
+
}
|
|
46510
|
+
function aftToolNameForV2Host(hostName) {
|
|
46511
|
+
return AFT_TOOL_FOR_V2_HOST[hostName] ?? hostName;
|
|
46512
|
+
}
|
|
46513
|
+
function renameV2ToolReferences(text) {
|
|
46514
|
+
return text.replace(/(?<![\w./-])bash(?![\w-]|\.\w|\s+(?:itself|REPLs|syntax)\b)/g, "shell").replace(/(?<![\w./-])apply_patch(?![\w-]|\.\w)/g, "patch");
|
|
46515
|
+
}
|
|
46516
|
+
|
|
46517
|
+
// src/tools/definitions/v2.ts
|
|
45646
46518
|
var V2_TOOL_NAME = /^[A-Za-z][A-Za-z0-9_-]{0,63}$/;
|
|
45647
46519
|
var V2_PATH_HEADER_TOOLS = new Set(["read", "write", "edit"]);
|
|
45648
46520
|
var V2_BASH_TOOLS = new Set(["bash"]);
|
|
@@ -45727,12 +46599,23 @@ function bareToolName3(name) {
|
|
|
45727
46599
|
return name.startsWith("aft_") ? name.slice(4) : name;
|
|
45728
46600
|
}
|
|
45729
46601
|
function projectArguments(name, definition) {
|
|
45730
|
-
|
|
45731
|
-
|
|
46602
|
+
const args = renameArgumentDescriptions(definition.args);
|
|
46603
|
+
if (!V2_PATH_HEADER_TOOLS.has(bareToolName3(name)) || !("filePath" in args)) {
|
|
46604
|
+
return args;
|
|
45732
46605
|
}
|
|
45733
|
-
const { filePath, ...rest } =
|
|
46606
|
+
const { filePath, ...rest } = args;
|
|
45734
46607
|
return { path: filePath, ...rest };
|
|
45735
46608
|
}
|
|
46609
|
+
function renameArgumentDescriptions(args) {
|
|
46610
|
+
const renamed = {};
|
|
46611
|
+
for (const [key, schema] of Object.entries(args)) {
|
|
46612
|
+
const described = schema;
|
|
46613
|
+
const description = described.description;
|
|
46614
|
+
const next = description === undefined ? undefined : renameV2ToolReferences(description);
|
|
46615
|
+
renamed[key] = next === undefined || next === description ? schema : described.describe(next);
|
|
46616
|
+
}
|
|
46617
|
+
return renamed;
|
|
46618
|
+
}
|
|
45736
46619
|
function executionArguments(name, input) {
|
|
45737
46620
|
if (!V2_PATH_HEADER_TOOLS.has(bareToolName3(name)) || !("path" in input))
|
|
45738
46621
|
return input;
|
|
@@ -45791,7 +46674,7 @@ function runtimeFor(name, location, context, signal, consumers) {
|
|
|
45791
46674
|
abort: signal,
|
|
45792
46675
|
effectAbort: signal,
|
|
45793
46676
|
metadata: (update) => {
|
|
45794
|
-
|
|
46677
|
+
Effect5.runPromise(context.progress(jsonSafeMetadata({
|
|
45795
46678
|
...update.metadata ?? {},
|
|
45796
46679
|
...update.title ? { title: update.title } : {}
|
|
45797
46680
|
})));
|
|
@@ -45806,36 +46689,46 @@ function runtimeFor(name, location, context, signal, consumers) {
|
|
|
45806
46689
|
};
|
|
45807
46690
|
}
|
|
45808
46691
|
function projectV2Tool(name, definition, location, consumers = {}) {
|
|
46692
|
+
const hostName = v2HostToolName(name);
|
|
45809
46693
|
const input = tool.schema.object(projectArguments(name, definition));
|
|
45810
|
-
assertV2Contract(
|
|
46694
|
+
assertV2Contract(hostName, input);
|
|
45811
46695
|
const sharedOptions = definition.options;
|
|
45812
46696
|
const permission = hostPermission(name);
|
|
45813
46697
|
return {
|
|
45814
|
-
name,
|
|
45815
|
-
description: definition.description,
|
|
46698
|
+
name: hostName,
|
|
46699
|
+
description: renameV2ToolReferences(definition.description),
|
|
45816
46700
|
input,
|
|
45817
46701
|
options: {
|
|
45818
46702
|
...sharedOptions,
|
|
45819
46703
|
...permission ? { permission } : {},
|
|
45820
46704
|
codemode: false
|
|
45821
46705
|
},
|
|
45822
|
-
execute: (rawInput, context) =>
|
|
46706
|
+
execute: (rawInput, context) => Effect5.tryPromise({
|
|
45823
46707
|
try: async (signal) => {
|
|
45824
|
-
|
|
45825
|
-
|
|
46708
|
+
const onAbort = () => {
|
|
46709
|
+
if (context.sessionID)
|
|
46710
|
+
consumers.onToolAbort?.(context.sessionID);
|
|
46711
|
+
};
|
|
46712
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
46713
|
+
try {
|
|
46714
|
+
if (name === "bash_status" || name === "bash_watch") {
|
|
46715
|
+
consumers.registerSession?.(context.sessionID);
|
|
46716
|
+
}
|
|
46717
|
+
const runtime = runtimeFor(name, location, context, signal, consumers);
|
|
46718
|
+
const input = executionArguments(name, rawInput);
|
|
46719
|
+
const result = consumers.executeBash && V2_BASH_TOOLS.has(name) ? await consumers.executeBash({
|
|
46720
|
+
name,
|
|
46721
|
+
input,
|
|
46722
|
+
context: runtime,
|
|
46723
|
+
definition
|
|
46724
|
+
}) : await definition.execute(input, runtime);
|
|
46725
|
+
const refusal = failureEnvelopeMessage(result);
|
|
46726
|
+
if (refusal)
|
|
46727
|
+
throw new Error(refusal);
|
|
46728
|
+
return resultContent(result);
|
|
46729
|
+
} finally {
|
|
46730
|
+
signal.removeEventListener("abort", onAbort);
|
|
45826
46731
|
}
|
|
45827
|
-
const runtime = runtimeFor(name, location, context, signal, consumers);
|
|
45828
|
-
const input = executionArguments(name, rawInput);
|
|
45829
|
-
const result = consumers.executeBash && V2_BASH_TOOLS.has(name) ? await consumers.executeBash({
|
|
45830
|
-
name,
|
|
45831
|
-
input,
|
|
45832
|
-
context: runtime,
|
|
45833
|
-
definition
|
|
45834
|
-
}) : await definition.execute(input, runtime);
|
|
45835
|
-
const refusal = failureEnvelopeMessage(result);
|
|
45836
|
-
if (refusal)
|
|
45837
|
-
throw new Error(refusal);
|
|
45838
|
-
return resultContent(result);
|
|
45839
46732
|
},
|
|
45840
46733
|
catch: failure
|
|
45841
46734
|
})
|
|
@@ -45864,10 +46757,10 @@ function joinList(items) {
|
|
|
45864
46757
|
}
|
|
45865
46758
|
|
|
45866
46759
|
// src/shared/read-vision.ts
|
|
45867
|
-
import { Effect as
|
|
46760
|
+
import { Effect as Effect6 } from "effect";
|
|
45868
46761
|
var VISION_HOST_TIMEOUT_MS = 1500;
|
|
45869
46762
|
var states = new WeakMap;
|
|
45870
|
-
function
|
|
46763
|
+
function stateFor3(client) {
|
|
45871
46764
|
let state = states.get(client);
|
|
45872
46765
|
if (!state) {
|
|
45873
46766
|
state = { models: new Map, history: new Map, capabilities: new Map };
|
|
@@ -45876,7 +46769,7 @@ function stateFor2(client) {
|
|
|
45876
46769
|
return state;
|
|
45877
46770
|
}
|
|
45878
46771
|
function rememberReadModel(client, sessionID, model) {
|
|
45879
|
-
|
|
46772
|
+
stateFor3(client).models.set(sessionID, model);
|
|
45880
46773
|
}
|
|
45881
46774
|
async function bounded(call) {
|
|
45882
46775
|
let timer;
|
|
@@ -45899,7 +46792,7 @@ function record3(value) {
|
|
|
45899
46792
|
async function currentSessionVisionCapability(client, sessionID) {
|
|
45900
46793
|
if (!sessionID)
|
|
45901
46794
|
return;
|
|
45902
|
-
const state =
|
|
46795
|
+
const state = stateFor3(client);
|
|
45903
46796
|
let model = state.models.get(sessionID);
|
|
45904
46797
|
if (!model) {
|
|
45905
46798
|
let pending = state.history.get(sessionID);
|
|
@@ -45923,7 +46816,7 @@ async function currentSessionVisionCapability(client, sessionID) {
|
|
|
45923
46816
|
return;
|
|
45924
46817
|
const list = async () => {
|
|
45925
46818
|
const result = api.list?.();
|
|
45926
|
-
return isV2PluginContext3(client) ?
|
|
46819
|
+
return isV2PluginContext3(client) ? Effect6.runPromise(result) : await result;
|
|
45927
46820
|
};
|
|
45928
46821
|
const listed = record3(await bounded(list));
|
|
45929
46822
|
const catalog = record3(listed?.data) ?? listed;
|
|
@@ -46014,6 +46907,7 @@ function createBashWatchTool(ctx) {
|
|
|
46014
46907
|
throw new Error(`invalid_request: ${message}`);
|
|
46015
46908
|
throw new Error(`${code}: ${message}`);
|
|
46016
46909
|
}
|
|
46910
|
+
await trackV2Watch(ctx, context, taskId, registered.watch_id);
|
|
46017
46911
|
const metadata = context.metadata;
|
|
46018
46912
|
metadata?.({ taskId, registered: true, watchId: registered.watch_id });
|
|
46019
46913
|
return `Watch registered: ${registered.watch_id} on task ${taskId}
|
|
@@ -46023,6 +46917,11 @@ A notification will fire when the pattern matches or the task exits.`;
|
|
|
46023
46917
|
const role = isSubagent ? "worker" : "primary";
|
|
46024
46918
|
const effectiveWaitMs = subagentForcedSync ? bashCfg.worker_wait_max_ms : resolveWatchTimeoutMs(coerceConfiguredWatchTimeout(args.timeoutMs, role, syncWaitCap), role, syncWaitCap, bashCfg.worker_wait_max_ms);
|
|
46025
46919
|
const data = await waitForBashStatus(ctx, context, taskId, undefined, waitFor, effectiveWaitMs, role);
|
|
46920
|
+
if (isSubagent && context.abort.aborted)
|
|
46921
|
+
await awaitV2ExecutionSettlement(ctx.client, context.sessionID);
|
|
46922
|
+
const stopped = isSubagent && v2SubagentStopped(ctx.client, context.sessionID);
|
|
46923
|
+
if (stopped && data.waited?.reason === "user_message")
|
|
46924
|
+
data.waited.reason = "aborted";
|
|
46026
46925
|
const waited = data.waited;
|
|
46027
46926
|
if (waited?.reason === "user_message") {
|
|
46028
46927
|
const convertedText = await convertToAsyncWatchOnAbort(ctx, context, taskId, waitFor, coerceBoolean(args.once, true), waited.elapsed_ms, role);
|
|
@@ -46041,6 +46940,7 @@ async function convertToAsyncWatchOnAbort(ctx, context, taskId, waitFor, once, e
|
|
|
46041
46940
|
const interrupted = `Sync watch for task ${taskId} was interrupted because you sent a message after ${elapsedMs}ms of waiting. `;
|
|
46042
46941
|
const reminderFallback = interruptedWatchTail(role, `The task is still running in the background. A completion reminder will be ` + `delivered automatically when the task exits.`);
|
|
46043
46942
|
if (!waitFor) {
|
|
46943
|
+
allowV2WatchWake(ctx.client, context.sessionID, taskId);
|
|
46044
46944
|
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.`);
|
|
46045
46945
|
}
|
|
46046
46946
|
const notifyParams = {
|
|
@@ -46058,6 +46958,7 @@ async function convertToAsyncWatchOnAbort(ctx, context, taskId, waitFor, once, e
|
|
|
46058
46958
|
unmarkExplicitControl(context.sessionID, taskId);
|
|
46059
46959
|
return interrupted + `Auto-registering an async watch failed (${String(registered.message ?? "unknown error")}). ` + reminderFallback;
|
|
46060
46960
|
}
|
|
46961
|
+
await trackV2Watch(ctx, context, taskId, registered.watch_id);
|
|
46061
46962
|
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.`);
|
|
46062
46963
|
} catch (err) {
|
|
46063
46964
|
unmarkExplicitControl(context.sessionID, taskId);
|
|
@@ -46174,6 +47075,9 @@ async function waitForBashStatus(ctx, runtime, taskId, outputMode, waitFor, effe
|
|
|
46174
47075
|
}
|
|
46175
47076
|
busySince = undefined;
|
|
46176
47077
|
lastData = data;
|
|
47078
|
+
if (role === "worker" && abortSignal?.aborted) {
|
|
47079
|
+
return withWaited(data, waited("aborted"));
|
|
47080
|
+
}
|
|
46177
47081
|
const terminal = isTerminalStatus(data.status);
|
|
46178
47082
|
if (waitFor) {
|
|
46179
47083
|
const scan = await readNewTaskOutput(data, spillCursor);
|
|
@@ -46862,9 +47766,6 @@ function createHashlineEditTool(ctx) {
|
|
|
46862
47766
|
});
|
|
46863
47767
|
if (denial)
|
|
46864
47768
|
return permissionDeniedResponse(denial);
|
|
46865
|
-
const preview = await callToolCall(ctx, context, "edit", rawArgs, { preview: true });
|
|
46866
|
-
if (preview.success === false)
|
|
46867
|
-
throw toolErrorFromResponse("edit", preview);
|
|
46868
47769
|
const data = await callToolCall(ctx, context, "edit", rawArgs);
|
|
46869
47770
|
if (data.success === false)
|
|
46870
47771
|
throw toolErrorFromResponse("edit", data);
|
|
@@ -47493,12 +48394,13 @@ function createInspectTier2IdleScheduler(options) {
|
|
|
47493
48394
|
}
|
|
47494
48395
|
function inspectTools(ctx) {
|
|
47495
48396
|
const inspectTool = {
|
|
47496
|
-
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
|
|
48397
|
+
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.
|
|
47497
48398
|
|
|
47498
48399
|
` + "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.",
|
|
47499
48400
|
args: {
|
|
47500
|
-
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.
|
|
47501
|
-
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
|
|
48401
|
+
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.")),
|
|
48402
|
+
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'.")),
|
|
48403
|
+
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.")),
|
|
47502
48404
|
topK: arg(z9.number().int().positive().max(100).optional().describe("Max drill-down items per category. Default 20, max 100."))
|
|
47503
48405
|
},
|
|
47504
48406
|
execute: async (args, context) => {
|
|
@@ -47513,6 +48415,8 @@ function inspectTools(ctx) {
|
|
|
47513
48415
|
rawArgs.scope = scoped.scope;
|
|
47514
48416
|
if (args.topK !== undefined && args.topK !== null)
|
|
47515
48417
|
rawArgs.topK = args.topK;
|
|
48418
|
+
if (args.offset !== undefined && args.offset !== null)
|
|
48419
|
+
rawArgs.offset = args.offset;
|
|
47516
48420
|
const response = await callToolCall(ctx, context, "inspect", rawArgs, {
|
|
47517
48421
|
transportTimeoutMs: resolveInspectDiagnosticsTimeoutMs(ctx.config) + INSPECT_TRANSPORT_HEADROOM_MS
|
|
47518
48422
|
});
|
|
@@ -48392,11 +49296,11 @@ function describeBashForRegisteredSurface(allTools, config) {
|
|
|
48392
49296
|
}
|
|
48393
49297
|
function registerAftTools(context, location, definitions, consumers = {}) {
|
|
48394
49298
|
return context.tool.transform((editor) => {
|
|
48395
|
-
const
|
|
48396
|
-
|
|
48397
|
-
if (V2_BUILTIN_REPLACEMENTS.has(
|
|
48398
|
-
editor.remove(
|
|
48399
|
-
editor.add(
|
|
49299
|
+
for (const [name, definition] of Object.entries(definitions)) {
|
|
49300
|
+
const projected = projectV2Tool(name, definition, location, consumers);
|
|
49301
|
+
if (V2_BUILTIN_REPLACEMENTS.has(name))
|
|
49302
|
+
editor.remove(projected.name);
|
|
49303
|
+
editor.add(projected);
|
|
48400
49304
|
}
|
|
48401
49305
|
});
|
|
48402
49306
|
}
|
|
@@ -48797,6 +49701,7 @@ function startOpenCodeLiveConfigReload(options) {
|
|
|
48797
49701
|
const { userConfigPath, projectConfigPath } = resolveCortexKitConfigPaths(options.directory);
|
|
48798
49702
|
return startLiveConfigReload({
|
|
48799
49703
|
paths: [userConfigPath, projectConfigPath],
|
|
49704
|
+
projectPath: projectConfigPath,
|
|
48800
49705
|
load: () => loadOpenCodeConfigForLiveReload(options.directory),
|
|
48801
49706
|
initialSources: options.initialSources,
|
|
48802
49707
|
initialProjectText: options.initialSourceTexts?.[projectConfigPath] ?? null,
|
|
@@ -49363,21 +50268,21 @@ import {
|
|
|
49363
50268
|
mkdirSync as mkdirSync8,
|
|
49364
50269
|
openSync as openSync11,
|
|
49365
50270
|
readFileSync as readFileSync17,
|
|
49366
|
-
renameSync as
|
|
50271
|
+
renameSync as renameSync10,
|
|
49367
50272
|
rmSync as rmSync8,
|
|
49368
50273
|
statSync as statSync16,
|
|
49369
|
-
writeFileSync as
|
|
50274
|
+
writeFileSync as writeFileSync11
|
|
49370
50275
|
} from "node:fs";
|
|
49371
50276
|
import { dirname as dirname16 } from "node:path";
|
|
49372
50277
|
|
|
49373
50278
|
// src/hooks/auto-update-checker/cache.ts
|
|
49374
|
-
import { cpSync, existsSync as existsSync19, mkdtempSync, readFileSync as readFileSync16, rmSync as rmSync7, writeFileSync as
|
|
50279
|
+
import { cpSync, existsSync as existsSync19, mkdtempSync, readFileSync as readFileSync16, rmSync as rmSync7, writeFileSync as writeFileSync10 } from "node:fs";
|
|
49375
50280
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
49376
50281
|
import { basename as basename6, dirname as dirname15, join as join25 } from "node:path";
|
|
49377
50282
|
var import_comment_json3 = __toESM(require_src2(), 1);
|
|
49378
50283
|
|
|
49379
50284
|
// src/hooks/auto-update-checker/checker.ts
|
|
49380
|
-
import { existsSync as existsSync18, readFileSync as readFileSync15, statSync as statSync15, writeFileSync as
|
|
50285
|
+
import { existsSync as existsSync18, readFileSync as readFileSync15, statSync as statSync15, writeFileSync as writeFileSync9 } from "node:fs";
|
|
49381
50286
|
import { readFile } from "node:fs/promises";
|
|
49382
50287
|
import { homedir as homedir15 } from "node:os";
|
|
49383
50288
|
import { dirname as dirname14, isAbsolute as isAbsolute13, join as join24, resolve as resolve15 } from "node:path";
|
|
@@ -49765,11 +50670,11 @@ function restoreAutoUpdateSnapshot(snapshot) {
|
|
|
49765
50670
|
if (snapshot.packageJson === null)
|
|
49766
50671
|
rmSync7(snapshot.packageJsonPath, { force: true });
|
|
49767
50672
|
else
|
|
49768
|
-
|
|
50673
|
+
writeFileSync10(snapshot.packageJsonPath, snapshot.packageJson);
|
|
49769
50674
|
if (snapshot.lockfile === null)
|
|
49770
50675
|
rmSync7(snapshot.lockfilePath, { force: true });
|
|
49771
50676
|
else
|
|
49772
|
-
|
|
50677
|
+
writeFileSync10(snapshot.lockfilePath, snapshot.lockfile);
|
|
49773
50678
|
rmSync7(snapshot.packageDir, { recursive: true, force: true });
|
|
49774
50679
|
if (snapshot.stagedPackageDir) {
|
|
49775
50680
|
cpSync(snapshot.stagedPackageDir, snapshot.packageDir, { recursive: true });
|
|
@@ -49806,7 +50711,7 @@ function removeFromPackageLock(installDir, packageName) {
|
|
|
49806
50711
|
modified = true;
|
|
49807
50712
|
}
|
|
49808
50713
|
if (modified) {
|
|
49809
|
-
|
|
50714
|
+
writeFileSync10(lockPath, JSON.stringify(lock, null, 2));
|
|
49810
50715
|
log2(`[auto-update-checker] Removed from package-lock.json: ${packageName}`);
|
|
49811
50716
|
}
|
|
49812
50717
|
return modified;
|
|
@@ -49828,7 +50733,7 @@ function ensureDependencyVersion(packageJsonPath, packageName, version) {
|
|
|
49828
50733
|
return true;
|
|
49829
50734
|
dependencies[packageName] = version;
|
|
49830
50735
|
nextPackageJson.dependencies = dependencies;
|
|
49831
|
-
|
|
50736
|
+
writeFileSync10(packageJsonPath, JSON.stringify(nextPackageJson, null, 2));
|
|
49832
50737
|
log2(`[auto-update-checker] Updated dependency in package.json: ${packageName} → ${version}`);
|
|
49833
50738
|
return true;
|
|
49834
50739
|
} catch (err) {
|
|
@@ -50072,7 +50977,7 @@ function claimCheckSlot(storageDir, intervalMs) {
|
|
|
50072
50977
|
let lockFd = null;
|
|
50073
50978
|
try {
|
|
50074
50979
|
lockFd = openSync11(lockPath, "wx");
|
|
50075
|
-
|
|
50980
|
+
writeFileSync11(lockFd, JSON.stringify(owner));
|
|
50076
50981
|
closeSync12(lockFd);
|
|
50077
50982
|
lockFd = null;
|
|
50078
50983
|
} catch (err) {
|
|
@@ -50086,9 +50991,9 @@ function claimCheckSlot(storageDir, intervalMs) {
|
|
|
50086
50991
|
if (!isOrphanedCheckLock(lockPath))
|
|
50087
50992
|
return null;
|
|
50088
50993
|
const replacementPath = `${lockPath}.tmp.${process.pid}`;
|
|
50089
|
-
|
|
50994
|
+
writeFileSync11(replacementPath, JSON.stringify(owner), { encoding: "utf-8", mode: 384 });
|
|
50090
50995
|
try {
|
|
50091
|
-
|
|
50996
|
+
renameSync10(replacementPath, lockPath);
|
|
50092
50997
|
} catch (replaceError) {
|
|
50093
50998
|
rmSync8(replacementPath, { force: true });
|
|
50094
50999
|
throw replaceError;
|
|
@@ -50168,8 +51073,8 @@ function hasRecentCheckTimestamp(file, intervalMs) {
|
|
|
50168
51073
|
}
|
|
50169
51074
|
function writeCheckTimestamp(file) {
|
|
50170
51075
|
const tmp = `${file}.tmp.${process.pid}`;
|
|
50171
|
-
|
|
50172
|
-
|
|
51076
|
+
writeFileSync11(tmp, JSON.stringify({ lastCheckedMs: Date.now() }), "utf-8");
|
|
51077
|
+
renameSync10(tmp, file);
|
|
50173
51078
|
}
|
|
50174
51079
|
function isNewerVersion(candidate, installed) {
|
|
50175
51080
|
const semver = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
@@ -50373,9 +51278,9 @@ import {
|
|
|
50373
51278
|
mkdirSync as mkdirSync9,
|
|
50374
51279
|
readdirSync as readdirSync6,
|
|
50375
51280
|
readFileSync as readFileSync18,
|
|
50376
|
-
renameSync as
|
|
50377
|
-
unlinkSync as
|
|
50378
|
-
writeFileSync as
|
|
51281
|
+
renameSync as renameSync11,
|
|
51282
|
+
unlinkSync as unlinkSync7,
|
|
51283
|
+
writeFileSync as writeFileSync12
|
|
50379
51284
|
} from "node:fs";
|
|
50380
51285
|
import { createServer } from "node:http";
|
|
50381
51286
|
import { dirname as dirname17, join as join27 } from "node:path";
|
|
@@ -50531,13 +51436,13 @@ class AftRpcServer {
|
|
|
50531
51436
|
const dir = dirname17(this.portFilePath);
|
|
50532
51437
|
mkdirSync9(dir, { recursive: true, mode: 448 });
|
|
50533
51438
|
const tmpPath = `${this.portFilePath}.tmp`;
|
|
50534
|
-
|
|
51439
|
+
writeFileSync12(tmpPath, JSON.stringify({
|
|
50535
51440
|
port: this.port,
|
|
50536
51441
|
token: this.token,
|
|
50537
51442
|
pid: process.pid,
|
|
50538
51443
|
started_at: Date.now()
|
|
50539
51444
|
}), { encoding: "utf-8", mode: 384 });
|
|
50540
|
-
|
|
51445
|
+
renameSync11(tmpPath, this.portFilePath);
|
|
50541
51446
|
}
|
|
50542
51447
|
sweepDeadPortFiles() {
|
|
50543
51448
|
let entries;
|
|
@@ -50555,11 +51460,11 @@ class AftRpcServer {
|
|
|
50555
51460
|
try {
|
|
50556
51461
|
const record = parseRpcPortRecord(readFileSync18(filePath, "utf-8"));
|
|
50557
51462
|
if (record === null) {
|
|
50558
|
-
|
|
51463
|
+
unlinkSync7(filePath);
|
|
50559
51464
|
continue;
|
|
50560
51465
|
}
|
|
50561
51466
|
if (record.pid !== undefined && !isPidAlive(record.pid)) {
|
|
50562
|
-
|
|
51467
|
+
unlinkSync7(filePath);
|
|
50563
51468
|
}
|
|
50564
51469
|
} catch {}
|
|
50565
51470
|
}
|
|
@@ -50595,7 +51500,7 @@ class AftRpcServer {
|
|
|
50595
51500
|
this.bunServer = null;
|
|
50596
51501
|
}
|
|
50597
51502
|
try {
|
|
50598
|
-
|
|
51503
|
+
unlinkSync7(this.portFilePath);
|
|
50599
51504
|
} catch {}
|
|
50600
51505
|
}
|
|
50601
51506
|
async handleFetch(req, srv) {
|
|
@@ -50824,7 +51729,13 @@ function readStatusBar(value) {
|
|
|
50824
51729
|
const duplicates = readOptionalNumber(bar.duplicates);
|
|
50825
51730
|
const todos = readOptionalNumber(bar.todos);
|
|
50826
51731
|
const noLanguageServer = bar.diagnostics === "no_language_server";
|
|
50827
|
-
|
|
51732
|
+
const producerMissing = bar.diagnostics === "producer_missing";
|
|
51733
|
+
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) => ({
|
|
51734
|
+
language: gap.language,
|
|
51735
|
+
producer: gap.producer,
|
|
51736
|
+
root: gap.root
|
|
51737
|
+
})) : [];
|
|
51738
|
+
if (errors === null && warnings === null && deadCode === null && unusedExports === null && duplicates === null && todos === null && !noLanguageServer && !producerMissing && disabled.length === 0) {
|
|
50828
51739
|
return;
|
|
50829
51740
|
}
|
|
50830
51741
|
return {
|
|
@@ -50836,7 +51747,8 @@ function readStatusBar(value) {
|
|
|
50836
51747
|
...duplicates !== null ? { duplicates } : {},
|
|
50837
51748
|
...todos !== null ? { todos } : {},
|
|
50838
51749
|
...bar.tier2_stale === true ? { tier2_stale: true } : {},
|
|
50839
|
-
...noLanguageServer ? { diagnostics: "no_language_server" } : {}
|
|
51750
|
+
...noLanguageServer ? { diagnostics: "no_language_server" } : {},
|
|
51751
|
+
...producerMissing ? { diagnostics: "producer_missing", diagnostics_gaps: diagnosticsGaps } : {}
|
|
50840
51752
|
};
|
|
50841
51753
|
}
|
|
50842
51754
|
function formatFlag(enabled) {
|
|
@@ -51288,11 +52200,11 @@ function throwSentinel(command) {
|
|
|
51288
52200
|
throw sentinel;
|
|
51289
52201
|
}
|
|
51290
52202
|
var PLUGIN_VERSION = resolvePluginVersion(import.meta.url);
|
|
51291
|
-
var ANNOUNCEMENT_VERSION = "0.
|
|
52203
|
+
var ANNOUNCEMENT_VERSION = "0.60.0";
|
|
51292
52204
|
var ANNOUNCEMENT_FEATURES = [
|
|
51293
|
-
"
|
|
51294
|
-
"
|
|
51295
|
-
"
|
|
52205
|
+
"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.",
|
|
52206
|
+
"`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.",
|
|
52207
|
+
"OpenCode 2 gets native `shell` and `patch` tools, and stopped subagents are no longer woken by their own background commands."
|
|
51296
52208
|
];
|
|
51297
52209
|
var ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/DSa65w8wuf";
|
|
51298
52210
|
var plugin = async (input) => initializePluginForDirectory(input);
|
|
@@ -51785,7 +52697,7 @@ var src_default = plugin;
|
|
|
51785
52697
|
import { Effect as Effect14 } from "effect";
|
|
51786
52698
|
|
|
51787
52699
|
// src/rpc/register.ts
|
|
51788
|
-
import { Effect as
|
|
52700
|
+
import { Effect as Effect7 } from "effect";
|
|
51789
52701
|
|
|
51790
52702
|
// src/tui/aft-rpc.ts
|
|
51791
52703
|
import { Schema } from "effect";
|
|
@@ -51885,7 +52797,7 @@ function restoreOwnProperty(target, key, descriptor) {
|
|
|
51885
52797
|
Reflect.deleteProperty(target, key);
|
|
51886
52798
|
}
|
|
51887
52799
|
function registerAftRpc(context, location, pool) {
|
|
51888
|
-
return
|
|
52800
|
+
return Effect7.gen(function* () {
|
|
51889
52801
|
const root = locationRoot(location);
|
|
51890
52802
|
const bridgeUnsubscribes = new Map;
|
|
51891
52803
|
let disposed = false;
|
|
@@ -51893,7 +52805,7 @@ function registerAftRpc(context, location, pool) {
|
|
|
51893
52805
|
const emit = async (name, payload) => {
|
|
51894
52806
|
if (disposed)
|
|
51895
52807
|
return;
|
|
51896
|
-
await
|
|
52808
|
+
await Effect7.runPromise(registration.events.emit(name, payload));
|
|
51897
52809
|
};
|
|
51898
52810
|
const publishStatus = (snapshot) => {
|
|
51899
52811
|
const sessionID = sessionFromSnapshot(snapshot);
|
|
@@ -51907,21 +52819,21 @@ function registerAftRpc(context, location, pool) {
|
|
|
51907
52819
|
return;
|
|
51908
52820
|
bridgeUnsubscribes.set(bridge, bridge.subscribeStatus(publishStatus));
|
|
51909
52821
|
};
|
|
51910
|
-
const getStatus = (input) =>
|
|
52822
|
+
const getStatus = (input) => Effect7.suspend(() => {
|
|
51911
52823
|
const sessionID = input.sessionID || "rpc";
|
|
51912
52824
|
const bridge = pool.getActiveBridgeForRoot(root);
|
|
51913
52825
|
if (!bridge)
|
|
51914
|
-
return
|
|
52826
|
+
return Effect7.succeed(placeholderStatus());
|
|
51915
52827
|
observeBridge(bridge);
|
|
51916
52828
|
const cached = bridge.getCachedStatus();
|
|
51917
52829
|
const cachedSessionID = cached ? sessionFromSnapshot(cached) : undefined;
|
|
51918
52830
|
if (cached && cachedSessionID === sessionID) {
|
|
51919
|
-
return
|
|
52831
|
+
return Effect7.succeed({ success: true, ...cached });
|
|
51920
52832
|
}
|
|
51921
|
-
return
|
|
52833
|
+
return Effect7.tryPromise({
|
|
51922
52834
|
try: (signal) => bridge.send("status", { session_id: sessionID }, { abortSignal: signal }),
|
|
51923
52835
|
catch: (error) => error
|
|
51924
|
-
}).pipe(
|
|
52836
|
+
}).pipe(Effect7.tap((response) => Effect7.sync(() => {
|
|
51925
52837
|
if (response.success !== false)
|
|
51926
52838
|
bridge.cacheStatusSnapshot(response);
|
|
51927
52839
|
})));
|
|
@@ -51983,21 +52895,21 @@ function registerAftRpc(context, location, pool) {
|
|
|
51983
52895
|
bridgeUnsubscribes.clear();
|
|
51984
52896
|
restoreOwnProperty(pool, "getBridge", ownGetBridge);
|
|
51985
52897
|
restoreOwnProperty(pool, "toolCall", ownToolCall);
|
|
51986
|
-
await
|
|
52898
|
+
await Effect7.runPromise(registration.dispose);
|
|
51987
52899
|
}
|
|
51988
52900
|
};
|
|
51989
52901
|
});
|
|
51990
52902
|
}
|
|
51991
52903
|
function registerAftConfigErrorRpc(context, message) {
|
|
51992
|
-
return
|
|
52904
|
+
return Effect7.gen(function* () {
|
|
51993
52905
|
let disposed = false;
|
|
51994
52906
|
const registration = yield* context.rpc.register(AftRpc, {
|
|
51995
|
-
getStatus: () =>
|
|
52907
|
+
getStatus: () => Effect7.succeed(configErrorStatusSnapshot(message))
|
|
51996
52908
|
});
|
|
51997
52909
|
const emit = async (name, payload) => {
|
|
51998
52910
|
if (disposed)
|
|
51999
52911
|
return;
|
|
52000
|
-
await
|
|
52912
|
+
await Effect7.runPromise(registration.events.emit(name, payload));
|
|
52001
52913
|
};
|
|
52002
52914
|
return {
|
|
52003
52915
|
emitStatusInvalidated: (payload = {}) => emit("statusInvalidated", payload),
|
|
@@ -52008,14 +52920,14 @@ function registerAftConfigErrorRpc(context, message) {
|
|
|
52008
52920
|
if (disposed)
|
|
52009
52921
|
return;
|
|
52010
52922
|
disposed = true;
|
|
52011
|
-
await
|
|
52923
|
+
await Effect7.runPromise(registration.dispose);
|
|
52012
52924
|
}
|
|
52013
52925
|
};
|
|
52014
52926
|
});
|
|
52015
52927
|
}
|
|
52016
52928
|
|
|
52017
52929
|
// src/permissions/v2.ts
|
|
52018
|
-
import { Effect as
|
|
52930
|
+
import { Effect as Effect8 } from "effect";
|
|
52019
52931
|
|
|
52020
52932
|
class PermissionDeniedError extends Error {
|
|
52021
52933
|
name = "PermissionDeniedError";
|
|
@@ -52071,7 +52983,7 @@ function detail(error) {
|
|
|
52071
52983
|
return error instanceof Error ? error.message : String(error);
|
|
52072
52984
|
}
|
|
52073
52985
|
async function runHostEffect(effect) {
|
|
52074
|
-
return await
|
|
52986
|
+
return await Effect8.runPromise(effect);
|
|
52075
52987
|
}
|
|
52076
52988
|
async function sessionRecord(host, sessionID) {
|
|
52077
52989
|
if (!sessionID)
|
|
@@ -52848,11 +53760,11 @@ function v2PromptChannelFor(settings) {
|
|
|
52848
53760
|
return createV2PromptChannel(() => discoverPromptServer(settings));
|
|
52849
53761
|
}
|
|
52850
53762
|
|
|
52851
|
-
// src/v2-
|
|
52852
|
-
import { Effect as
|
|
53763
|
+
// src/v2-prompt-detach.ts
|
|
53764
|
+
import { Effect as Effect10 } from "effect";
|
|
52853
53765
|
|
|
52854
53766
|
// src/wakes/session-delivery.ts
|
|
52855
|
-
import { Effect as
|
|
53767
|
+
import { Effect as Effect9, Exit } from "effect";
|
|
52856
53768
|
var AFT_PROMPT_SOURCE = "aft";
|
|
52857
53769
|
function isAftOriginatedPrompt(metadata) {
|
|
52858
53770
|
return typeof metadata === "object" && metadata !== null && metadata.source === AFT_PROMPT_SOURCE;
|
|
@@ -52866,43 +53778,49 @@ class V2SessionDelivery {
|
|
|
52866
53778
|
this.session = session;
|
|
52867
53779
|
}
|
|
52868
53780
|
completion(input) {
|
|
52869
|
-
return
|
|
53781
|
+
return Effect9.suspend(() => {
|
|
52870
53782
|
if (this.disposed)
|
|
52871
|
-
return
|
|
53783
|
+
return Effect9.succeed(false);
|
|
52872
53784
|
const keys = [...new Set(input.taskIDs)].map((taskID) => `${input.sessionID}\x00${taskID}`);
|
|
52873
53785
|
if (keys.length === 0 || keys.every((key) => this.admittedCompletions.has(key))) {
|
|
52874
|
-
return
|
|
53786
|
+
return Effect9.succeed(false);
|
|
52875
53787
|
}
|
|
52876
53788
|
const reserved = keys.filter((key) => !this.admittedCompletions.has(key));
|
|
52877
53789
|
for (const key of reserved)
|
|
52878
53790
|
this.admittedCompletions.add(key);
|
|
52879
|
-
return
|
|
52880
|
-
|
|
52881
|
-
|
|
52882
|
-
|
|
52883
|
-
|
|
52884
|
-
|
|
52885
|
-
|
|
52886
|
-
|
|
52887
|
-
|
|
52888
|
-
|
|
53791
|
+
return Effect9.suspend(() => this.session.get({ sessionID: input.sessionID }).pipe(Effect9.flatMap((session) => {
|
|
53792
|
+
if (session.revert) {
|
|
53793
|
+
return Effect9.fail(new Error("Background bash completion deferred: session has a staged revert"));
|
|
53794
|
+
}
|
|
53795
|
+
return this.session.synthetic({
|
|
53796
|
+
sessionID: input.sessionID,
|
|
53797
|
+
text: input.text,
|
|
53798
|
+
description: "Background bash completed",
|
|
53799
|
+
delivery: "steer",
|
|
53800
|
+
metadata: {
|
|
53801
|
+
...input.metadata,
|
|
53802
|
+
source: AFT_PROMPT_SOURCE,
|
|
53803
|
+
task_ids: [...new Set(input.taskIDs)]
|
|
53804
|
+
}
|
|
53805
|
+
});
|
|
53806
|
+
}))).pipe(Effect9.onExit((exit) => Effect9.sync(() => {
|
|
52889
53807
|
if (Exit.isFailure(exit))
|
|
52890
53808
|
for (const key of reserved)
|
|
52891
53809
|
this.admittedCompletions.delete(key);
|
|
52892
|
-
})),
|
|
53810
|
+
})), Effect9.map(() => true));
|
|
52893
53811
|
});
|
|
52894
53812
|
}
|
|
52895
53813
|
status(input) {
|
|
52896
|
-
return
|
|
53814
|
+
return Effect9.suspend(() => {
|
|
52897
53815
|
if (this.disposed)
|
|
52898
|
-
return
|
|
53816
|
+
return Effect9.succeed(undefined);
|
|
52899
53817
|
return this.session.synthetic({
|
|
52900
53818
|
sessionID: input.sessionID,
|
|
52901
53819
|
text: input.text,
|
|
52902
53820
|
description: input.description,
|
|
52903
53821
|
metadata: input.metadata,
|
|
52904
53822
|
resume: false
|
|
52905
|
-
}).pipe(
|
|
53823
|
+
}).pipe(Effect9.asVoid);
|
|
52906
53824
|
});
|
|
52907
53825
|
}
|
|
52908
53826
|
dispose() {
|
|
@@ -52913,117 +53831,7 @@ class V2SessionDelivery {
|
|
|
52913
53831
|
}
|
|
52914
53832
|
}
|
|
52915
53833
|
|
|
52916
|
-
// src/v2-host-tool-overlap.ts
|
|
52917
|
-
var OVERLAPS = [
|
|
52918
|
-
{ aft: "apply_patch", host: "patch" },
|
|
52919
|
-
{ aft: "bash", host: "shell" }
|
|
52920
|
-
];
|
|
52921
|
-
var FIX_COMMAND = "`npx @cortexkit/aft doctor --fix`";
|
|
52922
|
-
function findHostToolOverlaps(hostToolNames, registeredAftTools) {
|
|
52923
|
-
return OVERLAPS.filter((pair) => registeredAftTools.has(pair.aft) && hostToolNames.has(pair.host) && !registeredAftTools.has(pair.host));
|
|
52924
|
-
}
|
|
52925
|
-
function hostToolOverlapNotice(overlaps) {
|
|
52926
|
-
const patch = overlaps.some((pair) => pair.host === "patch");
|
|
52927
|
-
const shell = overlaps.some((pair) => pair.host === "shell");
|
|
52928
|
-
if (patch && shell) {
|
|
52929
|
-
return `OpenCode's built-in patch and shell tools are still enabled beside AFT's apply_patch and bash, so the model sees two editing tools and two shells. Run ${FIX_COMMAND} to turn the built-in ones off.`;
|
|
52930
|
-
}
|
|
52931
|
-
if (patch) {
|
|
52932
|
-
return `OpenCode's built-in patch tool is still enabled beside AFT's apply_patch, so the model sees two editing tools. Run ${FIX_COMMAND} to turn the built-in one off.`;
|
|
52933
|
-
}
|
|
52934
|
-
if (shell) {
|
|
52935
|
-
return `OpenCode's built-in shell tool is still enabled beside AFT's bash, so the model sees two shells. Run ${FIX_COMMAND} to turn the built-in one off.`;
|
|
52936
|
-
}
|
|
52937
|
-
return null;
|
|
52938
|
-
}
|
|
52939
|
-
var noticeClaimed = false;
|
|
52940
|
-
var loggedUnavailableReasons = new Set;
|
|
52941
|
-
function logDetectionUnavailable(reason) {
|
|
52942
|
-
if (loggedUnavailableReasons.has(reason))
|
|
52943
|
-
return;
|
|
52944
|
-
loggedUnavailableReasons.add(reason);
|
|
52945
|
-
debug2(`Built-in tool overlap check skipped: ${reason}; no notice shown`);
|
|
52946
|
-
}
|
|
52947
|
-
function reasonOf(error) {
|
|
52948
|
-
return error instanceof Error ? error.message : String(error);
|
|
52949
|
-
}
|
|
52950
|
-
async function hostToolNames(context) {
|
|
52951
|
-
const tool = context.tool;
|
|
52952
|
-
const list = tool?.list;
|
|
52953
|
-
if (typeof list !== "function") {
|
|
52954
|
-
logDetectionUnavailable("the host context has no tool.list()");
|
|
52955
|
-
return null;
|
|
52956
|
-
}
|
|
52957
|
-
let entries;
|
|
52958
|
-
try {
|
|
52959
|
-
entries = await Effect9.runPromise(list.call(tool));
|
|
52960
|
-
} catch (error) {
|
|
52961
|
-
logDetectionUnavailable(`tool.list() failed: ${reasonOf(error)}`);
|
|
52962
|
-
return null;
|
|
52963
|
-
}
|
|
52964
|
-
if (!Array.isArray(entries)) {
|
|
52965
|
-
logDetectionUnavailable("tool.list() did not return a list");
|
|
52966
|
-
return null;
|
|
52967
|
-
}
|
|
52968
|
-
const names = new Set;
|
|
52969
|
-
for (const entry of entries) {
|
|
52970
|
-
const name = typeof entry?.id === "string" ? entry.id : entry?.name;
|
|
52971
|
-
if (typeof name === "string")
|
|
52972
|
-
names.add(name);
|
|
52973
|
-
}
|
|
52974
|
-
return names;
|
|
52975
|
-
}
|
|
52976
|
-
async function checkAndNotify(context, registeredAftTools, deliver) {
|
|
52977
|
-
const names = await hostToolNames(context);
|
|
52978
|
-
if (!names)
|
|
52979
|
-
return;
|
|
52980
|
-
const overlaps = findHostToolOverlaps(names, registeredAftTools);
|
|
52981
|
-
const text = hostToolOverlapNotice(overlaps);
|
|
52982
|
-
if (!text)
|
|
52983
|
-
return;
|
|
52984
|
-
if (noticeClaimed)
|
|
52985
|
-
return;
|
|
52986
|
-
noticeClaimed = true;
|
|
52987
|
-
try {
|
|
52988
|
-
await deliver(text);
|
|
52989
|
-
debug2(`Built-in tool overlap notice sent to the TUI: ${overlaps.map((pair) => `${pair.host} beside ${pair.aft}`).join(", ")}`);
|
|
52990
|
-
} catch (error) {
|
|
52991
|
-
noticeClaimed = false;
|
|
52992
|
-
warn2(`Built-in tool overlap notice could not be delivered: ${reasonOf(error)}`);
|
|
52993
|
-
}
|
|
52994
|
-
}
|
|
52995
|
-
function createHostToolOverlapPromptHook(context, registeredAftTools, deliver, onChecked) {
|
|
52996
|
-
const host = context;
|
|
52997
|
-
let checked = false;
|
|
52998
|
-
return (event) => Effect9.sync(() => {
|
|
52999
|
-
if (checked || noticeClaimed)
|
|
53000
|
-
return;
|
|
53001
|
-
if (isAftOriginatedPrompt(event?.metadata))
|
|
53002
|
-
return;
|
|
53003
|
-
if (typeof event?.sessionID !== "string" || event.sessionID.length === 0)
|
|
53004
|
-
return;
|
|
53005
|
-
checked = true;
|
|
53006
|
-
const done = checkAndNotify(host, registeredAftTools, deliver).catch((error) => {
|
|
53007
|
-
warn2(`Built-in tool overlap check failed: ${reasonOf(error)}`);
|
|
53008
|
-
});
|
|
53009
|
-
onChecked?.(done);
|
|
53010
|
-
});
|
|
53011
|
-
}
|
|
53012
|
-
function registerV2HostToolOverlapNotice(context, registeredAftTools, deliver, onChecked) {
|
|
53013
|
-
if (findHostToolOverlaps(new Set(["patch", "shell"]), registeredAftTools).length === 0) {
|
|
53014
|
-
return Effect9.succeed(false);
|
|
53015
|
-
}
|
|
53016
|
-
const session = context?.session;
|
|
53017
|
-
const hook = session?.hook;
|
|
53018
|
-
if (typeof hook !== "function") {
|
|
53019
|
-
logDetectionUnavailable("the host context has no session prompt hook to check from");
|
|
53020
|
-
return Effect9.succeed(false);
|
|
53021
|
-
}
|
|
53022
|
-
return hook.call(session, "prompt", createHostToolOverlapPromptHook(context, registeredAftTools, deliver, onChecked)).pipe(Effect9.as(true));
|
|
53023
|
-
}
|
|
53024
|
-
|
|
53025
53834
|
// src/v2-prompt-detach.ts
|
|
53026
|
-
import { Effect as Effect10 } from "effect";
|
|
53027
53835
|
function mapOffset(offset, edits) {
|
|
53028
53836
|
let shift = 0;
|
|
53029
53837
|
for (const edit of edits) {
|
|
@@ -53102,25 +53910,55 @@ function gateEditingTools(event, registeredTools) {
|
|
|
53102
53910
|
const prefersPatch = id.includes("gpt-") && !id.includes("oss") && !id.includes("gpt-4");
|
|
53103
53911
|
for (const name of prefersPatch ? ["edit", "write"] : ["apply_patch"]) {
|
|
53104
53912
|
if (registeredTools.has(name))
|
|
53105
|
-
delete event.tools[name];
|
|
53913
|
+
delete event.tools[v2HostToolName(name)];
|
|
53106
53914
|
}
|
|
53107
53915
|
}
|
|
53108
|
-
|
|
53916
|
+
var HOST_SHELL_DESCRIPTION = /^Execute a shell command\b/;
|
|
53917
|
+
function restoreShellDescription(event, registeredTools, shellDescription) {
|
|
53918
|
+
if (shellDescription === undefined || !registeredTools.has("bash"))
|
|
53919
|
+
return;
|
|
53920
|
+
const shell = event.tools[v2HostToolName("bash")];
|
|
53921
|
+
if (!shell || shell.description === shellDescription)
|
|
53922
|
+
return;
|
|
53923
|
+
if (!HOST_SHELL_DESCRIPTION.test(shell.description))
|
|
53924
|
+
return;
|
|
53925
|
+
shell.description = shellDescription;
|
|
53926
|
+
}
|
|
53927
|
+
function registerV2ToolHooks(context, runtime, registeredTools, bashDescription) {
|
|
53109
53928
|
const host = context;
|
|
53929
|
+
const shellDescription = bashDescription === undefined ? undefined : renameV2ToolReferences(bashDescription);
|
|
53110
53930
|
return Effect11.gen(function* () {
|
|
53111
53931
|
if (typeof host.tool?.hook === "function") {
|
|
53932
|
+
yield* host.tool.hook("execute.after", (event) => Effect11.promise(async () => {
|
|
53933
|
+
if (!runtime.pool || event.status !== "completed" || !hasHeldBgCompletions(event.sessionID))
|
|
53934
|
+
return;
|
|
53935
|
+
const content = event.result.content;
|
|
53936
|
+
const output = { output: "" };
|
|
53937
|
+
await appendInTurnBgCompletions({
|
|
53938
|
+
ctx: runtime,
|
|
53939
|
+
directory: context.location.directory,
|
|
53940
|
+
sessionID: event.sessionID
|
|
53941
|
+
}, output);
|
|
53942
|
+
if (!output.output)
|
|
53943
|
+
return;
|
|
53944
|
+
event.result = {
|
|
53945
|
+
...event.result,
|
|
53946
|
+
content: typeof content === "string" ? content + output.output : [...content ?? [], { type: "text", text: output.output }]
|
|
53947
|
+
};
|
|
53948
|
+
}));
|
|
53112
53949
|
yield* host.tool.hook("execute.before", (event) => Effect11.try({
|
|
53113
53950
|
try: () => {
|
|
53114
|
-
|
|
53951
|
+
const name = aftToolNameForV2Host(event.tool);
|
|
53952
|
+
if (!registeredTools.has(name))
|
|
53115
53953
|
return;
|
|
53116
|
-
event.input = prepareOpenCodeArguments(
|
|
53954
|
+
event.input = prepareOpenCodeArguments(name, event.input, {
|
|
53117
53955
|
hashlineEffective: runtime.hashlineEffective
|
|
53118
53956
|
});
|
|
53119
53957
|
},
|
|
53120
53958
|
catch: (error) => new ArgumentError({ message: error instanceof Error ? error.message : String(error) })
|
|
53121
53959
|
}));
|
|
53122
53960
|
yield* host.tool.hook("execute.after", (event) => Effect11.sync(() => {
|
|
53123
|
-
if (event.tool !== "bash" || event.status !== "completed" || !registeredTools.has("bash") || !registeredTools.has("aft_conflicts"))
|
|
53961
|
+
if (aftToolNameForV2Host(event.tool) !== "bash" || event.status !== "completed" || !registeredTools.has("bash") || !registeredTools.has("aft_conflicts"))
|
|
53124
53962
|
return;
|
|
53125
53963
|
const content = event.result.content;
|
|
53126
53964
|
event.result = {
|
|
@@ -53131,10 +53969,14 @@ function registerV2ToolHooks(context, runtime, registeredTools) {
|
|
|
53131
53969
|
}
|
|
53132
53970
|
if (typeof host.session?.hook === "function") {
|
|
53133
53971
|
for (const name of ["compaction", "generate"]) {
|
|
53134
|
-
yield* host.session.hook(name, (event) => Effect11.sync(() =>
|
|
53972
|
+
yield* host.session.hook(name, (event) => Effect11.sync(() => {
|
|
53973
|
+
gateEditingTools(event, registeredTools);
|
|
53974
|
+
restoreShellDescription(event, registeredTools, shellDescription);
|
|
53975
|
+
}));
|
|
53135
53976
|
}
|
|
53136
53977
|
yield* host.session.hook("context", (event) => Effect11.sync(() => {
|
|
53137
53978
|
gateEditingTools(event, registeredTools);
|
|
53979
|
+
restoreShellDescription(event, registeredTools, shellDescription);
|
|
53138
53980
|
rememberReadModel(context, event.sessionID, {
|
|
53139
53981
|
providerID: event.model.providerID,
|
|
53140
53982
|
modelID: event.model.id
|
|
@@ -53279,15 +54121,33 @@ var sharedBridgeOptions = {
|
|
|
53279
54121
|
onBashCompletion: routeCompletion,
|
|
53280
54122
|
onBashLongRunning: routeLongRunning
|
|
53281
54123
|
};
|
|
53282
|
-
function
|
|
54124
|
+
function detachWaitsForCompletion(sessionID, text, bridge, config) {
|
|
54125
|
+
const onlyThisBridge = {
|
|
54126
|
+
getActiveBridgeForRoot: () => bridge,
|
|
54127
|
+
activeBridges: () => [bridge]
|
|
54128
|
+
};
|
|
54129
|
+
interruptBashWaitsForChatMessage(onlyThisBridge, config, "", sessionID, {
|
|
54130
|
+
parts: [{ type: "text", text, synthetic: true }]
|
|
54131
|
+
});
|
|
54132
|
+
}
|
|
54133
|
+
function createV2RuntimeConsumer(context, getConfig = () => ({})) {
|
|
53283
54134
|
const delivery = new V2SessionDelivery(context.session);
|
|
53284
54135
|
const sessions = new Set;
|
|
53285
54136
|
const route = {
|
|
53286
54137
|
completion: async (completion, bridge) => {
|
|
54138
|
+
if (await resolveIsSubagent(context, completion.session_id)) {
|
|
54139
|
+
await awaitV2ExecutionSettlement(context, completion.session_id);
|
|
54140
|
+
}
|
|
54141
|
+
if (await resolveIsSubagent(context, completion.session_id) && !v2WatchWakeAllowed(context, completion.session_id, completion.task_id)) {
|
|
54142
|
+
holdBgCompletion(completion.session_id, completion, v2SubagentStopped(context, completion.session_id));
|
|
54143
|
+
forgetV2Watch(context, completion.session_id, completion.task_id);
|
|
54144
|
+
return;
|
|
54145
|
+
}
|
|
54146
|
+
const text = formatSystemReminder([completion]);
|
|
53287
54147
|
const admitted = await Effect13.runPromise(delivery.completion({
|
|
53288
54148
|
sessionID: completion.session_id,
|
|
53289
54149
|
taskIDs: [completion.task_id],
|
|
53290
|
-
text
|
|
54150
|
+
text,
|
|
53291
54151
|
metadata: {
|
|
53292
54152
|
source: "aft",
|
|
53293
54153
|
kind: "bash_completion",
|
|
@@ -53297,10 +54157,12 @@ function createV2RuntimeConsumer(context) {
|
|
|
53297
54157
|
}));
|
|
53298
54158
|
if (!admitted)
|
|
53299
54159
|
return;
|
|
54160
|
+
detachWaitsForCompletion(completion.session_id, text, bridge, getConfig());
|
|
53300
54161
|
await bridge.send("bash_ack_completions", {
|
|
53301
54162
|
session_id: completion.session_id,
|
|
53302
54163
|
task_ids: [completion.task_id]
|
|
53303
54164
|
});
|
|
54165
|
+
forgetV2Watch(context, completion.session_id, completion.task_id);
|
|
53304
54166
|
},
|
|
53305
54167
|
longRunning: (reminder) => Effect13.runPromise(delivery.status({
|
|
53306
54168
|
sessionID: reminder.session_id,
|
|
@@ -53324,6 +54186,7 @@ function createV2RuntimeConsumer(context) {
|
|
|
53324
54186
|
return {
|
|
53325
54187
|
bridgeOptions: sharedBridgeOptions,
|
|
53326
54188
|
registerSession,
|
|
54189
|
+
onToolAbort: (sessionID) => noteV2ToolAbort(context, sessionID),
|
|
53327
54190
|
executeBash: (execution) => {
|
|
53328
54191
|
registerSession(execution.context.sessionID);
|
|
53329
54192
|
return executeV2Bash(execution);
|
|
@@ -53350,10 +54213,9 @@ var defaults = {
|
|
|
53350
54213
|
registerPromptHook: registerV2PromptDetachHook,
|
|
53351
54214
|
registerToolHooks: registerV2ToolHooks,
|
|
53352
54215
|
registerWorkflowHints: registerV2WorkflowHints,
|
|
53353
|
-
|
|
53354
|
-
toolConsumers: (context, config) => ({
|
|
54216
|
+
toolConsumers: (context, config, getConfig = () => config) => ({
|
|
53355
54217
|
...hoistedV2ToolConsumers(context, v2PromptChannelFor(config?.opencode)),
|
|
53356
|
-
...createV2RuntimeConsumer(context)
|
|
54218
|
+
...createV2RuntimeConsumer(context, getConfig)
|
|
53357
54219
|
}),
|
|
53358
54220
|
acquireBridge,
|
|
53359
54221
|
releaseBridge,
|
|
@@ -53378,7 +54240,8 @@ async function bootLocation(context, location, dependencies) {
|
|
|
53378
54240
|
const pluginVersion = dependencies.resolveVersion();
|
|
53379
54241
|
const environment = await prepareBridgeEnvironment({ configRoot: directory, lspDirectory: directory, config, pluginVersion, notify }, dependencies);
|
|
53380
54242
|
const canonicalDirectory = location.project?.canonical ?? directory;
|
|
53381
|
-
|
|
54243
|
+
let liveConfig = config;
|
|
54244
|
+
const consumers = dependencies.toolConsumers(context, config, () => liveConfig);
|
|
53382
54245
|
const isProjectEnabled = createProjectAcceptance(directory, dependencies);
|
|
53383
54246
|
const pool = await dependencies.acquireBridge(canonicalDirectory, {
|
|
53384
54247
|
harness: "opencode",
|
|
@@ -53403,6 +54266,7 @@ async function bootLocation(context, location, dependencies) {
|
|
|
53403
54266
|
client: context,
|
|
53404
54267
|
config,
|
|
53405
54268
|
hashlineEffective: openCodeHashlineEffective(config),
|
|
54269
|
+
hostToolNames: V2_HOST_TOOL_NAMES,
|
|
53406
54270
|
storageDir: environment.storageDir,
|
|
53407
54271
|
isProjectEnabled
|
|
53408
54272
|
};
|
|
@@ -53411,7 +54275,8 @@ async function bootLocation(context, location, dependencies) {
|
|
|
53411
54275
|
const { hashlineEditRegistered } = applyToolSurfaceOverrides(pool, config, registeredTools);
|
|
53412
54276
|
reportHashlineDowngrade(config, registeredTools, notify);
|
|
53413
54277
|
toolContext.hashlineEffective = hashlineEditRegistered;
|
|
53414
|
-
const
|
|
54278
|
+
const hints = buildHintsForRegisteredTools(config, registeredTools, hashlineEditRegistered);
|
|
54279
|
+
const hintsBlock = hints === null ? null : renameV2ToolReferences(hints);
|
|
53415
54280
|
const liveConfigReload = dependencies.startLiveConfigReload({
|
|
53416
54281
|
directory,
|
|
53417
54282
|
initialSources: bootstrap.sources ?? [],
|
|
@@ -53419,6 +54284,7 @@ async function bootLocation(context, location, dependencies) {
|
|
|
53419
54284
|
getConfig: () => toolContext.config,
|
|
53420
54285
|
setConfig: (next) => {
|
|
53421
54286
|
toolContext.config = next;
|
|
54287
|
+
liveConfig = next;
|
|
53422
54288
|
},
|
|
53423
54289
|
notify
|
|
53424
54290
|
});
|
|
@@ -53470,6 +54336,7 @@ function makeServerEffect(overrides = {}) {
|
|
|
53470
54336
|
}));
|
|
53471
54337
|
const rpc = yield* dependencies.registerRpc(context, location, runtime.pool);
|
|
53472
54338
|
yield* Effect14.addFinalizer(() => Effect14.promise(() => rpc.dispose()));
|
|
54339
|
+
yield* registerV2SessionLifecycle(context, runtime.toolContext);
|
|
53473
54340
|
yield* dependencies.registerPromptHook(context, {
|
|
53474
54341
|
pool: runtime.pool,
|
|
53475
54342
|
projectRoot: runtime.directory,
|
|
@@ -53477,9 +54344,8 @@ function makeServerEffect(overrides = {}) {
|
|
|
53477
54344
|
registerSession: runtime.consumers.registerSession
|
|
53478
54345
|
});
|
|
53479
54346
|
yield* dependencies.registerTools(context, location, runtime.tools, runtime.consumers);
|
|
53480
|
-
yield* dependencies.registerToolHooks(context, runtime.toolContext, new Set(Object.keys(runtime.tools)));
|
|
54347
|
+
yield* dependencies.registerToolHooks(context, runtime.toolContext, new Set(Object.keys(runtime.tools)), runtime.tools.bash?.description);
|
|
53481
54348
|
yield* dependencies.registerWorkflowHints(context, runtime.hintsBlock);
|
|
53482
|
-
yield* dependencies.registerHostToolOverlapNotice(context, new Set(Object.keys(runtime.tools)), (message) => rpc.emitHostToolOverlap({ message }));
|
|
53483
54349
|
});
|
|
53484
54350
|
};
|
|
53485
54351
|
}
|