@cortexkit/aft-pi 0.45.1 → 0.47.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/README.md +1 -1
- package/dist/bash-wait-detach.d.ts +5 -0
- package/dist/bash-wait-detach.d.ts.map +1 -0
- package/dist/config.d.ts +4 -20
- package/dist/config.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1794 -683
- package/dist/logger.d.ts.map +1 -1
- package/dist/lsp-auto-install.d.ts +11 -7
- package/dist/lsp-auto-install.d.ts.map +1 -1
- package/dist/lsp-github-install.d.ts +2 -0
- package/dist/lsp-github-install.d.ts.map +1 -1
- package/dist/tools/_shared.d.ts +10 -1
- package/dist/tools/_shared.d.ts.map +1 -1
- package/dist/tools/bash.d.ts +2 -2
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/hoisted.d.ts +18 -4
- package/dist/tools/hoisted.d.ts.map +1 -1
- package/dist/tools/navigate.d.ts +1 -1
- package/dist/tools/refactor.d.ts +3 -3
- package/dist/tools/semantic.d.ts +1 -0
- package/dist/tools/semantic.d.ts.map +1 -1
- package/package.json +8 -8
package/dist/index.js
CHANGED
|
@@ -9697,6 +9697,16 @@ function isEmptyParam(value) {
|
|
|
9697
9697
|
return Object.keys(value).length === 0;
|
|
9698
9698
|
return false;
|
|
9699
9699
|
}
|
|
9700
|
+
// ../aft-bridge/dist/config-keys.js
|
|
9701
|
+
var OPENCODE_ONLY_KEYS = ["hoist_builtin_tools", "auto_update"];
|
|
9702
|
+
function stripHarnessSpecificConfigKeys(value, keys) {
|
|
9703
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
9704
|
+
return value;
|
|
9705
|
+
const stripped = { ...value };
|
|
9706
|
+
for (const key of keys)
|
|
9707
|
+
delete stripped[key];
|
|
9708
|
+
return stripped;
|
|
9709
|
+
}
|
|
9700
9710
|
// ../aft-bridge/dist/config-tiers.js
|
|
9701
9711
|
import { existsSync, readFileSync as readFileSync2 } from "node:fs";
|
|
9702
9712
|
import { resolve as resolve2 } from "node:path";
|
|
@@ -10017,6 +10027,117 @@ async function fetchLatestTag() {
|
|
|
10017
10027
|
clearTimeout(timeout);
|
|
10018
10028
|
}
|
|
10019
10029
|
}
|
|
10030
|
+
// ../aft-bridge/dist/durable-log.js
|
|
10031
|
+
import { appendFile, mkdir, rename, rm, stat } from "node:fs/promises";
|
|
10032
|
+
import { homedir as homedir5 } from "node:os";
|
|
10033
|
+
import { dirname, join as join4 } from "node:path";
|
|
10034
|
+
var DEFAULT_LOG_BYTES = 20 * 1024 * 1024;
|
|
10035
|
+
var DEFAULT_LOG_GENERATIONS = 5;
|
|
10036
|
+
function homeDir() {
|
|
10037
|
+
if (process.platform === "win32")
|
|
10038
|
+
return process.env.USERPROFILE || process.env.HOME || homedir5();
|
|
10039
|
+
return process.env.HOME || homedir5();
|
|
10040
|
+
}
|
|
10041
|
+
function dataHome() {
|
|
10042
|
+
if (process.env.XDG_DATA_HOME)
|
|
10043
|
+
return process.env.XDG_DATA_HOME;
|
|
10044
|
+
if (process.platform === "win32") {
|
|
10045
|
+
return process.env.LOCALAPPDATA || process.env.APPDATA || join4(homeDir(), "AppData", "Local");
|
|
10046
|
+
}
|
|
10047
|
+
return join4(homeDir(), ".local", "share");
|
|
10048
|
+
}
|
|
10049
|
+
function resolveAftStorageRoot(configuredRoot) {
|
|
10050
|
+
if (configuredRoot)
|
|
10051
|
+
return configuredRoot;
|
|
10052
|
+
if (process.env.AFT_CACHE_DIR)
|
|
10053
|
+
return join4(process.env.AFT_CACHE_DIR, "aft");
|
|
10054
|
+
return join4(dataHome(), "cortexkit", "aft");
|
|
10055
|
+
}
|
|
10056
|
+
function resolveAftLogPath(filename, configuredRoot) {
|
|
10057
|
+
return join4(resolveAftStorageRoot(configuredRoot), "logs", filename);
|
|
10058
|
+
}
|
|
10059
|
+
|
|
10060
|
+
class RotatingLogSink {
|
|
10061
|
+
path;
|
|
10062
|
+
maxBytes;
|
|
10063
|
+
generations;
|
|
10064
|
+
estimatedBytes = null;
|
|
10065
|
+
queue = Promise.resolve();
|
|
10066
|
+
disabled = false;
|
|
10067
|
+
failureReported = false;
|
|
10068
|
+
constructor(path2, options = {}) {
|
|
10069
|
+
this.path = path2;
|
|
10070
|
+
this.maxBytes = options.maxBytes ?? DEFAULT_LOG_BYTES;
|
|
10071
|
+
this.generations = options.generations ?? DEFAULT_LOG_GENERATIONS;
|
|
10072
|
+
}
|
|
10073
|
+
append(data) {
|
|
10074
|
+
if (this.disabled || data.length === 0)
|
|
10075
|
+
return;
|
|
10076
|
+
this.queue = this.queue.then(() => this.write(data)).catch((error2) => {
|
|
10077
|
+
this.disabled = true;
|
|
10078
|
+
if (!this.failureReported) {
|
|
10079
|
+
this.failureReported = true;
|
|
10080
|
+
try {
|
|
10081
|
+
process.stderr.write(`[aft-plugin] durable log disabled for ${this.path}: ${error2 instanceof Error ? error2.message : String(error2)}
|
|
10082
|
+
`);
|
|
10083
|
+
} catch {}
|
|
10084
|
+
}
|
|
10085
|
+
});
|
|
10086
|
+
}
|
|
10087
|
+
async drain() {
|
|
10088
|
+
await this.queue;
|
|
10089
|
+
}
|
|
10090
|
+
async write(data) {
|
|
10091
|
+
await mkdir(dirname(this.path), { recursive: true });
|
|
10092
|
+
if (this.estimatedBytes === null) {
|
|
10093
|
+
try {
|
|
10094
|
+
this.estimatedBytes = (await stat(this.path)).size;
|
|
10095
|
+
} catch (error2) {
|
|
10096
|
+
if (!isMissing(error2))
|
|
10097
|
+
throw error2;
|
|
10098
|
+
this.estimatedBytes = 0;
|
|
10099
|
+
}
|
|
10100
|
+
}
|
|
10101
|
+
const bytes = Buffer.byteLength(data);
|
|
10102
|
+
if (this.estimatedBytes > 0 && this.estimatedBytes + bytes > this.maxBytes) {
|
|
10103
|
+
await this.rotate();
|
|
10104
|
+
}
|
|
10105
|
+
await appendFile(this.path, data, "utf8");
|
|
10106
|
+
this.estimatedBytes = (this.estimatedBytes ?? 0) + bytes;
|
|
10107
|
+
}
|
|
10108
|
+
async rotate() {
|
|
10109
|
+
if (this.generations <= 0) {
|
|
10110
|
+
await removeIfPresent(this.path);
|
|
10111
|
+
this.estimatedBytes = 0;
|
|
10112
|
+
return;
|
|
10113
|
+
}
|
|
10114
|
+
await removeIfPresent(`${this.path}.${this.generations}`);
|
|
10115
|
+
for (let generation = this.generations - 1;generation >= 1; generation -= 1) {
|
|
10116
|
+
await renameIfPresent(`${this.path}.${generation}`, `${this.path}.${generation + 1}`);
|
|
10117
|
+
}
|
|
10118
|
+
await renameIfPresent(this.path, `${this.path}.1`);
|
|
10119
|
+
this.estimatedBytes = 0;
|
|
10120
|
+
}
|
|
10121
|
+
}
|
|
10122
|
+
function isMissing(error2) {
|
|
10123
|
+
return typeof error2 === "object" && error2 !== null && "code" in error2 && error2.code === "ENOENT";
|
|
10124
|
+
}
|
|
10125
|
+
async function removeIfPresent(path2) {
|
|
10126
|
+
try {
|
|
10127
|
+
await rm(path2, { force: true });
|
|
10128
|
+
} catch (error2) {
|
|
10129
|
+
if (!isMissing(error2))
|
|
10130
|
+
throw error2;
|
|
10131
|
+
}
|
|
10132
|
+
}
|
|
10133
|
+
async function renameIfPresent(from, to) {
|
|
10134
|
+
try {
|
|
10135
|
+
await rename(from, to);
|
|
10136
|
+
} catch (error2) {
|
|
10137
|
+
if (!isMissing(error2))
|
|
10138
|
+
throw error2;
|
|
10139
|
+
}
|
|
10140
|
+
}
|
|
10020
10141
|
// ../aft-bridge/dist/edit-summary.js
|
|
10021
10142
|
function formatEditSummary(data) {
|
|
10022
10143
|
if (data.rolled_back === true) {
|
|
@@ -10087,32 +10208,32 @@ function stripJsoncSymbols(value) {
|
|
|
10087
10208
|
// ../aft-bridge/dist/migration.js
|
|
10088
10209
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
10089
10210
|
import { closeSync as closeSync3, existsSync as existsSync5, mkdirSync as mkdirSync4, openSync as openSync3, readFileSync as readFileSync5, renameSync as renameSync4, rmSync as rmSync2, statSync as statSync2, unlinkSync as unlinkSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
10090
|
-
import { homedir as
|
|
10091
|
-
import { basename, dirname as
|
|
10211
|
+
import { homedir as homedir8, tmpdir } from "node:os";
|
|
10212
|
+
import { basename, dirname as dirname3, join as join7, resolve as resolve4 } from "node:path";
|
|
10092
10213
|
|
|
10093
10214
|
// ../aft-bridge/dist/paths.js
|
|
10094
10215
|
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync4, renameSync as renameSync2, writeFileSync } from "node:fs";
|
|
10095
|
-
import { homedir as
|
|
10096
|
-
import { dirname, isAbsolute as isAbsolute2, join as
|
|
10097
|
-
function
|
|
10216
|
+
import { homedir as homedir6 } from "node:os";
|
|
10217
|
+
import { dirname as dirname2, isAbsolute as isAbsolute2, join as join5, resolve as resolve3 } from "node:path";
|
|
10218
|
+
function homeDir2() {
|
|
10098
10219
|
if (process.platform === "win32")
|
|
10099
|
-
return process.env.USERPROFILE || process.env.HOME ||
|
|
10100
|
-
return process.env.HOME ||
|
|
10220
|
+
return process.env.USERPROFILE || process.env.HOME || homedir6();
|
|
10221
|
+
return process.env.HOME || homedir6();
|
|
10101
10222
|
}
|
|
10102
10223
|
function configHome() {
|
|
10103
10224
|
const xdg = process.env.XDG_CONFIG_HOME;
|
|
10104
10225
|
if (xdg && isAbsolute2(xdg))
|
|
10105
10226
|
return xdg;
|
|
10106
|
-
return
|
|
10227
|
+
return join5(homeDir2(), ".config");
|
|
10107
10228
|
}
|
|
10108
10229
|
function legacyOpenCodeConfigDir() {
|
|
10109
10230
|
const envDir = process.env.OPENCODE_CONFIG_DIR?.trim();
|
|
10110
10231
|
if (envDir)
|
|
10111
10232
|
return resolve3(envDir);
|
|
10112
|
-
return
|
|
10233
|
+
return join5(configHome(), "opencode");
|
|
10113
10234
|
}
|
|
10114
10235
|
function legacyPiAgentDir() {
|
|
10115
|
-
return
|
|
10236
|
+
return join5(homeDir2(), ".pi", "agent");
|
|
10116
10237
|
}
|
|
10117
10238
|
function legacySources(basePath, label, harness) {
|
|
10118
10239
|
return [
|
|
@@ -10121,10 +10242,10 @@ function legacySources(basePath, label, harness) {
|
|
|
10121
10242
|
];
|
|
10122
10243
|
}
|
|
10123
10244
|
function resolveCortexKitUserConfigPath() {
|
|
10124
|
-
return
|
|
10245
|
+
return join5(configHome(), "cortexkit", "aft.jsonc");
|
|
10125
10246
|
}
|
|
10126
10247
|
function resolveCortexKitProjectConfigPath(projectDirectory) {
|
|
10127
|
-
return
|
|
10248
|
+
return join5(projectDirectory, ".cortexkit", "aft.jsonc");
|
|
10128
10249
|
}
|
|
10129
10250
|
function resolveCortexKitConfigPaths(projectDirectory) {
|
|
10130
10251
|
return {
|
|
@@ -10135,25 +10256,25 @@ function resolveCortexKitConfigPaths(projectDirectory) {
|
|
|
10135
10256
|
function resolveLegacyAftConfigSources(projectDirectory) {
|
|
10136
10257
|
return {
|
|
10137
10258
|
user: [
|
|
10138
|
-
...legacySources(
|
|
10139
|
-
...legacySources(
|
|
10259
|
+
...legacySources(join5(legacyOpenCodeConfigDir(), "aft"), "OpenCode user", "opencode"),
|
|
10260
|
+
...legacySources(join5(legacyPiAgentDir(), "aft"), "Pi user", "pi")
|
|
10140
10261
|
],
|
|
10141
10262
|
project: [
|
|
10142
|
-
...legacySources(
|
|
10143
|
-
...legacySources(
|
|
10263
|
+
...legacySources(join5(projectDirectory, ".opencode", "aft"), "OpenCode project", "opencode"),
|
|
10264
|
+
...legacySources(join5(projectDirectory, ".pi", "aft"), "Pi project", "pi")
|
|
10144
10265
|
]
|
|
10145
10266
|
};
|
|
10146
10267
|
}
|
|
10147
10268
|
function resolveHarnessStoragePath(storageRoot, harness, ...segments) {
|
|
10148
|
-
return
|
|
10269
|
+
return join5(storageRoot, harness, ...segments);
|
|
10149
10270
|
}
|
|
10150
10271
|
function repairRootScopedStorageFile(storageRoot, harness, fileName) {
|
|
10151
10272
|
const harnessPath = resolveHarnessStoragePath(storageRoot, harness, fileName);
|
|
10152
|
-
const rootPath =
|
|
10273
|
+
const rootPath = join5(storageRoot, fileName);
|
|
10153
10274
|
if (existsSync3(harnessPath) || !existsSync3(rootPath))
|
|
10154
10275
|
return harnessPath;
|
|
10155
10276
|
try {
|
|
10156
|
-
mkdirSync2(
|
|
10277
|
+
mkdirSync2(dirname2(harnessPath), { recursive: true });
|
|
10157
10278
|
renameSync2(rootPath, harnessPath);
|
|
10158
10279
|
} catch {}
|
|
10159
10280
|
return harnessPath;
|
|
@@ -10174,7 +10295,7 @@ function shouldShowAnnouncement(storageRoot, harness, currentVersion) {
|
|
|
10174
10295
|
return false;
|
|
10175
10296
|
if (!lastVersion) {
|
|
10176
10297
|
try {
|
|
10177
|
-
mkdirSync2(
|
|
10298
|
+
mkdirSync2(dirname2(versionFile), { recursive: true });
|
|
10178
10299
|
writeFileSync(versionFile, currentVersion);
|
|
10179
10300
|
} catch {}
|
|
10180
10301
|
return false;
|
|
@@ -10186,7 +10307,7 @@ function markAnnouncementSeen(storageRoot, harness, currentVersion) {
|
|
|
10186
10307
|
return;
|
|
10187
10308
|
const versionFile = repairRootScopedStorageFile(storageRoot, harness, "last_announced_version");
|
|
10188
10309
|
try {
|
|
10189
|
-
mkdirSync2(
|
|
10310
|
+
mkdirSync2(dirname2(versionFile), { recursive: true });
|
|
10190
10311
|
writeFileSync(versionFile, currentVersion);
|
|
10191
10312
|
} catch {}
|
|
10192
10313
|
}
|
|
@@ -10195,8 +10316,8 @@ function markAnnouncementSeen(storageRoot, harness, currentVersion) {
|
|
|
10195
10316
|
import { execSync } from "node:child_process";
|
|
10196
10317
|
import { chmodSync as chmodSync2, closeSync as closeSync2, copyFileSync as copyFileSync2, existsSync as existsSync4, mkdirSync as mkdirSync3, openSync as openSync2, readSync, renameSync as renameSync3, unlinkSync as unlinkSync2 } from "node:fs";
|
|
10197
10318
|
import { createRequire as createRequire2 } from "node:module";
|
|
10198
|
-
import { homedir as
|
|
10199
|
-
import { join as
|
|
10319
|
+
import { homedir as homedir7 } from "node:os";
|
|
10320
|
+
import { join as join6 } from "node:path";
|
|
10200
10321
|
var ensureBinaryForResolver = ensureBinary;
|
|
10201
10322
|
function copyToVersionedCache(npmBinaryPath, knownVersion) {
|
|
10202
10323
|
try {
|
|
@@ -10205,9 +10326,9 @@ function copyToVersionedCache(npmBinaryPath, knownVersion) {
|
|
|
10205
10326
|
return null;
|
|
10206
10327
|
const tag = version.startsWith("v") ? version : `v${version}`;
|
|
10207
10328
|
const cacheDir = getCacheDir();
|
|
10208
|
-
const versionedDir =
|
|
10329
|
+
const versionedDir = join6(cacheDir, tag);
|
|
10209
10330
|
const ext = process.platform === "win32" ? ".exe" : "";
|
|
10210
|
-
const cachedPath =
|
|
10331
|
+
const cachedPath = join6(versionedDir, `aft${ext}`);
|
|
10211
10332
|
if (existsSync4(cachedPath)) {
|
|
10212
10333
|
const cachedVersion = readBinaryVersion(cachedPath);
|
|
10213
10334
|
if (cachedVersion === version)
|
|
@@ -10237,18 +10358,18 @@ function normalizeBareVersion(version) {
|
|
|
10237
10358
|
return version.startsWith("v") ? version.slice(1) : version;
|
|
10238
10359
|
}
|
|
10239
10360
|
function homeDirFromEnv(env) {
|
|
10240
|
-
return (process.platform === "win32" ? env.USERPROFILE || env.HOME : env.HOME) ||
|
|
10361
|
+
return (process.platform === "win32" ? env.USERPROFILE || env.HOME : env.HOME) || homedir7();
|
|
10241
10362
|
}
|
|
10242
10363
|
function cacheDirFromEnv(env) {
|
|
10243
10364
|
if (process.platform === "win32") {
|
|
10244
|
-
const base2 = env.LOCALAPPDATA || env.APPDATA ||
|
|
10245
|
-
return
|
|
10365
|
+
const base2 = env.LOCALAPPDATA || env.APPDATA || join6(homeDirFromEnv(env), "AppData", "Local");
|
|
10366
|
+
return join6(base2, "aft", "bin");
|
|
10246
10367
|
}
|
|
10247
|
-
const base = env.XDG_CACHE_HOME ||
|
|
10248
|
-
return
|
|
10368
|
+
const base = env.XDG_CACHE_HOME || join6(homeDirFromEnv(env), ".cache");
|
|
10369
|
+
return join6(base, "aft", "bin");
|
|
10249
10370
|
}
|
|
10250
10371
|
function cachedBinaryPathFromEnv(version, env, ext) {
|
|
10251
|
-
const binaryPath =
|
|
10372
|
+
const binaryPath = join6(cacheDirFromEnv(env), version, `aft${ext}`);
|
|
10252
10373
|
return existsSync4(binaryPath) ? binaryPath : null;
|
|
10253
10374
|
}
|
|
10254
10375
|
function isExpectedCachedBinary2(binaryPath, expectedVersion) {
|
|
@@ -10367,7 +10488,7 @@ function findBinarySync(expectedVersion) {
|
|
|
10367
10488
|
return usable;
|
|
10368
10489
|
}
|
|
10369
10490
|
} catch {}
|
|
10370
|
-
const cargoPath =
|
|
10491
|
+
const cargoPath = join6(homeDirFromEnv(env), ".cargo", "bin", `aft${ext}`);
|
|
10371
10492
|
if (existsSync4(cargoPath)) {
|
|
10372
10493
|
const usable = probeBinaryCandidate(cargoPath, "cargo", expectedVersion);
|
|
10373
10494
|
if (usable)
|
|
@@ -10409,26 +10530,26 @@ async function findBinary(expectedVersion) {
|
|
|
10409
10530
|
var spawnSyncForMigration = spawnSync2;
|
|
10410
10531
|
var TARGET_MARKER = ".migrated_from_legacy";
|
|
10411
10532
|
var DEFAULT_TIMEOUT_MS = 30 * 60 * 1000;
|
|
10412
|
-
function
|
|
10533
|
+
function dataHome2() {
|
|
10413
10534
|
if (process.env.XDG_DATA_HOME)
|
|
10414
10535
|
return process.env.XDG_DATA_HOME;
|
|
10415
10536
|
if (process.platform === "win32") {
|
|
10416
|
-
return process.env.LOCALAPPDATA || process.env.APPDATA ||
|
|
10537
|
+
return process.env.LOCALAPPDATA || process.env.APPDATA || join7(homeDir3(), "AppData", "Local");
|
|
10417
10538
|
}
|
|
10418
|
-
return
|
|
10539
|
+
return join7(homeDir3(), ".local", "share");
|
|
10419
10540
|
}
|
|
10420
|
-
function
|
|
10541
|
+
function homeDir3() {
|
|
10421
10542
|
if (process.platform === "win32")
|
|
10422
|
-
return process.env.USERPROFILE || process.env.HOME ||
|
|
10423
|
-
return process.env.HOME ||
|
|
10543
|
+
return process.env.USERPROFILE || process.env.HOME || homedir8();
|
|
10544
|
+
return process.env.HOME || homedir8();
|
|
10424
10545
|
}
|
|
10425
10546
|
function resolveLegacyStorageRoot(harness) {
|
|
10426
10547
|
if (harness === "pi")
|
|
10427
|
-
return
|
|
10428
|
-
return
|
|
10548
|
+
return join7(homeDir3(), ".pi", "agent", "aft");
|
|
10549
|
+
return join7(dataHome2(), "opencode", "storage", "plugin", "aft");
|
|
10429
10550
|
}
|
|
10430
10551
|
function resolveCortexKitStorageRoot() {
|
|
10431
|
-
return
|
|
10552
|
+
return join7(dataHome2(), "cortexkit", "aft");
|
|
10432
10553
|
}
|
|
10433
10554
|
function stripJsoncForParse(input) {
|
|
10434
10555
|
let out = "";
|
|
@@ -10557,8 +10678,8 @@ function acquireConfigMigrationLock(lockDir) {
|
|
|
10557
10678
|
}
|
|
10558
10679
|
}
|
|
10559
10680
|
function atomicCopyConfigFile(sourcePath, targetPath) {
|
|
10560
|
-
mkdirSync4(
|
|
10561
|
-
const tmpPath =
|
|
10681
|
+
mkdirSync4(dirname3(targetPath), { recursive: true });
|
|
10682
|
+
const tmpPath = join7(dirname3(targetPath), `.${basename(targetPath)}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`);
|
|
10562
10683
|
let fd = null;
|
|
10563
10684
|
try {
|
|
10564
10685
|
fd = openSync3(tmpPath, "wx", 384);
|
|
@@ -10579,8 +10700,8 @@ function atomicCopyConfigFile(sourcePath, targetPath) {
|
|
|
10579
10700
|
}
|
|
10580
10701
|
}
|
|
10581
10702
|
function atomicWriteConfigFile(targetPath, content) {
|
|
10582
|
-
mkdirSync4(
|
|
10583
|
-
const tmpPath =
|
|
10703
|
+
mkdirSync4(dirname3(targetPath), { recursive: true });
|
|
10704
|
+
const tmpPath = join7(dirname3(targetPath), `.${basename(targetPath)}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`);
|
|
10584
10705
|
let fd = null;
|
|
10585
10706
|
try {
|
|
10586
10707
|
fd = openSync3(tmpPath, "wx", 384);
|
|
@@ -10705,7 +10826,7 @@ function migrateAftConfigFile(opts) {
|
|
|
10705
10826
|
if (existingSources.length === 0) {
|
|
10706
10827
|
return { migrated: false, conflict: false, targetPath: opts.targetPath, warnings };
|
|
10707
10828
|
}
|
|
10708
|
-
mkdirSync4(
|
|
10829
|
+
mkdirSync4(dirname3(opts.targetPath), { recursive: true });
|
|
10709
10830
|
const release = acquireConfigMigrationLock(`${opts.targetPath}.lock`);
|
|
10710
10831
|
try {
|
|
10711
10832
|
const sources = existingSources.map((source) => ({
|
|
@@ -10762,13 +10883,13 @@ function spawnErrorLabel(error2) {
|
|
|
10762
10883
|
return [code, error2.message].filter(Boolean).join(": ");
|
|
10763
10884
|
}
|
|
10764
10885
|
function migrationLogPath(newRoot, harness, logger) {
|
|
10765
|
-
const desired =
|
|
10886
|
+
const desired = join7(newRoot, "logs", "migration", `${harness}-${Date.now()}.jsonl`);
|
|
10766
10887
|
try {
|
|
10767
|
-
mkdirSync4(
|
|
10888
|
+
mkdirSync4(dirname3(desired), { recursive: true });
|
|
10768
10889
|
return desired;
|
|
10769
10890
|
} catch (err) {
|
|
10770
|
-
const fallback =
|
|
10771
|
-
logger?.warn?.(`Failed to create AFT migration log directory ${
|
|
10891
|
+
const fallback = join7(tmpdir(), `aft-migration-${harness}-${Date.now()}.jsonl`);
|
|
10892
|
+
logger?.warn?.(`Failed to create AFT migration log directory ${dirname3(desired)}: ${err instanceof Error ? err.message : String(err)}. ` + `Using fallback log path ${fallback}.`);
|
|
10772
10893
|
return fallback;
|
|
10773
10894
|
}
|
|
10774
10895
|
}
|
|
@@ -10830,13 +10951,13 @@ async function ensureStorageMigrated(opts) {
|
|
|
10830
10951
|
}
|
|
10831
10952
|
// ../aft-bridge/dist/npm-resolver.js
|
|
10832
10953
|
import { readdirSync, statSync as statSync3 } from "node:fs";
|
|
10833
|
-
import { homedir as
|
|
10834
|
-
import { delimiter, dirname as
|
|
10954
|
+
import { homedir as homedir9 } from "node:os";
|
|
10955
|
+
import { delimiter, dirname as dirname4, isAbsolute as isAbsolute3, join as join8 } from "node:path";
|
|
10835
10956
|
function defaultDeps() {
|
|
10836
10957
|
return {
|
|
10837
10958
|
platform: process.platform,
|
|
10838
10959
|
env: process.env,
|
|
10839
|
-
home:
|
|
10960
|
+
home: homedir9(),
|
|
10840
10961
|
execPath: process.execPath
|
|
10841
10962
|
};
|
|
10842
10963
|
}
|
|
@@ -10857,14 +10978,14 @@ function npmFromPath(deps) {
|
|
|
10857
10978
|
const dir = entry.trim().replace(/^"|"$/g, "");
|
|
10858
10979
|
if (!dir || !isAbsolute3(dir))
|
|
10859
10980
|
continue;
|
|
10860
|
-
if (isFile(
|
|
10981
|
+
if (isFile(join8(dir, name)))
|
|
10861
10982
|
return dir;
|
|
10862
10983
|
}
|
|
10863
10984
|
return null;
|
|
10864
10985
|
}
|
|
10865
10986
|
function npmAdjacentToNode(deps) {
|
|
10866
|
-
const dir =
|
|
10867
|
-
return isFile(
|
|
10987
|
+
const dir = dirname4(deps.execPath);
|
|
10988
|
+
return isFile(join8(dir, npmBinaryName(deps.platform))) ? dir : null;
|
|
10868
10989
|
}
|
|
10869
10990
|
function highestVersionedNodeBin(installsDir, name) {
|
|
10870
10991
|
let entries;
|
|
@@ -10873,8 +10994,8 @@ function highestVersionedNodeBin(installsDir, name) {
|
|
|
10873
10994
|
} catch {
|
|
10874
10995
|
return null;
|
|
10875
10996
|
}
|
|
10876
|
-
const candidates = entries.filter((v) => isFile(
|
|
10877
|
-
return candidates.length > 0 ?
|
|
10997
|
+
const candidates = entries.filter((v) => isFile(join8(installsDir, v, "bin", name))).sort((a, b) => compareVersionsDesc(a, b));
|
|
10998
|
+
return candidates.length > 0 ? join8(installsDir, candidates[0], "bin") : null;
|
|
10878
10999
|
}
|
|
10879
11000
|
function compareVersionsDesc(a, b) {
|
|
10880
11001
|
const pa = a.replace(/^v/, "").split(".").map((n) => Number.parseInt(n, 10));
|
|
@@ -10899,22 +11020,22 @@ function wellKnownNpmDirs(deps) {
|
|
|
10899
11020
|
const programFiles = env.ProgramFiles || "C:\\Program Files";
|
|
10900
11021
|
const appData = env.APPDATA;
|
|
10901
11022
|
const localAppData = env.LOCALAPPDATA;
|
|
10902
|
-
push(
|
|
11023
|
+
push(join8(programFiles, "nodejs"));
|
|
10903
11024
|
if (appData)
|
|
10904
|
-
push(
|
|
11025
|
+
push(join8(appData, "npm"));
|
|
10905
11026
|
if (localAppData)
|
|
10906
|
-
push(
|
|
11027
|
+
push(join8(localAppData, "Volta", "bin"));
|
|
10907
11028
|
if (env.NVM_SYMLINK)
|
|
10908
11029
|
push(env.NVM_SYMLINK);
|
|
10909
11030
|
} else {
|
|
10910
11031
|
if (env.NVM_BIN)
|
|
10911
11032
|
push(env.NVM_BIN);
|
|
10912
|
-
push(highestVersionedNodeBin(
|
|
10913
|
-
push(highestVersionedNodeBin(
|
|
10914
|
-
push(highestVersionedNodeBin(
|
|
10915
|
-
push(
|
|
10916
|
-
push(
|
|
10917
|
-
const systemDirs = deps.systemNpmDirs ?? (platform === "darwin" ? ["/opt/homebrew/bin", "/usr/local/bin"] : ["/usr/local/bin", "/usr/bin",
|
|
11033
|
+
push(highestVersionedNodeBin(join8(home, ".nvm", "versions", "node"), name));
|
|
11034
|
+
push(highestVersionedNodeBin(join8(home, ".local", "share", "mise", "installs", "node"), name));
|
|
11035
|
+
push(highestVersionedNodeBin(join8(home, ".asdf", "installs", "nodejs"), name));
|
|
11036
|
+
push(join8(home, ".volta", "bin"));
|
|
11037
|
+
push(join8(home, ".asdf", "shims"));
|
|
11038
|
+
const systemDirs = deps.systemNpmDirs ?? (platform === "darwin" ? ["/opt/homebrew/bin", "/usr/local/bin"] : ["/usr/local/bin", "/usr/bin", join8(home, ".local", "bin")]);
|
|
10918
11039
|
for (const dir of systemDirs)
|
|
10919
11040
|
push(dir);
|
|
10920
11041
|
}
|
|
@@ -10924,12 +11045,12 @@ function resolveNpm(deps = defaultDeps()) {
|
|
|
10924
11045
|
const name = npmBinaryName(deps.platform);
|
|
10925
11046
|
const onPath = npmFromPath(deps);
|
|
10926
11047
|
if (onPath)
|
|
10927
|
-
return { command:
|
|
11048
|
+
return { command: join8(onPath, name), binDir: onPath };
|
|
10928
11049
|
const adjacent = npmAdjacentToNode(deps);
|
|
10929
11050
|
if (adjacent)
|
|
10930
|
-
return { command:
|
|
11051
|
+
return { command: join8(adjacent, name), binDir: adjacent };
|
|
10931
11052
|
for (const dir of wellKnownNpmDirs(deps)) {
|
|
10932
|
-
const candidate =
|
|
11053
|
+
const candidate = join8(dir, name);
|
|
10933
11054
|
if (isFile(candidate))
|
|
10934
11055
|
return { command: candidate, binDir: dir };
|
|
10935
11056
|
}
|
|
@@ -10946,7 +11067,7 @@ function npmSpawnEnv(resolved, baseEnv = process.env) {
|
|
|
10946
11067
|
import { execFileSync } from "node:child_process";
|
|
10947
11068
|
import { createHash as createHash3 } from "node:crypto";
|
|
10948
11069
|
import { chmodSync as chmodSync3, closeSync as closeSync4, copyFileSync as copyFileSync3, createWriteStream as createWriteStream2, existsSync as existsSync6, lstatSync, mkdirSync as mkdirSync5, openSync as openSync4, readdirSync as readdirSync2, readFileSync as readFileSync6, readlinkSync, realpathSync, rmSync as rmSync3, statSync as statSync4, symlinkSync, unlinkSync as unlinkSync4, writeFileSync as writeFileSync3 } from "node:fs";
|
|
10949
|
-
import { basename as basename2, dirname as
|
|
11070
|
+
import { basename as basename2, dirname as dirname5, isAbsolute as isAbsolute4, join as join9, relative as relative2, resolve as resolve5, win32 } from "node:path";
|
|
10950
11071
|
import { Readable as Readable2 } from "node:stream";
|
|
10951
11072
|
import { pipeline as pipeline2 } from "node:stream/promises";
|
|
10952
11073
|
var ORT_VERSION = "1.24.4";
|
|
@@ -11009,10 +11130,10 @@ function getManualInstallHint() {
|
|
|
11009
11130
|
}
|
|
11010
11131
|
async function ensureOnnxRuntime(storageDir) {
|
|
11011
11132
|
const info = getPlatformInfo();
|
|
11012
|
-
const ortVersionDir =
|
|
11133
|
+
const ortVersionDir = join9(storageDir, "onnxruntime", ORT_VERSION);
|
|
11013
11134
|
const libName = info?.libName ?? "libonnxruntime.dylib";
|
|
11014
11135
|
const resolvedOrtDir = resolveCachedOnnxRuntimeDir(ortVersionDir, libName);
|
|
11015
|
-
const libPath =
|
|
11136
|
+
const libPath = join9(resolvedOrtDir, libName);
|
|
11016
11137
|
if (existsSync6(libPath)) {
|
|
11017
11138
|
const meta = readOnnxInstalledMeta(ortVersionDir);
|
|
11018
11139
|
if (meta?.sha256) {
|
|
@@ -11042,9 +11163,9 @@ async function ensureOnnxRuntime(storageDir) {
|
|
|
11042
11163
|
warn(`ONNX Runtime auto-download not available for ${process.platform}/${process.arch}. Install manually: ${getManualInstallHint()}`);
|
|
11043
11164
|
return null;
|
|
11044
11165
|
}
|
|
11045
|
-
const onnxBaseDir =
|
|
11166
|
+
const onnxBaseDir = join9(storageDir, "onnxruntime");
|
|
11046
11167
|
mkdirSync5(onnxBaseDir, { recursive: true });
|
|
11047
|
-
const lockPath =
|
|
11168
|
+
const lockPath = join9(onnxBaseDir, ONNX_LOCK_FILE);
|
|
11048
11169
|
cleanupAbandonedStagingDirs(onnxBaseDir);
|
|
11049
11170
|
if (!acquireLock(lockPath)) {
|
|
11050
11171
|
warn(`ONNX Runtime install already in progress in another process (lock: ${lockPath}). Skipping.`);
|
|
@@ -11063,7 +11184,7 @@ function cleanupAbandonedStagingDirs(onnxBaseDir) {
|
|
|
11063
11184
|
for (const entry of entries) {
|
|
11064
11185
|
if (!entry.startsWith(`${ORT_VERSION}.tmp.`))
|
|
11065
11186
|
continue;
|
|
11066
|
-
const stagingDir =
|
|
11187
|
+
const stagingDir = join9(onnxBaseDir, entry);
|
|
11067
11188
|
const parts = entry.split(".");
|
|
11068
11189
|
const pidStr = parts[parts.length - 2];
|
|
11069
11190
|
const pid = pidStr ? Number.parseInt(pidStr, 10) : NaN;
|
|
@@ -11100,7 +11221,7 @@ function cleanupAbandonedStagingDirs(onnxBaseDir) {
|
|
|
11100
11221
|
}
|
|
11101
11222
|
function cleanupIncompleteTargetIfUnowned(ortDir) {
|
|
11102
11223
|
try {
|
|
11103
|
-
if (existsSync6(ortDir) && !existsSync6(
|
|
11224
|
+
if (existsSync6(ortDir) && !existsSync6(join9(ortDir, ONNX_INSTALLED_META_FILE))) {
|
|
11104
11225
|
log(`[onnx] removing half-populated install dir ${ortDir} (no meta file)`);
|
|
11105
11226
|
rmSync3(ortDir, { recursive: true, force: true });
|
|
11106
11227
|
}
|
|
@@ -11144,7 +11265,7 @@ function detectOnnxVersion(libDir, libName) {
|
|
|
11144
11265
|
if (version)
|
|
11145
11266
|
return version;
|
|
11146
11267
|
}
|
|
11147
|
-
const base =
|
|
11268
|
+
const base = join9(libDir, libName);
|
|
11148
11269
|
if (existsSync6(base)) {
|
|
11149
11270
|
try {
|
|
11150
11271
|
const real = realpathSync(base);
|
|
@@ -11183,6 +11304,17 @@ function pathEntriesForPlatform() {
|
|
|
11183
11304
|
return isAbsolute4(entry) || win32.isAbsolute(entry);
|
|
11184
11305
|
});
|
|
11185
11306
|
}
|
|
11307
|
+
function isWindowsSystem32Directory(dir) {
|
|
11308
|
+
if (process.platform !== "win32")
|
|
11309
|
+
return false;
|
|
11310
|
+
const normalizedDir = win32.resolve(dir).replace(/[\\/]+$/, "").toLowerCase();
|
|
11311
|
+
const windowsRoots = [process.env.SystemRoot, process.env.windir, "C:\\Windows"];
|
|
11312
|
+
return windowsRoots.some((root) => {
|
|
11313
|
+
if (!root)
|
|
11314
|
+
return false;
|
|
11315
|
+
return normalizedDir === win32.resolve(root, "System32").replace(/[\\/]+$/, "").toLowerCase();
|
|
11316
|
+
});
|
|
11317
|
+
}
|
|
11186
11318
|
function directoryContainsLibrary(dir, libName) {
|
|
11187
11319
|
try {
|
|
11188
11320
|
const entries = readdirSync2(dir);
|
|
@@ -11196,10 +11328,10 @@ function directoryContainsLibrary(dir, libName) {
|
|
|
11196
11328
|
}
|
|
11197
11329
|
}
|
|
11198
11330
|
function resolveCachedOnnxRuntimeDir(ortVersionDir, libName) {
|
|
11199
|
-
if (existsSync6(
|
|
11331
|
+
if (existsSync6(join9(ortVersionDir, libName)))
|
|
11200
11332
|
return ortVersionDir;
|
|
11201
|
-
const libSubdir =
|
|
11202
|
-
if (existsSync6(
|
|
11333
|
+
const libSubdir = join9(ortVersionDir, "lib");
|
|
11334
|
+
if (existsSync6(join9(libSubdir, libName)))
|
|
11203
11335
|
return libSubdir;
|
|
11204
11336
|
return ortVersionDir;
|
|
11205
11337
|
}
|
|
@@ -11214,12 +11346,12 @@ function findSystemOnnxRuntime(libName) {
|
|
|
11214
11346
|
} else if (process.platform === "win32") {
|
|
11215
11347
|
const programFiles = process.env.ProgramFiles ?? "C:\\Program Files";
|
|
11216
11348
|
const programFilesX86 = process.env["ProgramFiles(x86)"] ?? "C:\\Program Files (x86)";
|
|
11217
|
-
searchPaths.push(
|
|
11349
|
+
searchPaths.push(join9(programFiles, "onnxruntime", "lib"), join9(programFiles, "Microsoft ONNX Runtime", "lib"), join9(programFiles, "Microsoft Machine Learning", "lib"), join9(programFilesX86, "onnxruntime", "lib"), ...(() => {
|
|
11218
11350
|
const nugetPaths = [];
|
|
11219
11351
|
const userProfile = process.env.USERPROFILE ?? "";
|
|
11220
11352
|
if (!userProfile)
|
|
11221
11353
|
return nugetPaths;
|
|
11222
|
-
const nugetPackageDir =
|
|
11354
|
+
const nugetPackageDir = join9(userProfile, ".nuget", "packages", "microsoft.ml.onnxruntime");
|
|
11223
11355
|
if (!existsSync6(nugetPackageDir))
|
|
11224
11356
|
return nugetPaths;
|
|
11225
11357
|
try {
|
|
@@ -11228,7 +11360,7 @@ function findSystemOnnxRuntime(libName) {
|
|
|
11228
11360
|
continue;
|
|
11229
11361
|
if (entry.name === "__globalPackagesFolder" || entry.name.startsWith("."))
|
|
11230
11362
|
continue;
|
|
11231
|
-
nugetPaths.push(
|
|
11363
|
+
nugetPaths.push(join9(nugetPackageDir, entry.name, "runtimes", "win-x64", "native"), join9(nugetPackageDir, entry.name, "runtimes", "win-arm64", "native"));
|
|
11232
11364
|
}
|
|
11233
11365
|
} catch (err) {
|
|
11234
11366
|
warn(`Failed to scan NuGet ONNX Runtime cache ${nugetPackageDir}: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -11250,7 +11382,7 @@ function findSystemOnnxRuntime(libName) {
|
|
|
11250
11382
|
});
|
|
11251
11383
|
const unknownVersionPaths = [];
|
|
11252
11384
|
for (const dir of uniquePaths) {
|
|
11253
|
-
const libPath =
|
|
11385
|
+
const libPath = join9(dir, libName);
|
|
11254
11386
|
if (process.platform === "win32") {
|
|
11255
11387
|
if (!directoryContainsLibrary(dir, libName))
|
|
11256
11388
|
continue;
|
|
@@ -11259,6 +11391,10 @@ function findSystemOnnxRuntime(libName) {
|
|
|
11259
11391
|
}
|
|
11260
11392
|
const version = detectOnnxVersion(dir, libName);
|
|
11261
11393
|
if (!version) {
|
|
11394
|
+
if (isWindowsSystem32Directory(dir)) {
|
|
11395
|
+
warn(`Skipping unversioned Windows system ONNX Runtime at ${dir}; falling through to AFT-managed download.`);
|
|
11396
|
+
continue;
|
|
11397
|
+
}
|
|
11262
11398
|
unknownVersionPaths.push(dir);
|
|
11263
11399
|
continue;
|
|
11264
11400
|
}
|
|
@@ -11286,7 +11422,7 @@ async function downloadFileWithCap(url, destPath) {
|
|
|
11286
11422
|
if (Number.isFinite(advertised) && advertised > MAX_DOWNLOAD_BYTES2) {
|
|
11287
11423
|
throw new Error(`Content-Length ${advertised} exceeds max ${MAX_DOWNLOAD_BYTES2}`);
|
|
11288
11424
|
}
|
|
11289
|
-
mkdirSync5(
|
|
11425
|
+
mkdirSync5(dirname5(destPath), { recursive: true });
|
|
11290
11426
|
let bytesWritten = 0;
|
|
11291
11427
|
const guard = new TransformStream({
|
|
11292
11428
|
transform(chunk, transformController) {
|
|
@@ -11316,11 +11452,11 @@ function validateExtractedTree(stagingRoot) {
|
|
|
11316
11452
|
const walk = (dir) => {
|
|
11317
11453
|
const entries = readdirSync2(dir);
|
|
11318
11454
|
for (const entry of entries) {
|
|
11319
|
-
const fullPath =
|
|
11455
|
+
const fullPath = join9(dir, entry);
|
|
11320
11456
|
const lst = lstatSync(fullPath);
|
|
11321
11457
|
if (lst.isSymbolicLink()) {
|
|
11322
11458
|
const linkTarget = readlinkSync(fullPath);
|
|
11323
|
-
const resolvedTarget = resolve5(
|
|
11459
|
+
const resolvedTarget = resolve5(dirname5(fullPath), linkTarget);
|
|
11324
11460
|
const rel2 = relative2(realRoot, resolvedTarget);
|
|
11325
11461
|
if (rel2.startsWith("..") || isAbsolute4(rel2) || win32.isAbsolute(rel2)) {
|
|
11326
11462
|
throw new Error(`extracted symlink ${fullPath} points outside staging root: ${linkTarget}`);
|
|
@@ -11351,7 +11487,7 @@ async function downloadOnnxRuntime(info, targetDir) {
|
|
|
11351
11487
|
const tmpDir = `${targetDir}.tmp.${process.pid}.${Date.now().toString(36)}`;
|
|
11352
11488
|
try {
|
|
11353
11489
|
mkdirSync5(tmpDir, { recursive: true });
|
|
11354
|
-
const archivePath =
|
|
11490
|
+
const archivePath = join9(tmpDir, `onnxruntime.${info.archiveType}`);
|
|
11355
11491
|
await downloadFileWithCap(url, archivePath);
|
|
11356
11492
|
const archiveSha256 = sha256File(archivePath);
|
|
11357
11493
|
log(`ONNX Runtime archive sha256=${archiveSha256}`);
|
|
@@ -11367,7 +11503,7 @@ async function downloadOnnxRuntime(info, targetDir) {
|
|
|
11367
11503
|
unlinkSync4(archivePath);
|
|
11368
11504
|
} catch {}
|
|
11369
11505
|
validateExtractedTree(tmpDir);
|
|
11370
|
-
const extractedDir =
|
|
11506
|
+
const extractedDir = join9(tmpDir, info.assetName, "lib");
|
|
11371
11507
|
if (!existsSync6(extractedDir)) {
|
|
11372
11508
|
throw new Error(`Expected directory not found: ${extractedDir}`);
|
|
11373
11509
|
}
|
|
@@ -11376,11 +11512,11 @@ async function downloadOnnxRuntime(info, targetDir) {
|
|
|
11376
11512
|
const realFiles = [];
|
|
11377
11513
|
const symlinks = [];
|
|
11378
11514
|
for (const libFile of libFiles) {
|
|
11379
|
-
const src =
|
|
11515
|
+
const src = join9(extractedDir, libFile);
|
|
11380
11516
|
try {
|
|
11381
|
-
const
|
|
11382
|
-
log(`ORT extract: ${libFile} — isSymlink=${
|
|
11383
|
-
if (
|
|
11517
|
+
const stat2 = lstatSync(src);
|
|
11518
|
+
log(`ORT extract: ${libFile} — isSymlink=${stat2.isSymbolicLink()}, isFile=${stat2.isFile()}, size=${stat2.size}`);
|
|
11519
|
+
if (stat2.isSymbolicLink()) {
|
|
11384
11520
|
symlinks.push({ name: libFile, target: readlinkSync(src) });
|
|
11385
11521
|
} else {
|
|
11386
11522
|
realFiles.push(libFile);
|
|
@@ -11391,7 +11527,7 @@ async function downloadOnnxRuntime(info, targetDir) {
|
|
|
11391
11527
|
}
|
|
11392
11528
|
}
|
|
11393
11529
|
copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks);
|
|
11394
|
-
const libPath =
|
|
11530
|
+
const libPath = join9(targetDir, info.libName);
|
|
11395
11531
|
let libHash = null;
|
|
11396
11532
|
try {
|
|
11397
11533
|
libHash = sha256File(libPath);
|
|
@@ -11416,8 +11552,8 @@ async function downloadOnnxRuntime(info, targetDir) {
|
|
|
11416
11552
|
function copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks, copyFile = copyFileSync3) {
|
|
11417
11553
|
const requiredLibs = new Set([info.libName]);
|
|
11418
11554
|
for (const libFile of realFiles) {
|
|
11419
|
-
const src =
|
|
11420
|
-
const dst =
|
|
11555
|
+
const src = join9(extractedDir, libFile);
|
|
11556
|
+
const dst = join9(targetDir, libFile);
|
|
11421
11557
|
try {
|
|
11422
11558
|
copyFile(src, dst);
|
|
11423
11559
|
if (process.platform !== "win32") {
|
|
@@ -11433,12 +11569,12 @@ function copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks, c
|
|
|
11433
11569
|
}
|
|
11434
11570
|
const targetRoot = realpathSync(targetDir);
|
|
11435
11571
|
for (const link of symlinks) {
|
|
11436
|
-
const dst =
|
|
11572
|
+
const dst = join9(targetDir, link.name);
|
|
11437
11573
|
try {
|
|
11438
11574
|
unlinkSync4(dst);
|
|
11439
11575
|
} catch {}
|
|
11440
|
-
const dstForContainment =
|
|
11441
|
-
const resolvedTarget = resolve5(
|
|
11576
|
+
const dstForContainment = join9(targetRoot, link.name);
|
|
11577
|
+
const resolvedTarget = resolve5(dirname5(dstForContainment), link.target);
|
|
11442
11578
|
if (!isPathInsideRoot(targetRoot, resolvedTarget)) {
|
|
11443
11579
|
const message = `ONNX Runtime symlink ${link.name} points outside install dir: ${link.target}`;
|
|
11444
11580
|
if (requiredLibs.has(link.name)) {
|
|
@@ -11458,7 +11594,7 @@ function copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks, c
|
|
|
11458
11594
|
log(`ORT extract: failed to symlink optional ${link.name}: ${symlinkErr}`);
|
|
11459
11595
|
}
|
|
11460
11596
|
}
|
|
11461
|
-
const requiredPath =
|
|
11597
|
+
const requiredPath = join9(targetDir, info.libName);
|
|
11462
11598
|
if (!existsSync6(requiredPath)) {
|
|
11463
11599
|
rmSync3(targetDir, { recursive: true, force: true });
|
|
11464
11600
|
throw new Error(`Required ONNX Runtime library missing after install: ${requiredPath}`);
|
|
@@ -11485,13 +11621,13 @@ function writeOnnxInstalledMeta(installDir, version, sha256, archiveSha256) {
|
|
|
11485
11621
|
...sha256 ? { sha256 } : {},
|
|
11486
11622
|
archiveSha256
|
|
11487
11623
|
};
|
|
11488
|
-
writeFileSync3(
|
|
11624
|
+
writeFileSync3(join9(installDir, ONNX_INSTALLED_META_FILE), JSON.stringify(meta), "utf8");
|
|
11489
11625
|
} catch (err) {
|
|
11490
11626
|
log(`[onnx] failed to write installed-meta in ${installDir}: ${err}`);
|
|
11491
11627
|
}
|
|
11492
11628
|
}
|
|
11493
11629
|
function readOnnxInstalledMeta(installDir) {
|
|
11494
|
-
const path2 =
|
|
11630
|
+
const path2 = join9(installDir, ONNX_INSTALLED_META_FILE);
|
|
11495
11631
|
try {
|
|
11496
11632
|
if (!statSync4(path2).isFile())
|
|
11497
11633
|
return null;
|
|
@@ -11628,7 +11764,7 @@ function isProcessAlive(pid) {
|
|
|
11628
11764
|
}
|
|
11629
11765
|
}
|
|
11630
11766
|
// ../aft-bridge/dist/pool.js
|
|
11631
|
-
import { homedir as
|
|
11767
|
+
import { homedir as homedir10 } from "node:os";
|
|
11632
11768
|
|
|
11633
11769
|
// ../aft-bridge/dist/project-identity.js
|
|
11634
11770
|
import { realpathSync as realpathSync2 } from "node:fs";
|
|
@@ -11667,7 +11803,7 @@ var DEFAULT_MAX_POOL_SIZE = 8;
|
|
|
11667
11803
|
var CLEANUP_INTERVAL_MS = 60 * 1000;
|
|
11668
11804
|
function canonicalHomeDir() {
|
|
11669
11805
|
try {
|
|
11670
|
-
const home =
|
|
11806
|
+
const home = homedir10();
|
|
11671
11807
|
if (!home)
|
|
11672
11808
|
return null;
|
|
11673
11809
|
return canonicalizeProjectRoot(home);
|
|
@@ -11693,6 +11829,7 @@ class BridgePool {
|
|
|
11693
11829
|
projectConfigLoader;
|
|
11694
11830
|
logger;
|
|
11695
11831
|
cleanupTimer = null;
|
|
11832
|
+
shutdownCalled = false;
|
|
11696
11833
|
constructor(binaryPath, options = {}, configOverrides = {}) {
|
|
11697
11834
|
this.binaryPath = binaryPath;
|
|
11698
11835
|
this.maxPoolSize = options.maxPoolSize ?? DEFAULT_MAX_POOL_SIZE;
|
|
@@ -11714,12 +11851,11 @@ class BridgePool {
|
|
|
11714
11851
|
childEnv: options.childEnv
|
|
11715
11852
|
};
|
|
11716
11853
|
this.configOverrides = configOverrides;
|
|
11717
|
-
|
|
11718
|
-
this.cleanupTimer = setInterval(() => this.cleanup(), CLEANUP_INTERVAL_MS);
|
|
11719
|
-
this.cleanupTimer.unref();
|
|
11720
|
-
}
|
|
11854
|
+
this.startCleanupTimer();
|
|
11721
11855
|
}
|
|
11722
11856
|
getActiveBridgeForRoot(projectRoot) {
|
|
11857
|
+
if (this.shutdownCalled)
|
|
11858
|
+
return null;
|
|
11723
11859
|
const key = normalizeKey(projectRoot);
|
|
11724
11860
|
const entry = this.bridges.get(key);
|
|
11725
11861
|
if (!entry?.bridge.isAlive())
|
|
@@ -11727,7 +11863,21 @@ class BridgePool {
|
|
|
11727
11863
|
entry.lastUsed = Date.now();
|
|
11728
11864
|
return entry.bridge;
|
|
11729
11865
|
}
|
|
11866
|
+
activeBridges() {
|
|
11867
|
+
if (this.shutdownCalled)
|
|
11868
|
+
return [];
|
|
11869
|
+
const alive = [];
|
|
11870
|
+
for (const entry of this.bridges.values()) {
|
|
11871
|
+
if (entry.bridge.isAlive())
|
|
11872
|
+
alive.push(entry.bridge);
|
|
11873
|
+
}
|
|
11874
|
+
return alive;
|
|
11875
|
+
}
|
|
11730
11876
|
getBridge(projectRoot) {
|
|
11877
|
+
if (this.shutdownCalled) {
|
|
11878
|
+
this.shutdownCalled = false;
|
|
11879
|
+
this.startCleanupTimer();
|
|
11880
|
+
}
|
|
11731
11881
|
const key = normalizeKey(projectRoot);
|
|
11732
11882
|
const existing = this.bridges.get(key);
|
|
11733
11883
|
if (existing) {
|
|
@@ -11737,15 +11887,7 @@ class BridgePool {
|
|
|
11737
11887
|
if (this.bridges.size >= this.maxPoolSize) {
|
|
11738
11888
|
this.evictLRU();
|
|
11739
11889
|
}
|
|
11740
|
-
|
|
11741
|
-
if (this.projectConfigLoader) {
|
|
11742
|
-
try {
|
|
11743
|
-
projectOverrides = this.projectConfigLoader(key) ?? {};
|
|
11744
|
-
} catch (err) {
|
|
11745
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
11746
|
-
this.error(`projectConfigLoader failed; using global overrides only: ${message}`);
|
|
11747
|
-
}
|
|
11748
|
-
}
|
|
11890
|
+
const projectOverrides = this.loadProjectOverrides(key);
|
|
11749
11891
|
const mergedOverrides = { ...this.configOverrides, ...projectOverrides };
|
|
11750
11892
|
const bridge = new BinaryBridge(this.binaryPath, key, this.bridgeOptions, mergedOverrides);
|
|
11751
11893
|
this.bridges.set(key, { bridge, lastUsed: Date.now() });
|
|
@@ -11795,6 +11937,7 @@ class BridgePool {
|
|
|
11795
11937
|
}
|
|
11796
11938
|
async closeSession(_projectRoot, _session) {}
|
|
11797
11939
|
async shutdown() {
|
|
11940
|
+
this.shutdownCalled = true;
|
|
11798
11941
|
if (this.cleanupTimer) {
|
|
11799
11942
|
clearInterval(this.cleanupTimer);
|
|
11800
11943
|
this.cleanupTimer = null;
|
|
@@ -11807,6 +11950,9 @@ class BridgePool {
|
|
|
11807
11950
|
this.staleBridges.clear();
|
|
11808
11951
|
await Promise.allSettled(shutdowns);
|
|
11809
11952
|
}
|
|
11953
|
+
isShutdown() {
|
|
11954
|
+
return this.shutdownCalled;
|
|
11955
|
+
}
|
|
11810
11956
|
async replaceBinary(newPath) {
|
|
11811
11957
|
this.binaryPath = newPath;
|
|
11812
11958
|
for (const entry of this.bridges.values()) {
|
|
@@ -11816,6 +11962,12 @@ class BridgePool {
|
|
|
11816
11962
|
this.log(`Binary path updated to ${newPath}. Active bridges marked stale — next calls will use the new binary.`);
|
|
11817
11963
|
return newPath;
|
|
11818
11964
|
}
|
|
11965
|
+
startCleanupTimer() {
|
|
11966
|
+
if (!Number.isFinite(this.idleTimeoutMs) || this.cleanupTimer)
|
|
11967
|
+
return;
|
|
11968
|
+
this.cleanupTimer = setInterval(() => this.cleanup(), CLEANUP_INTERVAL_MS);
|
|
11969
|
+
this.cleanupTimer.unref();
|
|
11970
|
+
}
|
|
11819
11971
|
log(message, meta) {
|
|
11820
11972
|
const logger = this.logger ?? getActiveLogger();
|
|
11821
11973
|
if (logger) {
|
|
@@ -11847,6 +11999,36 @@ class BridgePool {
|
|
|
11847
11999
|
this.configOverrides[key] = value;
|
|
11848
12000
|
}
|
|
11849
12001
|
}
|
|
12002
|
+
async reconfigure(projectRoot, overrides) {
|
|
12003
|
+
const key = normalizeKey(projectRoot);
|
|
12004
|
+
for (const [name, value] of Object.entries(overrides)) {
|
|
12005
|
+
this.setConfigureOverride(name, value);
|
|
12006
|
+
}
|
|
12007
|
+
const bridge = this.getActiveBridgeForRoot(key);
|
|
12008
|
+
if (!bridge)
|
|
12009
|
+
return;
|
|
12010
|
+
const projectOverrides = this.loadProjectOverrides(key);
|
|
12011
|
+
const response = await bridge.send("configure", {
|
|
12012
|
+
project_root: key,
|
|
12013
|
+
...this.configOverrides,
|
|
12014
|
+
...projectOverrides,
|
|
12015
|
+
...overrides
|
|
12016
|
+
});
|
|
12017
|
+
if (response.success === false) {
|
|
12018
|
+
throw new Error(`configure refresh failed for ${key}: ${String(response.message ?? "unknown error")}`);
|
|
12019
|
+
}
|
|
12020
|
+
}
|
|
12021
|
+
loadProjectOverrides(key) {
|
|
12022
|
+
if (!this.projectConfigLoader)
|
|
12023
|
+
return {};
|
|
12024
|
+
try {
|
|
12025
|
+
return this.projectConfigLoader(key) ?? {};
|
|
12026
|
+
} catch (err) {
|
|
12027
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
12028
|
+
this.error(`projectConfigLoader failed; using global overrides only: ${message}`);
|
|
12029
|
+
return {};
|
|
12030
|
+
}
|
|
12031
|
+
}
|
|
11850
12032
|
get size() {
|
|
11851
12033
|
return this.bridges.size;
|
|
11852
12034
|
}
|
|
@@ -11860,11 +12042,185 @@ class BridgePool {
|
|
|
11860
12042
|
function normalizeKey(projectRoot) {
|
|
11861
12043
|
return canonicalizeProjectRoot(projectRoot);
|
|
11862
12044
|
}
|
|
11863
|
-
//
|
|
12045
|
+
// ../aft-bridge/dist/revivable-transport.js
|
|
12046
|
+
class RevivableTransportPool {
|
|
12047
|
+
createPool;
|
|
12048
|
+
onBinaryReplaced;
|
|
12049
|
+
activePool;
|
|
12050
|
+
revival = null;
|
|
12051
|
+
transports = new Map;
|
|
12052
|
+
configureOverrides = new Map;
|
|
12053
|
+
constructor(initialPool, createPool, onBinaryReplaced) {
|
|
12054
|
+
this.createPool = createPool;
|
|
12055
|
+
this.onBinaryReplaced = onBinaryReplaced;
|
|
12056
|
+
this.activePool = initialPool;
|
|
12057
|
+
}
|
|
12058
|
+
getBridge(projectRoot) {
|
|
12059
|
+
const key = canonicalizeProjectRoot(projectRoot);
|
|
12060
|
+
let transport = this.transports.get(key);
|
|
12061
|
+
if (!transport) {
|
|
12062
|
+
transport = new RevivableProjectTransport(this, key);
|
|
12063
|
+
this.transports.set(key, transport);
|
|
12064
|
+
}
|
|
12065
|
+
return transport;
|
|
12066
|
+
}
|
|
12067
|
+
activeBridges() {
|
|
12068
|
+
return this.activePool.activeBridges();
|
|
12069
|
+
}
|
|
12070
|
+
getActiveBridgeForRoot(projectRoot) {
|
|
12071
|
+
const key = canonicalizeProjectRoot(projectRoot);
|
|
12072
|
+
const bridge = this.currentBridge(key);
|
|
12073
|
+
if (!bridge)
|
|
12074
|
+
return null;
|
|
12075
|
+
const transport = this.getBridge(key);
|
|
12076
|
+
transport.refreshStatusSubscription(bridge);
|
|
12077
|
+
return transport;
|
|
12078
|
+
}
|
|
12079
|
+
async toolCall(projectRoot, runtime, name, rawArgs = {}, options) {
|
|
12080
|
+
return this.getBridge(projectRoot).toolCall(runtime.sessionID, name, rawArgs, options);
|
|
12081
|
+
}
|
|
12082
|
+
setConfigureOverride(key, value) {
|
|
12083
|
+
if (value === undefined)
|
|
12084
|
+
this.configureOverrides.delete(key);
|
|
12085
|
+
else
|
|
12086
|
+
this.configureOverrides.set(key, value);
|
|
12087
|
+
this.activePool.setConfigureOverride(key, value);
|
|
12088
|
+
}
|
|
12089
|
+
async reconfigure(projectRoot, overrides) {
|
|
12090
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
12091
|
+
if (value === undefined)
|
|
12092
|
+
this.configureOverrides.delete(key);
|
|
12093
|
+
else
|
|
12094
|
+
this.configureOverrides.set(key, value);
|
|
12095
|
+
}
|
|
12096
|
+
const pool = await this.ensureActivePool();
|
|
12097
|
+
await pool.reconfigure(projectRoot, overrides);
|
|
12098
|
+
}
|
|
12099
|
+
async replaceBinary(path2) {
|
|
12100
|
+
const replaced = await this.activePool.replaceBinary(path2);
|
|
12101
|
+
this.onBinaryReplaced?.(replaced);
|
|
12102
|
+
return replaced;
|
|
12103
|
+
}
|
|
12104
|
+
closeSession(projectRoot, session) {
|
|
12105
|
+
return this.activePool.closeSession(projectRoot, session);
|
|
12106
|
+
}
|
|
12107
|
+
async shutdown() {
|
|
12108
|
+
const revival = this.revival;
|
|
12109
|
+
if (revival) {
|
|
12110
|
+
await Promise.allSettled([revival]);
|
|
12111
|
+
}
|
|
12112
|
+
await this.activePool.shutdown();
|
|
12113
|
+
for (const transport of this.transports.values()) {
|
|
12114
|
+
transport.refreshStatusSubscription(null);
|
|
12115
|
+
}
|
|
12116
|
+
}
|
|
12117
|
+
isShutdown() {
|
|
12118
|
+
return this.activePool.isShutdown();
|
|
12119
|
+
}
|
|
12120
|
+
async ensureActivePool() {
|
|
12121
|
+
if (!this.activePool.isShutdown())
|
|
12122
|
+
return this.activePool;
|
|
12123
|
+
if (this.revival)
|
|
12124
|
+
return this.revival;
|
|
12125
|
+
warn("transport was shut down but new demand arrived — reviving (host quit hook fired without process exit?)");
|
|
12126
|
+
const revival = this.createPool().then((pool) => {
|
|
12127
|
+
for (const [key, value] of this.configureOverrides) {
|
|
12128
|
+
pool.setConfigureOverride(key, value);
|
|
12129
|
+
}
|
|
12130
|
+
this.activePool = pool;
|
|
12131
|
+
for (const [root, transport] of this.transports) {
|
|
12132
|
+
transport.refreshStatusSubscription(pool.getActiveBridgeForRoot(root));
|
|
12133
|
+
}
|
|
12134
|
+
return pool;
|
|
12135
|
+
});
|
|
12136
|
+
this.revival = revival;
|
|
12137
|
+
revival.then(() => {
|
|
12138
|
+
if (this.revival === revival)
|
|
12139
|
+
this.revival = null;
|
|
12140
|
+
}, () => {
|
|
12141
|
+
if (this.revival === revival)
|
|
12142
|
+
this.revival = null;
|
|
12143
|
+
});
|
|
12144
|
+
return revival;
|
|
12145
|
+
}
|
|
12146
|
+
currentBridge(projectRoot) {
|
|
12147
|
+
return this.activePool.getActiveBridgeForRoot(projectRoot);
|
|
12148
|
+
}
|
|
12149
|
+
async send(projectRoot, command, params, options) {
|
|
12150
|
+
const pool = await this.ensureActivePool();
|
|
12151
|
+
const bridge = pool.getBridge(projectRoot);
|
|
12152
|
+
this.getBridge(projectRoot).refreshStatusSubscription(bridge);
|
|
12153
|
+
return bridge.send(command, params, options);
|
|
12154
|
+
}
|
|
12155
|
+
async toolCallOnProject(projectRoot, sessionId, name, rawArgs, options) {
|
|
12156
|
+
const pool = await this.ensureActivePool();
|
|
12157
|
+
const bridge = pool.getBridge(projectRoot);
|
|
12158
|
+
this.getBridge(projectRoot).refreshStatusSubscription(bridge);
|
|
12159
|
+
return bridge.toolCall(sessionId, name, rawArgs, options);
|
|
12160
|
+
}
|
|
12161
|
+
}
|
|
12162
|
+
|
|
12163
|
+
class RevivableProjectTransport {
|
|
12164
|
+
owner;
|
|
12165
|
+
projectRoot;
|
|
12166
|
+
statusListeners = new Map;
|
|
12167
|
+
statusBridges = new Map;
|
|
12168
|
+
constructor(owner, projectRoot) {
|
|
12169
|
+
this.owner = owner;
|
|
12170
|
+
this.projectRoot = projectRoot;
|
|
12171
|
+
}
|
|
12172
|
+
getCwd() {
|
|
12173
|
+
return this.projectRoot;
|
|
12174
|
+
}
|
|
12175
|
+
getStatusBar() {
|
|
12176
|
+
return this.owner.currentBridge(this.projectRoot)?.getStatusBar();
|
|
12177
|
+
}
|
|
12178
|
+
getCachedStatus() {
|
|
12179
|
+
return this.owner.currentBridge(this.projectRoot)?.getCachedStatus() ?? null;
|
|
12180
|
+
}
|
|
12181
|
+
cacheStatusSnapshot(snapshot) {
|
|
12182
|
+
this.owner.currentBridge(this.projectRoot)?.cacheStatusSnapshot(snapshot);
|
|
12183
|
+
}
|
|
12184
|
+
send(command, params, options) {
|
|
12185
|
+
return this.owner.send(this.projectRoot, command, params, options);
|
|
12186
|
+
}
|
|
12187
|
+
toolCall(sessionId, name, rawArgs, options) {
|
|
12188
|
+
return this.owner.toolCallOnProject(this.projectRoot, sessionId, name, rawArgs, options);
|
|
12189
|
+
}
|
|
12190
|
+
subscribeStatus(listener) {
|
|
12191
|
+
if (this.statusListeners.has(listener))
|
|
12192
|
+
return () => this.removeStatusListener(listener);
|
|
12193
|
+
this.statusListeners.set(listener, () => {});
|
|
12194
|
+
this.bindStatusListener(listener, this.owner.currentBridge(this.projectRoot));
|
|
12195
|
+
return () => this.removeStatusListener(listener);
|
|
12196
|
+
}
|
|
12197
|
+
refreshStatusSubscription(bridge) {
|
|
12198
|
+
for (const listener of this.statusListeners.keys()) {
|
|
12199
|
+
this.bindStatusListener(listener, bridge);
|
|
12200
|
+
}
|
|
12201
|
+
}
|
|
12202
|
+
bindStatusListener(listener, bridge) {
|
|
12203
|
+
if (this.statusBridges.get(listener) === bridge)
|
|
12204
|
+
return;
|
|
12205
|
+
const previousUnsubscribe = this.statusListeners.get(listener);
|
|
12206
|
+
previousUnsubscribe?.();
|
|
12207
|
+
const maybe = bridge;
|
|
12208
|
+
const unsubscribe = maybe && typeof maybe.subscribeStatus === "function" ? maybe.subscribeStatus(listener) : () => {};
|
|
12209
|
+
this.statusListeners.set(listener, unsubscribe);
|
|
12210
|
+
this.statusBridges.set(listener, bridge);
|
|
12211
|
+
}
|
|
12212
|
+
removeStatusListener(listener) {
|
|
12213
|
+
const unsubscribe = this.statusListeners.get(listener);
|
|
12214
|
+
this.statusListeners.delete(listener);
|
|
12215
|
+
this.statusBridges.delete(listener);
|
|
12216
|
+
unsubscribe?.();
|
|
12217
|
+
}
|
|
12218
|
+
}
|
|
12219
|
+
// ../../node_modules/.bun/@cortexkit+subc-client@0.5.0/node_modules/@cortexkit/subc-client/dist/client.js
|
|
11864
12220
|
import { promises as fs2 } from "node:fs";
|
|
11865
12221
|
import { debuglog } from "node:util";
|
|
11866
12222
|
|
|
11867
|
-
// ../../node_modules/.bun/@cortexkit+subc-client@0.
|
|
12223
|
+
// ../../node_modules/.bun/@cortexkit+subc-client@0.5.0/node_modules/@cortexkit/subc-client/dist/auth.js
|
|
11868
12224
|
import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
|
|
11869
12225
|
var NONCE_LEN = 32;
|
|
11870
12226
|
var MAX_AUTH_MESSAGE_LEN = 4096;
|
|
@@ -11928,8 +12284,146 @@ async function authenticateClient(sock, conn, deadlineMs) {
|
|
|
11928
12284
|
await writeMessage(sock, { client_auth: Array.from(clientAuth) }, deadlineMs);
|
|
11929
12285
|
}
|
|
11930
12286
|
|
|
11931
|
-
// ../../node_modules/.bun/@cortexkit+subc-client@0.
|
|
12287
|
+
// ../../node_modules/.bun/@cortexkit+subc-client@0.5.0/node_modules/@cortexkit/subc-client/dist/connection-file.js
|
|
11932
12288
|
import { promises as fs } from "node:fs";
|
|
12289
|
+
|
|
12290
|
+
// ../../node_modules/.bun/@cortexkit+subc-client@0.5.0/node_modules/@cortexkit/subc-client/dist/envelope.js
|
|
12291
|
+
var PROTOCOL_VERSION = 2;
|
|
12292
|
+
var HEADER_LEN = 21;
|
|
12293
|
+
var FROZEN_PREFIX_LEN = 5;
|
|
12294
|
+
var MAX_FRAME_BODY_LEN = 64 * 1024 * 1024;
|
|
12295
|
+
var FrameType;
|
|
12296
|
+
(function(FrameType2) {
|
|
12297
|
+
FrameType2[FrameType2["Request"] = 0] = "Request";
|
|
12298
|
+
FrameType2[FrameType2["Response"] = 1] = "Response";
|
|
12299
|
+
FrameType2[FrameType2["Push"] = 2] = "Push";
|
|
12300
|
+
FrameType2[FrameType2["StreamData"] = 3] = "StreamData";
|
|
12301
|
+
FrameType2[FrameType2["StreamEnd"] = 4] = "StreamEnd";
|
|
12302
|
+
FrameType2[FrameType2["Error"] = 5] = "Error";
|
|
12303
|
+
FrameType2[FrameType2["Cancel"] = 6] = "Cancel";
|
|
12304
|
+
FrameType2[FrameType2["Ping"] = 7] = "Ping";
|
|
12305
|
+
FrameType2[FrameType2["Pong"] = 8] = "Pong";
|
|
12306
|
+
FrameType2[FrameType2["Hello"] = 9] = "Hello";
|
|
12307
|
+
FrameType2[FrameType2["HelloAck"] = 10] = "HelloAck";
|
|
12308
|
+
FrameType2[FrameType2["Goodbye"] = 11] = "Goodbye";
|
|
12309
|
+
})(FrameType || (FrameType = {}));
|
|
12310
|
+
var FRAME_TYPE_MAX = FrameType.Goodbye;
|
|
12311
|
+
function isPureHeader(ty) {
|
|
12312
|
+
return ty === FrameType.Cancel || ty === FrameType.Ping || ty === FrameType.Pong || ty === FrameType.Goodbye;
|
|
12313
|
+
}
|
|
12314
|
+
var Priority;
|
|
12315
|
+
(function(Priority2) {
|
|
12316
|
+
Priority2[Priority2["Passive"] = 0] = "Passive";
|
|
12317
|
+
Priority2[Priority2["Interactive"] = 1] = "Interactive";
|
|
12318
|
+
Priority2[Priority2["Background"] = 2] = "Background";
|
|
12319
|
+
})(Priority || (Priority = {}));
|
|
12320
|
+
var AdmissionClass;
|
|
12321
|
+
(function(AdmissionClass2) {
|
|
12322
|
+
AdmissionClass2[AdmissionClass2["Normal"] = 0] = "Normal";
|
|
12323
|
+
AdmissionClass2[AdmissionClass2["Expedite"] = 1] = "Expedite";
|
|
12324
|
+
AdmissionClass2[AdmissionClass2["Sheddable"] = 2] = "Sheddable";
|
|
12325
|
+
})(AdmissionClass || (AdmissionClass = {}));
|
|
12326
|
+
var FLAG_BINARY = 1;
|
|
12327
|
+
var FLAG_PRIORITY_MASK = 6;
|
|
12328
|
+
var FLAG_PRIORITY_SHIFT = 1;
|
|
12329
|
+
var FLAG_LAST = 8;
|
|
12330
|
+
var FLAG_ADMISSION_MASK = 48;
|
|
12331
|
+
var FLAG_ADMISSION_SHIFT = 4;
|
|
12332
|
+
var FLAG_RESERVED_MASK = 192;
|
|
12333
|
+
function buildFlags(binary, priority, last, admissionClass = AdmissionClass.Normal) {
|
|
12334
|
+
let flags = 0;
|
|
12335
|
+
if (binary)
|
|
12336
|
+
flags |= FLAG_BINARY;
|
|
12337
|
+
flags |= priority << FLAG_PRIORITY_SHIFT;
|
|
12338
|
+
if (last)
|
|
12339
|
+
flags |= FLAG_LAST;
|
|
12340
|
+
flags |= admissionClass << FLAG_ADMISSION_SHIFT;
|
|
12341
|
+
return flags;
|
|
12342
|
+
}
|
|
12343
|
+
function encodeHeader(header) {
|
|
12344
|
+
const buffer = new Uint8Array(HEADER_LEN);
|
|
12345
|
+
const view = new DataView(buffer.buffer);
|
|
12346
|
+
view.setUint32(0, header.len, true);
|
|
12347
|
+
buffer[4] = header.ver;
|
|
12348
|
+
buffer[5] = header.ty;
|
|
12349
|
+
buffer[6] = header.flags;
|
|
12350
|
+
view.setUint16(7, header.channel, true);
|
|
12351
|
+
view.setUint32(9, header.epoch, true);
|
|
12352
|
+
view.setBigUint64(13, header.corr, true);
|
|
12353
|
+
return buffer;
|
|
12354
|
+
}
|
|
12355
|
+
|
|
12356
|
+
class DecodeError extends Error {
|
|
12357
|
+
code;
|
|
12358
|
+
constructor(message, code) {
|
|
12359
|
+
super(message);
|
|
12360
|
+
this.code = code;
|
|
12361
|
+
this.name = "DecodeError";
|
|
12362
|
+
}
|
|
12363
|
+
}
|
|
12364
|
+
function decodeHeader(bytes) {
|
|
12365
|
+
if (bytes.length < FROZEN_PREFIX_LEN) {
|
|
12366
|
+
throw new DecodeError(`header shorter than frozen prefix: have ${bytes.length} bytes`, "too_short_for_prefix");
|
|
12367
|
+
}
|
|
12368
|
+
const ver = bytes[4];
|
|
12369
|
+
if (ver !== PROTOCOL_VERSION)
|
|
12370
|
+
throw new DecodeError(`unsupported envelope version ${ver}`, "unsupported_version");
|
|
12371
|
+
if (bytes.length < HEADER_LEN) {
|
|
12372
|
+
throw new DecodeError(`header too short for version: have ${bytes.length} bytes, need ${HEADER_LEN}`, "too_short_for_header");
|
|
12373
|
+
}
|
|
12374
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
12375
|
+
const len = view.getUint32(0, true);
|
|
12376
|
+
const typeByte = bytes[5];
|
|
12377
|
+
if (typeByte > FRAME_TYPE_MAX)
|
|
12378
|
+
throw new DecodeError(`unknown frame type byte ${typeByte}`, "unknown_frame_type");
|
|
12379
|
+
const ty = typeByte;
|
|
12380
|
+
const flags = bytes[6];
|
|
12381
|
+
if ((flags & FLAG_RESERVED_MASK) !== 0) {
|
|
12382
|
+
throw new DecodeError(`reserved flag bits set in flags 0b${flags.toString(2).padStart(8, "0")}`, "reserved_flag_bits");
|
|
12383
|
+
}
|
|
12384
|
+
if ((flags & FLAG_PRIORITY_MASK) >> FLAG_PRIORITY_SHIFT === 3) {
|
|
12385
|
+
throw new DecodeError(`reserved priority bits set in flags 0b${flags.toString(2).padStart(8, "0")}`, "reserved_priority_bits");
|
|
12386
|
+
}
|
|
12387
|
+
const admission = (flags & FLAG_ADMISSION_MASK) >> FLAG_ADMISSION_SHIFT;
|
|
12388
|
+
if (admission === 3) {
|
|
12389
|
+
throw new DecodeError(`reserved admission class set in flags 0b${flags.toString(2).padStart(8, "0")}`, "reserved_admission_class");
|
|
12390
|
+
}
|
|
12391
|
+
if (admission === AdmissionClass.Sheddable && ty !== FrameType.Push && ty !== FrameType.StreamData) {
|
|
12392
|
+
throw new DecodeError(`SHEDDABLE admission class is illegal on ${FrameType[ty]} in flags 0b${flags.toString(2).padStart(8, "0")}`, "sheddable_illegal_frame_type");
|
|
12393
|
+
}
|
|
12394
|
+
const channel = view.getUint16(7, true);
|
|
12395
|
+
const epoch = view.getUint32(9, true);
|
|
12396
|
+
if (channel === 0 && epoch !== 0) {
|
|
12397
|
+
throw new DecodeError(`control channel carried nonzero epoch ${epoch}`, "nonzero_epoch_on_control_channel");
|
|
12398
|
+
}
|
|
12399
|
+
if (isPureHeader(ty) && len !== 0) {
|
|
12400
|
+
throw new DecodeError(`pure-header frame ${FrameType[ty]} declared non-zero body length ${len}`, "pure_header_frame_with_body");
|
|
12401
|
+
}
|
|
12402
|
+
return { len, ver, ty, flags, channel, epoch, corr: view.getBigUint64(13, true) };
|
|
12403
|
+
}
|
|
12404
|
+
function buildFrame(ty, flags, channel, epoch, corr, body) {
|
|
12405
|
+
return buildFrameWithVersion(PROTOCOL_VERSION, ty, flags, channel, epoch, corr, body);
|
|
12406
|
+
}
|
|
12407
|
+
function buildFrameWithVersion(ver, ty, flags, channel, epoch, corr, body) {
|
|
12408
|
+
if (body.length > MAX_FRAME_BODY_LEN) {
|
|
12409
|
+
throw new DecodeError(`frame body ${body.length} exceeds max ${MAX_FRAME_BODY_LEN}`, "frame_body_too_large");
|
|
12410
|
+
}
|
|
12411
|
+
const header = { len: body.length, ver, ty, flags, channel, epoch, corr };
|
|
12412
|
+
decodeHeader(encodeHeader(header));
|
|
12413
|
+
return { header, body };
|
|
12414
|
+
}
|
|
12415
|
+
function encodeFrame(frame) {
|
|
12416
|
+
if (frame.header.len !== frame.body.length) {
|
|
12417
|
+
throw new DecodeError(`frame header length ${frame.header.len} does not match body length ${frame.body.length}`, "frame_length_mismatch");
|
|
12418
|
+
}
|
|
12419
|
+
const header = encodeHeader(frame.header);
|
|
12420
|
+
const output = new Uint8Array(header.length + frame.body.length);
|
|
12421
|
+
output.set(header, 0);
|
|
12422
|
+
output.set(frame.body, header.length);
|
|
12423
|
+
return output;
|
|
12424
|
+
}
|
|
12425
|
+
|
|
12426
|
+
// ../../node_modules/.bun/@cortexkit+subc-client@0.5.0/node_modules/@cortexkit/subc-client/dist/connection-file.js
|
|
11933
12427
|
var SCHEMA_VERSION = 1;
|
|
11934
12428
|
var MIN_KEY_LEN = 32;
|
|
11935
12429
|
var DAEMON_ID_LEN = 16;
|
|
@@ -11959,8 +12453,8 @@ function validate(info) {
|
|
|
11959
12453
|
async function verifyOwnerOnly(path2) {
|
|
11960
12454
|
if (process.platform === "win32")
|
|
11961
12455
|
return;
|
|
11962
|
-
const
|
|
11963
|
-
const mode =
|
|
12456
|
+
const stat2 = await fs.stat(path2);
|
|
12457
|
+
const mode = stat2.mode & 511;
|
|
11964
12458
|
if ((mode & 63) !== 0) {
|
|
11965
12459
|
throw new ConnectionFileError(`connection file ${path2} has insecure permissions 0o${mode.toString(8)}; expected owner-only 0600`);
|
|
11966
12460
|
}
|
|
@@ -11974,6 +12468,10 @@ async function readConnectionFile(path2) {
|
|
|
11974
12468
|
} catch (err) {
|
|
11975
12469
|
throw new ConnectionFileError(`connection file JSON read failed for ${path2}: ${String(err)}`);
|
|
11976
12470
|
}
|
|
12471
|
+
const wireVersion = parsed.wire_version;
|
|
12472
|
+
if (wireVersion !== undefined && wireVersion !== PROTOCOL_VERSION) {
|
|
12473
|
+
throw new ConnectionFileError(`connection file wire_version ${String(wireVersion)} but this client speaks ${PROTOCOL_VERSION}; the client library must be upgraded`);
|
|
12474
|
+
}
|
|
11977
12475
|
const endpointsRaw = parsed.endpoints;
|
|
11978
12476
|
if (!Array.isArray(endpointsRaw)) {
|
|
11979
12477
|
throw new ConnectionFileError("connection file 'endpoints' must be an array");
|
|
@@ -11997,116 +12495,54 @@ async function readConnectionFile(path2) {
|
|
|
11997
12495
|
return info;
|
|
11998
12496
|
}
|
|
11999
12497
|
|
|
12000
|
-
// ../../node_modules/.bun/@cortexkit+subc-client@0.
|
|
12001
|
-
var
|
|
12002
|
-
var HEADER_LEN = 17;
|
|
12003
|
-
var FROZEN_PREFIX_LEN = 5;
|
|
12004
|
-
var MAX_FRAME_BODY_LEN = 64 * 1024 * 1024;
|
|
12005
|
-
var FrameType;
|
|
12006
|
-
((FrameType2) => {
|
|
12007
|
-
FrameType2[FrameType2["Request"] = 0] = "Request";
|
|
12008
|
-
FrameType2[FrameType2["Response"] = 1] = "Response";
|
|
12009
|
-
FrameType2[FrameType2["Push"] = 2] = "Push";
|
|
12010
|
-
FrameType2[FrameType2["StreamData"] = 3] = "StreamData";
|
|
12011
|
-
FrameType2[FrameType2["StreamEnd"] = 4] = "StreamEnd";
|
|
12012
|
-
FrameType2[FrameType2["Error"] = 5] = "Error";
|
|
12013
|
-
FrameType2[FrameType2["Cancel"] = 6] = "Cancel";
|
|
12014
|
-
FrameType2[FrameType2["Ping"] = 7] = "Ping";
|
|
12015
|
-
FrameType2[FrameType2["Pong"] = 8] = "Pong";
|
|
12016
|
-
FrameType2[FrameType2["Hello"] = 9] = "Hello";
|
|
12017
|
-
FrameType2[FrameType2["HelloAck"] = 10] = "HelloAck";
|
|
12018
|
-
FrameType2[FrameType2["Goodbye"] = 11] = "Goodbye";
|
|
12019
|
-
})(FrameType ||= {});
|
|
12020
|
-
var FRAME_TYPE_MAX = 11 /* Goodbye */;
|
|
12021
|
-
function isPureHeader(ty) {
|
|
12022
|
-
return ty === 6 /* Cancel */ || ty === 7 /* Ping */ || ty === 8 /* Pong */ || ty === 11 /* Goodbye */;
|
|
12023
|
-
}
|
|
12024
|
-
var FLAG_BINARY = 1;
|
|
12025
|
-
var FLAG_PRIORITY_MASK = 6;
|
|
12026
|
-
var FLAG_PRIORITY_SHIFT = 1;
|
|
12027
|
-
var FLAG_LAST = 8;
|
|
12028
|
-
var FLAG_RESERVED_MASK = 240;
|
|
12029
|
-
function buildFlags(binary, priority, last) {
|
|
12030
|
-
let b = 0;
|
|
12031
|
-
if (binary)
|
|
12032
|
-
b |= FLAG_BINARY;
|
|
12033
|
-
b |= priority << FLAG_PRIORITY_SHIFT;
|
|
12034
|
-
if (last)
|
|
12035
|
-
b |= FLAG_LAST;
|
|
12036
|
-
return b;
|
|
12037
|
-
}
|
|
12038
|
-
function encodeHeader(h) {
|
|
12039
|
-
const buf = new Uint8Array(HEADER_LEN);
|
|
12040
|
-
const view = new DataView(buf.buffer);
|
|
12041
|
-
view.setUint32(0, h.len, true);
|
|
12042
|
-
buf[4] = h.ver;
|
|
12043
|
-
buf[5] = h.ty;
|
|
12044
|
-
buf[6] = h.flags;
|
|
12045
|
-
view.setUint16(7, h.channel, true);
|
|
12046
|
-
view.setBigUint64(9, h.corr, true);
|
|
12047
|
-
return buf;
|
|
12048
|
-
}
|
|
12498
|
+
// ../../node_modules/.bun/@cortexkit+subc-client@0.5.0/node_modules/@cortexkit/subc-client/dist/route-handle.js
|
|
12499
|
+
var connectionToken = new WeakMap;
|
|
12049
12500
|
|
|
12050
|
-
class
|
|
12051
|
-
|
|
12052
|
-
|
|
12053
|
-
|
|
12054
|
-
|
|
12055
|
-
|
|
12056
|
-
|
|
12057
|
-
|
|
12058
|
-
|
|
12059
|
-
|
|
12060
|
-
|
|
12061
|
-
|
|
12062
|
-
|
|
12063
|
-
|
|
12064
|
-
const len = view.getUint32(0, true);
|
|
12065
|
-
const tyByte = bytes[5];
|
|
12066
|
-
if (tyByte > FRAME_TYPE_MAX) {
|
|
12067
|
-
throw new DecodeError(`unknown frame type byte ${tyByte}`);
|
|
12068
|
-
}
|
|
12069
|
-
const ty = tyByte;
|
|
12070
|
-
const flags = bytes[6];
|
|
12071
|
-
if ((flags & FLAG_RESERVED_MASK) !== 0) {
|
|
12072
|
-
throw new DecodeError(`reserved flag bits set in flags 0b${flags.toString(2)}`);
|
|
12073
|
-
}
|
|
12074
|
-
if ((flags & FLAG_PRIORITY_MASK) >> FLAG_PRIORITY_SHIFT === 3) {
|
|
12075
|
-
throw new DecodeError(`reserved priority bits set in flags 0b${flags.toString(2)}`);
|
|
12501
|
+
class RouteHandle {
|
|
12502
|
+
channel;
|
|
12503
|
+
epoch;
|
|
12504
|
+
constructor(channel, epoch, token) {
|
|
12505
|
+
if (!Number.isInteger(channel) || channel <= 0 || channel > 65535) {
|
|
12506
|
+
throw new RangeError(`route channel must be an integer in 1..65535, got ${channel}`);
|
|
12507
|
+
}
|
|
12508
|
+
if (!Number.isInteger(epoch) || epoch <= 0 || epoch > 4294967295) {
|
|
12509
|
+
throw new RangeError(`route epoch must be an integer in 1..4294967295, got ${epoch}`);
|
|
12510
|
+
}
|
|
12511
|
+
this.channel = channel;
|
|
12512
|
+
this.epoch = epoch;
|
|
12513
|
+
connectionToken.set(this, token);
|
|
12514
|
+
Object.freeze(this);
|
|
12076
12515
|
}
|
|
12077
|
-
|
|
12078
|
-
|
|
12516
|
+
static create(channel, epoch, token) {
|
|
12517
|
+
return new RouteHandle(channel, epoch, token);
|
|
12079
12518
|
}
|
|
12080
|
-
const channel = view.getUint16(7, true);
|
|
12081
|
-
const corr = view.getBigUint64(9, true);
|
|
12082
|
-
return { len, ver, ty, flags, channel, corr };
|
|
12083
12519
|
}
|
|
12084
|
-
function
|
|
12085
|
-
|
|
12520
|
+
function createRouteHandle(channel, epoch, token) {
|
|
12521
|
+
const factory = RouteHandle;
|
|
12522
|
+
return factory.create(channel, epoch, token);
|
|
12086
12523
|
}
|
|
12087
|
-
|
|
12088
|
-
|
|
12089
|
-
|
|
12090
|
-
|
|
12091
|
-
|
|
12092
|
-
|
|
12524
|
+
|
|
12525
|
+
class StaleRouteHandleError extends Error {
|
|
12526
|
+
handle;
|
|
12527
|
+
code = "stale_route_handle";
|
|
12528
|
+
constructor(handle) {
|
|
12529
|
+
super(`route handle (${handle.channel}, ${handle.epoch}) is not live on the current connection`);
|
|
12530
|
+
this.handle = handle;
|
|
12531
|
+
this.name = "StaleRouteHandleError";
|
|
12093
12532
|
}
|
|
12094
|
-
return {
|
|
12095
|
-
header: { len: body.length, ver, ty, flags, channel, corr },
|
|
12096
|
-
body
|
|
12097
|
-
};
|
|
12098
12533
|
}
|
|
12099
|
-
function
|
|
12100
|
-
|
|
12101
|
-
|
|
12102
|
-
|
|
12103
|
-
|
|
12104
|
-
|
|
12534
|
+
function newConnectionToken() {
|
|
12535
|
+
return Object.freeze({});
|
|
12536
|
+
}
|
|
12537
|
+
function belongsToConnection(handle, token) {
|
|
12538
|
+
return connectionToken.get(handle) === token;
|
|
12539
|
+
}
|
|
12540
|
+
function sameRouteHandle(left, right) {
|
|
12541
|
+
return left === right;
|
|
12105
12542
|
}
|
|
12106
12543
|
|
|
12107
|
-
// ../../node_modules/.bun/@cortexkit+subc-client@0.
|
|
12544
|
+
// ../../node_modules/.bun/@cortexkit+subc-client@0.5.0/node_modules/@cortexkit/subc-client/dist/socket.js
|
|
12108
12545
|
import net from "node:net";
|
|
12109
|
-
|
|
12110
12546
|
class SocketClosedError extends Error {
|
|
12111
12547
|
}
|
|
12112
12548
|
|
|
@@ -12175,6 +12611,24 @@ class SubcSocket {
|
|
|
12175
12611
|
});
|
|
12176
12612
|
});
|
|
12177
12613
|
}
|
|
12614
|
+
async readFrame(headerDeadlineMs, bodyDeadline, onHeader) {
|
|
12615
|
+
const prefix = await this.readExact(FROZEN_PREFIX_LEN, headerDeadlineMs);
|
|
12616
|
+
const version = prefix[4];
|
|
12617
|
+
if (version !== PROTOCOL_VERSION)
|
|
12618
|
+
throw new DecodeError(`unsupported envelope version ${version}`, "unsupported_version");
|
|
12619
|
+
const remainder = await this.readExact(HEADER_LEN - FROZEN_PREFIX_LEN, headerDeadlineMs);
|
|
12620
|
+
const headerBytes = new Uint8Array(HEADER_LEN);
|
|
12621
|
+
headerBytes.set(prefix);
|
|
12622
|
+
headerBytes.set(remainder, FROZEN_PREFIX_LEN);
|
|
12623
|
+
const header = decodeHeader(headerBytes);
|
|
12624
|
+
if (header.len > MAX_FRAME_BODY_LEN) {
|
|
12625
|
+
throw new DecodeError(`frame body ${header.len} exceeds max ${MAX_FRAME_BODY_LEN}`, "frame_body_too_large");
|
|
12626
|
+
}
|
|
12627
|
+
onHeader?.();
|
|
12628
|
+
const bodyDeadlineMs = typeof bodyDeadline === "number" ? bodyDeadline : Date.now() + bodyDeadline.afterHeaderMs;
|
|
12629
|
+
const body = header.len === 0 ? new Uint8Array(0) : await this.readExact(header.len, bodyDeadlineMs);
|
|
12630
|
+
return { header, body };
|
|
12631
|
+
}
|
|
12178
12632
|
readExact(n, deadlineMs) {
|
|
12179
12633
|
if (this.waiter) {
|
|
12180
12634
|
return Promise.reject(new Error("concurrent readExact is not supported"));
|
|
@@ -12296,14 +12750,14 @@ class SubcSocket {
|
|
|
12296
12750
|
}
|
|
12297
12751
|
}
|
|
12298
12752
|
|
|
12299
|
-
// ../../node_modules/.bun/@cortexkit+subc-client@0.
|
|
12753
|
+
// ../../node_modules/.bun/@cortexkit+subc-client@0.5.0/node_modules/@cortexkit/subc-client/dist/client.js
|
|
12300
12754
|
var debug = debuglog("subc-client");
|
|
12301
12755
|
var DEFAULT_HANDSHAKE_TIMEOUT_MS = 1e4;
|
|
12302
12756
|
var DEFAULT_REQUEST_TIMEOUT_MS = 30000;
|
|
12303
12757
|
var TIMEOUT_ARBITRATION_GRACE_MS = 50;
|
|
12304
12758
|
var REQUEST_DEADLINE_MARKER = "request_deadline";
|
|
12305
12759
|
var DEADLINE_NO_DROP_CODE = "deadline_exceeded_no_drop_observed";
|
|
12306
|
-
var ROUTE_OPEN_RETRY_DEADLINE_MS =
|
|
12760
|
+
var ROUTE_OPEN_RETRY_DEADLINE_MS = 30000;
|
|
12307
12761
|
var BODY_READ_TIMEOUT_MS = 30000;
|
|
12308
12762
|
var EMPTY_BODY = new Uint8Array(0);
|
|
12309
12763
|
var DEFAULT_MANAGED_TARGET_KIND = "management_surface";
|
|
@@ -12342,7 +12796,11 @@ class SubcClient {
|
|
|
12342
12796
|
opts;
|
|
12343
12797
|
nextCorr = 1n;
|
|
12344
12798
|
pending = new Map;
|
|
12799
|
+
lateResponses = new Map;
|
|
12345
12800
|
routes = new Map;
|
|
12801
|
+
liveRoutes = new Map;
|
|
12802
|
+
connectionToken = newConnectionToken();
|
|
12803
|
+
ingressEpochDropCount = 0;
|
|
12346
12804
|
closedErr = null;
|
|
12347
12805
|
closeStarted = false;
|
|
12348
12806
|
reconnecting = null;
|
|
@@ -12370,34 +12828,71 @@ class SubcClient {
|
|
|
12370
12828
|
}
|
|
12371
12829
|
async routeOpen(target, identity, opts = {}) {
|
|
12372
12830
|
const consumerIdentity = routeOpenConsumerIdentity(opts);
|
|
12831
|
+
const consumerCapabilities = opts.consumerCapabilities;
|
|
12373
12832
|
const body = this.encode({
|
|
12374
12833
|
op: "route.open",
|
|
12375
12834
|
target,
|
|
12376
12835
|
identity,
|
|
12377
|
-
...consumerIdentity ? { consumer_identity: consumerIdentity } : {}
|
|
12836
|
+
...consumerIdentity ? { consumer_identity: consumerIdentity } : {},
|
|
12837
|
+
...consumerCapabilities !== undefined ? { consumer_capabilities: consumerCapabilities } : {}
|
|
12378
12838
|
});
|
|
12379
|
-
|
|
12380
|
-
const
|
|
12381
|
-
|
|
12382
|
-
|
|
12383
|
-
|
|
12384
|
-
|
|
12839
|
+
let installed = null;
|
|
12840
|
+
const install = (frame) => {
|
|
12841
|
+
if (frame.header.ty !== FrameType.Response)
|
|
12842
|
+
return true;
|
|
12843
|
+
const parsed = this.parseJson(frame);
|
|
12844
|
+
if (typeof parsed.route_channel !== "number" || typeof parsed.route_epoch !== "number") {
|
|
12845
|
+
throw new SubcError(`route.open returned no route handle: ${JSON.stringify(parsed)}`);
|
|
12846
|
+
}
|
|
12847
|
+
installed = this.installRoute(parsed.route_channel, parsed.route_epoch);
|
|
12848
|
+
return true;
|
|
12849
|
+
};
|
|
12850
|
+
const closeLateRoute = (frame) => {
|
|
12851
|
+
if (frame.header.ty !== FrameType.Response)
|
|
12852
|
+
return;
|
|
12853
|
+
try {
|
|
12854
|
+
const parsed = this.parseJson(frame);
|
|
12855
|
+
if (typeof parsed.route_channel !== "number" || typeof parsed.route_epoch !== "number")
|
|
12856
|
+
return;
|
|
12857
|
+
const lateHandle = this.installRoute(parsed.route_channel, parsed.route_epoch);
|
|
12858
|
+
this.failHandle(lateHandle, new SubcError("late route.open was closed", "route_closed"));
|
|
12859
|
+
this.liveRoutes.delete(lateHandle.channel);
|
|
12860
|
+
this.sendRouteGoodbye(lateHandle, true);
|
|
12861
|
+
} catch {
|
|
12862
|
+
this.closeConnectionAfterCleanupFailure();
|
|
12863
|
+
}
|
|
12864
|
+
};
|
|
12865
|
+
await this.controlRpc(body, install, closeLateRoute);
|
|
12866
|
+
if (!installed)
|
|
12867
|
+
throw new SubcError("route.open response was not installed");
|
|
12868
|
+
return installed;
|
|
12385
12869
|
}
|
|
12386
|
-
async request(
|
|
12870
|
+
async request(handle, body, opts = {}) {
|
|
12871
|
+
this.assertLiveHandle(handle);
|
|
12387
12872
|
const bytes = body instanceof Uint8Array ? body : this.encode(body);
|
|
12388
|
-
const priority = opts.priority ??
|
|
12389
|
-
const
|
|
12873
|
+
const priority = opts.priority ?? Priority.Interactive;
|
|
12874
|
+
const admission = opts.admissionClass ?? AdmissionClass.Normal;
|
|
12875
|
+
const reply = await this.send(handle, bytes, priority, admission, opts.timeoutMs, opts.onProgress);
|
|
12390
12876
|
return this.parseJson(reply);
|
|
12391
12877
|
}
|
|
12392
12878
|
async call(moduleId, method, params, opts = {}) {
|
|
12393
12879
|
const body = params === undefined ? { method } : { method, params };
|
|
12880
|
+
let retriedUnknownChannel = false;
|
|
12394
12881
|
for (;; ) {
|
|
12395
|
-
const
|
|
12882
|
+
const routeHandle = await this.cachedRouteHandle(moduleId, opts);
|
|
12396
12883
|
try {
|
|
12397
|
-
return await this.managedRequest(
|
|
12884
|
+
return await this.managedRequest(routeHandle, body, opts);
|
|
12398
12885
|
} catch (err) {
|
|
12399
12886
|
if (!(err instanceof SubcCallError))
|
|
12400
12887
|
throw this.terminalCallError("managed call failed", err);
|
|
12888
|
+
if (err.code === "unknown_channel" && !retriedUnknownChannel && !this.closeStarted) {
|
|
12889
|
+
retriedUnknownChannel = true;
|
|
12890
|
+
this.evictRouteHandle(routeHandle);
|
|
12891
|
+
continue;
|
|
12892
|
+
}
|
|
12893
|
+
if (err.code === "unknown_channel" && retriedUnknownChannel) {
|
|
12894
|
+
this.evictRouteHandle(routeHandle);
|
|
12895
|
+
}
|
|
12401
12896
|
if (err.kind === "not_sent") {
|
|
12402
12897
|
try {
|
|
12403
12898
|
await this.reconnectAfterDrop(err);
|
|
@@ -12413,29 +12908,31 @@ class SubcClient {
|
|
|
12413
12908
|
}
|
|
12414
12909
|
}
|
|
12415
12910
|
}
|
|
12416
|
-
subscribe(
|
|
12911
|
+
subscribe(handle, body, onEvent, opts = {}) {
|
|
12912
|
+
this.assertLiveHandle(handle);
|
|
12417
12913
|
const bytes = body instanceof Uint8Array ? body : this.encode(body);
|
|
12418
|
-
const priority = opts.priority ??
|
|
12419
|
-
const
|
|
12420
|
-
const
|
|
12914
|
+
const priority = opts.priority ?? Priority.Interactive;
|
|
12915
|
+
const admission = opts.admissionClass ?? AdmissionClass.Normal;
|
|
12916
|
+
const corr = this.allocateCorr();
|
|
12917
|
+
const key = pendingKey(handle, corr);
|
|
12421
12918
|
const closed = new Promise((resolve7, reject) => {
|
|
12422
12919
|
if (this.closedErr) {
|
|
12423
12920
|
reject(this.closedErr);
|
|
12424
12921
|
return;
|
|
12425
12922
|
}
|
|
12426
12923
|
this.pending.set(key, {
|
|
12427
|
-
|
|
12924
|
+
handle,
|
|
12428
12925
|
resolve: () => resolve7(),
|
|
12429
12926
|
reject,
|
|
12430
12927
|
onProgress: onEvent,
|
|
12431
12928
|
timer: null,
|
|
12432
12929
|
subscription: true
|
|
12433
12930
|
});
|
|
12434
|
-
const frame = buildFrame(
|
|
12931
|
+
const frame = buildFrame(FrameType.Request, buildFlags(false, priority, false, admission), handle.channel, handle.epoch, corr, bytes);
|
|
12435
12932
|
this.sock.write(encodeFrame(frame), Date.now() + DEFAULT_REQUEST_TIMEOUT_MS).catch((err) => {
|
|
12436
|
-
const
|
|
12437
|
-
if (
|
|
12438
|
-
this.rejectPending(key,
|
|
12933
|
+
const pending = this.pending.get(key);
|
|
12934
|
+
if (pending)
|
|
12935
|
+
this.rejectPending(key, pending, err instanceof Error ? err : new SubcError(String(err)));
|
|
12439
12936
|
});
|
|
12440
12937
|
});
|
|
12441
12938
|
let cancelled = false;
|
|
@@ -12443,45 +12940,77 @@ class SubcClient {
|
|
|
12443
12940
|
if (cancelled)
|
|
12444
12941
|
return;
|
|
12445
12942
|
cancelled = true;
|
|
12446
|
-
|
|
12447
|
-
this.sock.write(encodeFrame(cancel), Date.now() + DEFAULT_REQUEST_TIMEOUT_MS).catch(() => {});
|
|
12943
|
+
this.cancel(handle, corr, priority);
|
|
12448
12944
|
};
|
|
12449
12945
|
return { unsubscribe, closed };
|
|
12450
12946
|
}
|
|
12451
|
-
|
|
12947
|
+
cancel(handle, corr, priority = Priority.Interactive) {
|
|
12948
|
+
this.assertLiveHandle(handle);
|
|
12949
|
+
const cancel = buildFrame(FrameType.Cancel, buildFlags(false, priority, false), handle.channel, handle.epoch, corr, EMPTY_BODY);
|
|
12950
|
+
this.sock.write(encodeFrame(cancel), Date.now() + DEFAULT_REQUEST_TIMEOUT_MS).catch(() => {
|
|
12951
|
+
return;
|
|
12952
|
+
});
|
|
12953
|
+
}
|
|
12954
|
+
async routePoll(handle, kind) {
|
|
12955
|
+
this.assertLiveHandle(handle);
|
|
12956
|
+
const body = this.encode({
|
|
12957
|
+
op: "route.poll",
|
|
12958
|
+
route_channel: handle.channel,
|
|
12959
|
+
route_epoch: handle.epoch,
|
|
12960
|
+
kind
|
|
12961
|
+
});
|
|
12962
|
+
const reply = await this.controlRpc(body, (frame) => {
|
|
12963
|
+
if (frame.header.ty !== FrameType.Response)
|
|
12964
|
+
return true;
|
|
12965
|
+
const parsed = this.parseJson(frame);
|
|
12966
|
+
return parsed.route_channel === handle.channel && parsed.route_epoch === handle.epoch;
|
|
12967
|
+
});
|
|
12968
|
+
return this.parseJson(reply);
|
|
12969
|
+
}
|
|
12970
|
+
async closeRoute(handle, opts = {}) {
|
|
12971
|
+
this.assertLiveHandle(handle);
|
|
12972
|
+
for (const [key, cached] of this.routes) {
|
|
12973
|
+
if (cached.handle && sameRouteHandle(cached.handle, handle)) {
|
|
12974
|
+
cached.closed = true;
|
|
12975
|
+
cached.handle = null;
|
|
12976
|
+
this.routes.delete(key);
|
|
12977
|
+
}
|
|
12978
|
+
}
|
|
12979
|
+
if (opts.drain)
|
|
12980
|
+
await this.drainUnaryOnHandle(handle);
|
|
12981
|
+
this.failHandle(handle, new SubcError("route closed by closeRoute", "route_closed"));
|
|
12982
|
+
if (this.liveRoutes.get(handle.channel) === handle)
|
|
12983
|
+
this.liveRoutes.delete(handle.channel);
|
|
12984
|
+
this.sendRouteGoodbye(handle);
|
|
12985
|
+
}
|
|
12986
|
+
async closeManagedRoute(target, identity, opts = {}) {
|
|
12452
12987
|
const key = routeCacheKey(target, identity, routeOpenConsumerIdentity(opts));
|
|
12453
12988
|
const cached = this.routes.get(key);
|
|
12454
12989
|
if (!cached)
|
|
12455
12990
|
return;
|
|
12456
12991
|
cached.closed = true;
|
|
12457
12992
|
this.routes.delete(key);
|
|
12458
|
-
const
|
|
12459
|
-
cached.
|
|
12460
|
-
if (
|
|
12461
|
-
await this.
|
|
12993
|
+
const handle = cached.handle;
|
|
12994
|
+
cached.handle = null;
|
|
12995
|
+
if (handle)
|
|
12996
|
+
await this.closeRoute(handle, opts);
|
|
12462
12997
|
}
|
|
12463
|
-
async closeRouteChannel(
|
|
12464
|
-
|
|
12465
|
-
return;
|
|
12466
|
-
if (opts.drain) {
|
|
12467
|
-
await this.drainUnaryOnChannel(channel);
|
|
12468
|
-
}
|
|
12469
|
-
this.failChannel(channel, new SubcError("route closed by closeRoute", "route_closed"));
|
|
12470
|
-
this.sendRouteGoodbye(channel);
|
|
12998
|
+
async closeRouteChannel(handle, opts = {}) {
|
|
12999
|
+
await this.closeRoute(handle, opts);
|
|
12471
13000
|
}
|
|
12472
13001
|
close() {
|
|
12473
13002
|
this.closeStarted = true;
|
|
12474
13003
|
this.fail(new SubcError("client closed"));
|
|
12475
13004
|
this.sock.close();
|
|
12476
13005
|
}
|
|
12477
|
-
|
|
13006
|
+
drainUnaryOnHandle(handle) {
|
|
12478
13007
|
const waiters = [];
|
|
12479
13008
|
for (const pending of this.pending.values()) {
|
|
12480
|
-
if (pending.
|
|
13009
|
+
if (pending.handle === handle && !pending.subscription) {
|
|
12481
13010
|
waiters.push(new Promise((resolve7) => {
|
|
12482
|
-
const
|
|
13011
|
+
const previous = pending.onSettle;
|
|
12483
13012
|
pending.onSettle = () => {
|
|
12484
|
-
|
|
13013
|
+
previous?.();
|
|
12485
13014
|
resolve7();
|
|
12486
13015
|
};
|
|
12487
13016
|
}));
|
|
@@ -12491,11 +13020,21 @@ class SubcClient {
|
|
|
12491
13020
|
return;
|
|
12492
13021
|
});
|
|
12493
13022
|
}
|
|
12494
|
-
sendRouteGoodbye(
|
|
12495
|
-
|
|
13023
|
+
sendRouteGoodbye(handle, closeOnQueueFailure = false) {
|
|
13024
|
+
this.assertLiveConnection(handle);
|
|
13025
|
+
if (this.closedErr) {
|
|
13026
|
+
if (closeOnQueueFailure)
|
|
13027
|
+
this.closeConnectionAfterCleanupFailure();
|
|
12496
13028
|
return;
|
|
12497
|
-
|
|
12498
|
-
|
|
13029
|
+
}
|
|
13030
|
+
const goodbye = buildFrame(FrameType.Goodbye, buildFlags(false, Priority.Interactive, false), handle.channel, handle.epoch, 0n, EMPTY_BODY);
|
|
13031
|
+
const write = this.sock.writeTracked(encodeFrame(goodbye), Date.now() + DEFAULT_REQUEST_TIMEOUT_MS);
|
|
13032
|
+
if (!write.queued && closeOnQueueFailure)
|
|
13033
|
+
this.closeConnectionAfterCleanupFailure();
|
|
13034
|
+
write.completed.catch(() => {
|
|
13035
|
+
if (closeOnQueueFailure && !write.queued)
|
|
13036
|
+
this.closeConnectionAfterCleanupFailure();
|
|
13037
|
+
});
|
|
12499
13038
|
}
|
|
12500
13039
|
static async openConnection(opts) {
|
|
12501
13040
|
const conn = await readConnectionFile(opts.connectionFile);
|
|
@@ -12510,37 +13049,48 @@ class SubcClient {
|
|
|
12510
13049
|
}
|
|
12511
13050
|
return { sock, conn };
|
|
12512
13051
|
}
|
|
12513
|
-
async controlRpc(body) {
|
|
12514
|
-
return this.send(
|
|
13052
|
+
async controlRpc(body, acceptFrame, onLateResponse) {
|
|
13053
|
+
return this.send(null, body, Priority.Interactive, AdmissionClass.Normal, undefined, undefined, acceptFrame, onLateResponse);
|
|
12515
13054
|
}
|
|
12516
|
-
send(
|
|
13055
|
+
send(handle, body, priority, admission, timeoutMs, onProgress, acceptFrame, onLateResponse) {
|
|
13056
|
+
if (handle)
|
|
13057
|
+
this.assertLiveHandle(handle);
|
|
12517
13058
|
if (this.closedErr)
|
|
12518
13059
|
return Promise.reject(this.closedErr);
|
|
12519
|
-
|
|
12520
|
-
|
|
12521
|
-
|
|
13060
|
+
let corr;
|
|
13061
|
+
try {
|
|
13062
|
+
corr = this.allocateCorr();
|
|
13063
|
+
} catch (error2) {
|
|
13064
|
+
return Promise.reject(error2);
|
|
13065
|
+
}
|
|
13066
|
+
const key = pendingKey(handle, corr);
|
|
13067
|
+
const channel = handle?.channel ?? 0;
|
|
13068
|
+
const epoch = handle?.epoch ?? 0;
|
|
13069
|
+
const frame = buildFrame(FrameType.Request, buildFlags(false, priority, false, admission), channel, epoch, corr, body);
|
|
12522
13070
|
return new Promise((resolve7, reject) => {
|
|
12523
13071
|
const ms = timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
12524
13072
|
const pending = {
|
|
12525
|
-
|
|
13073
|
+
handle,
|
|
12526
13074
|
resolve: resolve7,
|
|
12527
13075
|
reject,
|
|
12528
13076
|
onProgress,
|
|
12529
|
-
timer: null
|
|
13077
|
+
timer: null,
|
|
13078
|
+
acceptFrame,
|
|
13079
|
+
onLateResponse
|
|
12530
13080
|
};
|
|
12531
|
-
pending.timer = setTimeout(() =>
|
|
12532
|
-
this.arbitrateTimeout(key, pending, channel, corr, ms);
|
|
12533
|
-
}, ms);
|
|
13081
|
+
pending.timer = setTimeout(() => this.arbitrateTimeout(key, pending, channel, corr, ms), ms);
|
|
12534
13082
|
this.pending.set(key, pending);
|
|
12535
|
-
this.sock.write(encodeFrame(frame), Date.now() + ms).catch((
|
|
12536
|
-
const
|
|
12537
|
-
if (
|
|
12538
|
-
this.rejectPending(key,
|
|
13083
|
+
this.sock.write(encodeFrame(frame), Date.now() + ms).catch((error2) => {
|
|
13084
|
+
const current = this.pending.get(key);
|
|
13085
|
+
if (current)
|
|
13086
|
+
this.rejectPending(key, current, error2 instanceof Error ? error2 : new SubcError(String(error2)));
|
|
12539
13087
|
});
|
|
12540
13088
|
});
|
|
12541
13089
|
}
|
|
12542
13090
|
arbitrateTimeout(key, pending, channel, corr, ms) {
|
|
12543
13091
|
const settleAsTimeout = () => {
|
|
13092
|
+
if (pending.onLateResponse)
|
|
13093
|
+
this.lateResponses.set(key, pending.onLateResponse);
|
|
12544
13094
|
this.rejectPending(key, pending, new SubcError(this.timeoutMessage(channel, corr, ms), REQUEST_DEADLINE_MARKER));
|
|
12545
13095
|
};
|
|
12546
13096
|
const graceDeadline = Date.now() + this.opts.timeoutArbitrationGraceMs;
|
|
@@ -12556,59 +13106,67 @@ class SubcClient {
|
|
|
12556
13106
|
};
|
|
12557
13107
|
setImmediate(arbitrate);
|
|
12558
13108
|
}
|
|
12559
|
-
async managedRequest(
|
|
13109
|
+
async managedRequest(handle, body, opts) {
|
|
12560
13110
|
const bytes = body instanceof Uint8Array ? body : this.encode(body);
|
|
12561
|
-
const priority = opts.priority ??
|
|
13111
|
+
const priority = opts.priority ?? Priority.Interactive;
|
|
13112
|
+
const admission = opts.admissionClass ?? AdmissionClass.Normal;
|
|
12562
13113
|
try {
|
|
12563
|
-
const reply = await this.sendManaged(
|
|
13114
|
+
const reply = await this.sendManaged(handle, bytes, priority, admission, opts.timeoutMs, opts.onProgress);
|
|
12564
13115
|
return this.parseJson(reply);
|
|
12565
|
-
} catch (
|
|
12566
|
-
if (
|
|
12567
|
-
throw
|
|
12568
|
-
throw this.terminalCallError("managed call failed",
|
|
13116
|
+
} catch (error2) {
|
|
13117
|
+
if (error2 instanceof SubcCallError)
|
|
13118
|
+
throw error2;
|
|
13119
|
+
throw this.terminalCallError("managed call failed", error2);
|
|
12569
13120
|
}
|
|
12570
13121
|
}
|
|
12571
|
-
sendManaged(
|
|
13122
|
+
sendManaged(handle, body, priority, admission, timeoutMs, onProgress) {
|
|
13123
|
+
try {
|
|
13124
|
+
this.assertLiveHandle(handle);
|
|
13125
|
+
} catch (error2) {
|
|
13126
|
+
return Promise.reject(this.notSentCallError("request used a stale route handle", error2));
|
|
13127
|
+
}
|
|
12572
13128
|
if (this.closedErr) {
|
|
12573
13129
|
return Promise.reject(this.notSentCallError("request was not sent because the subc connection was already closed", this.closedErr));
|
|
12574
13130
|
}
|
|
12575
|
-
|
|
12576
|
-
|
|
12577
|
-
|
|
13131
|
+
let corr;
|
|
13132
|
+
try {
|
|
13133
|
+
corr = this.allocateCorr();
|
|
13134
|
+
} catch (error2) {
|
|
13135
|
+
return Promise.reject(this.notSentCallError("request correlation allocator was exhausted", error2));
|
|
13136
|
+
}
|
|
13137
|
+
const key = pendingKey(handle, corr);
|
|
13138
|
+
const frame = buildFrame(FrameType.Request, buildFlags(false, priority, false, admission), handle.channel, handle.epoch, corr, body);
|
|
12578
13139
|
let handedToSocket = false;
|
|
12579
|
-
const classifyFailure = (
|
|
12580
|
-
if (!handedToSocket)
|
|
12581
|
-
return this.notSentCallError("request bytes were not queued to the subc socket",
|
|
12582
|
-
|
|
12583
|
-
|
|
12584
|
-
return new SubcCallError("outcome_unknown", `managed call deadline exceeded after request bytes were queued to the local socket; no terminal response was observed; outcome unknown${causeMessage(err)}`, DEADLINE_NO_DROP_CODE, err);
|
|
13140
|
+
const classifyFailure = (error2) => {
|
|
13141
|
+
if (!handedToSocket)
|
|
13142
|
+
return this.notSentCallError("request bytes were not queued to the subc socket", error2);
|
|
13143
|
+
if (error2 instanceof SubcError && error2.code === REQUEST_DEADLINE_MARKER) {
|
|
13144
|
+
return new SubcCallError("outcome_unknown", `managed call deadline exceeded after request bytes were queued to the local socket; no terminal response was observed; outcome unknown${causeMessage(error2)}`, DEADLINE_NO_DROP_CODE, error2);
|
|
12585
13145
|
}
|
|
12586
|
-
return this.outcomeUnknownCallError("connection dropped before the managed call returned a response",
|
|
13146
|
+
return this.outcomeUnknownCallError("connection dropped before the managed call returned a response", error2);
|
|
12587
13147
|
};
|
|
12588
13148
|
return new Promise((resolve7, reject) => {
|
|
12589
13149
|
const ms = timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
12590
13150
|
const pending = {
|
|
12591
|
-
|
|
13151
|
+
handle,
|
|
12592
13152
|
resolve: resolve7,
|
|
12593
13153
|
reject,
|
|
12594
13154
|
onProgress,
|
|
12595
13155
|
timer: null,
|
|
12596
13156
|
classifyFailure
|
|
12597
13157
|
};
|
|
12598
|
-
pending.timer = setTimeout(() =>
|
|
12599
|
-
this.arbitrateTimeout(key, pending, channel, corr, ms);
|
|
12600
|
-
}, ms);
|
|
13158
|
+
pending.timer = setTimeout(() => this.arbitrateTimeout(key, pending, handle.channel, corr, ms), ms);
|
|
12601
13159
|
this.pending.set(key, pending);
|
|
12602
13160
|
const write = this.sock.writeTracked(encodeFrame(frame), Date.now() + ms);
|
|
12603
13161
|
handedToSocket = write.queued;
|
|
12604
|
-
write.completed.catch((
|
|
12605
|
-
const
|
|
12606
|
-
if (
|
|
12607
|
-
this.rejectPending(key,
|
|
13162
|
+
write.completed.catch((error2) => {
|
|
13163
|
+
const current = this.pending.get(key);
|
|
13164
|
+
if (current)
|
|
13165
|
+
this.rejectPending(key, current, error2 instanceof Error ? error2 : new SubcError(String(error2)));
|
|
12608
13166
|
});
|
|
12609
13167
|
});
|
|
12610
13168
|
}
|
|
12611
|
-
async
|
|
13169
|
+
async cachedRouteHandle(moduleId, opts) {
|
|
12612
13170
|
const identity = opts.identity ?? this.opts.identity;
|
|
12613
13171
|
if (!identity) {
|
|
12614
13172
|
throw new SubcCallError("terminal", "managed call requires a BindIdentity in SubcClient.connect({ identity }) or call(..., { identity })", "missing_identity");
|
|
@@ -12624,15 +13182,13 @@ class SubcClient {
|
|
|
12624
13182
|
target,
|
|
12625
13183
|
identity,
|
|
12626
13184
|
consumerIdentity,
|
|
12627
|
-
|
|
12628
|
-
generation: 0,
|
|
13185
|
+
handle: null,
|
|
12629
13186
|
opening: null
|
|
12630
13187
|
};
|
|
12631
13188
|
this.routes.set(key, cached);
|
|
12632
13189
|
}
|
|
12633
|
-
if (cached.
|
|
12634
|
-
return cached.
|
|
12635
|
-
}
|
|
13190
|
+
if (cached.handle && this.isLiveHandle(cached.handle))
|
|
13191
|
+
return cached.handle;
|
|
12636
13192
|
if (!cached.opening) {
|
|
12637
13193
|
cached.opening = this.openCachedRoute(cached).finally(() => {
|
|
12638
13194
|
cached.opening = null;
|
|
@@ -12649,44 +13205,43 @@ class SubcClient {
|
|
|
12649
13205
|
throw this.routeClosedDuringOpen();
|
|
12650
13206
|
try {
|
|
12651
13207
|
await this.ensureConnectedForManaged();
|
|
12652
|
-
} catch (
|
|
12653
|
-
throw this.notSentRecoveryError("route.open could not run because reconnect failed",
|
|
12654
|
-
}
|
|
12655
|
-
if (cached.channel !== null && cached.generation === this.generation && !this.closedErr) {
|
|
12656
|
-
return cached.channel;
|
|
13208
|
+
} catch (error2) {
|
|
13209
|
+
throw this.notSentRecoveryError("route.open could not run because reconnect failed", error2);
|
|
12657
13210
|
}
|
|
13211
|
+
if (cached.handle && this.isLiveHandle(cached.handle))
|
|
13212
|
+
return cached.handle;
|
|
12658
13213
|
try {
|
|
12659
|
-
const
|
|
13214
|
+
const handle = await this.routeOpen(cached.target, cached.identity, {
|
|
12660
13215
|
consumerIdentity: cached.consumerIdentity ?? null
|
|
12661
13216
|
});
|
|
12662
13217
|
if (cached.closed) {
|
|
12663
|
-
this.
|
|
13218
|
+
this.liveRoutes.delete(handle.channel);
|
|
13219
|
+
this.sendRouteGoodbye(handle);
|
|
12664
13220
|
throw this.routeClosedDuringOpen();
|
|
12665
13221
|
}
|
|
12666
|
-
cached.
|
|
12667
|
-
|
|
12668
|
-
|
|
12669
|
-
|
|
12670
|
-
|
|
12671
|
-
|
|
12672
|
-
if (!this.closeStarted && isConsumerReconnectTransient(err)) {
|
|
13222
|
+
cached.handle = handle;
|
|
13223
|
+
return handle;
|
|
13224
|
+
} catch (error2) {
|
|
13225
|
+
if (error2 instanceof SubcCallError && error2.code === "route_closed")
|
|
13226
|
+
throw error2;
|
|
13227
|
+
if (!this.closeStarted && isConsumerReconnectTransient(error2)) {
|
|
12673
13228
|
try {
|
|
12674
|
-
await this.reconnectAfterDrop(
|
|
12675
|
-
} catch (
|
|
12676
|
-
throw this.notSentRecoveryError("route.open was not sent and reconnect failed",
|
|
13229
|
+
await this.reconnectAfterDrop(error2);
|
|
13230
|
+
} catch (reconnectError) {
|
|
13231
|
+
throw this.notSentRecoveryError("route.open was not sent and reconnect failed", reconnectError);
|
|
12677
13232
|
}
|
|
12678
13233
|
continue;
|
|
12679
13234
|
}
|
|
12680
|
-
if (!this.closeStarted &&
|
|
13235
|
+
if (!this.closeStarted && error2 instanceof SubcError && isRetryableRouteOpenCode(error2.code)) {
|
|
12681
13236
|
routeRetryAttempt += 1;
|
|
12682
13237
|
if (routeRetryAttempt < this.opts.reconnectBackoff.maxAttempts && Date.now() < routeRetryDeadline) {
|
|
12683
13238
|
await this.opts.sleep(routeRetryDelay);
|
|
12684
13239
|
routeRetryDelay = Math.min(routeRetryDelay * 2, this.opts.reconnectBackoff.capMs);
|
|
12685
13240
|
continue;
|
|
12686
13241
|
}
|
|
12687
|
-
throw this.notSentCallError(`route.open failed for module ${cached.moduleId}: ${
|
|
13242
|
+
throw this.notSentCallError(`route.open failed for module ${cached.moduleId}: ${error2.code} (retry budget exhausted)`, error2);
|
|
12688
13243
|
}
|
|
12689
|
-
throw this.terminalCallError(`route.open failed for module ${cached.moduleId}`,
|
|
13244
|
+
throw this.terminalCallError(`route.open failed for module ${cached.moduleId}`, error2);
|
|
12690
13245
|
}
|
|
12691
13246
|
}
|
|
12692
13247
|
}
|
|
@@ -12733,6 +13288,9 @@ class SubcClient {
|
|
|
12733
13288
|
return;
|
|
12734
13289
|
} catch (err) {
|
|
12735
13290
|
if (!isConsumerReconnectTransient(err) || attempt >= this.opts.reconnectBackoff.maxAttempts) {
|
|
13291
|
+
if (err instanceof AuthError && attempt > 1) {
|
|
13292
|
+
throw new AuthError(`reconnect gave up after ${attempt} attempts: ${err.message} — the connection file and the daemon's key disagree persistently ` + `(daemon restarting in a loop, split connection-file paths, or a genuinely foreign daemon on this port); ` + `check the daemon, then restart this host app`);
|
|
13293
|
+
}
|
|
12736
13294
|
throw err;
|
|
12737
13295
|
}
|
|
12738
13296
|
await this.opts.sleep(delay);
|
|
@@ -12746,25 +13304,27 @@ class SubcClient {
|
|
|
12746
13304
|
this.currentConn = opened.conn;
|
|
12747
13305
|
this.closedErr = null;
|
|
12748
13306
|
this.generation += 1;
|
|
13307
|
+
this.connectionToken = newConnectionToken();
|
|
13308
|
+
this.liveRoutes.clear();
|
|
13309
|
+
this.lateResponses.clear();
|
|
13310
|
+
this.nextCorr = 1n;
|
|
12749
13311
|
this.readLoop(opened.sock, this.generation);
|
|
12750
13312
|
}
|
|
12751
13313
|
async reopenCachedRoutes() {
|
|
12752
|
-
for (const cached of this.routes.values())
|
|
12753
|
-
cached.
|
|
12754
|
-
cached.generation = 0;
|
|
12755
|
-
}
|
|
13314
|
+
for (const cached of this.routes.values())
|
|
13315
|
+
cached.handle = null;
|
|
12756
13316
|
for (const cached of this.routes.values()) {
|
|
12757
13317
|
if (cached.closed)
|
|
12758
13318
|
continue;
|
|
12759
|
-
const
|
|
13319
|
+
const handle = await this.routeOpen(cached.target, cached.identity, {
|
|
12760
13320
|
consumerIdentity: cached.consumerIdentity ?? null
|
|
12761
13321
|
});
|
|
12762
13322
|
if (cached.closed) {
|
|
12763
|
-
this.
|
|
13323
|
+
this.liveRoutes.delete(handle.channel);
|
|
13324
|
+
this.sendRouteGoodbye(handle);
|
|
12764
13325
|
continue;
|
|
12765
13326
|
}
|
|
12766
|
-
cached.
|
|
12767
|
-
cached.generation = this.generation;
|
|
13327
|
+
cached.handle = handle;
|
|
12768
13328
|
}
|
|
12769
13329
|
}
|
|
12770
13330
|
timeoutMessage(channel, corr, ms) {
|
|
@@ -12778,52 +13338,69 @@ class SubcClient {
|
|
|
12778
13338
|
async readLoop(sock, generation) {
|
|
12779
13339
|
try {
|
|
12780
13340
|
for (;; ) {
|
|
12781
|
-
|
|
12782
|
-
|
|
13341
|
+
this.readerActive = false;
|
|
13342
|
+
const frame = await sock.readFrame(Number.POSITIVE_INFINITY, { afterHeaderMs: BODY_READ_TIMEOUT_MS }, () => {
|
|
13343
|
+
this.readerActive = true;
|
|
13344
|
+
});
|
|
12783
13345
|
try {
|
|
12784
|
-
|
|
12785
|
-
|
|
12786
|
-
if (this.sock === sock && this.generation === generation) {
|
|
12787
|
-
this.dispatch({ header, body });
|
|
12788
|
-
}
|
|
13346
|
+
if (this.sock === sock && this.generation === generation)
|
|
13347
|
+
this.dispatch(frame);
|
|
12789
13348
|
} finally {
|
|
12790
13349
|
this.readerActive = false;
|
|
12791
13350
|
}
|
|
12792
13351
|
}
|
|
12793
|
-
} catch (
|
|
13352
|
+
} catch (error2) {
|
|
12794
13353
|
if (this.sock === sock && this.generation === generation) {
|
|
12795
|
-
this.fail(
|
|
13354
|
+
this.fail(error2 instanceof Error ? error2 : new SubcError(String(error2)));
|
|
12796
13355
|
}
|
|
12797
13356
|
}
|
|
12798
13357
|
}
|
|
12799
13358
|
dispatch(frame) {
|
|
12800
|
-
|
|
13359
|
+
let handle = null;
|
|
13360
|
+
if (frame.header.channel !== 0) {
|
|
13361
|
+
handle = this.liveRoutes.get(frame.header.channel) ?? null;
|
|
13362
|
+
if (!handle || handle.epoch !== frame.header.epoch) {
|
|
13363
|
+
this.ingressEpochDropCount += 1;
|
|
13364
|
+
return;
|
|
13365
|
+
}
|
|
13366
|
+
}
|
|
13367
|
+
const key = pendingKey(handle, frame.header.corr);
|
|
12801
13368
|
const pending = this.pending.get(key);
|
|
12802
13369
|
if (pending) {
|
|
13370
|
+
if (pending.acceptFrame && !pending.acceptFrame(frame))
|
|
13371
|
+
return;
|
|
12803
13372
|
switch (frame.header.ty) {
|
|
12804
|
-
case
|
|
12805
|
-
case
|
|
13373
|
+
case FrameType.Push:
|
|
13374
|
+
case FrameType.StreamData:
|
|
12806
13375
|
pending.onProgress?.(frame.body);
|
|
12807
13376
|
return;
|
|
12808
|
-
case
|
|
12809
|
-
case
|
|
13377
|
+
case FrameType.Response:
|
|
13378
|
+
case FrameType.StreamEnd:
|
|
12810
13379
|
this.settle(key, pending, () => pending.resolve(frame));
|
|
12811
13380
|
return;
|
|
12812
|
-
case
|
|
13381
|
+
case FrameType.Error:
|
|
12813
13382
|
this.settle(key, pending, () => pending.reject(this.errorFromFrame(frame)));
|
|
12814
13383
|
return;
|
|
12815
13384
|
default:
|
|
12816
13385
|
return;
|
|
12817
13386
|
}
|
|
12818
13387
|
}
|
|
12819
|
-
|
|
12820
|
-
|
|
13388
|
+
const late = this.lateResponses.get(key);
|
|
13389
|
+
if (late && (frame.header.ty === FrameType.Response || frame.header.ty === FrameType.Error)) {
|
|
13390
|
+
this.lateResponses.delete(key);
|
|
13391
|
+
late(frame);
|
|
12821
13392
|
return;
|
|
12822
13393
|
}
|
|
12823
|
-
if (frame.header.ty ===
|
|
12824
|
-
|
|
13394
|
+
if (frame.header.ty === FrameType.Goodbye && handle) {
|
|
13395
|
+
this.failHandle(handle, new SubcError("route closed by subc (GOODBYE)"));
|
|
13396
|
+
if (this.liveRoutes.get(handle.channel) === handle)
|
|
13397
|
+
this.liveRoutes.delete(handle.channel);
|
|
13398
|
+
this.evictRouteHandle(handle);
|
|
12825
13399
|
return;
|
|
12826
13400
|
}
|
|
13401
|
+
if (frame.header.ty === FrameType.Response || frame.header.ty === FrameType.Error || frame.header.ty === FrameType.StreamEnd) {
|
|
13402
|
+
debug("dropped terminal frame with no waiter: type=%d channel=%d epoch=%d corr=%s port=%s", frame.header.ty, frame.header.channel, frame.header.epoch, frame.header.corr, this.sock.localPort() ?? "?");
|
|
13403
|
+
}
|
|
12827
13404
|
}
|
|
12828
13405
|
settle(key, pending, run) {
|
|
12829
13406
|
if (this.pending.get(key) !== pending)
|
|
@@ -12846,11 +13423,16 @@ class SubcClient {
|
|
|
12846
13423
|
return new SubcError(Buffer.from(frame.body).toString("utf8") || "subc error");
|
|
12847
13424
|
}
|
|
12848
13425
|
}
|
|
12849
|
-
|
|
13426
|
+
evictRouteHandle(handle) {
|
|
13427
|
+
for (const cached of this.routes.values()) {
|
|
13428
|
+
if (cached.handle && sameRouteHandle(cached.handle, handle))
|
|
13429
|
+
cached.handle = null;
|
|
13430
|
+
}
|
|
13431
|
+
}
|
|
13432
|
+
failHandle(handle, error2) {
|
|
12850
13433
|
for (const [key, pending] of this.pending) {
|
|
12851
|
-
if (pending.
|
|
12852
|
-
this.rejectPending(key, pending,
|
|
12853
|
-
}
|
|
13434
|
+
if (pending.handle && sameRouteHandle(pending.handle, handle))
|
|
13435
|
+
this.rejectPending(key, pending, error2);
|
|
12854
13436
|
}
|
|
12855
13437
|
}
|
|
12856
13438
|
fail(err) {
|
|
@@ -12878,6 +13460,44 @@ class SubcClient {
|
|
|
12878
13460
|
return this.notSentCallError(message, cause);
|
|
12879
13461
|
return this.terminalCallError(message, cause);
|
|
12880
13462
|
}
|
|
13463
|
+
get droppedIngressFrames() {
|
|
13464
|
+
return this.ingressEpochDropCount;
|
|
13465
|
+
}
|
|
13466
|
+
installRoute(channel, epoch) {
|
|
13467
|
+
const handle = createRouteHandle(channel, epoch, this.connectionToken);
|
|
13468
|
+
this.liveRoutes.set(channel, handle);
|
|
13469
|
+
return handle;
|
|
13470
|
+
}
|
|
13471
|
+
isLiveHandle(handle) {
|
|
13472
|
+
return belongsToConnection(handle, this.connectionToken) && this.liveRoutes.get(handle.channel) === handle;
|
|
13473
|
+
}
|
|
13474
|
+
assertLiveConnection(handle) {
|
|
13475
|
+
if (!belongsToConnection(handle, this.connectionToken))
|
|
13476
|
+
throw new StaleRouteHandleError(handle);
|
|
13477
|
+
}
|
|
13478
|
+
assertLiveHandle(handle) {
|
|
13479
|
+
if (!this.isLiveHandle(handle))
|
|
13480
|
+
throw new StaleRouteHandleError(handle);
|
|
13481
|
+
}
|
|
13482
|
+
allocateCorr() {
|
|
13483
|
+
const maximum = 0xffffffffffffffffn;
|
|
13484
|
+
if (this.nextCorr > maximum) {
|
|
13485
|
+
const error2 = new SubcError("channel-0 correlation id allocator exhausted", "corr_exhausted");
|
|
13486
|
+
this.fail(error2);
|
|
13487
|
+
this.sock.close();
|
|
13488
|
+
this.scheduleReconnectAfterDrop(error2);
|
|
13489
|
+
throw error2;
|
|
13490
|
+
}
|
|
13491
|
+
const corr = this.nextCorr;
|
|
13492
|
+
this.nextCorr += 1n;
|
|
13493
|
+
return corr;
|
|
13494
|
+
}
|
|
13495
|
+
closeConnectionAfterCleanupFailure() {
|
|
13496
|
+
const error2 = new SubcError("late route cleanup could not be queued", "late_route_cleanup_failed");
|
|
13497
|
+
this.fail(error2);
|
|
13498
|
+
this.sock.close();
|
|
13499
|
+
this.scheduleReconnectAfterDrop(error2);
|
|
13500
|
+
}
|
|
12881
13501
|
encode(value) {
|
|
12882
13502
|
return new Uint8Array(Buffer.from(JSON.stringify(value), "utf8"));
|
|
12883
13503
|
}
|
|
@@ -12892,7 +13512,9 @@ function isConsumerReconnectTransient(err) {
|
|
|
12892
13512
|
return true;
|
|
12893
13513
|
if (err instanceof SubcCallError)
|
|
12894
13514
|
return err.kind === "not_sent" || err.kind === "outcome_unknown";
|
|
12895
|
-
if (err instanceof
|
|
13515
|
+
if (err instanceof AuthError)
|
|
13516
|
+
return true;
|
|
13517
|
+
if (err instanceof SubcError || err instanceof ConnectionFileError)
|
|
12896
13518
|
return false;
|
|
12897
13519
|
const code = errorCode(err);
|
|
12898
13520
|
return code === "ECONNREFUSED" || code === "ECONNRESET" || code === "EPIPE" || code === "ETIMEDOUT" || code === "ENOENT";
|
|
@@ -12945,14 +13567,54 @@ function causeMessage(cause) {
|
|
|
12945
13567
|
return "";
|
|
12946
13568
|
return `: ${cause instanceof Error ? cause.message : String(cause)}`;
|
|
12947
13569
|
}
|
|
12948
|
-
|
|
13570
|
+
function pendingKey(handle, corr) {
|
|
13571
|
+
return handle ? `${handle.channel}:${handle.epoch}:${corr}` : `0:0:${corr}`;
|
|
13572
|
+
}
|
|
13573
|
+
// ../../node_modules/.bun/@cortexkit+subc-client@0.5.0/node_modules/@cortexkit/subc-client/dist/provider.js
|
|
12949
13574
|
import { Buffer as Buffer2 } from "node:buffer";
|
|
12950
13575
|
var DEFAULT_HANDSHAKE_TIMEOUT_MS2 = 1e4;
|
|
12951
13576
|
var BODY_READ_TIMEOUT_MS2 = 30000;
|
|
12952
13577
|
var WRITE_TIMEOUT_MS = 30000;
|
|
12953
13578
|
var DEFAULT_RESTORED_DEBOUNCE_MS = 250;
|
|
13579
|
+
var DEFAULT_PROVIDER_HANDLER_CAPACITY = 64;
|
|
13580
|
+
var HEALTH_CHECK_OP = "health.check";
|
|
12954
13581
|
var HELLO_CORR = 1n;
|
|
12955
13582
|
|
|
13583
|
+
class AsyncPermitPool {
|
|
13584
|
+
available;
|
|
13585
|
+
waiters = [];
|
|
13586
|
+
constructor(capacity) {
|
|
13587
|
+
if (!Number.isInteger(capacity) || capacity <= 0) {
|
|
13588
|
+
throw new SubcProviderError("provider handler capacity must be a positive integer", "invalid_handler_capacity");
|
|
13589
|
+
}
|
|
13590
|
+
this.available = capacity;
|
|
13591
|
+
}
|
|
13592
|
+
async acquire() {
|
|
13593
|
+
if (this.available > 0) {
|
|
13594
|
+
this.available -= 1;
|
|
13595
|
+
return this.releaseOnce();
|
|
13596
|
+
}
|
|
13597
|
+
await new Promise((resolve7) => {
|
|
13598
|
+
this.waiters.push(resolve7);
|
|
13599
|
+
});
|
|
13600
|
+
return this.releaseOnce();
|
|
13601
|
+
}
|
|
13602
|
+
releaseOnce() {
|
|
13603
|
+
let released = false;
|
|
13604
|
+
return () => {
|
|
13605
|
+
if (released)
|
|
13606
|
+
return;
|
|
13607
|
+
released = true;
|
|
13608
|
+
const next = this.waiters.shift();
|
|
13609
|
+
if (next) {
|
|
13610
|
+
next();
|
|
13611
|
+
} else {
|
|
13612
|
+
this.available += 1;
|
|
13613
|
+
}
|
|
13614
|
+
};
|
|
13615
|
+
}
|
|
13616
|
+
}
|
|
13617
|
+
|
|
12956
13618
|
class SubcProviderError extends Error {
|
|
12957
13619
|
code;
|
|
12958
13620
|
constructor(message, code) {
|
|
@@ -12971,6 +13633,12 @@ class SubcProvider {
|
|
|
12971
13633
|
closeStarted = false;
|
|
12972
13634
|
closedErr = null;
|
|
12973
13635
|
inflight = new Map;
|
|
13636
|
+
pending = new Map;
|
|
13637
|
+
liveRoutes = new Map;
|
|
13638
|
+
connectionToken = newConnectionToken();
|
|
13639
|
+
nextCorr = 1n;
|
|
13640
|
+
ingressEpochDropCount = 0;
|
|
13641
|
+
requestGate = new AsyncPermitPool(DEFAULT_PROVIDER_HANDLER_CAPACITY);
|
|
12974
13642
|
reconnecting = null;
|
|
12975
13643
|
generation = 1;
|
|
12976
13644
|
connectionEpoch = 1;
|
|
@@ -12989,12 +13657,55 @@ class SubcProvider {
|
|
|
12989
13657
|
this.readLoop(sock, this.generation);
|
|
12990
13658
|
this.enqueueConnectionState({ state: "connected", epoch: this.connectionEpoch });
|
|
12991
13659
|
}
|
|
13660
|
+
get droppedIngressFrames() {
|
|
13661
|
+
return this.ingressEpochDropCount;
|
|
13662
|
+
}
|
|
12992
13663
|
get conn() {
|
|
12993
13664
|
return this.currentConn;
|
|
12994
13665
|
}
|
|
12995
13666
|
currentEpoch() {
|
|
12996
13667
|
return this.connectionEpoch;
|
|
12997
13668
|
}
|
|
13669
|
+
async request(handle, body, opts = {}) {
|
|
13670
|
+
this.assertLiveHandle(handle);
|
|
13671
|
+
const corr = this.allocateCorr();
|
|
13672
|
+
const key = routeKey(handle, corr);
|
|
13673
|
+
const timeoutMs = opts.timeoutMs ?? WRITE_TIMEOUT_MS;
|
|
13674
|
+
const frame = buildFrame(FrameType.Request, buildFlags(false, opts.priority ?? Priority.Interactive, false, opts.admissionClass ?? AdmissionClass.Normal), handle.channel, handle.epoch, corr, body);
|
|
13675
|
+
return await new Promise((resolve7, reject) => {
|
|
13676
|
+
const timer = setTimeout(() => {
|
|
13677
|
+
if (this.pending.delete(key))
|
|
13678
|
+
reject(new SubcProviderError("reverse request timed out", "request_timeout"));
|
|
13679
|
+
}, timeoutMs);
|
|
13680
|
+
this.pending.set(key, {
|
|
13681
|
+
resolve: (response) => resolve7(response.body),
|
|
13682
|
+
reject,
|
|
13683
|
+
timer
|
|
13684
|
+
});
|
|
13685
|
+
this.sendOn(this.sock, this.generation, frame).catch((error2) => {
|
|
13686
|
+
const pending = this.pending.get(key);
|
|
13687
|
+
if (!pending)
|
|
13688
|
+
return;
|
|
13689
|
+
this.pending.delete(key);
|
|
13690
|
+
clearTimeout(pending.timer);
|
|
13691
|
+
reject(error2 instanceof Error ? error2 : new SubcProviderError(String(error2)));
|
|
13692
|
+
});
|
|
13693
|
+
});
|
|
13694
|
+
}
|
|
13695
|
+
async push(handle, body, opts = {}) {
|
|
13696
|
+
this.assertLiveHandle(handle);
|
|
13697
|
+
await this.sendOn(this.sock, this.generation, buildFrame(FrameType.Push, buildFlags(false, opts.priority ?? Priority.Interactive, false, opts.admissionClass ?? AdmissionClass.Normal), handle.channel, handle.epoch, 0n, body));
|
|
13698
|
+
}
|
|
13699
|
+
cancel(handle, corr) {
|
|
13700
|
+
this.assertLiveHandle(handle);
|
|
13701
|
+
this.sendOn(this.sock, this.generation, buildFrame(FrameType.Cancel, controlFlags(), handle.channel, handle.epoch, corr, new Uint8Array(0)));
|
|
13702
|
+
}
|
|
13703
|
+
closeRoute(handle) {
|
|
13704
|
+
this.assertLiveHandle(handle);
|
|
13705
|
+
this.liveRoutes.delete(handle.channel);
|
|
13706
|
+
this.abortHandle(handle);
|
|
13707
|
+
this.sendOn(this.sock, this.generation, buildFrame(FrameType.Goodbye, controlFlags(), handle.channel, handle.epoch, 0n, new Uint8Array(0)));
|
|
13708
|
+
}
|
|
12998
13709
|
static async connect(opts) {
|
|
12999
13710
|
if (opts.manifest.protocol_ver !== PROTOCOL_VERSION) {
|
|
13000
13711
|
throw new SubcProviderError(`manifest protocol_ver ${opts.manifest.protocol_ver} does not match client protocol ${PROTOCOL_VERSION}`, "invalid_manifest");
|
|
@@ -13009,7 +13720,7 @@ class SubcProvider {
|
|
|
13009
13720
|
this.cancelRestoredDebounce();
|
|
13010
13721
|
const sock = this.sock;
|
|
13011
13722
|
try {
|
|
13012
|
-
await sendFrame(sock, buildFrame(
|
|
13723
|
+
await sendFrame(sock, buildFrame(FrameType.Goodbye, controlFlags(), 0, 0, 0n, new Uint8Array(0)));
|
|
13013
13724
|
} catch {} finally {
|
|
13014
13725
|
sock.close();
|
|
13015
13726
|
this.finishClosed();
|
|
@@ -13017,12 +13728,13 @@ class SubcProvider {
|
|
|
13017
13728
|
}
|
|
13018
13729
|
await this.closed;
|
|
13019
13730
|
}
|
|
13020
|
-
static async openConnection(opts) {
|
|
13731
|
+
static async openConnection(opts, onSocket) {
|
|
13021
13732
|
const conn = await readConnectionFile(opts.connectionFile);
|
|
13022
13733
|
const deadline = Date.now() + (opts.handshakeTimeoutMs ?? DEFAULT_HANDSHAKE_TIMEOUT_MS2);
|
|
13023
13734
|
const endpoint = conn.endpoints[0];
|
|
13024
13735
|
const sock = await SubcSocket.connect(endpoint.host, endpoint.port, deadline);
|
|
13025
13736
|
try {
|
|
13737
|
+
onSocket?.(sock);
|
|
13026
13738
|
await authenticateClient(sock, conn, deadline);
|
|
13027
13739
|
await sendFrame(sock, buildHelloFrame(opts));
|
|
13028
13740
|
const ack = await expectHelloAck(sock, deadline);
|
|
@@ -13035,19 +13747,17 @@ class SubcProvider {
|
|
|
13035
13747
|
async readLoop(sock, generation) {
|
|
13036
13748
|
try {
|
|
13037
13749
|
for (;; ) {
|
|
13038
|
-
const
|
|
13039
|
-
const
|
|
13040
|
-
const body = header.len === 0 ? new Uint8Array(0) : await sock.readExact(header.len, Date.now() + BODY_READ_TIMEOUT_MS2);
|
|
13041
|
-
const keepGoing = await this.dispatch({ header, body }, sock, generation);
|
|
13750
|
+
const frame = await sock.readFrame(Number.POSITIVE_INFINITY, { afterHeaderMs: BODY_READ_TIMEOUT_MS2 });
|
|
13751
|
+
const keepGoing = await this.dispatch(frame, sock, generation);
|
|
13042
13752
|
if (!keepGoing) {
|
|
13043
13753
|
if (this.sock === sock && this.generation === generation)
|
|
13044
13754
|
this.closeStarted = true;
|
|
13045
13755
|
break;
|
|
13046
13756
|
}
|
|
13047
13757
|
}
|
|
13048
|
-
} catch (
|
|
13758
|
+
} catch (error2) {
|
|
13049
13759
|
if (this.sock === sock && this.generation === generation && !this.closeStarted) {
|
|
13050
|
-
this.handleUnexpectedDrop(sock, generation,
|
|
13760
|
+
this.handleUnexpectedDrop(sock, generation, error2 instanceof Error ? error2 : new SubcProviderError(String(error2)));
|
|
13051
13761
|
return;
|
|
13052
13762
|
}
|
|
13053
13763
|
} finally {
|
|
@@ -13059,28 +13769,58 @@ class SubcProvider {
|
|
|
13059
13769
|
}
|
|
13060
13770
|
}
|
|
13061
13771
|
async dispatch(frame, sock, generation) {
|
|
13772
|
+
let handle = null;
|
|
13773
|
+
if (frame.header.channel !== 0) {
|
|
13774
|
+
handle = this.liveRoutes.get(frame.header.channel) ?? null;
|
|
13775
|
+
if (!handle || handle.epoch !== frame.header.epoch) {
|
|
13776
|
+
this.ingressEpochDropCount += 1;
|
|
13777
|
+
return true;
|
|
13778
|
+
}
|
|
13779
|
+
}
|
|
13780
|
+
if (handle) {
|
|
13781
|
+
const pendingKey2 = routeKey(handle, frame.header.corr);
|
|
13782
|
+
const pending = this.pending.get(pendingKey2);
|
|
13783
|
+
if (pending) {
|
|
13784
|
+
if (frame.header.ty === FrameType.Push || frame.header.ty === FrameType.StreamData)
|
|
13785
|
+
return true;
|
|
13786
|
+
if (frame.header.ty === FrameType.Response || frame.header.ty === FrameType.StreamEnd) {
|
|
13787
|
+
this.pending.delete(pendingKey2);
|
|
13788
|
+
clearTimeout(pending.timer);
|
|
13789
|
+
pending.resolve(frame);
|
|
13790
|
+
return true;
|
|
13791
|
+
}
|
|
13792
|
+
if (frame.header.ty === FrameType.Error) {
|
|
13793
|
+
this.pending.delete(pendingKey2);
|
|
13794
|
+
clearTimeout(pending.timer);
|
|
13795
|
+
pending.reject(providerErrorFromFrame(frame));
|
|
13796
|
+
return true;
|
|
13797
|
+
}
|
|
13798
|
+
}
|
|
13799
|
+
}
|
|
13062
13800
|
switch (frame.header.ty) {
|
|
13063
|
-
case
|
|
13801
|
+
case FrameType.Ping:
|
|
13064
13802
|
if (frame.header.channel === 0) {
|
|
13065
|
-
await this.sendOn(sock, generation, buildFrameWithVersion(frame.header.ver,
|
|
13803
|
+
await this.sendOn(sock, generation, buildFrameWithVersion(frame.header.ver, FrameType.Pong, frame.header.flags, 0, 0, frame.header.corr, new Uint8Array(0)));
|
|
13066
13804
|
}
|
|
13067
13805
|
return true;
|
|
13068
|
-
case
|
|
13069
|
-
if (
|
|
13806
|
+
case FrameType.Goodbye:
|
|
13807
|
+
if (!handle)
|
|
13070
13808
|
return false;
|
|
13071
|
-
this.
|
|
13072
|
-
|
|
13809
|
+
this.liveRoutes.delete(handle.channel);
|
|
13810
|
+
this.abortHandle(handle);
|
|
13811
|
+
await this.opts.onRouteGone?.(handle);
|
|
13073
13812
|
return true;
|
|
13074
|
-
case
|
|
13075
|
-
|
|
13813
|
+
case FrameType.Cancel:
|
|
13814
|
+
if (handle)
|
|
13815
|
+
this.inflight.get(routeKey(handle, frame.header.corr))?.abort();
|
|
13076
13816
|
return true;
|
|
13077
|
-
case
|
|
13817
|
+
case FrameType.Request:
|
|
13078
13818
|
if (frame.header.channel === 0) {
|
|
13079
13819
|
await this.handleControlRequest(frame, sock, generation);
|
|
13080
|
-
} else {
|
|
13081
|
-
this.handleDataRequest(frame, sock, generation).catch((
|
|
13820
|
+
} else if (handle) {
|
|
13821
|
+
this.handleDataRequest(frame, handle, sock, generation).catch((error2) => {
|
|
13082
13822
|
if (!this.closeStarted && this.sock === sock && this.generation === generation) {
|
|
13083
|
-
console.warn("SubcProvider handler failed after its request was dispatched",
|
|
13823
|
+
console.warn("SubcProvider handler failed after its request was dispatched", error2);
|
|
13084
13824
|
}
|
|
13085
13825
|
});
|
|
13086
13826
|
}
|
|
@@ -13089,19 +13829,28 @@ class SubcProvider {
|
|
|
13089
13829
|
return true;
|
|
13090
13830
|
}
|
|
13091
13831
|
}
|
|
13092
|
-
|
|
13093
|
-
const prefix = `${
|
|
13832
|
+
abortHandle(handle) {
|
|
13833
|
+
const prefix = `${handle.channel}:${handle.epoch}:`;
|
|
13094
13834
|
for (const [key, controller] of this.inflight) {
|
|
13095
13835
|
if (key.startsWith(prefix))
|
|
13096
13836
|
controller.abort();
|
|
13097
13837
|
}
|
|
13838
|
+
for (const [key, pending] of this.pending) {
|
|
13839
|
+
if (!key.startsWith(prefix))
|
|
13840
|
+
continue;
|
|
13841
|
+
this.pending.delete(key);
|
|
13842
|
+
clearTimeout(pending.timer);
|
|
13843
|
+
pending.reject(new StaleRouteHandleError(handle));
|
|
13844
|
+
}
|
|
13098
13845
|
}
|
|
13099
|
-
abortGeneration(
|
|
13100
|
-
|
|
13101
|
-
for (const [key,
|
|
13102
|
-
|
|
13103
|
-
|
|
13846
|
+
abortGeneration(_generation) {
|
|
13847
|
+
this.abortAllInflight();
|
|
13848
|
+
for (const [key, pending] of this.pending) {
|
|
13849
|
+
this.pending.delete(key);
|
|
13850
|
+
clearTimeout(pending.timer);
|
|
13851
|
+
pending.reject(new SubcProviderError("provider connection dropped", "connection_dropped"));
|
|
13104
13852
|
}
|
|
13853
|
+
this.liveRoutes.clear();
|
|
13105
13854
|
}
|
|
13106
13855
|
abortAllInflight() {
|
|
13107
13856
|
for (const controller of this.inflight.values())
|
|
@@ -13109,56 +13858,136 @@ class SubcProvider {
|
|
|
13109
13858
|
}
|
|
13110
13859
|
async handleControlRequest(frame, sock, generation) {
|
|
13111
13860
|
const request = parseJson(frame.body);
|
|
13861
|
+
if (request.op === HEALTH_CHECK_OP) {
|
|
13862
|
+
this.handleHealthRequest(frame, sock, generation).catch((error2) => {
|
|
13863
|
+
if (!this.closeStarted && this.sock === sock && this.generation === generation) {
|
|
13864
|
+
console.warn("SubcProvider health handler failed after its request was dispatched", error2);
|
|
13865
|
+
}
|
|
13866
|
+
});
|
|
13867
|
+
return;
|
|
13868
|
+
}
|
|
13112
13869
|
if (request.op !== "route.bind") {
|
|
13113
13870
|
throw new SubcProviderError(`unsupported module control request ${request.op ?? "<missing op>"}`);
|
|
13114
13871
|
}
|
|
13872
|
+
const boundChannel = numberField(request.route_channel, "route_channel");
|
|
13873
|
+
const boundEpoch = numberField(request.epoch, "epoch");
|
|
13874
|
+
const stale = this.liveRoutes.get(boundChannel);
|
|
13875
|
+
if (stale) {
|
|
13876
|
+
if (boundEpoch <= stale.epoch) {
|
|
13877
|
+
await this.sendError(frame, "route_rejected", `route.bind epoch ${boundEpoch} does not supersede installed epoch ${stale.epoch} on channel ${boundChannel}`, controlFlags(), sock, generation);
|
|
13878
|
+
return;
|
|
13879
|
+
}
|
|
13880
|
+
this.liveRoutes.delete(stale.channel);
|
|
13881
|
+
this.abortHandle(stale);
|
|
13882
|
+
await this.opts.onRouteGone?.(stale);
|
|
13883
|
+
}
|
|
13884
|
+
const tentative = createRouteHandle(boundChannel, boundEpoch, this.connectionToken);
|
|
13115
13885
|
const bindRequest = {
|
|
13116
|
-
|
|
13886
|
+
handle: tentative,
|
|
13117
13887
|
target: request.target,
|
|
13118
13888
|
identity: request.identity,
|
|
13119
|
-
principal: request.principal
|
|
13889
|
+
principal: request.principal,
|
|
13890
|
+
consumer_capabilities: request.consumer_capabilities
|
|
13120
13891
|
};
|
|
13121
|
-
|
|
13892
|
+
let decision;
|
|
13893
|
+
try {
|
|
13894
|
+
decision = await this.opts.onBind?.(bindRequest);
|
|
13895
|
+
} catch (error2) {
|
|
13896
|
+
try {
|
|
13897
|
+
await this.sendError(frame, "route_rejected", error2 instanceof Error ? error2.message : String(error2), controlFlags(), sock, generation);
|
|
13898
|
+
} finally {
|
|
13899
|
+
await this.opts.onRouteGone?.(tentative);
|
|
13900
|
+
}
|
|
13901
|
+
return;
|
|
13902
|
+
}
|
|
13122
13903
|
const rejection = bindRejection(decision);
|
|
13123
13904
|
if (rejection) {
|
|
13124
|
-
|
|
13905
|
+
try {
|
|
13906
|
+
await this.sendError(frame, rejection.code, rejection.message, controlFlags(), sock, generation);
|
|
13907
|
+
} finally {
|
|
13908
|
+
await this.opts.onRouteGone?.(tentative);
|
|
13909
|
+
}
|
|
13910
|
+
return;
|
|
13911
|
+
}
|
|
13912
|
+
try {
|
|
13913
|
+
await this.sendOn(sock, generation, buildFrameWithVersion(frame.header.ver, FrameType.Response, controlFlags(), 0, 0, frame.header.corr, encodeJson({ op: "route.bind" })));
|
|
13914
|
+
} catch (error2) {
|
|
13915
|
+
await this.opts.onRouteGone?.(tentative);
|
|
13916
|
+
throw error2;
|
|
13917
|
+
}
|
|
13918
|
+
if (this.sock !== sock || this.generation !== generation || this.closeStarted || this.closedErr) {
|
|
13919
|
+
await this.opts.onRouteGone?.(tentative);
|
|
13125
13920
|
return;
|
|
13126
13921
|
}
|
|
13127
|
-
|
|
13922
|
+
this.liveRoutes.set(tentative.channel, tentative);
|
|
13923
|
+
await this.opts.onBound?.(tentative);
|
|
13128
13924
|
}
|
|
13129
|
-
async handleDataRequest(frame, sock, generation) {
|
|
13130
|
-
const {
|
|
13131
|
-
const key = routeKey(
|
|
13925
|
+
async handleDataRequest(frame, handle, sock, generation) {
|
|
13926
|
+
const { corr, ver } = frame.header;
|
|
13927
|
+
const key = routeKey(handle, corr);
|
|
13132
13928
|
const controller = new AbortController;
|
|
13133
13929
|
this.inflight.set(key, controller);
|
|
13134
|
-
const
|
|
13135
|
-
|
|
13930
|
+
const context = {
|
|
13931
|
+
handle,
|
|
13136
13932
|
signal: controller.signal,
|
|
13137
13933
|
currentEpoch: () => this.connectionEpoch,
|
|
13138
|
-
emit: async (eventBody) => {
|
|
13934
|
+
emit: async (eventBody, options = {}) => {
|
|
13935
|
+
this.assertLiveHandle(handle);
|
|
13139
13936
|
if (controller.signal.aborted)
|
|
13140
13937
|
return;
|
|
13141
|
-
await this.sendOn(sock, generation, buildFrameWithVersion(ver,
|
|
13938
|
+
await this.sendOn(sock, generation, buildFrameWithVersion(ver, FrameType.StreamData, buildFlags(false, options.priority ?? Priority.Interactive, false, options.admissionClass ?? AdmissionClass.Normal), handle.channel, handle.epoch, corr, eventBody));
|
|
13142
13939
|
}
|
|
13143
13940
|
};
|
|
13941
|
+
const releasePermit = await (this.requestGate ?? new AsyncPermitPool(DEFAULT_PROVIDER_HANDLER_CAPACITY)).acquire();
|
|
13942
|
+
const dataFlags = buildFlags(false, Priority.Interactive, false);
|
|
13144
13943
|
try {
|
|
13145
|
-
const body = await this.opts.handler(
|
|
13944
|
+
const body = await this.opts.handler(handle, frame.body, context);
|
|
13945
|
+
if (controller.signal.aborted)
|
|
13946
|
+
return;
|
|
13947
|
+
this.assertLiveHandle(handle);
|
|
13146
13948
|
if (body === undefined) {
|
|
13147
|
-
await this.sendOn(sock, generation, buildFrameWithVersion(ver,
|
|
13949
|
+
await this.sendOn(sock, generation, buildFrameWithVersion(ver, FrameType.StreamEnd, dataFlags, handle.channel, handle.epoch, corr, new Uint8Array(0)));
|
|
13148
13950
|
} else if (body instanceof Uint8Array) {
|
|
13149
|
-
await this.sendOn(sock, generation, buildFrameWithVersion(ver,
|
|
13951
|
+
await this.sendOn(sock, generation, buildFrameWithVersion(ver, FrameType.Response, dataFlags, handle.channel, handle.epoch, corr, body));
|
|
13150
13952
|
} else {
|
|
13151
13953
|
throw new SubcProviderError("provider handler must return a Uint8Array or void", "invalid_handler_response");
|
|
13152
13954
|
}
|
|
13153
|
-
} catch (
|
|
13154
|
-
|
|
13955
|
+
} catch (error2) {
|
|
13956
|
+
if (error2 instanceof StaleRouteHandleError || controller.signal.aborted)
|
|
13957
|
+
return;
|
|
13958
|
+
await this.sendError(frame, error2 instanceof SubcProviderError && error2.code ? error2.code : "handler_error", error2 instanceof Error ? error2.message : String(error2), dataFlags, sock, generation);
|
|
13959
|
+
} finally {
|
|
13960
|
+
releasePermit();
|
|
13961
|
+
if (this.inflight.get(key) === controller)
|
|
13962
|
+
this.inflight.delete(key);
|
|
13963
|
+
}
|
|
13964
|
+
}
|
|
13965
|
+
async handleHealthRequest(frame, sock, generation) {
|
|
13966
|
+
const { corr, ver } = frame.header;
|
|
13967
|
+
const key = `control:${generation}:${corr}`;
|
|
13968
|
+
const controller = new AbortController;
|
|
13969
|
+
this.inflight.set(key, controller);
|
|
13970
|
+
const releasePermit = await (this.requestGate ?? new AsyncPermitPool(DEFAULT_PROVIDER_HANDLER_CAPACITY)).acquire();
|
|
13971
|
+
try {
|
|
13972
|
+
if (controller.signal.aborted)
|
|
13973
|
+
return;
|
|
13974
|
+
const report = await this.opts.health();
|
|
13975
|
+
await this.sendOn(sock, generation, buildFrameWithVersion(ver, FrameType.Response, controlFlags(), 0, 0, corr, encodeJson({
|
|
13976
|
+
op: HEALTH_CHECK_OP,
|
|
13977
|
+
status: report.status,
|
|
13978
|
+
...report.detail === undefined ? {} : { detail: report.detail },
|
|
13979
|
+
...report.metrics === undefined ? {} : { metrics: report.metrics }
|
|
13980
|
+
})));
|
|
13981
|
+
} catch (error2) {
|
|
13982
|
+
await this.sendError(frame, error2 instanceof SubcProviderError && error2.code ? error2.code : "health_error", error2 instanceof Error ? error2.message : String(error2), controlFlags(), sock, generation);
|
|
13155
13983
|
} finally {
|
|
13984
|
+
releasePermit();
|
|
13156
13985
|
if (this.inflight.get(key) === controller)
|
|
13157
13986
|
this.inflight.delete(key);
|
|
13158
13987
|
}
|
|
13159
13988
|
}
|
|
13160
13989
|
async sendError(frame, code, message, flags, sock, generation) {
|
|
13161
|
-
await this.sendOn(sock, generation, buildFrameWithVersion(frame.header.ver,
|
|
13990
|
+
await this.sendOn(sock, generation, buildFrameWithVersion(frame.header.ver, FrameType.Error, flags, frame.header.channel, frame.header.epoch, frame.header.corr, encodeJson({ code, message })));
|
|
13162
13991
|
}
|
|
13163
13992
|
async sendOn(sock, generation, frame) {
|
|
13164
13993
|
if (this.sock !== sock || this.generation !== generation || this.closeStarted || this.closedErr)
|
|
@@ -13166,42 +13995,79 @@ class SubcProvider {
|
|
|
13166
13995
|
await sendFrame(sock, frame);
|
|
13167
13996
|
}
|
|
13168
13997
|
handleUnexpectedDrop(sock, generation, cause) {
|
|
13998
|
+
if (this.closeStarted || this.sock !== sock || this.generation !== generation)
|
|
13999
|
+
return;
|
|
13169
14000
|
this.cancelRestoredDebounce();
|
|
13170
14001
|
this.abortGeneration(generation);
|
|
13171
14002
|
this.generation += 1;
|
|
13172
14003
|
sock.close();
|
|
13173
|
-
this.
|
|
13174
|
-
this.scheduleReconnectAfterDrop(cause);
|
|
14004
|
+
this.scheduleReconnectAfterDrop(cause, this.generation, sock);
|
|
13175
14005
|
}
|
|
13176
|
-
scheduleReconnectAfterDrop(
|
|
13177
|
-
if (this.closeStarted
|
|
14006
|
+
scheduleReconnectAfterDrop(cause, generation, droppedSocket) {
|
|
14007
|
+
if (this.closeStarted)
|
|
13178
14008
|
return;
|
|
13179
|
-
const
|
|
13180
|
-
|
|
14009
|
+
const previous = this.reconnecting;
|
|
14010
|
+
if (previous) {
|
|
14011
|
+
if (!this.shouldSupersedeReconnect(previous, generation, droppedSocket))
|
|
14012
|
+
return;
|
|
14013
|
+
previous.superseded = true;
|
|
14014
|
+
previous.socket?.close();
|
|
14015
|
+
}
|
|
14016
|
+
const cycle = {
|
|
14017
|
+
generation,
|
|
14018
|
+
socket: null,
|
|
14019
|
+
socketDied: false,
|
|
14020
|
+
superseded: false
|
|
14021
|
+
};
|
|
14022
|
+
this.reconnecting = cycle;
|
|
14023
|
+
this.enqueueConnectionState({ state: "down", cause });
|
|
14024
|
+
this.reconnectWithRetry(cycle).catch((err) => {
|
|
14025
|
+
if (this.isCurrentReconnect(cycle) && !this.closeStarted) {
|
|
13181
14026
|
this.failFatal(err instanceof Error ? err : new SubcProviderError(String(err)));
|
|
14027
|
+
}
|
|
13182
14028
|
}).finally(() => {
|
|
13183
|
-
if (this.
|
|
14029
|
+
if (this.isCurrentReconnect(cycle))
|
|
13184
14030
|
this.reconnecting = null;
|
|
13185
14031
|
});
|
|
13186
|
-
this.reconnecting = promise;
|
|
13187
14032
|
}
|
|
13188
|
-
async reconnectWithRetry(
|
|
14033
|
+
async reconnectWithRetry(cycle) {
|
|
13189
14034
|
let attempt = 0;
|
|
13190
14035
|
let delay = this.opts.reconnectBackoff.baseMs;
|
|
13191
14036
|
for (;; ) {
|
|
14037
|
+
if (!this.isCurrentReconnect(cycle))
|
|
14038
|
+
return;
|
|
13192
14039
|
if (this.closeStarted)
|
|
13193
14040
|
throw new SubcProviderError("provider closed");
|
|
14041
|
+
cycle.socket = null;
|
|
14042
|
+
cycle.socketDied = false;
|
|
13194
14043
|
attempt += 1;
|
|
13195
|
-
this.
|
|
14044
|
+
this.enqueueReconnectState(cycle, attempt);
|
|
13196
14045
|
try {
|
|
13197
|
-
const opened = await SubcProvider.openConnection(this.opts)
|
|
13198
|
-
|
|
14046
|
+
const opened = await SubcProvider.openConnection(this.opts, (sock) => {
|
|
14047
|
+
if (!this.isCurrentReconnect(cycle)) {
|
|
14048
|
+
sock.close();
|
|
14049
|
+
return;
|
|
14050
|
+
}
|
|
14051
|
+
cycle.socket = sock;
|
|
14052
|
+
});
|
|
14053
|
+
if (!this.isCurrentReconnect(cycle) || this.closeStarted) {
|
|
13199
14054
|
opened.sock.close();
|
|
13200
|
-
|
|
14055
|
+
if (this.closeStarted)
|
|
14056
|
+
throw new SubcProviderError("provider closed");
|
|
14057
|
+
return;
|
|
13201
14058
|
}
|
|
13202
|
-
this.replaceConnection(opened);
|
|
14059
|
+
const epoch = this.replaceConnection(opened, cycle.generation);
|
|
14060
|
+
this.reconnecting = null;
|
|
14061
|
+
if (this.reconnecting !== null) {
|
|
14062
|
+
throw new SubcProviderError("reconnect state must clear before restored", "reconnect_state");
|
|
14063
|
+
}
|
|
14064
|
+
this.scheduleRestored(cycle.generation, epoch);
|
|
13203
14065
|
return;
|
|
13204
14066
|
} catch (err) {
|
|
14067
|
+
if (!this.isCurrentReconnect(cycle))
|
|
14068
|
+
return;
|
|
14069
|
+
if (cycle.socket)
|
|
14070
|
+
cycle.socketDied = true;
|
|
13205
14071
|
if (this.closeStarted)
|
|
13206
14072
|
throw err;
|
|
13207
14073
|
if (!isProviderReconnectTransient(err))
|
|
@@ -13211,16 +14077,29 @@ class SubcProvider {
|
|
|
13211
14077
|
}
|
|
13212
14078
|
}
|
|
13213
14079
|
}
|
|
13214
|
-
|
|
14080
|
+
isCurrentReconnect(cycle) {
|
|
14081
|
+
return !cycle.superseded && this.reconnecting === cycle && this.generation === cycle.generation;
|
|
14082
|
+
}
|
|
14083
|
+
shouldSupersedeReconnect(cycle, generation, droppedSocket) {
|
|
14084
|
+
return cycle.generation < generation || cycle.socketDied || cycle.socket === droppedSocket;
|
|
14085
|
+
}
|
|
14086
|
+
enqueueReconnectState(cycle, attempt) {
|
|
14087
|
+
if (!this.isCurrentReconnect(cycle))
|
|
14088
|
+
return;
|
|
14089
|
+
this.enqueueConnectionState({ state: "reconnecting", attempt }, cycle.generation, cycle);
|
|
14090
|
+
}
|
|
14091
|
+
replaceConnection(opened, generation) {
|
|
13215
14092
|
this.sock.close();
|
|
13216
14093
|
this.sock = opened.sock;
|
|
13217
14094
|
this.currentConn = opened.conn;
|
|
13218
14095
|
this.storage = opened.ack.storage;
|
|
13219
14096
|
this.closedErr = null;
|
|
13220
14097
|
this.connectionEpoch += 1;
|
|
13221
|
-
|
|
14098
|
+
this.connectionToken = newConnectionToken();
|
|
14099
|
+
this.liveRoutes.clear();
|
|
14100
|
+
this.nextCorr = 1n;
|
|
13222
14101
|
this.readLoop(opened.sock, generation);
|
|
13223
|
-
|
|
14102
|
+
return this.connectionEpoch;
|
|
13224
14103
|
}
|
|
13225
14104
|
scheduleRestored(generation, epoch) {
|
|
13226
14105
|
if (!this.opts.onConnectionState)
|
|
@@ -13228,7 +14107,7 @@ class SubcProvider {
|
|
|
13228
14107
|
const token = ++this.restoredDebounceToken;
|
|
13229
14108
|
this.opts.sleep(this.opts.restoredDebounceMs).then(() => {
|
|
13230
14109
|
if (token === this.restoredDebounceToken && !this.closeStarted && this.sock && this.generation === generation && this.connectionEpoch === epoch) {
|
|
13231
|
-
this.enqueueConnectionState({ state: "restored", epoch });
|
|
14110
|
+
this.enqueueConnectionState({ state: "restored", epoch }, generation);
|
|
13232
14111
|
}
|
|
13233
14112
|
}).catch((err) => {
|
|
13234
14113
|
if (token === this.restoredDebounceToken && !this.closeStarted) {
|
|
@@ -13239,10 +14118,10 @@ class SubcProvider {
|
|
|
13239
14118
|
cancelRestoredDebounce() {
|
|
13240
14119
|
this.restoredDebounceToken += 1;
|
|
13241
14120
|
}
|
|
13242
|
-
enqueueConnectionState(event) {
|
|
14121
|
+
enqueueConnectionState(event, generation, reconnect) {
|
|
13243
14122
|
if (!this.opts.onConnectionState)
|
|
13244
14123
|
return;
|
|
13245
|
-
this.stateQueue.push(event);
|
|
14124
|
+
this.stateQueue.push({ event, generation, reconnect });
|
|
13246
14125
|
if (!this.drainingStateQueue)
|
|
13247
14126
|
this.drainConnectionStateQueue();
|
|
13248
14127
|
}
|
|
@@ -13252,7 +14131,12 @@ class SubcProvider {
|
|
|
13252
14131
|
this.drainingStateQueue = true;
|
|
13253
14132
|
try {
|
|
13254
14133
|
while (this.stateQueue.length > 0) {
|
|
13255
|
-
const
|
|
14134
|
+
const queued = this.stateQueue[0];
|
|
14135
|
+
if (this.closeStarted || queued.generation !== undefined && queued.generation !== this.generation || queued.reconnect?.superseded) {
|
|
14136
|
+
this.stateQueue.shift();
|
|
14137
|
+
continue;
|
|
14138
|
+
}
|
|
14139
|
+
const { event } = queued;
|
|
13256
14140
|
try {
|
|
13257
14141
|
await this.opts.onConnectionState?.(event);
|
|
13258
14142
|
this.stateQueue.shift();
|
|
@@ -13272,6 +14156,24 @@ class SubcProvider {
|
|
|
13272
14156
|
this.drainConnectionStateQueue();
|
|
13273
14157
|
}
|
|
13274
14158
|
}
|
|
14159
|
+
isLiveHandle(handle) {
|
|
14160
|
+
return belongsToConnection(handle, this.connectionToken) && this.liveRoutes.get(handle.channel) === handle;
|
|
14161
|
+
}
|
|
14162
|
+
assertLiveHandle(handle) {
|
|
14163
|
+
if (!this.isLiveHandle(handle))
|
|
14164
|
+
throw new StaleRouteHandleError(handle);
|
|
14165
|
+
}
|
|
14166
|
+
allocateCorr() {
|
|
14167
|
+
const maximum = 0xffffffffffffffffn;
|
|
14168
|
+
if (this.nextCorr > maximum) {
|
|
14169
|
+
const error2 = new SubcProviderError("channel-0 correlation id allocator exhausted", "corr_exhausted");
|
|
14170
|
+
this.handleUnexpectedDrop(this.sock, this.generation, error2);
|
|
14171
|
+
throw error2;
|
|
14172
|
+
}
|
|
14173
|
+
const corr = this.nextCorr;
|
|
14174
|
+
this.nextCorr += 1n;
|
|
14175
|
+
return corr;
|
|
14176
|
+
}
|
|
13275
14177
|
failFatal(err) {
|
|
13276
14178
|
if (!this.closedErr)
|
|
13277
14179
|
this.closedErr = err;
|
|
@@ -13285,8 +14187,16 @@ class SubcProvider {
|
|
|
13285
14187
|
this.resolveClosed();
|
|
13286
14188
|
}
|
|
13287
14189
|
}
|
|
13288
|
-
function routeKey(
|
|
13289
|
-
return `${
|
|
14190
|
+
function routeKey(handle, corr) {
|
|
14191
|
+
return `${handle.channel}:${handle.epoch}:${corr}`;
|
|
14192
|
+
}
|
|
14193
|
+
function providerErrorFromFrame(frame) {
|
|
14194
|
+
try {
|
|
14195
|
+
const body = parseJson(frame.body);
|
|
14196
|
+
return new SubcProviderError(body.message ?? "subc error", body.code);
|
|
14197
|
+
} catch {
|
|
14198
|
+
return new SubcProviderError(Buffer2.from(frame.body).toString("utf8") || "subc error");
|
|
14199
|
+
}
|
|
13290
14200
|
}
|
|
13291
14201
|
function launchNonce(opts) {
|
|
13292
14202
|
const nonce = opts.launchNonce ?? process.env[SUBC_LAUNCH_NONCE_ENV2];
|
|
@@ -13298,9 +14208,11 @@ function normalizeProviderConnectOptions(opts) {
|
|
|
13298
14208
|
connectionFile: opts.connectionFile,
|
|
13299
14209
|
manifest: opts.manifest,
|
|
13300
14210
|
handler: opts.handler,
|
|
14211
|
+
health: opts.health ?? (() => ({ status: "ok" })),
|
|
13301
14212
|
handshakeTimeoutMs: opts.handshakeTimeoutMs,
|
|
13302
14213
|
controlOps: opts.controlOps,
|
|
13303
14214
|
onBind: opts.onBind,
|
|
14215
|
+
onBound: opts.onBound,
|
|
13304
14216
|
onRouteGone: opts.onRouteGone,
|
|
13305
14217
|
reconnectBackoff: opts.reconnectBackoff ?? DEFAULT_RECONNECT_BACKOFF,
|
|
13306
14218
|
sleep: opts.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms))),
|
|
@@ -13309,12 +14221,19 @@ function normalizeProviderConnectOptions(opts) {
|
|
|
13309
14221
|
launchNonce: opts.launchNonce
|
|
13310
14222
|
};
|
|
13311
14223
|
}
|
|
14224
|
+
function normalizedControlOps(controlOps) {
|
|
14225
|
+
if (controlOps === null)
|
|
14226
|
+
return null;
|
|
14227
|
+
const merged = new Set(controlOps ?? []);
|
|
14228
|
+
merged.add(HEALTH_CHECK_OP);
|
|
14229
|
+
return [...merged];
|
|
14230
|
+
}
|
|
13312
14231
|
function buildHelloFrame(opts) {
|
|
13313
14232
|
const nonce = launchNonce(opts);
|
|
13314
|
-
return buildFrame(
|
|
14233
|
+
return buildFrame(FrameType.Hello, controlFlags(), 0, 0, HELLO_CORR, encodeJson({
|
|
13315
14234
|
manifest: normalizeManifest(opts.manifest),
|
|
13316
14235
|
protocol_ver: PROTOCOL_VERSION,
|
|
13317
|
-
control_ops: opts.controlOps
|
|
14236
|
+
control_ops: normalizedControlOps(opts.controlOps),
|
|
13318
14237
|
...nonce ? { launch_nonce: nonce } : {}
|
|
13319
14238
|
}));
|
|
13320
14239
|
}
|
|
@@ -13325,7 +14244,9 @@ function isProviderReconnectTransient(err) {
|
|
|
13325
14244
|
return true;
|
|
13326
14245
|
if (err instanceof SocketWriteNotQueuedError || err instanceof SocketWriteQueuedError)
|
|
13327
14246
|
return true;
|
|
13328
|
-
if (err instanceof
|
|
14247
|
+
if (err instanceof AuthError)
|
|
14248
|
+
return true;
|
|
14249
|
+
if (err instanceof ConnectionFileError)
|
|
13329
14250
|
return false;
|
|
13330
14251
|
const code = errorCode2(err);
|
|
13331
14252
|
return code === "ECONNREFUSED" || code === "ECONNRESET" || code === "EPIPE" || code === "ETIMEDOUT" || code === "ENOENT";
|
|
@@ -13342,20 +14263,23 @@ async function pauseBeforeStateRetry() {
|
|
|
13342
14263
|
await new Promise((resolve7) => setTimeout(resolve7, 0));
|
|
13343
14264
|
}
|
|
13344
14265
|
function controlFlags() {
|
|
13345
|
-
return buildFlags(false,
|
|
14266
|
+
return buildFlags(false, Priority.Passive, false);
|
|
13346
14267
|
}
|
|
13347
14268
|
async function sendFrame(sock, frame) {
|
|
13348
14269
|
await sock.write(encodeFrame(frame), Date.now() + WRITE_TIMEOUT_MS);
|
|
13349
14270
|
}
|
|
13350
14271
|
async function expectHelloAck(sock, deadline) {
|
|
13351
|
-
const
|
|
13352
|
-
|
|
13353
|
-
|
|
13354
|
-
|
|
13355
|
-
|
|
13356
|
-
|
|
13357
|
-
|
|
13358
|
-
|
|
14272
|
+
const frame = await sock.readFrame(deadline, deadline);
|
|
14273
|
+
switch (frame.header.ty) {
|
|
14274
|
+
case FrameType.HelloAck: {
|
|
14275
|
+
const ack = parseJson(frame.body);
|
|
14276
|
+
if (ack.negotiated_ver !== PROTOCOL_VERSION) {
|
|
14277
|
+
throw new SubcProviderError(`subc negotiated protocol ${ack.negotiated_ver}; expected exactly ${PROTOCOL_VERSION}`, "unsupported_version");
|
|
14278
|
+
}
|
|
14279
|
+
return ack;
|
|
14280
|
+
}
|
|
14281
|
+
case FrameType.Error: {
|
|
14282
|
+
const error2 = parseJson(frame.body);
|
|
13359
14283
|
throw new SubcProviderError(`subc rejected HELLO: ${error2.code ?? "unknown"} — ${error2.message ?? "subc error"}`, error2.code);
|
|
13360
14284
|
}
|
|
13361
14285
|
default:
|
|
@@ -13420,6 +14344,7 @@ function normalizeProviderRole(role) {
|
|
|
13420
14344
|
role: "tool_provider",
|
|
13421
14345
|
tools: role.tools.map((tool) => ({
|
|
13422
14346
|
name: tool.name,
|
|
14347
|
+
...tool.description === undefined ? {} : { description: tool.description },
|
|
13423
14348
|
execution_mode: tool.execution_mode,
|
|
13424
14349
|
schema: tool.schema
|
|
13425
14350
|
})),
|
|
@@ -13509,15 +14434,17 @@ class BgSubscription {
|
|
|
13509
14434
|
identity;
|
|
13510
14435
|
acquireClient;
|
|
13511
14436
|
dropClient;
|
|
14437
|
+
consumerIdentity;
|
|
13512
14438
|
onNudge;
|
|
13513
14439
|
sleep;
|
|
13514
14440
|
stopped = false;
|
|
13515
14441
|
current = null;
|
|
13516
14442
|
loop;
|
|
13517
|
-
constructor(identity, acquireClient, dropClient, onNudge, sleep2) {
|
|
14443
|
+
constructor(identity, acquireClient, dropClient, consumerIdentity, onNudge, sleep2) {
|
|
13518
14444
|
this.identity = identity;
|
|
13519
14445
|
this.acquireClient = acquireClient;
|
|
13520
14446
|
this.dropClient = dropClient;
|
|
14447
|
+
this.consumerIdentity = consumerIdentity;
|
|
13521
14448
|
this.onNudge = onNudge;
|
|
13522
14449
|
this.sleep = sleep2;
|
|
13523
14450
|
this.loop = this.run();
|
|
@@ -13546,9 +14473,9 @@ class BgSubscription {
|
|
|
13546
14473
|
}
|
|
13547
14474
|
if (this.stopped)
|
|
13548
14475
|
return;
|
|
13549
|
-
let
|
|
14476
|
+
let route;
|
|
13550
14477
|
try {
|
|
13551
|
-
|
|
14478
|
+
route = await client.routeOpen({ kind: "tool_provider", module_id: AFT_MODULE_ID }, this.identity, { consumerIdentity: this.consumerIdentity });
|
|
13552
14479
|
} catch (err) {
|
|
13553
14480
|
if (isConsumerReconnectTransient(err))
|
|
13554
14481
|
this.dropClient(client);
|
|
@@ -13556,12 +14483,12 @@ class BgSubscription {
|
|
|
13556
14483
|
continue;
|
|
13557
14484
|
}
|
|
13558
14485
|
if (this.stopped) {
|
|
13559
|
-
safeCloseRoute(client,
|
|
14486
|
+
safeCloseRoute(client, route);
|
|
13560
14487
|
return;
|
|
13561
14488
|
}
|
|
13562
14489
|
const subscribedAt = Date.now();
|
|
13563
14490
|
try {
|
|
13564
|
-
const sub = client.subscribe(
|
|
14491
|
+
const sub = client.subscribe(route, { op: "bg_events" }, () => {
|
|
13565
14492
|
if (!this.stopped)
|
|
13566
14493
|
this.onNudge();
|
|
13567
14494
|
});
|
|
@@ -13581,7 +14508,7 @@ class BgSubscription {
|
|
|
13581
14508
|
attempt = 0;
|
|
13582
14509
|
} finally {
|
|
13583
14510
|
this.current = null;
|
|
13584
|
-
safeCloseRoute(client,
|
|
14511
|
+
safeCloseRoute(client, route);
|
|
13585
14512
|
}
|
|
13586
14513
|
await this.backoff(attempt++);
|
|
13587
14514
|
}
|
|
@@ -13594,9 +14521,12 @@ class BgSubscription {
|
|
|
13594
14521
|
function isRecord(value) {
|
|
13595
14522
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13596
14523
|
}
|
|
13597
|
-
function
|
|
14524
|
+
function isRouteProvenAbsentError(err) {
|
|
14525
|
+
return err instanceof SubcError && err.code === "unknown_channel" || err instanceof StaleRouteHandleError;
|
|
14526
|
+
}
|
|
14527
|
+
function safeCloseRoute(client, route) {
|
|
13598
14528
|
try {
|
|
13599
|
-
client.closeRouteChannel(
|
|
14529
|
+
client.closeRouteChannel(route).catch(() => {
|
|
13600
14530
|
return;
|
|
13601
14531
|
});
|
|
13602
14532
|
} catch {}
|
|
@@ -13673,7 +14603,7 @@ class SubcTransport {
|
|
|
13673
14603
|
if (!options)
|
|
13674
14604
|
return {};
|
|
13675
14605
|
const preview = options.preview;
|
|
13676
|
-
const timeoutMs = options.timeoutMs;
|
|
14606
|
+
const timeoutMs = options.transportTimeoutMs ?? options.timeoutMs;
|
|
13677
14607
|
const onProgress = options.onProgress;
|
|
13678
14608
|
return { preview, timeoutMs, onProgress };
|
|
13679
14609
|
}
|
|
@@ -13683,6 +14613,7 @@ class SubcTransportPool {
|
|
|
13683
14613
|
harness;
|
|
13684
14614
|
connectionFile;
|
|
13685
14615
|
handshakeTimeoutMs;
|
|
14616
|
+
consumerIdentity;
|
|
13686
14617
|
connectFn;
|
|
13687
14618
|
onBgEventsNudge;
|
|
13688
14619
|
bgBackoffSleep;
|
|
@@ -13696,6 +14627,7 @@ class SubcTransportPool {
|
|
|
13696
14627
|
this.connectionFile = options.connectionFile;
|
|
13697
14628
|
this.harness = options.harness;
|
|
13698
14629
|
this.handshakeTimeoutMs = options.handshakeTimeoutMs;
|
|
14630
|
+
this.consumerIdentity = options.consumerIdentity;
|
|
13699
14631
|
this.connectFn = options.connect ?? ((opts) => SubcClient.connect(opts));
|
|
13700
14632
|
this.onBgEventsNudge = options.onBgEventsNudge;
|
|
13701
14633
|
this.bgBackoffSleep = options.bgBackoffSleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
|
|
@@ -13718,6 +14650,11 @@ class SubcTransportPool {
|
|
|
13718
14650
|
return null;
|
|
13719
14651
|
return this.transports.get(key) ?? null;
|
|
13720
14652
|
}
|
|
14653
|
+
activeBridges() {
|
|
14654
|
+
if (!this.client || this.shuttingDown)
|
|
14655
|
+
return [];
|
|
14656
|
+
return [...this.transports.values()];
|
|
14657
|
+
}
|
|
13721
14658
|
async toolCall(projectRoot, runtime, name, rawArgs = {}, options) {
|
|
13722
14659
|
return this.getBridge(projectRoot).toolCall(runtime.sessionID, name, rawArgs, options);
|
|
13723
14660
|
}
|
|
@@ -13743,45 +14680,66 @@ class SubcTransportPool {
|
|
|
13743
14680
|
record.inflight += 1;
|
|
13744
14681
|
try {
|
|
13745
14682
|
const client = await this.ensureClient();
|
|
13746
|
-
|
|
13747
|
-
throw new RouteTornDownError("subc session closed");
|
|
13748
|
-
}
|
|
13749
|
-
let channel;
|
|
13750
|
-
let entry;
|
|
13751
|
-
try {
|
|
13752
|
-
({ channel, entry } = await this.routeChannel(client, identity, record));
|
|
14683
|
+
const openRoute = async () => {
|
|
13753
14684
|
if (!this.isCurrentSession(key, record)) {
|
|
13754
14685
|
throw new RouteTornDownError("subc session closed");
|
|
13755
14686
|
}
|
|
13756
|
-
|
|
13757
|
-
|
|
14687
|
+
try {
|
|
14688
|
+
const opened = await this.routeHandle(client, identity, record);
|
|
14689
|
+
if (!this.isCurrentSession(key, record)) {
|
|
14690
|
+
throw new RouteTornDownError("subc session closed");
|
|
14691
|
+
}
|
|
14692
|
+
return opened;
|
|
14693
|
+
} catch (err) {
|
|
14694
|
+
if (err instanceof RouteTornDownError)
|
|
14695
|
+
throw err;
|
|
14696
|
+
if (isConsumerReconnectTransient(err) && this.isCurrentSession(key, record) && this.client === client) {
|
|
14697
|
+
this.dropClient(client);
|
|
14698
|
+
}
|
|
13758
14699
|
throw err;
|
|
13759
|
-
if (isConsumerReconnectTransient(err) && this.isCurrentSession(key, record) && this.client === client) {
|
|
13760
|
-
this.dropClient(client);
|
|
13761
|
-
}
|
|
13762
|
-
throw err;
|
|
13763
|
-
}
|
|
13764
|
-
try {
|
|
13765
|
-
const reply = await client.request(channel, body, { timeoutMs, onProgress });
|
|
13766
|
-
if (this.isCurrentSession(key, record) && this.client === client) {
|
|
13767
|
-
this.transportFailures = 0;
|
|
13768
14700
|
}
|
|
13769
|
-
|
|
13770
|
-
|
|
13771
|
-
|
|
13772
|
-
|
|
13773
|
-
entry.closed = true;
|
|
14701
|
+
};
|
|
14702
|
+
const clearRouteEntry = (entry2) => {
|
|
14703
|
+
if (record.routeEntry === entry2) {
|
|
14704
|
+
entry2.closed = true;
|
|
13774
14705
|
record.routeEntry = null;
|
|
13775
14706
|
}
|
|
14707
|
+
};
|
|
14708
|
+
const handleRequestFailure = (err, entry2) => {
|
|
14709
|
+
clearRouteEntry(entry2);
|
|
13776
14710
|
if (this.isCurrentSession(key, record) && this.client === client) {
|
|
13777
14711
|
if (isConsumerReconnectTransient(err)) {
|
|
13778
14712
|
this.transportFailures = 0;
|
|
13779
14713
|
this.dropClient(client);
|
|
13780
|
-
} else if (++this.transportFailures >= MAX_CONSECUTIVE_TRANSPORT_FAILURES) {
|
|
14714
|
+
} else if (!isRouteProvenAbsentError(err) && ++this.transportFailures >= MAX_CONSECUTIVE_TRANSPORT_FAILURES) {
|
|
13781
14715
|
this.transportFailures = 0;
|
|
13782
14716
|
this.dropClient(client);
|
|
13783
14717
|
}
|
|
13784
14718
|
}
|
|
14719
|
+
};
|
|
14720
|
+
const requestOnRoute = async (route2) => {
|
|
14721
|
+
const reply = await client.request(route2, body, { timeoutMs, onProgress });
|
|
14722
|
+
if (this.isCurrentSession(key, record) && this.client === client) {
|
|
14723
|
+
this.transportFailures = 0;
|
|
14724
|
+
}
|
|
14725
|
+
this.ensureBgSubscription(identity, record);
|
|
14726
|
+
return reply;
|
|
14727
|
+
};
|
|
14728
|
+
let { route, entry } = await openRoute();
|
|
14729
|
+
try {
|
|
14730
|
+
return await requestOnRoute(route);
|
|
14731
|
+
} catch (err) {
|
|
14732
|
+
if (isRouteProvenAbsentError(err) && this.isCurrentSession(key, record) && this.client === client) {
|
|
14733
|
+
clearRouteEntry(entry);
|
|
14734
|
+
({ route, entry } = await openRoute());
|
|
14735
|
+
try {
|
|
14736
|
+
return await requestOnRoute(route);
|
|
14737
|
+
} catch (retryErr) {
|
|
14738
|
+
handleRequestFailure(retryErr, entry);
|
|
14739
|
+
throw retryErr;
|
|
14740
|
+
}
|
|
14741
|
+
}
|
|
14742
|
+
handleRequestFailure(err, entry);
|
|
13785
14743
|
throw err;
|
|
13786
14744
|
}
|
|
13787
14745
|
} finally {
|
|
@@ -13817,24 +14775,26 @@ class SubcTransportPool {
|
|
|
13817
14775
|
});
|
|
13818
14776
|
return this.connecting;
|
|
13819
14777
|
}
|
|
13820
|
-
async
|
|
14778
|
+
async routeHandle(client, identity, record) {
|
|
13821
14779
|
const key = identityKey(identity);
|
|
13822
14780
|
const existing = record.routeEntry;
|
|
13823
|
-
if (existing?.
|
|
13824
|
-
return {
|
|
14781
|
+
if (existing?.handle != null)
|
|
14782
|
+
return { route: existing.handle, entry: existing };
|
|
13825
14783
|
if (existing?.opening)
|
|
13826
|
-
return {
|
|
13827
|
-
const entry = { client, opening: null,
|
|
13828
|
-
const opening = client.routeOpen({ kind: "tool_provider", module_id: AFT_MODULE_ID }, identity
|
|
14784
|
+
return { route: await existing.opening, entry: existing };
|
|
14785
|
+
const entry = { client, opening: null, handle: null, closed: false };
|
|
14786
|
+
const opening = client.routeOpen({ kind: "tool_provider", module_id: AFT_MODULE_ID }, identity, {
|
|
14787
|
+
consumerIdentity: this.consumerIdentity
|
|
14788
|
+
}).then((route) => {
|
|
13829
14789
|
if (!this.isCurrentSession(key, record) || record.routeEntry !== entry || entry.closed || this.client !== client) {
|
|
13830
|
-
safeCloseRoute(client,
|
|
14790
|
+
safeCloseRoute(client, route);
|
|
13831
14791
|
if (record.routeEntry === entry)
|
|
13832
14792
|
record.routeEntry = null;
|
|
13833
14793
|
throw new RouteTornDownError("subc route opened after teardown");
|
|
13834
14794
|
}
|
|
13835
|
-
entry.
|
|
14795
|
+
entry.handle = route;
|
|
13836
14796
|
entry.opening = null;
|
|
13837
|
-
return
|
|
14797
|
+
return route;
|
|
13838
14798
|
}).catch((err) => {
|
|
13839
14799
|
const current = this.isCurrentSession(key, record);
|
|
13840
14800
|
if (record.routeEntry === entry) {
|
|
@@ -13848,7 +14808,7 @@ class SubcTransportPool {
|
|
|
13848
14808
|
});
|
|
13849
14809
|
entry.opening = opening;
|
|
13850
14810
|
record.routeEntry = entry;
|
|
13851
|
-
return {
|
|
14811
|
+
return { route: await opening, entry };
|
|
13852
14812
|
}
|
|
13853
14813
|
ensureBgSubscription(identity, record) {
|
|
13854
14814
|
if (this.shuttingDown || !this.onBgEventsNudge)
|
|
@@ -13859,7 +14819,7 @@ class SubcTransportPool {
|
|
|
13859
14819
|
if (record.bgSub)
|
|
13860
14820
|
return;
|
|
13861
14821
|
const onNudge = () => this.onBgEventsNudge?.(identity.project_root, identity.session);
|
|
13862
|
-
const sub = new BgSubscription(identity, () => this.ensureClient(), (client) => this.dropClient(client), onNudge, this.bgBackoffSleep);
|
|
14822
|
+
const sub = new BgSubscription(identity, () => this.ensureClient(), (client) => this.dropClient(client), this.consumerIdentity, onNudge, this.bgBackoffSleep);
|
|
13863
14823
|
record.bgSub = sub;
|
|
13864
14824
|
}
|
|
13865
14825
|
dropClient(client) {
|
|
@@ -13880,9 +14840,13 @@ class SubcTransportPool {
|
|
|
13880
14840
|
}
|
|
13881
14841
|
}
|
|
13882
14842
|
setConfigureOverride(_key, _value) {}
|
|
14843
|
+
async reconfigure(_projectRoot, _overrides) {}
|
|
13883
14844
|
async replaceBinary(path2) {
|
|
13884
14845
|
return path2;
|
|
13885
14846
|
}
|
|
14847
|
+
isShutdown() {
|
|
14848
|
+
return this.shuttingDown;
|
|
14849
|
+
}
|
|
13886
14850
|
async shutdown() {
|
|
13887
14851
|
this.shuttingDown = true;
|
|
13888
14852
|
const subs = [];
|
|
@@ -13906,10 +14870,10 @@ class SubcTransportPool {
|
|
|
13906
14870
|
this.transports.clear();
|
|
13907
14871
|
await Promise.allSettled(subs.map((sub) => sub.stop()));
|
|
13908
14872
|
await Promise.allSettled(entries.map(async (entry) => {
|
|
13909
|
-
if (entry.
|
|
14873
|
+
if (entry.handle == null)
|
|
13910
14874
|
return;
|
|
13911
14875
|
try {
|
|
13912
|
-
await entry.client.closeRouteChannel(entry.
|
|
14876
|
+
await entry.client.closeRouteChannel(entry.handle);
|
|
13913
14877
|
} catch {}
|
|
13914
14878
|
}));
|
|
13915
14879
|
if (client) {
|
|
@@ -13938,9 +14902,9 @@ class SubcTransportPool {
|
|
|
13938
14902
|
entry.closed = true;
|
|
13939
14903
|
if (sub)
|
|
13940
14904
|
await sub.stop();
|
|
13941
|
-
if (entry?.
|
|
14905
|
+
if (entry?.handle != null) {
|
|
13942
14906
|
try {
|
|
13943
|
-
await entry.client.closeRouteChannel(entry.
|
|
14907
|
+
await entry.client.closeRouteChannel(entry.handle);
|
|
13944
14908
|
} catch {}
|
|
13945
14909
|
}
|
|
13946
14910
|
}
|
|
@@ -14017,18 +14981,25 @@ function collectStructuredExtras(response) {
|
|
|
14017
14981
|
return stringifyData(extras);
|
|
14018
14982
|
}
|
|
14019
14983
|
// ../aft-bridge/dist/transport-factory.js
|
|
14020
|
-
import { homedir as
|
|
14021
|
-
import { isAbsolute as isAbsolute5, join as
|
|
14984
|
+
import { homedir as homedir11 } from "node:os";
|
|
14985
|
+
import { isAbsolute as isAbsolute5, join as join10 } from "node:path";
|
|
14022
14986
|
function resolveConnectionFilePath(raw) {
|
|
14023
14987
|
const trimmed = raw.trim();
|
|
14024
14988
|
if (trimmed.startsWith("~")) {
|
|
14025
|
-
return
|
|
14989
|
+
return join10(homedir11(), trimmed.slice(1).replace(/^[/\\]/, ""));
|
|
14026
14990
|
}
|
|
14027
14991
|
if (isAbsolute5(trimmed))
|
|
14028
14992
|
return trimmed;
|
|
14029
|
-
return
|
|
14993
|
+
return join10(homedir11(), trimmed);
|
|
14030
14994
|
}
|
|
14031
14995
|
async function createAftTransportPool(opts) {
|
|
14996
|
+
let binaryPath = opts.binaryPath;
|
|
14997
|
+
const createPool = () => createConcreteAftTransportPool({ ...opts, binaryPath });
|
|
14998
|
+
return new RevivableTransportPool(await createPool(), createPool, (path2) => {
|
|
14999
|
+
binaryPath = path2;
|
|
15000
|
+
});
|
|
15001
|
+
}
|
|
15002
|
+
async function createConcreteAftTransportPool(opts) {
|
|
14032
15003
|
const raw = opts.subcConnectionFile?.trim();
|
|
14033
15004
|
if (raw && raw.length > 0) {
|
|
14034
15005
|
const connectionFile = resolveConnectionFilePath(raw);
|
|
@@ -14039,18 +15010,17 @@ async function createAftTransportPool(opts) {
|
|
|
14039
15010
|
return new SubcTransportPool({
|
|
14040
15011
|
connectionFile,
|
|
14041
15012
|
harness: opts.harness,
|
|
15013
|
+
consumerIdentity: opts.subcConsumerIdentity,
|
|
14042
15014
|
onBgEventsNudge: opts.onBgEventsNudge
|
|
14043
15015
|
});
|
|
14044
15016
|
}
|
|
14045
15017
|
return new BridgePool(opts.binaryPath, opts.poolOptions, opts.configOverrides);
|
|
14046
15018
|
}
|
|
14047
15019
|
// src/logger.ts
|
|
14048
|
-
import * as fs3 from "node:fs";
|
|
14049
|
-
import * as os2 from "node:os";
|
|
14050
|
-
import * as path2 from "node:path";
|
|
14051
15020
|
var TAG = "[aft-pi]";
|
|
14052
15021
|
var isTestEnv = process.env.BUN_TEST === "1" || false;
|
|
14053
|
-
var logFile =
|
|
15022
|
+
var logFile = resolveAftLogPath(isTestEnv ? "aft-plugin-test.log" : "aft-plugin.log");
|
|
15023
|
+
var fileSink = new RotatingLogSink(logFile);
|
|
14054
15024
|
var useStderr = process.env.AFT_LOG_STDERR === "1";
|
|
14055
15025
|
var buffer = [];
|
|
14056
15026
|
var flushTimer = null;
|
|
@@ -14065,7 +15035,7 @@ function flush() {
|
|
|
14065
15035
|
if (useStderr) {
|
|
14066
15036
|
process.stderr.write(data);
|
|
14067
15037
|
} else {
|
|
14068
|
-
|
|
15038
|
+
fileSink.append(data);
|
|
14069
15039
|
}
|
|
14070
15040
|
} catch {}
|
|
14071
15041
|
}
|
|
@@ -14778,7 +15748,7 @@ import {
|
|
|
14778
15748
|
// package.json
|
|
14779
15749
|
var package_default = {
|
|
14780
15750
|
name: "@cortexkit/aft-pi",
|
|
14781
|
-
version: "0.
|
|
15751
|
+
version: "0.47.0",
|
|
14782
15752
|
type: "module",
|
|
14783
15753
|
description: "Pi coding agent extension for Agent File Tools (AFT) — tree-sitter and LSP-powered code analysis",
|
|
14784
15754
|
main: "dist/index.js",
|
|
@@ -14801,19 +15771,19 @@ var package_default = {
|
|
|
14801
15771
|
"test:unit": "bun test src/__tests__ --path-ignore-patterns 'src/__tests__/e2e/**'"
|
|
14802
15772
|
},
|
|
14803
15773
|
dependencies: {
|
|
14804
|
-
"@cortexkit/aft-bridge": "0.
|
|
15774
|
+
"@cortexkit/aft-bridge": "0.47.0",
|
|
14805
15775
|
"comment-json": "^5.0.0",
|
|
14806
15776
|
diff: "^8.0.4",
|
|
14807
15777
|
typebox: "1.1.38",
|
|
14808
15778
|
zod: "^4.4.3"
|
|
14809
15779
|
},
|
|
14810
15780
|
optionalDependencies: {
|
|
14811
|
-
"@cortexkit/aft-darwin-arm64": "0.
|
|
14812
|
-
"@cortexkit/aft-darwin-x64": "0.
|
|
14813
|
-
"@cortexkit/aft-linux-arm64": "0.
|
|
14814
|
-
"@cortexkit/aft-linux-x64": "0.
|
|
14815
|
-
"@cortexkit/aft-win32-arm64": "0.
|
|
14816
|
-
"@cortexkit/aft-win32-x64": "0.
|
|
15781
|
+
"@cortexkit/aft-darwin-arm64": "0.47.0",
|
|
15782
|
+
"@cortexkit/aft-darwin-x64": "0.47.0",
|
|
15783
|
+
"@cortexkit/aft-linux-arm64": "0.47.0",
|
|
15784
|
+
"@cortexkit/aft-linux-x64": "0.47.0",
|
|
15785
|
+
"@cortexkit/aft-win32-arm64": "0.47.0",
|
|
15786
|
+
"@cortexkit/aft-win32-x64": "0.47.0"
|
|
14817
15787
|
},
|
|
14818
15788
|
devDependencies: {
|
|
14819
15789
|
"@earendil-works/pi-coding-agent": "^0.80.2",
|
|
@@ -15044,9 +16014,15 @@ function formatStatusDialogMessage(status) {
|
|
|
15044
16014
|
}
|
|
15045
16015
|
|
|
15046
16016
|
// src/tools/_shared.ts
|
|
16017
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
15047
16018
|
import { Type } from "typebox";
|
|
15048
|
-
var optionalInt = (
|
|
16019
|
+
var optionalInt = (min, max, description = "(integer)") => Type.Optional(Type.Union([Type.Integer({ minimum: min, maximum: max }), Type.String()], {
|
|
16020
|
+
description
|
|
16021
|
+
}));
|
|
15049
16022
|
function bridgeFor(ctx, cwd) {
|
|
16023
|
+
if (!existsSync7(cwd)) {
|
|
16024
|
+
throw new Error(`project directory no longer exists: ${cwd} (stale restored session?)`);
|
|
16025
|
+
}
|
|
15050
16026
|
return ctx.pool.getBridge(cwd);
|
|
15051
16027
|
}
|
|
15052
16028
|
function resolveSessionId(extCtx) {
|
|
@@ -15402,7 +16378,7 @@ function registerStatusCommand(pi, ctx) {
|
|
|
15402
16378
|
}
|
|
15403
16379
|
|
|
15404
16380
|
// src/config.ts
|
|
15405
|
-
import { existsSync as
|
|
16381
|
+
import { existsSync as existsSync8, readFileSync as readFileSync7, renameSync as renameSync5, unlinkSync as unlinkSync5, writeFileSync as writeFileSync4 } from "node:fs";
|
|
15406
16382
|
var import_comment_json = __toESM(require_src2(), 1);
|
|
15407
16383
|
|
|
15408
16384
|
// ../../node_modules/.bun/zod@4.4.3/node_modules/zod/v4/classic/external.js
|
|
@@ -16169,10 +17145,10 @@ function mergeDefs(...defs) {
|
|
|
16169
17145
|
function cloneDef(schema) {
|
|
16170
17146
|
return mergeDefs(schema._zod.def);
|
|
16171
17147
|
}
|
|
16172
|
-
function getElementAtPath(obj,
|
|
16173
|
-
if (!
|
|
17148
|
+
function getElementAtPath(obj, path2) {
|
|
17149
|
+
if (!path2)
|
|
16174
17150
|
return obj;
|
|
16175
|
-
return
|
|
17151
|
+
return path2.reduce((acc, key) => acc?.[key], obj);
|
|
16176
17152
|
}
|
|
16177
17153
|
function promiseAllObject(promisesObj) {
|
|
16178
17154
|
const keys = Object.keys(promisesObj);
|
|
@@ -16580,11 +17556,11 @@ function explicitlyAborted(x, startIndex = 0) {
|
|
|
16580
17556
|
}
|
|
16581
17557
|
return false;
|
|
16582
17558
|
}
|
|
16583
|
-
function prefixIssues(
|
|
17559
|
+
function prefixIssues(path2, issues) {
|
|
16584
17560
|
return issues.map((iss) => {
|
|
16585
17561
|
var _a2;
|
|
16586
17562
|
(_a2 = iss).path ?? (_a2.path = []);
|
|
16587
|
-
iss.path.unshift(
|
|
17563
|
+
iss.path.unshift(path2);
|
|
16588
17564
|
return iss;
|
|
16589
17565
|
});
|
|
16590
17566
|
}
|
|
@@ -16731,16 +17707,16 @@ function flattenError(error3, mapper = (issue2) => issue2.message) {
|
|
|
16731
17707
|
}
|
|
16732
17708
|
function formatError(error3, mapper = (issue2) => issue2.message) {
|
|
16733
17709
|
const fieldErrors = { _errors: [] };
|
|
16734
|
-
const processError = (error4,
|
|
17710
|
+
const processError = (error4, path2 = []) => {
|
|
16735
17711
|
for (const issue2 of error4.issues) {
|
|
16736
17712
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
16737
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
17713
|
+
issue2.errors.map((issues) => processError({ issues }, [...path2, ...issue2.path]));
|
|
16738
17714
|
} else if (issue2.code === "invalid_key") {
|
|
16739
|
-
processError({ issues: issue2.issues }, [...
|
|
17715
|
+
processError({ issues: issue2.issues }, [...path2, ...issue2.path]);
|
|
16740
17716
|
} else if (issue2.code === "invalid_element") {
|
|
16741
|
-
processError({ issues: issue2.issues }, [...
|
|
17717
|
+
processError({ issues: issue2.issues }, [...path2, ...issue2.path]);
|
|
16742
17718
|
} else {
|
|
16743
|
-
const fullpath = [...
|
|
17719
|
+
const fullpath = [...path2, ...issue2.path];
|
|
16744
17720
|
if (fullpath.length === 0) {
|
|
16745
17721
|
fieldErrors._errors.push(mapper(issue2));
|
|
16746
17722
|
} else {
|
|
@@ -16767,17 +17743,17 @@ function formatError(error3, mapper = (issue2) => issue2.message) {
|
|
|
16767
17743
|
}
|
|
16768
17744
|
function treeifyError(error3, mapper = (issue2) => issue2.message) {
|
|
16769
17745
|
const result = { errors: [] };
|
|
16770
|
-
const processError = (error4,
|
|
17746
|
+
const processError = (error4, path2 = []) => {
|
|
16771
17747
|
var _a2, _b;
|
|
16772
17748
|
for (const issue2 of error4.issues) {
|
|
16773
17749
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
16774
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
17750
|
+
issue2.errors.map((issues) => processError({ issues }, [...path2, ...issue2.path]));
|
|
16775
17751
|
} else if (issue2.code === "invalid_key") {
|
|
16776
|
-
processError({ issues: issue2.issues }, [...
|
|
17752
|
+
processError({ issues: issue2.issues }, [...path2, ...issue2.path]);
|
|
16777
17753
|
} else if (issue2.code === "invalid_element") {
|
|
16778
|
-
processError({ issues: issue2.issues }, [...
|
|
17754
|
+
processError({ issues: issue2.issues }, [...path2, ...issue2.path]);
|
|
16779
17755
|
} else {
|
|
16780
|
-
const fullpath = [...
|
|
17756
|
+
const fullpath = [...path2, ...issue2.path];
|
|
16781
17757
|
if (fullpath.length === 0) {
|
|
16782
17758
|
result.errors.push(mapper(issue2));
|
|
16783
17759
|
continue;
|
|
@@ -16809,8 +17785,8 @@ function treeifyError(error3, mapper = (issue2) => issue2.message) {
|
|
|
16809
17785
|
}
|
|
16810
17786
|
function toDotPath(_path) {
|
|
16811
17787
|
const segs = [];
|
|
16812
|
-
const
|
|
16813
|
-
for (const seg of
|
|
17788
|
+
const path2 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
17789
|
+
for (const seg of path2) {
|
|
16814
17790
|
if (typeof seg === "number")
|
|
16815
17791
|
segs.push(`[${seg}]`);
|
|
16816
17792
|
else if (typeof seg === "symbol")
|
|
@@ -29269,13 +30245,13 @@ function resolveRef(ref, ctx) {
|
|
|
29269
30245
|
if (!ref.startsWith("#")) {
|
|
29270
30246
|
throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
|
|
29271
30247
|
}
|
|
29272
|
-
const
|
|
29273
|
-
if (
|
|
30248
|
+
const path2 = ref.slice(1).split("/").filter(Boolean);
|
|
30249
|
+
if (path2.length === 0) {
|
|
29274
30250
|
return ctx.rootSchema;
|
|
29275
30251
|
}
|
|
29276
30252
|
const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
|
|
29277
|
-
if (
|
|
29278
|
-
const key =
|
|
30253
|
+
if (path2[0] === defsKey) {
|
|
30254
|
+
const key = path2[1];
|
|
29279
30255
|
if (!key || !ctx.defs[key]) {
|
|
29280
30256
|
throw new Error(`Reference not found: ${ref}`);
|
|
29281
30257
|
}
|
|
@@ -29761,6 +30737,7 @@ var SemanticConfigSchema = exports_external.object({
|
|
|
29761
30737
|
base_url: exports_external.string().trim().min(1).optional(),
|
|
29762
30738
|
api_key_env: exports_external.string().trim().min(1).optional(),
|
|
29763
30739
|
timeout_ms: exports_external.number().int().positive().optional(),
|
|
30740
|
+
query_timeout_ms: exports_external.number().int().positive().optional(),
|
|
29764
30741
|
max_batch_size: exports_external.number().int().positive().optional(),
|
|
29765
30742
|
max_files: exports_external.number().int().positive().optional()
|
|
29766
30743
|
});
|
|
@@ -29818,16 +30795,7 @@ var InspectConfigSchema = exports_external.object({
|
|
|
29818
30795
|
tier2_soft_deadline_ms: exports_external.number().int().positive().optional(),
|
|
29819
30796
|
max_drill_down_items: exports_external.number().int().positive().max(100).optional(),
|
|
29820
30797
|
duplicates: exports_external.object({
|
|
29821
|
-
|
|
29822
|
-
discard_cost: exports_external.number().int().min(0).optional(),
|
|
29823
|
-
expected_mirrors: exports_external.array(exports_external.tuple([exports_external.string().trim().min(1), exports_external.string().trim().min(1)])).optional(),
|
|
29824
|
-
anonymize: exports_external.object({
|
|
29825
|
-
variables: exports_external.boolean().optional(),
|
|
29826
|
-
fields: exports_external.boolean().optional(),
|
|
29827
|
-
methods: exports_external.boolean().optional(),
|
|
29828
|
-
types: exports_external.boolean().optional(),
|
|
29829
|
-
literals: exports_external.boolean().optional()
|
|
29830
|
-
}).optional()
|
|
30798
|
+
expected_mirrors: exports_external.array(exports_external.tuple([exports_external.string().trim().min(1), exports_external.string().trim().min(1)])).optional()
|
|
29831
30799
|
}).optional()
|
|
29832
30800
|
});
|
|
29833
30801
|
var BackupConfigSchema = exports_external.object({
|
|
@@ -29835,7 +30803,7 @@ var BackupConfigSchema = exports_external.object({
|
|
|
29835
30803
|
max_depth: exports_external.number().int().positive().optional(),
|
|
29836
30804
|
max_file_size: exports_external.number().int().positive().optional()
|
|
29837
30805
|
});
|
|
29838
|
-
var AftConfigSchema = exports_external.object({
|
|
30806
|
+
var AftConfigSchema = exports_external.preprocess((value) => stripHarnessSpecificConfigKeys(value, OPENCODE_ONLY_KEYS), exports_external.object({
|
|
29839
30807
|
$schema: exports_external.string().optional(),
|
|
29840
30808
|
enabled: exports_external.boolean().optional(),
|
|
29841
30809
|
format_on_edit: exports_external.boolean().optional(),
|
|
@@ -29860,7 +30828,7 @@ var AftConfigSchema = exports_external.object({
|
|
|
29860
30828
|
semantic: SemanticConfigSchema.optional(),
|
|
29861
30829
|
bridge: BridgeConfigSchema.optional(),
|
|
29862
30830
|
subc: SubcConfigSchema.optional()
|
|
29863
|
-
}).strict();
|
|
30831
|
+
}).strict());
|
|
29864
30832
|
var CONFIG_MIGRATIONS = [
|
|
29865
30833
|
{ oldKey: "experimental_search_index", newPath: ["search_index"] },
|
|
29866
30834
|
{ oldKey: "experimental_semantic_search", newPath: ["semantic_search"] },
|
|
@@ -29885,9 +30853,9 @@ function extractCommentsForPreservation(content) {
|
|
|
29885
30853
|
}
|
|
29886
30854
|
return comments;
|
|
29887
30855
|
}
|
|
29888
|
-
function ensureRecordAtPath(root,
|
|
30856
|
+
function ensureRecordAtPath(root, path2) {
|
|
29889
30857
|
let current = root;
|
|
29890
|
-
for (const segment of
|
|
30858
|
+
for (const segment of path2) {
|
|
29891
30859
|
const existing = current[segment];
|
|
29892
30860
|
if (!existing || typeof existing !== "object" || Array.isArray(existing)) {
|
|
29893
30861
|
current[segment] = {};
|
|
@@ -29896,9 +30864,9 @@ function ensureRecordAtPath(root, path3) {
|
|
|
29896
30864
|
}
|
|
29897
30865
|
return current;
|
|
29898
30866
|
}
|
|
29899
|
-
function hasPath(root,
|
|
30867
|
+
function hasPath(root, path2) {
|
|
29900
30868
|
let current = root;
|
|
29901
|
-
for (const segment of
|
|
30869
|
+
for (const segment of path2) {
|
|
29902
30870
|
if (!current || typeof current !== "object" || Array.isArray(current))
|
|
29903
30871
|
return false;
|
|
29904
30872
|
const record2 = current;
|
|
@@ -29908,9 +30876,9 @@ function hasPath(root, path3) {
|
|
|
29908
30876
|
}
|
|
29909
30877
|
return true;
|
|
29910
30878
|
}
|
|
29911
|
-
function setPath(root,
|
|
29912
|
-
const parent = ensureRecordAtPath(root,
|
|
29913
|
-
parent[
|
|
30879
|
+
function setPath(root, path2, value) {
|
|
30880
|
+
const parent = ensureRecordAtPath(root, path2.slice(0, -1));
|
|
30881
|
+
parent[path2[path2.length - 1]] = value;
|
|
29914
30882
|
}
|
|
29915
30883
|
function migrateRawConfig(rawConfig, configPath, logger) {
|
|
29916
30884
|
const oldKeys = [];
|
|
@@ -29971,7 +30939,7 @@ function migrateExperimentalBashBlock(rawConfig, configPath, logger) {
|
|
|
29971
30939
|
return movedKeys;
|
|
29972
30940
|
}
|
|
29973
30941
|
function migrateAftConfigFile2(configPath, logger = { log: log2, warn: warn2 }) {
|
|
29974
|
-
if (!
|
|
30942
|
+
if (!existsSync8(configPath)) {
|
|
29975
30943
|
return { migrated: false, oldKeys: [] };
|
|
29976
30944
|
}
|
|
29977
30945
|
let tmpPath = null;
|
|
@@ -30024,7 +30992,7 @@ function recordConfigParseFailure(configPath, errorMessage) {
|
|
|
30024
30992
|
}
|
|
30025
30993
|
function loadConfigFromPath(configPath) {
|
|
30026
30994
|
try {
|
|
30027
|
-
if (!
|
|
30995
|
+
if (!existsSync8(configPath))
|
|
30028
30996
|
return null;
|
|
30029
30997
|
const content = readFileSync7(configPath, "utf-8");
|
|
30030
30998
|
const rawConfig = import_comment_json.parse(content);
|
|
@@ -30109,16 +31077,9 @@ function mergeInspectConfig(baseInspect, overrideInspect) {
|
|
|
30109
31077
|
...overrideInspect,
|
|
30110
31078
|
duplicates: baseInspect?.duplicates || overrideInspect?.duplicates ? {
|
|
30111
31079
|
...baseInspect?.duplicates,
|
|
30112
|
-
...overrideInspect?.duplicates
|
|
30113
|
-
anonymize: baseInspect?.duplicates?.anonymize || overrideInspect?.duplicates?.anonymize ? {
|
|
30114
|
-
...baseInspect?.duplicates?.anonymize,
|
|
30115
|
-
...overrideInspect?.duplicates?.anonymize
|
|
30116
|
-
} : undefined
|
|
31080
|
+
...overrideInspect?.duplicates
|
|
30117
31081
|
} : undefined
|
|
30118
31082
|
};
|
|
30119
|
-
if (inspect.duplicates && inspect.duplicates.anonymize === undefined) {
|
|
30120
|
-
delete inspect.duplicates.anonymize;
|
|
30121
|
-
}
|
|
30122
31083
|
if (Object.values(inspect).every((value) => value === undefined)) {
|
|
30123
31084
|
return;
|
|
30124
31085
|
}
|
|
@@ -30312,7 +31273,7 @@ import { spawn as spawn2 } from "node:child_process";
|
|
|
30312
31273
|
import { createHash as createHash4 } from "node:crypto";
|
|
30313
31274
|
import {
|
|
30314
31275
|
createReadStream,
|
|
30315
|
-
existsSync as
|
|
31276
|
+
existsSync as existsSync10,
|
|
30316
31277
|
mkdirSync as mkdirSync7,
|
|
30317
31278
|
readFileSync as readFileSync10,
|
|
30318
31279
|
renameSync as renameSync6,
|
|
@@ -30332,7 +31293,7 @@ import {
|
|
|
30332
31293
|
unlinkSync as unlinkSync6,
|
|
30333
31294
|
writeFileSync as writeFileSync5
|
|
30334
31295
|
} from "node:fs";
|
|
30335
|
-
import { homedir as
|
|
31296
|
+
import { homedir as homedir12 } from "node:os";
|
|
30336
31297
|
import { join as join11 } from "node:path";
|
|
30337
31298
|
function aftCacheBase() {
|
|
30338
31299
|
const override = process.env.AFT_CACHE_DIR;
|
|
@@ -30340,10 +31301,10 @@ function aftCacheBase() {
|
|
|
30340
31301
|
return override;
|
|
30341
31302
|
if (process.platform === "win32") {
|
|
30342
31303
|
const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
|
|
30343
|
-
const base2 = localAppData || join11(
|
|
31304
|
+
const base2 = localAppData || join11(homedir12(), "AppData", "Local");
|
|
30344
31305
|
return join11(base2, "aft");
|
|
30345
31306
|
}
|
|
30346
|
-
const base = process.env.XDG_CACHE_HOME || join11(
|
|
31307
|
+
const base = process.env.XDG_CACHE_HOME || join11(homedir12(), ".cache");
|
|
30347
31308
|
return join11(base, "aft");
|
|
30348
31309
|
}
|
|
30349
31310
|
function lspCacheRoot() {
|
|
@@ -30387,11 +31348,11 @@ function writeInstalledMetaIn(installDir, version2, sha256) {
|
|
|
30387
31348
|
}
|
|
30388
31349
|
}
|
|
30389
31350
|
function readInstalledMetaIn(installDir) {
|
|
30390
|
-
const
|
|
31351
|
+
const path2 = join11(installDir, INSTALLED_META_FILE);
|
|
30391
31352
|
try {
|
|
30392
|
-
if (!statSync5(
|
|
31353
|
+
if (!statSync5(path2).isFile())
|
|
30393
31354
|
return null;
|
|
30394
|
-
const raw = readFileSync8(
|
|
31355
|
+
const raw = readFileSync8(path2, "utf8");
|
|
30395
31356
|
const parsed = JSON.parse(raw);
|
|
30396
31357
|
if (typeof parsed.version !== "string" || parsed.version.length === 0)
|
|
30397
31358
|
return null;
|
|
@@ -30697,7 +31658,7 @@ var NPM_LSP_TABLE = [
|
|
|
30697
31658
|
];
|
|
30698
31659
|
|
|
30699
31660
|
// src/lsp-project-relevance.ts
|
|
30700
|
-
import { existsSync as
|
|
31661
|
+
import { existsSync as existsSync9, readdirSync as readdirSync3, readFileSync as readFileSync9 } from "node:fs";
|
|
30701
31662
|
import { join as join12 } from "node:path";
|
|
30702
31663
|
var MAX_WALK_DIRS = 200;
|
|
30703
31664
|
var MAX_WALK_DEPTH = 4;
|
|
@@ -30715,7 +31676,7 @@ function hasRootMarker(projectRoot, rootMarkers) {
|
|
|
30715
31676
|
if (!rootMarkers)
|
|
30716
31677
|
return false;
|
|
30717
31678
|
for (const marker of rootMarkers) {
|
|
30718
|
-
if (
|
|
31679
|
+
if (existsSync9(join12(projectRoot, marker)))
|
|
30719
31680
|
return true;
|
|
30720
31681
|
}
|
|
30721
31682
|
return false;
|
|
@@ -30897,7 +31858,7 @@ async function resolveTargetVersion(spec, config2, fetchImpl = fetch) {
|
|
|
30897
31858
|
function ensureInstallAnchor(cwd) {
|
|
30898
31859
|
try {
|
|
30899
31860
|
const stub = join13(cwd, "package.json");
|
|
30900
|
-
if (!
|
|
31861
|
+
if (!existsSync10(stub)) {
|
|
30901
31862
|
writeFileSync6(stub, `${JSON.stringify({ name: "aft-lsp-cache", version: "0.0.0", private: true })}
|
|
30902
31863
|
`);
|
|
30903
31864
|
}
|
|
@@ -31083,8 +32044,8 @@ function installedBinaryPath(spec) {
|
|
|
31083
32044
|
}
|
|
31084
32045
|
return null;
|
|
31085
32046
|
}
|
|
31086
|
-
function sha256OfFileSync(
|
|
31087
|
-
return createHash4("sha256").update(readFileSync10(
|
|
32047
|
+
function sha256OfFileSync(path2) {
|
|
32048
|
+
return createHash4("sha256").update(readFileSync10(path2)).digest("hex");
|
|
31088
32049
|
}
|
|
31089
32050
|
function quarantineCachedNpmInstall(spec, reason) {
|
|
31090
32051
|
const packageDir = lspPackageDir(spec.npm);
|
|
@@ -31162,9 +32123,15 @@ function runAutoInstall(projectRoot, config2, fetchImpl = fetch) {
|
|
|
31162
32123
|
return installsStarted;
|
|
31163
32124
|
},
|
|
31164
32125
|
skipped,
|
|
31165
|
-
installsComplete: Promise.all(installPromises).then(() => {})
|
|
32126
|
+
installsComplete: Promise.all(installPromises).then(() => {}),
|
|
32127
|
+
getCachedBinDirs: () => NPM_LSP_TABLE.filter((spec) => isInstalled(spec.npm, spec.binary) && validateCachedNpmInstall(spec)).map((spec) => lspBinDir(spec.npm))
|
|
31166
32128
|
};
|
|
31167
32129
|
}
|
|
32130
|
+
async function pushLspPathsAfterAutoInstall(pool, projectRoot, cachedBinDirs) {
|
|
32131
|
+
const paths = [...new Set(cachedBinDirs)];
|
|
32132
|
+
pool.setConfigureOverride("lsp_paths_extra", paths);
|
|
32133
|
+
await pool.reconfigure(projectRoot, { lsp_paths_extra: paths });
|
|
32134
|
+
}
|
|
31168
32135
|
|
|
31169
32136
|
// src/lsp-github-install.ts
|
|
31170
32137
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
@@ -31173,7 +32140,7 @@ import {
|
|
|
31173
32140
|
copyFileSync as copyFileSync4,
|
|
31174
32141
|
createReadStream as createReadStream2,
|
|
31175
32142
|
createWriteStream as createWriteStream3,
|
|
31176
|
-
existsSync as
|
|
32143
|
+
existsSync as existsSync11,
|
|
31177
32144
|
lstatSync as lstatSync2,
|
|
31178
32145
|
mkdirSync as mkdirSync8,
|
|
31179
32146
|
readdirSync as readdirSync4,
|
|
@@ -31186,7 +32153,7 @@ import {
|
|
|
31186
32153
|
unlinkSync as unlinkSync7,
|
|
31187
32154
|
writeFileSync as writeFileSync7
|
|
31188
32155
|
} from "node:fs";
|
|
31189
|
-
import { dirname as
|
|
32156
|
+
import { dirname as dirname6, join as join14, relative as relative3, resolve as resolve7 } from "node:path";
|
|
31190
32157
|
import { Readable as Readable3 } from "node:stream";
|
|
31191
32158
|
import { pipeline as pipeline3 } from "node:stream/promises";
|
|
31192
32159
|
|
|
@@ -31308,10 +32275,10 @@ function ghBinaryCandidates(spec, platform) {
|
|
|
31308
32275
|
}
|
|
31309
32276
|
function readGithubInstalledMetaIn(installDir) {
|
|
31310
32277
|
try {
|
|
31311
|
-
const
|
|
31312
|
-
if (!statSync7(
|
|
32278
|
+
const path2 = join14(installDir, INSTALLED_META_FILE2);
|
|
32279
|
+
if (!statSync7(path2).isFile())
|
|
31313
32280
|
return null;
|
|
31314
|
-
const parsed = JSON.parse(readFileSync11(
|
|
32281
|
+
const parsed = JSON.parse(readFileSync11(path2, "utf8"));
|
|
31315
32282
|
if (typeof parsed.version !== "string" || parsed.version.length === 0)
|
|
31316
32283
|
return null;
|
|
31317
32284
|
return {
|
|
@@ -31342,17 +32309,17 @@ function writeGithubInstalledMetaIn(installDir, version2, binarySha256, archiveS
|
|
|
31342
32309
|
}
|
|
31343
32310
|
var MAX_DOWNLOAD_BYTES3 = 256 * 1024 * 1024;
|
|
31344
32311
|
var MAX_EXTRACT_BYTES2 = 1024 * 1024 * 1024;
|
|
31345
|
-
function sha256OfFile(
|
|
32312
|
+
function sha256OfFile(path2) {
|
|
31346
32313
|
return new Promise((resolve8, reject) => {
|
|
31347
32314
|
const hash2 = createHash5("sha256");
|
|
31348
|
-
const stream = createReadStream2(
|
|
32315
|
+
const stream = createReadStream2(path2);
|
|
31349
32316
|
stream.on("error", reject);
|
|
31350
32317
|
stream.on("data", (chunk) => hash2.update(chunk));
|
|
31351
32318
|
stream.on("end", () => resolve8(hash2.digest("hex")));
|
|
31352
32319
|
});
|
|
31353
32320
|
}
|
|
31354
|
-
function sha256OfFileSync2(
|
|
31355
|
-
return createHash5("sha256").update(readFileSync11(
|
|
32321
|
+
function sha256OfFileSync2(path2) {
|
|
32322
|
+
return createHash5("sha256").update(readFileSync11(path2)).digest("hex");
|
|
31356
32323
|
}
|
|
31357
32324
|
async function fetchReleaseByTag(githubRepo, tag, fetchImpl, signal) {
|
|
31358
32325
|
const candidates = [];
|
|
@@ -31528,7 +32495,7 @@ async function downloadFile(url2, destPath, fetchImpl, assetSize, signal) {
|
|
|
31528
32495
|
if (Number.isFinite(advertised) && advertised > MAX_DOWNLOAD_BYTES3) {
|
|
31529
32496
|
throw new Error(`Content-Length ${advertised} exceeds max ${MAX_DOWNLOAD_BYTES3}`);
|
|
31530
32497
|
}
|
|
31531
|
-
mkdirSync8(
|
|
32498
|
+
mkdirSync8(dirname6(destPath), { recursive: true });
|
|
31532
32499
|
let bytesWritten = 0;
|
|
31533
32500
|
const guard = new TransformStream({
|
|
31534
32501
|
transform(chunk, controller) {
|
|
@@ -31675,7 +32642,7 @@ function quarantineCachedGithubInstall(spec, reason) {
|
|
|
31675
32642
|
const dest = join14(ghCacheRoot(), ".quarantine", encodeURIComponent(spec.id), `${Date.now()}`);
|
|
31676
32643
|
warn2(`[lsp] tofu_mismatch ${spec.id}: ${reason}; quarantining ${packageDir} -> ${dest}`);
|
|
31677
32644
|
try {
|
|
31678
|
-
mkdirSync8(
|
|
32645
|
+
mkdirSync8(dirname6(dest), { recursive: true });
|
|
31679
32646
|
rmSync5(dest, { recursive: true, force: true });
|
|
31680
32647
|
renameSync7(packageDir, dest);
|
|
31681
32648
|
} catch (err) {
|
|
@@ -31804,12 +32771,12 @@ async function downloadAndInstall(spec, tag, assets, platform, arch, fetchImpl,
|
|
|
31804
32771
|
} catch {}
|
|
31805
32772
|
}
|
|
31806
32773
|
const innerBinaryPath = join14(extractDir, spec.binaryPathInArchive(platform, arch, version2));
|
|
31807
|
-
if (!
|
|
32774
|
+
if (!existsSync11(innerBinaryPath)) {
|
|
31808
32775
|
error2(`[lsp] ${spec.id}: extracted binary not found at ${innerBinaryPath}`);
|
|
31809
32776
|
return null;
|
|
31810
32777
|
}
|
|
31811
32778
|
const targetBinary = ghBinaryPath(spec, platform);
|
|
31812
|
-
mkdirSync8(
|
|
32779
|
+
mkdirSync8(dirname6(targetBinary), { recursive: true });
|
|
31813
32780
|
try {
|
|
31814
32781
|
copyFileSync4(innerBinaryPath, targetBinary);
|
|
31815
32782
|
if (platform !== "win32") {
|
|
@@ -31888,7 +32855,7 @@ function runGithubAutoInstall(relevantServers, config2, fetchImpl = fetch) {
|
|
|
31888
32855
|
if (!host) {
|
|
31889
32856
|
for (const spec of GITHUB_LSP_TABLE) {
|
|
31890
32857
|
try {
|
|
31891
|
-
if (
|
|
32858
|
+
if (existsSync11(ghBinDir(spec))) {
|
|
31892
32859
|
cachedBinDirs.push(ghBinDir(spec));
|
|
31893
32860
|
}
|
|
31894
32861
|
} catch {}
|
|
@@ -31898,7 +32865,8 @@ function runGithubAutoInstall(relevantServers, config2, fetchImpl = fetch) {
|
|
|
31898
32865
|
installsStarted: 0,
|
|
31899
32866
|
installingBinaries: [],
|
|
31900
32867
|
skipped,
|
|
31901
|
-
installsComplete: Promise.resolve()
|
|
32868
|
+
installsComplete: Promise.resolve(),
|
|
32869
|
+
getCachedBinDirs: () => [...cachedBinDirs]
|
|
31902
32870
|
};
|
|
31903
32871
|
}
|
|
31904
32872
|
for (const spec of GITHUB_LSP_TABLE) {
|
|
@@ -31940,7 +32908,13 @@ function runGithubAutoInstall(relevantServers, config2, fetchImpl = fetch) {
|
|
|
31940
32908
|
return installsStarted;
|
|
31941
32909
|
},
|
|
31942
32910
|
skipped,
|
|
31943
|
-
installsComplete: Promise.all(installPromises).then(() => {})
|
|
32911
|
+
installsComplete: Promise.all(installPromises).then(() => {}),
|
|
32912
|
+
getCachedBinDirs: () => {
|
|
32913
|
+
const currentHost = detectHostPlatform();
|
|
32914
|
+
if (!currentHost)
|
|
32915
|
+
return [];
|
|
32916
|
+
return GITHUB_LSP_TABLE.filter((spec) => isGithubInstalled(spec, currentHost.platform) && validateCachedGithubInstall(spec, currentHost.platform)).map((spec) => ghBinDir(spec));
|
|
32917
|
+
}
|
|
31944
32918
|
};
|
|
31945
32919
|
}
|
|
31946
32920
|
function discoverRelevantGithubServers(projectRoot) {
|
|
@@ -32126,6 +33100,27 @@ function statusBarBlockForSession(sessionID, counts, force = false) {
|
|
|
32126
33100
|
return shouldEmitStatusBar(state, counts) ? formatStatusBar(counts) : undefined;
|
|
32127
33101
|
}
|
|
32128
33102
|
|
|
33103
|
+
// src/bash-wait-detach.ts
|
|
33104
|
+
var BASH_TRANSPORT_TIMEOUT_MS = 30000;
|
|
33105
|
+
async function sendBashWaitDetach(bridge, sessionID) {
|
|
33106
|
+
const response = await bridge.send("bash_wait_detach", { session_id: sessionID }, { keepBridgeOnTimeout: true, transportTimeoutMs: BASH_TRANSPORT_TIMEOUT_MS });
|
|
33107
|
+
if (response.success === false) {
|
|
33108
|
+
throw new Error(String(response.message ?? "bash_wait_detach failed"));
|
|
33109
|
+
}
|
|
33110
|
+
}
|
|
33111
|
+
async function signalBashWaitDetachForProject(pool, projectRoot, sessionID) {
|
|
33112
|
+
if (!sessionID)
|
|
33113
|
+
return;
|
|
33114
|
+
const bridge = pool.getActiveBridgeForRoot(projectRoot);
|
|
33115
|
+
if (!bridge)
|
|
33116
|
+
return;
|
|
33117
|
+
try {
|
|
33118
|
+
await sendBashWaitDetach(bridge, sessionID);
|
|
33119
|
+
} catch (err) {
|
|
33120
|
+
warn2(`[bash_wait_detach] failed for session ${sessionID}: ${err instanceof Error ? err.message : String(err)}`);
|
|
33121
|
+
}
|
|
33122
|
+
}
|
|
33123
|
+
|
|
32129
33124
|
// src/shutdown-hooks.ts
|
|
32130
33125
|
var GLOBAL_KEY = "__aftPiShutdownHooks__";
|
|
32131
33126
|
function getState() {
|
|
@@ -32222,8 +33217,8 @@ import { StringEnum } from "@earendil-works/pi-ai";
|
|
|
32222
33217
|
import { Type as Type3 } from "typebox";
|
|
32223
33218
|
|
|
32224
33219
|
// src/tools/hoisted.ts
|
|
32225
|
-
import { stat } from "node:fs/promises";
|
|
32226
|
-
import { homedir as
|
|
33220
|
+
import { stat as stat2 } from "node:fs/promises";
|
|
33221
|
+
import { homedir as homedir13 } from "node:os";
|
|
32227
33222
|
import { isAbsolute as isAbsolute6, relative as relative4, resolve as resolve8, sep } from "node:path";
|
|
32228
33223
|
import {
|
|
32229
33224
|
renderDiff
|
|
@@ -32367,15 +33362,15 @@ function containsPath(parent, child) {
|
|
|
32367
33362
|
const rel = relative4(parent, child);
|
|
32368
33363
|
return rel === "" || !rel.startsWith("..") && !isAbsolute6(rel);
|
|
32369
33364
|
}
|
|
32370
|
-
function expandTilde2(
|
|
32371
|
-
if (!
|
|
32372
|
-
return
|
|
32373
|
-
if (
|
|
32374
|
-
return
|
|
32375
|
-
if (
|
|
32376
|
-
return resolve8(
|
|
33365
|
+
function expandTilde2(path2) {
|
|
33366
|
+
if (!path2 || !path2.startsWith("~"))
|
|
33367
|
+
return path2;
|
|
33368
|
+
if (path2 === "~")
|
|
33369
|
+
return homedir13();
|
|
33370
|
+
if (path2.startsWith(`~${sep}`) || path2.startsWith("~/")) {
|
|
33371
|
+
return resolve8(homedir13(), path2.slice(2));
|
|
32377
33372
|
}
|
|
32378
|
-
return
|
|
33373
|
+
return path2;
|
|
32379
33374
|
}
|
|
32380
33375
|
function absoluteSearchPath(cwd, target) {
|
|
32381
33376
|
const expanded = expandTilde2(target);
|
|
@@ -32383,7 +33378,7 @@ function absoluteSearchPath(cwd, target) {
|
|
|
32383
33378
|
}
|
|
32384
33379
|
async function searchPathExists(cwd, target) {
|
|
32385
33380
|
try {
|
|
32386
|
-
await
|
|
33381
|
+
await stat2(absoluteSearchPath(cwd, target));
|
|
32387
33382
|
return true;
|
|
32388
33383
|
} catch {
|
|
32389
33384
|
return false;
|
|
@@ -32440,6 +33435,8 @@ async function assertExternalDirectoryPermission(extCtx, target, options = {}) {
|
|
|
32440
33435
|
return;
|
|
32441
33436
|
if (options.restrictToProjectRoot === false)
|
|
32442
33437
|
return;
|
|
33438
|
+
if (options.serverValidatedRead === true)
|
|
33439
|
+
return;
|
|
32443
33440
|
throw new Error(`Blocked: '${absoluteTarget}' is outside the project root and restrict_to_project_root is ` + "enabled (AFT full isolation). Not overridable per-call; set restrict_to_project_root: false " + "in aft.jsonc to allow external paths.");
|
|
32444
33441
|
}
|
|
32445
33442
|
var ReadParams = Type2.Object({
|
|
@@ -32449,8 +33446,8 @@ var ReadParams = Type2.Object({
|
|
|
32449
33446
|
filePath: Type2.Optional(Type2.String({
|
|
32450
33447
|
description: "Alias for `path` — provide one of the two."
|
|
32451
33448
|
})),
|
|
32452
|
-
offset: optionalInt(1, Number.MAX_SAFE_INTEGER),
|
|
32453
|
-
limit: optionalInt(1, Number.MAX_SAFE_INTEGER)
|
|
33449
|
+
offset: optionalInt(1, Number.MAX_SAFE_INTEGER, "1-based line number to start reading from (use with limit)"),
|
|
33450
|
+
limit: optionalInt(1, Number.MAX_SAFE_INTEGER, "Maximum number of lines to return")
|
|
32454
33451
|
});
|
|
32455
33452
|
var WriteParams = Type2.Object({
|
|
32456
33453
|
filePath: Type2.Optional(Type2.String({
|
|
@@ -32461,6 +33458,17 @@ var WriteParams = Type2.Object({
|
|
|
32461
33458
|
})),
|
|
32462
33459
|
content: Type2.String({ description: "Full file contents to write" })
|
|
32463
33460
|
});
|
|
33461
|
+
var BatchEditParams = Type2.Object({
|
|
33462
|
+
oldString: Type2.Optional(Type2.String({ description: "Text to find for a batch find/replace edit" })),
|
|
33463
|
+
newString: Type2.Optional(Type2.String({ description: "Replacement text for a batch find/replace edit" })),
|
|
33464
|
+
replaceAll: Type2.Optional(Type2.Boolean({ description: "Replace every occurrence for this batch item" })),
|
|
33465
|
+
occurrence: optionalInt(0, Number.MAX_SAFE_INTEGER, "0-based occurrence for this batch item"),
|
|
33466
|
+
startLine: optionalInt(1, Number.MAX_SAFE_INTEGER, "1-based start line for a batch line-range edit"),
|
|
33467
|
+
endLine: optionalInt(1, Number.MAX_SAFE_INTEGER, "1-based end line for a batch line-range edit"),
|
|
33468
|
+
content: Type2.Optional(Type2.String({
|
|
33469
|
+
description: "Replacement text for a batch line-range edit (empty string deletes the lines)"
|
|
33470
|
+
}))
|
|
33471
|
+
});
|
|
32464
33472
|
var EditParams = Type2.Object({
|
|
32465
33473
|
filePath: Type2.Optional(Type2.String({
|
|
32466
33474
|
description: "Path to the file to edit (absolute or relative to project root)"
|
|
@@ -32471,9 +33479,12 @@ var EditParams = Type2.Object({
|
|
|
32471
33479
|
oldString: Type2.Optional(Type2.String({ description: "Text to find (exact match, fuzzy fallback)" })),
|
|
32472
33480
|
newString: Type2.Optional(Type2.String({ description: "Replacement text (omit to delete match)" })),
|
|
32473
33481
|
replaceAll: Type2.Optional(Type2.Boolean({ description: "Replace every occurrence" })),
|
|
32474
|
-
occurrence: optionalInt(0, Number.MAX_SAFE_INTEGER),
|
|
33482
|
+
occurrence: optionalInt(0, Number.MAX_SAFE_INTEGER, "0-based occurrence to replace when multiple matches exist"),
|
|
32475
33483
|
appendContent: Type2.Optional(Type2.String({
|
|
32476
|
-
description: "Append text to the end of the file (creates the file if missing, parent dirs auto-created). When set, oldString/newString are ignored."
|
|
33484
|
+
description: "Append text to the end of the file (creates the file if missing, parent dirs auto-created). When set, edits/oldString/newString are ignored."
|
|
33485
|
+
})),
|
|
33486
|
+
edits: Type2.Optional(Type2.Array(BatchEditParams, {
|
|
33487
|
+
description: "Batch edits — array of { oldString, newString }, { oldString, newString, replaceAll: true }, or { startLine, endLine, content } objects applied atomically."
|
|
32477
33488
|
}))
|
|
32478
33489
|
});
|
|
32479
33490
|
var GrepParams = Type2.Object({
|
|
@@ -32490,6 +33501,47 @@ function readPathArg(args) {
|
|
|
32490
33501
|
function mutationFilePathArg(args) {
|
|
32491
33502
|
return coerceAliasedStringParam(args.filePath, args.path);
|
|
32492
33503
|
}
|
|
33504
|
+
function hasOwn(record2, key) {
|
|
33505
|
+
return Object.hasOwn(record2, key);
|
|
33506
|
+
}
|
|
33507
|
+
function validateBatchEdit(edit, index) {
|
|
33508
|
+
if (!edit || typeof edit !== "object" || Array.isArray(edit)) {
|
|
33509
|
+
throw new Error(`batch: edit[${index}] must be an object`);
|
|
33510
|
+
}
|
|
33511
|
+
const record2 = edit;
|
|
33512
|
+
if (typeof record2.oldString === "string") {
|
|
33513
|
+
return;
|
|
33514
|
+
}
|
|
33515
|
+
if (hasOwn(record2, "startLine")) {
|
|
33516
|
+
if (typeof record2.startLine !== "number" || !Number.isInteger(record2.startLine) || record2.startLine < 0) {
|
|
33517
|
+
throw new Error(`batch: edit[${index}] 'startLine' must be a positive integer (1-based)`);
|
|
33518
|
+
}
|
|
33519
|
+
if (record2.startLine === 0) {
|
|
33520
|
+
throw new Error(`batch: edit[${index}] 'startLine' must be >= 1 (1-based)`);
|
|
33521
|
+
}
|
|
33522
|
+
if (typeof record2.endLine !== "number" || !Number.isInteger(record2.endLine) || record2.endLine < 0) {
|
|
33523
|
+
throw new Error(`batch: edit[${index}] 'endLine' must be a positive integer (1-based)`);
|
|
33524
|
+
}
|
|
33525
|
+
if (record2.endLine === 0) {
|
|
33526
|
+
throw new Error(`batch: edit[${index}] 'endLine' must be >= 1 (1-based)`);
|
|
33527
|
+
}
|
|
33528
|
+
return;
|
|
33529
|
+
}
|
|
33530
|
+
throw new Error(`batch: edit[${index}] must have either 'oldString' or 'startLine'/'endLine'`);
|
|
33531
|
+
}
|
|
33532
|
+
function validateBatchEdits(edits) {
|
|
33533
|
+
if (edits === undefined)
|
|
33534
|
+
return;
|
|
33535
|
+
if (!Array.isArray(edits)) {
|
|
33536
|
+
throw new Error("batch: missing required param 'edits' (expected array)");
|
|
33537
|
+
}
|
|
33538
|
+
if (edits.length === 0) {
|
|
33539
|
+
throw new Error("batch: 'edits' array must not be empty");
|
|
33540
|
+
}
|
|
33541
|
+
edits.forEach((edit, index) => {
|
|
33542
|
+
validateBatchEdit(edit, index);
|
|
33543
|
+
});
|
|
33544
|
+
}
|
|
32493
33545
|
function renderReadCall(args, theme, context) {
|
|
32494
33546
|
const text = reuseText(context.lastComponent);
|
|
32495
33547
|
const filePath = args ? readPathArg(args) : undefined;
|
|
@@ -32516,7 +33568,8 @@ function registerHoistedTools(pi, ctx, surface) {
|
|
|
32516
33568
|
const limit = coerceOptionalInt(params.limit, "limit", 1, Number.MAX_SAFE_INTEGER);
|
|
32517
33569
|
const filePath = await resolvePathArg(extCtx.cwd, pathArg);
|
|
32518
33570
|
await assertExternalDirectoryPermission(extCtx, filePath, {
|
|
32519
|
-
restrictToProjectRoot: surface.restrictToProjectRoot
|
|
33571
|
+
restrictToProjectRoot: surface.restrictToProjectRoot,
|
|
33572
|
+
serverValidatedRead: true
|
|
32520
33573
|
});
|
|
32521
33574
|
const rawArgs = { filePath: pathArg };
|
|
32522
33575
|
if (offset !== undefined)
|
|
@@ -32597,19 +33650,26 @@ PDFs aren't supported on the Pi harness yet.`, response);
|
|
|
32597
33650
|
pi.registerTool({
|
|
32598
33651
|
name: "edit",
|
|
32599
33652
|
label: "edit",
|
|
32600
|
-
description: "
|
|
32601
|
-
promptSnippet: "
|
|
33653
|
+
description: "Edit part of a file via `appendContent`, batch `edits[]`, or `oldString`/`newString` find-and-replace. Batch `{ oldString, newString, replaceAll: true }` replaces every match. Mode priority: appendContent > edits > oldString.",
|
|
33654
|
+
promptSnippet: "Partial file edits via appendContent, edits[], or oldString/newString (mode priority: appendContent > edits > oldString).",
|
|
32602
33655
|
promptGuidelines: [
|
|
32603
33656
|
"Prefer edit over write when changing part of an existing file.",
|
|
32604
|
-
"
|
|
32605
|
-
"Use
|
|
33657
|
+
"Use appendContent when adding text to the end of a file.",
|
|
33658
|
+
"Use edits[] for multiple atomic changes in one file.",
|
|
33659
|
+
"Include enough surrounding context in oldString to make the match unique, or set replaceAll/occurrence explicitly."
|
|
32606
33660
|
],
|
|
32607
33661
|
parameters: EditParams,
|
|
32608
33662
|
async execute(_toolCallId, params, _signal, _onUpdate, extCtx) {
|
|
33663
|
+
const argsRecord = params;
|
|
33664
|
+
if (argsRecord.startLine !== undefined || argsRecord.endLine !== undefined) {
|
|
33665
|
+
throw new Error("edit: 'startLine'/'endLine' are not top-level parameters. " + "For line-range edits, nest them inside the `edits` array: " + '`edits: [{ startLine: N, endLine: M, content: "..." }]`. ' + "For find/replace, use `oldString`/`newString` instead.");
|
|
33666
|
+
}
|
|
32609
33667
|
const filePathArg = mutationFilePathArg(params);
|
|
32610
33668
|
if (typeof filePathArg !== "string") {
|
|
32611
33669
|
throw new Error("edit: missing required parameter `filePath`");
|
|
32612
33670
|
}
|
|
33671
|
+
if (params.appendContent === undefined)
|
|
33672
|
+
validateBatchEdits(params.edits);
|
|
32613
33673
|
const filePath = await resolvePathArg(extCtx.cwd, filePathArg);
|
|
32614
33674
|
await assertExternalDirectoryPermission(extCtx, filePath, {
|
|
32615
33675
|
restrictToProjectRoot: surface.restrictToProjectRoot
|
|
@@ -32617,8 +33677,18 @@ PDFs aren't supported on the Pi harness yet.`, response);
|
|
|
32617
33677
|
const bridge = bridgeFor(ctx, extCtx.cwd);
|
|
32618
33678
|
const rawArgs = { filePath: filePathArg };
|
|
32619
33679
|
for (const key of ["appendContent", "oldString", "newString"]) {
|
|
32620
|
-
if (
|
|
32621
|
-
rawArgs[key] =
|
|
33680
|
+
if (argsRecord[key] !== undefined)
|
|
33681
|
+
rawArgs[key] = argsRecord[key];
|
|
33682
|
+
}
|
|
33683
|
+
if (Array.isArray(argsRecord.edits)) {
|
|
33684
|
+
rawArgs.edits = argsRecord.edits.map((item) => {
|
|
33685
|
+
if (!item || typeof item !== "object" || Array.isArray(item))
|
|
33686
|
+
return item;
|
|
33687
|
+
const batchItem = item;
|
|
33688
|
+
return batchItem.replaceAll === undefined ? batchItem : { ...batchItem, replaceAll: coerceBoolean(batchItem.replaceAll) };
|
|
33689
|
+
});
|
|
33690
|
+
} else if (argsRecord.edits !== undefined) {
|
|
33691
|
+
rawArgs.edits = argsRecord.edits;
|
|
32622
33692
|
}
|
|
32623
33693
|
if (params.replaceAll !== undefined)
|
|
32624
33694
|
rawArgs.replaceAll = coerceBoolean(params.replaceAll);
|
|
@@ -32682,6 +33752,7 @@ function buildMutationResult(response) {
|
|
|
32682
33752
|
const additions = diffObj?.additions ?? 0;
|
|
32683
33753
|
const deletions = diffObj?.deletions ?? 0;
|
|
32684
33754
|
const replacements = response.replacements;
|
|
33755
|
+
const editsApplied = response.edits_applied;
|
|
32685
33756
|
const diagnostics = response.lsp_diagnostics;
|
|
32686
33757
|
const truncated = diffObj?.truncated === true;
|
|
32687
33758
|
const noOp = response.no_op === true;
|
|
@@ -32728,6 +33799,7 @@ ${formatDiagnosticsText(diagnostics)}`;
|
|
|
32728
33799
|
additions,
|
|
32729
33800
|
deletions,
|
|
32730
33801
|
replacements,
|
|
33802
|
+
editsApplied,
|
|
32731
33803
|
diagnostics,
|
|
32732
33804
|
truncated: truncated || undefined,
|
|
32733
33805
|
formatted,
|
|
@@ -32802,7 +33874,8 @@ ${theme.fg("error", errorText || "edit failed")}`);
|
|
|
32802
33874
|
const additions = details?.additions ?? 0;
|
|
32803
33875
|
const deletions = details?.deletions ?? 0;
|
|
32804
33876
|
const text = reuseText(context.lastComponent);
|
|
32805
|
-
const
|
|
33877
|
+
const countDetail = typeof details?.editsApplied === "number" && details.editsApplied > 1 ? `, ${details.editsApplied} edits` : typeof details?.replacements === "number" && details.replacements > 1 ? `, ${details.replacements} replacements` : "";
|
|
33878
|
+
const summary = theme.fg("success", `+${additions}/-${deletions}${countDetail}`);
|
|
32806
33879
|
let suffix = "";
|
|
32807
33880
|
if (details?.truncated) {
|
|
32808
33881
|
suffix = ` ${theme.fg("muted", "(diff truncated)")}`;
|
|
@@ -32819,17 +33892,17 @@ ${summary}${suffix}`);
|
|
|
32819
33892
|
container.addChild(new Text(renderDiff(diff), 1, 0));
|
|
32820
33893
|
return container;
|
|
32821
33894
|
}
|
|
32822
|
-
function shortenPath2(
|
|
32823
|
-
const home =
|
|
32824
|
-
if (
|
|
32825
|
-
return `~${
|
|
32826
|
-
return
|
|
33895
|
+
function shortenPath2(path2) {
|
|
33896
|
+
const home = homedir13();
|
|
33897
|
+
if (path2.startsWith(home))
|
|
33898
|
+
return `~${path2.slice(home.length)}`;
|
|
33899
|
+
return path2;
|
|
32827
33900
|
}
|
|
32828
|
-
async function resolvePathArg(cwd,
|
|
32829
|
-
const expanded = expandTilde2(
|
|
32830
|
-
const abs = absoluteSearchPath(cwd,
|
|
33901
|
+
async function resolvePathArg(cwd, path2) {
|
|
33902
|
+
const expanded = expandTilde2(path2);
|
|
33903
|
+
const abs = absoluteSearchPath(cwd, path2);
|
|
32831
33904
|
try {
|
|
32832
|
-
await
|
|
33905
|
+
await stat2(abs);
|
|
32833
33906
|
return abs;
|
|
32834
33907
|
} catch {
|
|
32835
33908
|
return expanded;
|
|
@@ -32837,7 +33910,7 @@ async function resolvePathArg(cwd, path3) {
|
|
|
32837
33910
|
}
|
|
32838
33911
|
|
|
32839
33912
|
// src/tools/render-helpers.ts
|
|
32840
|
-
import { homedir as
|
|
33913
|
+
import { homedir as homedir14 } from "node:os";
|
|
32841
33914
|
import { renderDiff as renderDiff2 } from "@earendil-works/pi-coding-agent";
|
|
32842
33915
|
import { Container as Container2, Spacer as Spacer2, Text as Text2 } from "@earendil-works/pi-tui";
|
|
32843
33916
|
function reuseText2(last) {
|
|
@@ -32846,11 +33919,11 @@ function reuseText2(last) {
|
|
|
32846
33919
|
function reuseContainer2(last) {
|
|
32847
33920
|
return last instanceof Container2 ? last : new Container2;
|
|
32848
33921
|
}
|
|
32849
|
-
function shortenPath3(
|
|
32850
|
-
const home =
|
|
32851
|
-
if (
|
|
32852
|
-
return `~${
|
|
32853
|
-
return
|
|
33922
|
+
function shortenPath3(path2) {
|
|
33923
|
+
const home = homedir14();
|
|
33924
|
+
if (path2.startsWith(home))
|
|
33925
|
+
return `~${path2.slice(home.length)}`;
|
|
33926
|
+
return path2;
|
|
32854
33927
|
}
|
|
32855
33928
|
function renderToolCall(toolName, summary, theme, context) {
|
|
32856
33929
|
const text = reuseText2(context.lastComponent);
|
|
@@ -32858,10 +33931,10 @@ function renderToolCall(toolName, summary, theme, context) {
|
|
|
32858
33931
|
text.setText(`${theme.fg("toolTitle", theme.bold(toolName))}${suffix}`);
|
|
32859
33932
|
return text;
|
|
32860
33933
|
}
|
|
32861
|
-
function accentPath(theme,
|
|
32862
|
-
if (!
|
|
33934
|
+
function accentPath(theme, path2) {
|
|
33935
|
+
if (!path2)
|
|
32863
33936
|
return theme.fg("toolOutput", "...");
|
|
32864
|
-
return theme.fg("accent", shortenPath3(
|
|
33937
|
+
return theme.fg("accent", shortenPath3(path2));
|
|
32865
33938
|
}
|
|
32866
33939
|
function collectTextContent(result) {
|
|
32867
33940
|
return result.content.filter((part) => part.type === "text").map((part) => part.text ?? "").join(`
|
|
@@ -33046,17 +34119,17 @@ ${warningStrings.map((w) => ` ${w}`).join(`
|
|
|
33046
34119
|
async function resolveAstPaths(extCtx, paths) {
|
|
33047
34120
|
if (isEmptyParam(paths) || !Array.isArray(paths))
|
|
33048
34121
|
return;
|
|
33049
|
-
return Promise.all(paths.filter((
|
|
34122
|
+
return Promise.all(paths.filter((path2) => typeof path2 === "string").map((path2) => resolvePathArg(extCtx.cwd, path2)));
|
|
33050
34123
|
}
|
|
33051
34124
|
async function assertAstPathsPermission(extCtx, paths, restrictToProjectRoot) {
|
|
33052
34125
|
if (paths === undefined || paths.length === 0)
|
|
33053
34126
|
return;
|
|
33054
34127
|
const checked = new Set;
|
|
33055
|
-
for (const
|
|
33056
|
-
if (checked.has(
|
|
34128
|
+
for (const path2 of paths) {
|
|
34129
|
+
if (checked.has(path2))
|
|
33057
34130
|
continue;
|
|
33058
|
-
checked.add(
|
|
33059
|
-
await assertExternalDirectoryPermission(extCtx,
|
|
34131
|
+
checked.add(path2);
|
|
34132
|
+
await assertExternalDirectoryPermission(extCtx, path2, { restrictToProjectRoot });
|
|
33060
34133
|
}
|
|
33061
34134
|
}
|
|
33062
34135
|
function buildAstSearchSections(payload, theme) {
|
|
@@ -33238,7 +34311,7 @@ function registerAstTools(pi, ctx, surface) {
|
|
|
33238
34311
|
}
|
|
33239
34312
|
|
|
33240
34313
|
// src/tools/bash.ts
|
|
33241
|
-
import * as
|
|
34314
|
+
import * as fs3 from "node:fs/promises";
|
|
33242
34315
|
import { Container as Container3, Spacer as Spacer3, Text as Text3 } from "@earendil-works/pi-tui";
|
|
33243
34316
|
import { Type as Type4 } from "typebox";
|
|
33244
34317
|
var BASH_WAIT_POLL_INTERVAL_MS = 100;
|
|
@@ -33254,7 +34327,7 @@ function resolveForegroundWaitMs(configured) {
|
|
|
33254
34327
|
}
|
|
33255
34328
|
return configured;
|
|
33256
34329
|
}
|
|
33257
|
-
var
|
|
34330
|
+
var BASH_TRANSPORT_TIMEOUT_MS2 = 30000;
|
|
33258
34331
|
var DEFAULT_HARD_TIMEOUT_MS = 30 * 60 * 1000;
|
|
33259
34332
|
var BASH_TRANSPORT_MARGIN_MS = 1e4;
|
|
33260
34333
|
function orchestratedTransportTimeoutMs(blockToCompletion, wait, effectiveTimeout, foregroundWaitMs) {
|
|
@@ -33265,7 +34338,7 @@ var BashBaseParams = {
|
|
|
33265
34338
|
command: Type4.String({
|
|
33266
34339
|
description: "Shell command to execute. Supports pipes, redirections, and shell syntax."
|
|
33267
34340
|
}),
|
|
33268
|
-
timeout: optionalInt(1, Number.MAX_SAFE_INTEGER),
|
|
34341
|
+
timeout: optionalInt(1, Number.MAX_SAFE_INTEGER, "Hard kill timeout in milliseconds"),
|
|
33269
34342
|
workdir: Type4.Optional(Type4.String({
|
|
33270
34343
|
description: "Working directory for command execution. Relative paths resolve against the project root. Defaults to the current session's working directory."
|
|
33271
34344
|
})),
|
|
@@ -33273,7 +34346,7 @@ var BashBaseParams = {
|
|
|
33273
34346
|
description: "Human-readable description shown in UI logs. Helps users understand what the command does without reading shell syntax."
|
|
33274
34347
|
})),
|
|
33275
34348
|
wait: Type4.Optional(Type4.Boolean({
|
|
33276
|
-
description: "When true, run in the foreground without auto-promoting and wait until the command finishes or reaches its timeout. Use only when you know the result is required before doing anything else."
|
|
34349
|
+
description: "When true, run in the foreground without auto-promoting and wait until the command finishes or reaches its timeout; if you send a new message, the wait detaches to background. Use only when you know the result is required before doing anything else."
|
|
33277
34350
|
}))
|
|
33278
34351
|
};
|
|
33279
34352
|
var BashBackgroundFlagParam = {
|
|
@@ -33290,8 +34363,8 @@ var BashPtyParams = {
|
|
|
33290
34363
|
pty: Type4.Optional(Type4.Boolean({
|
|
33291
34364
|
description: 'Spawn the command in a real PTY for interactive programs. Implies background: true automatically. Inspect with bash_status({ task_id, output_mode: "screen" }) and send input with bash_write.'
|
|
33292
34365
|
})),
|
|
33293
|
-
ptyRows: optionalInt(1, 60),
|
|
33294
|
-
ptyCols: optionalInt(1, 140)
|
|
34366
|
+
ptyRows: optionalInt(1, 60, "PTY terminal height in rows (minimum 1, maximum 60)"),
|
|
34367
|
+
ptyCols: optionalInt(1, 140, "PTY terminal width in columns (minimum 1, maximum 140)")
|
|
33295
34368
|
};
|
|
33296
34369
|
var BashParams = Type4.Object({
|
|
33297
34370
|
...BashBaseParams,
|
|
@@ -33325,7 +34398,7 @@ var BashWatchParams = Type4.Object({
|
|
|
33325
34398
|
}),
|
|
33326
34399
|
pattern: Type4.Optional(Type4.Union([Type4.String(), Type4.Object({ regex: Type4.String() })])),
|
|
33327
34400
|
background: Type4.Optional(Type4.Boolean()),
|
|
33328
|
-
timeout_ms: optionalInt(1, MAX_BASH_STATUS_WAIT_TIMEOUT_MS),
|
|
34401
|
+
timeout_ms: optionalInt(1, MAX_BASH_STATUS_WAIT_TIMEOUT_MS, "Maximum time to wait in milliseconds"),
|
|
33329
34402
|
once: Type4.Optional(Type4.Boolean())
|
|
33330
34403
|
});
|
|
33331
34404
|
var BashWriteParams = Type4.Object({
|
|
@@ -33351,7 +34424,7 @@ function unavailableSnapshot() {
|
|
|
33351
34424
|
}
|
|
33352
34425
|
async function callBashBridge(bridge, command, params = {}, extCtx, options) {
|
|
33353
34426
|
return await callBridge(bridge, command, params, extCtx, {
|
|
33354
|
-
transportTimeoutMs:
|
|
34427
|
+
transportTimeoutMs: BASH_TRANSPORT_TIMEOUT_MS2,
|
|
33355
34428
|
...options,
|
|
33356
34429
|
keepBridgeOnTimeout: true
|
|
33357
34430
|
});
|
|
@@ -33382,7 +34455,7 @@ function registerBashTool(pi, ctx, aftSearchRegistered = false) {
|
|
|
33382
34455
|
const searchSteer = aftSearchRegistered ? "use `aft_search` (concepts, identifiers, regex, literals), `read`, `aft_outline`, or `aft_zoom` instead" : "use the `grep` tool, `read`, `aft_outline`, or `aft_zoom` instead";
|
|
33383
34456
|
const bashCfg = resolveBashConfig(ctx.config);
|
|
33384
34457
|
const compressionSentence = bashCfg.compress ? " 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`." : "";
|
|
33385
|
-
const tasksSentence = bashCfg.background ? ' Commands run in the foreground and return inline; `wait: true` blocks until a long command finishes instead of auto-promoting — use it when you need the result before doing anything else; keep it off otherwise so auto-promote can remind you while you work. Use `background: true` yourself ONLY when you have other useful work to do while it runs; then `bash_watch` waits on the task (sync blocks until exit/pattern, async notifies) and `bash_status` peeks at it — never background a command and immediately `bash_watch` it (that wastes a turn for what foreground returns in one), and never loop `bash_status` to wait. `pty: true` runs interactive programs (REPLs, TUIs), implies background, and is driven with `bash_status({ output_mode: "screen" })` plus `bash_write`.' : " Commands run in the foreground to completion; `timeout` is the hard kill cap (default 30 minutes).";
|
|
34458
|
+
const tasksSentence = bashCfg.background ? ' Commands run in the foreground and return inline; `wait: true` blocks until a long command finishes instead of auto-promoting, but detaches to background if you send a new message — use it when you need the result before doing anything else; keep it off otherwise so auto-promote can remind you while you work. Use `background: true` yourself ONLY when you have other useful work to do while it runs; then `bash_watch` waits on the task (sync blocks until exit/pattern, async notifies) and `bash_status` peeks at it — never background a command and immediately `bash_watch` it (that wastes a turn for what foreground returns in one), and never loop `bash_status` to wait. `pty: true` runs interactive programs (REPLs, TUIs), implies background, and is driven with `bash_status({ output_mode: "screen" })` plus `bash_write`.' : " Commands run in the foreground to completion; `timeout` is the hard kill cap (default 30 minutes).";
|
|
33386
34459
|
pi.registerTool({
|
|
33387
34460
|
name: "bash",
|
|
33388
34461
|
label: "bash",
|
|
@@ -33656,7 +34729,7 @@ async function waitForBashStatus(ctx, bridge, extCtx, taskId, outputMode, waitFo
|
|
|
33656
34729
|
let scanBaseOffset = 0;
|
|
33657
34730
|
const bridgeOptions = {
|
|
33658
34731
|
keepBridgeOnTimeout: true,
|
|
33659
|
-
transportTimeoutMs:
|
|
34732
|
+
transportTimeoutMs: BASH_TRANSPORT_TIMEOUT_MS2
|
|
33660
34733
|
};
|
|
33661
34734
|
if (waitFor?.kind === "regex") {
|
|
33662
34735
|
await validateWaitRegex(bridge, extCtx, waitFor);
|
|
@@ -33767,7 +34840,7 @@ async function readNewTaskOutput(data, cursor) {
|
|
|
33767
34840
|
};
|
|
33768
34841
|
}
|
|
33769
34842
|
async function readFileBytesFrom(outputPath, cursor) {
|
|
33770
|
-
const handle = await
|
|
34843
|
+
const handle = await fs3.open(outputPath, "r");
|
|
33771
34844
|
try {
|
|
33772
34845
|
const chunks = [];
|
|
33773
34846
|
let offset = cursor;
|
|
@@ -33896,7 +34969,7 @@ async function formatPtyStatus(_extCtx, taskId, details, requestedOutputMode) {
|
|
|
33896
34969
|
return `
|
|
33897
34970
|
[PTY output path unavailable]`;
|
|
33898
34971
|
const outputMode = requestedOutputMode ?? "screen";
|
|
33899
|
-
const raw = outputMode === "raw" || outputMode === "both" ? await
|
|
34972
|
+
const raw = outputMode === "raw" || outputMode === "both" ? await fs3.readFile(details.output_path) : undefined;
|
|
33900
34973
|
let suffix = "";
|
|
33901
34974
|
if (outputMode === "raw") {
|
|
33902
34975
|
suffix = raw && raw.length > 0 ? `
|
|
@@ -34017,12 +35090,12 @@ function registerConflictsTool(pi, ctx) {
|
|
|
34017
35090
|
async execute(_toolCallId, params, _signal, _onUpdate, extCtx) {
|
|
34018
35091
|
const bridge = bridgeFor(ctx, extCtx.cwd);
|
|
34019
35092
|
const reqParams = {};
|
|
34020
|
-
const
|
|
34021
|
-
if (typeof
|
|
34022
|
-
await assertExternalDirectoryPermission(extCtx,
|
|
35093
|
+
const path2 = params?.path;
|
|
35094
|
+
if (typeof path2 === "string" && path2.trim() !== "") {
|
|
35095
|
+
await assertExternalDirectoryPermission(extCtx, path2, {
|
|
34023
35096
|
restrictToProjectRoot: ctx.config.restrict_to_project_root ?? false
|
|
34024
35097
|
});
|
|
34025
|
-
reqParams.path = await resolvePathArg(extCtx.cwd,
|
|
35098
|
+
reqParams.path = await resolvePathArg(extCtx.cwd, path2);
|
|
34026
35099
|
}
|
|
34027
35100
|
const response = await callToolCall(bridge, "conflicts", reqParams, extCtx);
|
|
34028
35101
|
if (response.success === false) {
|
|
@@ -34434,8 +35507,8 @@ function tier2SummaryPart(summary, key, label) {
|
|
|
34434
35507
|
return `${label} ${status ?? "unavailable"}`;
|
|
34435
35508
|
}
|
|
34436
35509
|
function shortDupOccurrence(entry) {
|
|
34437
|
-
const [
|
|
34438
|
-
return
|
|
35510
|
+
const [path2] = entry.split(":");
|
|
35511
|
+
return path2?.split("/").pop() ?? entry;
|
|
34439
35512
|
}
|
|
34440
35513
|
function tier2TopPreview(summary, theme) {
|
|
34441
35514
|
const lines = [];
|
|
@@ -34619,7 +35692,7 @@ function navigateParamsSchema() {
|
|
|
34619
35692
|
description: "Source file containing the symbol (absolute or relative to project root)"
|
|
34620
35693
|
}),
|
|
34621
35694
|
symbol: Type9.String({ description: "Name of the symbol to analyze" }),
|
|
34622
|
-
depth: optionalInt(1, Number.MAX_SAFE_INTEGER),
|
|
35695
|
+
depth: optionalInt(1, Number.MAX_SAFE_INTEGER, "Maximum call-graph depth to traverse"),
|
|
34623
35696
|
expression: Type9.Optional(Type9.String({ description: "Expression to track (required for trace_data)" })),
|
|
34624
35697
|
toSymbol: Type9.Optional(Type9.String({
|
|
34625
35698
|
description: "Target symbol for trace_to_symbol; the returned path ends here"
|
|
@@ -35056,9 +36129,9 @@ var RefactorParams = Type11.Object({
|
|
|
35056
36129
|
destination: Type11.Optional(Type11.String({ description: "Target file (for move)" })),
|
|
35057
36130
|
scope: Type11.Optional(Type11.String({ description: "Disambiguation scope for move op" })),
|
|
35058
36131
|
name: Type11.Optional(Type11.String({ description: "New function name (for extract)" })),
|
|
35059
|
-
startLine: optionalInt(1, Number.MAX_SAFE_INTEGER),
|
|
35060
|
-
endLine: optionalInt(1, Number.MAX_SAFE_INTEGER),
|
|
35061
|
-
callSiteLine: optionalInt(1, Number.MAX_SAFE_INTEGER)
|
|
36132
|
+
startLine: optionalInt(1, Number.MAX_SAFE_INTEGER, "1-based start line for extract"),
|
|
36133
|
+
endLine: optionalInt(1, Number.MAX_SAFE_INTEGER, "1-based end line (inclusive) for extract"),
|
|
36134
|
+
callSiteLine: optionalInt(1, Number.MAX_SAFE_INTEGER, "1-based call site line for inline")
|
|
35062
36135
|
});
|
|
35063
36136
|
function buildRefactorSections(args, payload, theme) {
|
|
35064
36137
|
const response = asRecord3(payload);
|
|
@@ -35177,7 +36250,7 @@ function registerRefactorTool(pi, ctx) {
|
|
|
35177
36250
|
import { StringEnum as StringEnum5 } from "@earendil-works/pi-ai";
|
|
35178
36251
|
import { Type as Type12 } from "typebox";
|
|
35179
36252
|
function responsePaths(response) {
|
|
35180
|
-
return Array.isArray(response.paths) ? response.paths.filter((
|
|
36253
|
+
return Array.isArray(response.paths) ? response.paths.filter((path2) => typeof path2 === "string" && path2.length > 0) : [];
|
|
35181
36254
|
}
|
|
35182
36255
|
var SafetyParams = Type12.Object({
|
|
35183
36256
|
op: StringEnum5(["undo", "history", "checkpoint", "restore", "list"], {
|
|
@@ -35377,6 +36450,9 @@ var SearchParams2 = Type13.Object({
|
|
|
35377
36450
|
includeTests: Type13.Optional(Type13.Boolean({
|
|
35378
36451
|
default: false,
|
|
35379
36452
|
description: "Include test files (*.test.*, *_test.rs, __tests__/, …) plus test-support, fixture, mock, snapshot, and corpus files. Defaults to false."
|
|
36453
|
+
})),
|
|
36454
|
+
path: Type13.Optional(Type13.String({
|
|
36455
|
+
description: "Search a different project root (absolute or ~ path). Requires that project to have been indexed by AFT."
|
|
35380
36456
|
}))
|
|
35381
36457
|
});
|
|
35382
36458
|
function buildSemanticSections(args, payload, theme) {
|
|
@@ -35484,6 +36560,8 @@ function registerSemanticTool(pi, ctx) {
|
|
|
35484
36560
|
req.hint = params.hint;
|
|
35485
36561
|
if (params.includeTests !== undefined)
|
|
35486
36562
|
req.includeTests = params.includeTests;
|
|
36563
|
+
if (params.path !== undefined)
|
|
36564
|
+
req.path = params.path;
|
|
35487
36565
|
const response = await callToolCall(bridge, "search", req, extCtx);
|
|
35488
36566
|
if (response.success === false) {
|
|
35489
36567
|
throw new Error(response.text || response.message || "search failed");
|
|
@@ -35554,7 +36632,7 @@ function buildWorkflowHints(opts) {
|
|
|
35554
36632
|
}
|
|
35555
36633
|
if (hasBgBash) {
|
|
35556
36634
|
sections.push([
|
|
35557
|
-
`**Long-running commands** (builds, installs, full test suites): run them in the FOREGROUND — use \`${bashName}({ command, wait: true })\` when you know it is long and need the result before anything else; otherwise omit \`wait\` so auto-promote can hand you a reminder while you work.`,
|
|
36635
|
+
`**Long-running commands** (builds, installs, full test suites): run them in the FOREGROUND — use \`${bashName}({ command, wait: true })\` when you know it is long and need the result before anything else; if you send a new message, the wait detaches to background; otherwise omit \`wait\` so auto-promote can hand you a reminder while you work.`,
|
|
35558
36636
|
"- `background: true` is ONLY for when you have OTHER useful work to do while it runs: start it, do the other work, and the completion reminder delivers the result (or spawn a subagent for the side work). Do NOT background a command and then immediately `bash_watch` it — that spends a whole extra turn waiting for something foreground returns in one.",
|
|
35559
36637
|
"- `bash_watch` is for blocking on an ALREADY-backgrounded task once you've run out of parallel work (sync — the user can interrupt), or reacting to a specific early output line (async: background:true + pattern). Never loop `bash_status` to wait — it's a one-shot inspector."
|
|
35560
36638
|
].join(`
|
|
@@ -35625,18 +36703,18 @@ function createVersionMismatchHandler(getPool, ensureCompatibleBinary = ensureBi
|
|
|
35625
36703
|
const upgradePromise = (async () => {
|
|
35626
36704
|
warn2(`WARNING: aft binary v${binaryVersion} is older than plugin v${minVersion}. ` + "Some features may not work. Attempting to download a compatible binary...");
|
|
35627
36705
|
try {
|
|
35628
|
-
const
|
|
35629
|
-
if (!
|
|
36706
|
+
const path2 = await ensureCompatibleBinary(`v${minVersion}`);
|
|
36707
|
+
if (!path2) {
|
|
35630
36708
|
warn2(`Could not find or download v${minVersion}. Continuing with v${binaryVersion}.`);
|
|
35631
36709
|
return null;
|
|
35632
36710
|
}
|
|
35633
36711
|
const pool = getPool();
|
|
35634
36712
|
if (!pool) {
|
|
35635
|
-
warn2(`Found/downloaded compatible binary at ${
|
|
36713
|
+
warn2(`Found/downloaded compatible binary at ${path2}, but bridge pool is not ready.`);
|
|
35636
36714
|
return null;
|
|
35637
36715
|
}
|
|
35638
|
-
log2(`Found/downloaded compatible binary at ${
|
|
35639
|
-
const replaced = await pool.replaceBinary(
|
|
36716
|
+
log2(`Found/downloaded compatible binary at ${path2}. Replacing running bridges...`);
|
|
36717
|
+
const replaced = await pool.replaceBinary(path2);
|
|
35640
36718
|
log2("Binary replaced successfully. New bridges will use the updated binary.");
|
|
35641
36719
|
return replaced;
|
|
35642
36720
|
} catch (err) {
|
|
@@ -35658,13 +36736,14 @@ var PLUGIN_VERSION = (() => {
|
|
|
35658
36736
|
return "0.0.0";
|
|
35659
36737
|
}
|
|
35660
36738
|
})();
|
|
35661
|
-
var ANNOUNCEMENT_VERSION = "0.
|
|
36739
|
+
var ANNOUNCEMENT_VERSION = "0.47.0";
|
|
35662
36740
|
var ANNOUNCEMENT_FEATURES = [
|
|
35663
|
-
"
|
|
35664
|
-
"
|
|
35665
|
-
"
|
|
35666
|
-
"
|
|
35667
|
-
"
|
|
36741
|
+
"Big background CPU cuts: semantic re-embeds coalesce behind a 15s quiet window instead of firing per edit, file-change processing is sliced and budgeted, and a callgraph resolver bug that could pin a core at 100% on cyclic barrel re-exports is fixed.",
|
|
36742
|
+
"Faster search: indexed grep and glob skip a per-query filesystem walk (2-4x faster), and cross-project search reuses cached artifacts (~40x faster).",
|
|
36743
|
+
"bash with wait:true now detaches to the background the moment you send a new message instead of blocking the conversation.",
|
|
36744
|
+
"apply_patch failures show the nearest-miss file lines so a failed patch is fixable in one shot; batch edits support replaceAll per item.",
|
|
36745
|
+
"Groovy, Gradle, and Jenkinsfile support in outline, zoom, search, and AST tools.",
|
|
36746
|
+
"Fixed: LSP memory growth during repeated scoped diagnostics (#160), fresh LSP auto-installs picked up without restart (#153), and truthful delete results for symlinked directories and failed batches."
|
|
35668
36747
|
];
|
|
35669
36748
|
var ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/DSa65w8wuf";
|
|
35670
36749
|
var ALL_ONLY_TOOLS = new Set(["aft_callgraph", "aft_delete", "aft_move", "aft_refactor"]);
|
|
@@ -35861,6 +36940,7 @@ async function src_default(pi) {
|
|
|
35861
36940
|
const configOverrides = buildConfigTierConfigureParams(projectRoot, {
|
|
35862
36941
|
storage_dir: storageDir
|
|
35863
36942
|
});
|
|
36943
|
+
let lspInstallCompletion = null;
|
|
35864
36944
|
try {
|
|
35865
36945
|
const lspAutoInstall = config2.lsp?.auto_install ?? true;
|
|
35866
36946
|
const lspGraceDays = config2.lsp?.grace_days ?? 7;
|
|
@@ -35890,10 +36970,24 @@ async function src_default(pi) {
|
|
|
35890
36970
|
if (lspInflightInstalls.length > 0) {
|
|
35891
36971
|
configOverrides.lsp_inflight_installs = lspInflightInstalls;
|
|
35892
36972
|
}
|
|
35893
|
-
|
|
36973
|
+
const installsWereStarted = npmResult.installsStarted > 0 || ghResult.installsStarted > 0;
|
|
36974
|
+
if (installsWereStarted) {
|
|
35894
36975
|
log2(`[lsp] auto-install: ${npmResult.installsStarted} npm + ${ghResult.installsStarted} github install(s) running in background`);
|
|
35895
36976
|
}
|
|
35896
|
-
Promise.all([npmResult.installsComplete, ghResult.installsComplete]).then(() => {
|
|
36977
|
+
const installCompletion = Promise.all([npmResult.installsComplete, ghResult.installsComplete]).then(() => {
|
|
36978
|
+
if (installsWereStarted) {
|
|
36979
|
+
const updatedPaths = [
|
|
36980
|
+
...new Set([...npmResult.getCachedBinDirs(), ...ghResult.getCachedBinDirs()])
|
|
36981
|
+
];
|
|
36982
|
+
if (updatedPaths.length > 0) {
|
|
36983
|
+
configOverrides.lsp_paths_extra = updatedPaths;
|
|
36984
|
+
} else {
|
|
36985
|
+
delete configOverrides.lsp_paths_extra;
|
|
36986
|
+
}
|
|
36987
|
+
return updatedPaths;
|
|
36988
|
+
}
|
|
36989
|
+
return null;
|
|
36990
|
+
}).then((updatedPaths) => {
|
|
35897
36991
|
const actionable = [...npmResult.skipped, ...ghResult.skipped].filter((s) => {
|
|
35898
36992
|
const r = s.reason.toLowerCase();
|
|
35899
36993
|
if (r === "auto_install: false")
|
|
@@ -35908,16 +37002,20 @@ async function src_default(pi) {
|
|
|
35908
37002
|
return false;
|
|
35909
37003
|
return true;
|
|
35910
37004
|
});
|
|
35911
|
-
if (actionable.length
|
|
35912
|
-
|
|
35913
|
-
const lines = actionable.map((s) => ` • ${s.id}: ${s.reason}`).join(`
|
|
37005
|
+
if (actionable.length > 0) {
|
|
37006
|
+
const lines = actionable.map((s) => ` • ${s.id}: ${s.reason}`).join(`
|
|
35914
37007
|
`);
|
|
35915
|
-
|
|
37008
|
+
warn2(`[lsp] skipped or failed to install ${actionable.length} server(s):
|
|
35916
37009
|
${lines}
|
|
35917
37010
|
` + 'Pin a working version with `lsp.versions: { "<package>": "<version>" }` if grace is blocking, ' + "or set `lsp.auto_install: false` to suppress.");
|
|
37011
|
+
}
|
|
37012
|
+
return updatedPaths;
|
|
35918
37013
|
}).catch((err) => {
|
|
35919
37014
|
warn2(`[lsp] install-summary aggregation failed: ${err}`);
|
|
37015
|
+
return null;
|
|
35920
37016
|
});
|
|
37017
|
+
if (installsWereStarted)
|
|
37018
|
+
lspInstallCompletion = installCompletion;
|
|
35921
37019
|
} catch (err) {
|
|
35922
37020
|
warn2(`[lsp] auto-install setup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
35923
37021
|
}
|
|
@@ -35988,6 +37086,17 @@ ${lines}
|
|
|
35988
37086
|
});
|
|
35989
37087
|
}
|
|
35990
37088
|
});
|
|
37089
|
+
if (lspInstallCompletion) {
|
|
37090
|
+
lspInstallCompletion.then((updatedPaths) => {
|
|
37091
|
+
if (!updatedPaths)
|
|
37092
|
+
return;
|
|
37093
|
+
pushLspPathsAfterAutoInstall(pool, projectRoot, updatedPaths).then(() => {
|
|
37094
|
+
log2(`[lsp] lsp_paths_extra updated after auto-install: ${updatedPaths.length} dirs pushed to live bridges`);
|
|
37095
|
+
}).catch((err) => {
|
|
37096
|
+
warn2(`[lsp] live bridge lsp_paths_extra update failed: ${err}`);
|
|
37097
|
+
});
|
|
37098
|
+
});
|
|
37099
|
+
}
|
|
35991
37100
|
pool.setConfigureOverride("harness", "pi");
|
|
35992
37101
|
pool.setConfigureOverride("aft_search_registered", resolveToolSurface(config2).semantic);
|
|
35993
37102
|
const ctx = { pool, config: config2, storageDir };
|
|
@@ -36100,7 +37209,9 @@ ${lines}
|
|
|
36100
37209
|
}
|
|
36101
37210
|
});
|
|
36102
37211
|
pi.on("input", (_event, extCtx) => {
|
|
36103
|
-
|
|
37212
|
+
const sessionId = resolveSessionId(extCtx);
|
|
37213
|
+
signalSyncWatchAbort(sessionId);
|
|
37214
|
+
signalBashWaitDetachForProject(pool, extCtx.cwd, sessionId);
|
|
36104
37215
|
});
|
|
36105
37216
|
const unregisterShutdownCleanup = registerShutdownCleanup(async () => {
|
|
36106
37217
|
try {
|