@cortexkit/aft-pi 0.46.0 → 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 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1581 -653
- 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 +1 -0
- 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/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");
|
|
@@ -11991,128 +12489,60 @@ async function readConnectionFile(path2) {
|
|
|
11991
12489
|
key: toBytes(parsed.key, "key"),
|
|
11992
12490
|
daemonId: toBytes(parsed.daemon_id, "daemon_id"),
|
|
11993
12491
|
pid: parsed.pid,
|
|
11994
|
-
daemonVer: parsed.daemon_ver ?? ""
|
|
11995
|
-
};
|
|
11996
|
-
validate(info);
|
|
11997
|
-
return info;
|
|
11998
|
-
}
|
|
11999
|
-
|
|
12000
|
-
// ../../node_modules/.bun/@cortexkit+subc-client@0.3.4/node_modules/@cortexkit/subc-client/dist/envelope.js
|
|
12001
|
-
var PROTOCOL_VERSION = 1;
|
|
12002
|
-
var HEADER_LEN = 17;
|
|
12003
|
-
var FROZEN_PREFIX_LEN = 5;
|
|
12004
|
-
var MAX_FRAME_BODY_LEN = 64 * 1024 * 1024;
|
|
12005
|
-
var FrameType;
|
|
12006
|
-
(function(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 || (FrameType = {}));
|
|
12020
|
-
var FRAME_TYPE_MAX = FrameType.Goodbye;
|
|
12021
|
-
function isPureHeader(ty) {
|
|
12022
|
-
return ty === FrameType.Cancel || ty === FrameType.Ping || ty === FrameType.Pong || ty === FrameType.Goodbye;
|
|
12023
|
-
}
|
|
12024
|
-
var Priority;
|
|
12025
|
-
(function(Priority2) {
|
|
12026
|
-
Priority2[Priority2["Passive"] = 0] = "Passive";
|
|
12027
|
-
Priority2[Priority2["Interactive"] = 1] = "Interactive";
|
|
12028
|
-
Priority2[Priority2["Background"] = 2] = "Background";
|
|
12029
|
-
})(Priority || (Priority = {}));
|
|
12030
|
-
var FLAG_BINARY = 1;
|
|
12031
|
-
var FLAG_PRIORITY_MASK = 6;
|
|
12032
|
-
var FLAG_PRIORITY_SHIFT = 1;
|
|
12033
|
-
var FLAG_LAST = 8;
|
|
12034
|
-
var FLAG_RESERVED_MASK = 240;
|
|
12035
|
-
function buildFlags(binary, priority, last) {
|
|
12036
|
-
let b = 0;
|
|
12037
|
-
if (binary)
|
|
12038
|
-
b |= FLAG_BINARY;
|
|
12039
|
-
b |= priority << FLAG_PRIORITY_SHIFT;
|
|
12040
|
-
if (last)
|
|
12041
|
-
b |= FLAG_LAST;
|
|
12042
|
-
return b;
|
|
12043
|
-
}
|
|
12044
|
-
function encodeHeader(h) {
|
|
12045
|
-
const buf = new Uint8Array(HEADER_LEN);
|
|
12046
|
-
const view = new DataView(buf.buffer);
|
|
12047
|
-
view.setUint32(0, h.len, true);
|
|
12048
|
-
buf[4] = h.ver;
|
|
12049
|
-
buf[5] = h.ty;
|
|
12050
|
-
buf[6] = h.flags;
|
|
12051
|
-
view.setUint16(7, h.channel, true);
|
|
12052
|
-
view.setBigUint64(9, h.corr, true);
|
|
12053
|
-
return buf;
|
|
12492
|
+
daemonVer: parsed.daemon_ver ?? ""
|
|
12493
|
+
};
|
|
12494
|
+
validate(info);
|
|
12495
|
+
return info;
|
|
12054
12496
|
}
|
|
12055
12497
|
|
|
12056
|
-
|
|
12057
|
-
|
|
12058
|
-
|
|
12059
|
-
|
|
12060
|
-
|
|
12061
|
-
|
|
12062
|
-
|
|
12063
|
-
|
|
12064
|
-
|
|
12065
|
-
|
|
12066
|
-
|
|
12067
|
-
|
|
12068
|
-
|
|
12069
|
-
|
|
12070
|
-
|
|
12071
|
-
|
|
12072
|
-
|
|
12073
|
-
throw new DecodeError(`unknown frame type byte ${tyByte}`);
|
|
12074
|
-
}
|
|
12075
|
-
const ty = tyByte;
|
|
12076
|
-
const flags = bytes[6];
|
|
12077
|
-
if ((flags & FLAG_RESERVED_MASK) !== 0) {
|
|
12078
|
-
throw new DecodeError(`reserved flag bits set in flags 0b${flags.toString(2)}`);
|
|
12079
|
-
}
|
|
12080
|
-
if ((flags & FLAG_PRIORITY_MASK) >> FLAG_PRIORITY_SHIFT === 3) {
|
|
12081
|
-
throw new DecodeError(`reserved priority bits set in flags 0b${flags.toString(2)}`);
|
|
12498
|
+
// ../../node_modules/.bun/@cortexkit+subc-client@0.5.0/node_modules/@cortexkit/subc-client/dist/route-handle.js
|
|
12499
|
+
var connectionToken = new WeakMap;
|
|
12500
|
+
|
|
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);
|
|
12082
12515
|
}
|
|
12083
|
-
|
|
12084
|
-
|
|
12516
|
+
static create(channel, epoch, token) {
|
|
12517
|
+
return new RouteHandle(channel, epoch, token);
|
|
12085
12518
|
}
|
|
12086
|
-
const channel = view.getUint16(7, true);
|
|
12087
|
-
const corr = view.getBigUint64(9, true);
|
|
12088
|
-
return { len, ver, ty, flags, channel, corr };
|
|
12089
12519
|
}
|
|
12090
|
-
function
|
|
12091
|
-
|
|
12520
|
+
function createRouteHandle(channel, epoch, token) {
|
|
12521
|
+
const factory = RouteHandle;
|
|
12522
|
+
return factory.create(channel, epoch, token);
|
|
12092
12523
|
}
|
|
12093
|
-
|
|
12094
|
-
|
|
12095
|
-
|
|
12096
|
-
|
|
12097
|
-
|
|
12098
|
-
|
|
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";
|
|
12099
12532
|
}
|
|
12100
|
-
return {
|
|
12101
|
-
header: { len: body.length, ver, ty, flags, channel, corr },
|
|
12102
|
-
body
|
|
12103
|
-
};
|
|
12104
12533
|
}
|
|
12105
|
-
function
|
|
12106
|
-
|
|
12107
|
-
|
|
12108
|
-
|
|
12109
|
-
|
|
12110
|
-
|
|
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;
|
|
12111
12542
|
}
|
|
12112
12543
|
|
|
12113
|
-
// ../../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
|
|
12114
12545
|
import net from "node:net";
|
|
12115
|
-
|
|
12116
12546
|
class SocketClosedError extends Error {
|
|
12117
12547
|
}
|
|
12118
12548
|
|
|
@@ -12181,6 +12611,24 @@ class SubcSocket {
|
|
|
12181
12611
|
});
|
|
12182
12612
|
});
|
|
12183
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
|
+
}
|
|
12184
12632
|
readExact(n, deadlineMs) {
|
|
12185
12633
|
if (this.waiter) {
|
|
12186
12634
|
return Promise.reject(new Error("concurrent readExact is not supported"));
|
|
@@ -12302,7 +12750,7 @@ class SubcSocket {
|
|
|
12302
12750
|
}
|
|
12303
12751
|
}
|
|
12304
12752
|
|
|
12305
|
-
// ../../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
|
|
12306
12754
|
var debug = debuglog("subc-client");
|
|
12307
12755
|
var DEFAULT_HANDSHAKE_TIMEOUT_MS = 1e4;
|
|
12308
12756
|
var DEFAULT_REQUEST_TIMEOUT_MS = 30000;
|
|
@@ -12348,7 +12796,11 @@ class SubcClient {
|
|
|
12348
12796
|
opts;
|
|
12349
12797
|
nextCorr = 1n;
|
|
12350
12798
|
pending = new Map;
|
|
12799
|
+
lateResponses = new Map;
|
|
12351
12800
|
routes = new Map;
|
|
12801
|
+
liveRoutes = new Map;
|
|
12802
|
+
connectionToken = newConnectionToken();
|
|
12803
|
+
ingressEpochDropCount = 0;
|
|
12352
12804
|
closedErr = null;
|
|
12353
12805
|
closeStarted = false;
|
|
12354
12806
|
reconnecting = null;
|
|
@@ -12376,40 +12828,71 @@ class SubcClient {
|
|
|
12376
12828
|
}
|
|
12377
12829
|
async routeOpen(target, identity, opts = {}) {
|
|
12378
12830
|
const consumerIdentity = routeOpenConsumerIdentity(opts);
|
|
12831
|
+
const consumerCapabilities = opts.consumerCapabilities;
|
|
12379
12832
|
const body = this.encode({
|
|
12380
12833
|
op: "route.open",
|
|
12381
12834
|
target,
|
|
12382
12835
|
identity,
|
|
12383
|
-
...consumerIdentity ? { consumer_identity: consumerIdentity } : {}
|
|
12836
|
+
...consumerIdentity ? { consumer_identity: consumerIdentity } : {},
|
|
12837
|
+
...consumerCapabilities !== undefined ? { consumer_capabilities: consumerCapabilities } : {}
|
|
12384
12838
|
});
|
|
12385
|
-
|
|
12386
|
-
const
|
|
12387
|
-
|
|
12388
|
-
|
|
12389
|
-
|
|
12390
|
-
|
|
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;
|
|
12391
12869
|
}
|
|
12392
|
-
async request(
|
|
12870
|
+
async request(handle, body, opts = {}) {
|
|
12871
|
+
this.assertLiveHandle(handle);
|
|
12393
12872
|
const bytes = body instanceof Uint8Array ? body : this.encode(body);
|
|
12394
12873
|
const priority = opts.priority ?? Priority.Interactive;
|
|
12395
|
-
const
|
|
12874
|
+
const admission = opts.admissionClass ?? AdmissionClass.Normal;
|
|
12875
|
+
const reply = await this.send(handle, bytes, priority, admission, opts.timeoutMs, opts.onProgress);
|
|
12396
12876
|
return this.parseJson(reply);
|
|
12397
12877
|
}
|
|
12398
12878
|
async call(moduleId, method, params, opts = {}) {
|
|
12399
12879
|
const body = params === undefined ? { method } : { method, params };
|
|
12400
12880
|
let retriedUnknownChannel = false;
|
|
12401
12881
|
for (;; ) {
|
|
12402
|
-
const
|
|
12882
|
+
const routeHandle = await this.cachedRouteHandle(moduleId, opts);
|
|
12403
12883
|
try {
|
|
12404
|
-
return await this.managedRequest(
|
|
12884
|
+
return await this.managedRequest(routeHandle, body, opts);
|
|
12405
12885
|
} catch (err) {
|
|
12406
12886
|
if (!(err instanceof SubcCallError))
|
|
12407
12887
|
throw this.terminalCallError("managed call failed", err);
|
|
12408
12888
|
if (err.code === "unknown_channel" && !retriedUnknownChannel && !this.closeStarted) {
|
|
12409
12889
|
retriedUnknownChannel = true;
|
|
12410
|
-
this.
|
|
12890
|
+
this.evictRouteHandle(routeHandle);
|
|
12411
12891
|
continue;
|
|
12412
12892
|
}
|
|
12893
|
+
if (err.code === "unknown_channel" && retriedUnknownChannel) {
|
|
12894
|
+
this.evictRouteHandle(routeHandle);
|
|
12895
|
+
}
|
|
12413
12896
|
if (err.kind === "not_sent") {
|
|
12414
12897
|
try {
|
|
12415
12898
|
await this.reconnectAfterDrop(err);
|
|
@@ -12425,29 +12908,31 @@ class SubcClient {
|
|
|
12425
12908
|
}
|
|
12426
12909
|
}
|
|
12427
12910
|
}
|
|
12428
|
-
subscribe(
|
|
12911
|
+
subscribe(handle, body, onEvent, opts = {}) {
|
|
12912
|
+
this.assertLiveHandle(handle);
|
|
12429
12913
|
const bytes = body instanceof Uint8Array ? body : this.encode(body);
|
|
12430
12914
|
const priority = opts.priority ?? Priority.Interactive;
|
|
12431
|
-
const
|
|
12432
|
-
const
|
|
12915
|
+
const admission = opts.admissionClass ?? AdmissionClass.Normal;
|
|
12916
|
+
const corr = this.allocateCorr();
|
|
12917
|
+
const key = pendingKey(handle, corr);
|
|
12433
12918
|
const closed = new Promise((resolve7, reject) => {
|
|
12434
12919
|
if (this.closedErr) {
|
|
12435
12920
|
reject(this.closedErr);
|
|
12436
12921
|
return;
|
|
12437
12922
|
}
|
|
12438
12923
|
this.pending.set(key, {
|
|
12439
|
-
|
|
12924
|
+
handle,
|
|
12440
12925
|
resolve: () => resolve7(),
|
|
12441
12926
|
reject,
|
|
12442
12927
|
onProgress: onEvent,
|
|
12443
12928
|
timer: null,
|
|
12444
12929
|
subscription: true
|
|
12445
12930
|
});
|
|
12446
|
-
const frame = buildFrame(FrameType.Request, buildFlags(false, priority, false),
|
|
12931
|
+
const frame = buildFrame(FrameType.Request, buildFlags(false, priority, false, admission), handle.channel, handle.epoch, corr, bytes);
|
|
12447
12932
|
this.sock.write(encodeFrame(frame), Date.now() + DEFAULT_REQUEST_TIMEOUT_MS).catch((err) => {
|
|
12448
|
-
const
|
|
12449
|
-
if (
|
|
12450
|
-
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)));
|
|
12451
12936
|
});
|
|
12452
12937
|
});
|
|
12453
12938
|
let cancelled = false;
|
|
@@ -12455,45 +12940,77 @@ class SubcClient {
|
|
|
12455
12940
|
if (cancelled)
|
|
12456
12941
|
return;
|
|
12457
12942
|
cancelled = true;
|
|
12458
|
-
|
|
12459
|
-
this.sock.write(encodeFrame(cancel), Date.now() + DEFAULT_REQUEST_TIMEOUT_MS).catch(() => {});
|
|
12943
|
+
this.cancel(handle, corr, priority);
|
|
12460
12944
|
};
|
|
12461
12945
|
return { unsubscribe, closed };
|
|
12462
12946
|
}
|
|
12463
|
-
|
|
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 = {}) {
|
|
12464
12987
|
const key = routeCacheKey(target, identity, routeOpenConsumerIdentity(opts));
|
|
12465
12988
|
const cached = this.routes.get(key);
|
|
12466
12989
|
if (!cached)
|
|
12467
12990
|
return;
|
|
12468
12991
|
cached.closed = true;
|
|
12469
12992
|
this.routes.delete(key);
|
|
12470
|
-
const
|
|
12471
|
-
cached.
|
|
12472
|
-
if (
|
|
12473
|
-
await this.
|
|
12993
|
+
const handle = cached.handle;
|
|
12994
|
+
cached.handle = null;
|
|
12995
|
+
if (handle)
|
|
12996
|
+
await this.closeRoute(handle, opts);
|
|
12474
12997
|
}
|
|
12475
|
-
async closeRouteChannel(
|
|
12476
|
-
|
|
12477
|
-
return;
|
|
12478
|
-
if (opts.drain) {
|
|
12479
|
-
await this.drainUnaryOnChannel(channel);
|
|
12480
|
-
}
|
|
12481
|
-
this.failChannel(channel, new SubcError("route closed by closeRoute", "route_closed"));
|
|
12482
|
-
this.sendRouteGoodbye(channel);
|
|
12998
|
+
async closeRouteChannel(handle, opts = {}) {
|
|
12999
|
+
await this.closeRoute(handle, opts);
|
|
12483
13000
|
}
|
|
12484
13001
|
close() {
|
|
12485
13002
|
this.closeStarted = true;
|
|
12486
13003
|
this.fail(new SubcError("client closed"));
|
|
12487
13004
|
this.sock.close();
|
|
12488
13005
|
}
|
|
12489
|
-
|
|
13006
|
+
drainUnaryOnHandle(handle) {
|
|
12490
13007
|
const waiters = [];
|
|
12491
13008
|
for (const pending of this.pending.values()) {
|
|
12492
|
-
if (pending.
|
|
13009
|
+
if (pending.handle === handle && !pending.subscription) {
|
|
12493
13010
|
waiters.push(new Promise((resolve7) => {
|
|
12494
|
-
const
|
|
13011
|
+
const previous = pending.onSettle;
|
|
12495
13012
|
pending.onSettle = () => {
|
|
12496
|
-
|
|
13013
|
+
previous?.();
|
|
12497
13014
|
resolve7();
|
|
12498
13015
|
};
|
|
12499
13016
|
}));
|
|
@@ -12503,11 +13020,21 @@ class SubcClient {
|
|
|
12503
13020
|
return;
|
|
12504
13021
|
});
|
|
12505
13022
|
}
|
|
12506
|
-
sendRouteGoodbye(
|
|
12507
|
-
|
|
13023
|
+
sendRouteGoodbye(handle, closeOnQueueFailure = false) {
|
|
13024
|
+
this.assertLiveConnection(handle);
|
|
13025
|
+
if (this.closedErr) {
|
|
13026
|
+
if (closeOnQueueFailure)
|
|
13027
|
+
this.closeConnectionAfterCleanupFailure();
|
|
12508
13028
|
return;
|
|
12509
|
-
|
|
12510
|
-
|
|
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
|
+
});
|
|
12511
13038
|
}
|
|
12512
13039
|
static async openConnection(opts) {
|
|
12513
13040
|
const conn = await readConnectionFile(opts.connectionFile);
|
|
@@ -12522,37 +13049,48 @@ class SubcClient {
|
|
|
12522
13049
|
}
|
|
12523
13050
|
return { sock, conn };
|
|
12524
13051
|
}
|
|
12525
|
-
async controlRpc(body) {
|
|
12526
|
-
return this.send(
|
|
13052
|
+
async controlRpc(body, acceptFrame, onLateResponse) {
|
|
13053
|
+
return this.send(null, body, Priority.Interactive, AdmissionClass.Normal, undefined, undefined, acceptFrame, onLateResponse);
|
|
12527
13054
|
}
|
|
12528
|
-
send(
|
|
13055
|
+
send(handle, body, priority, admission, timeoutMs, onProgress, acceptFrame, onLateResponse) {
|
|
13056
|
+
if (handle)
|
|
13057
|
+
this.assertLiveHandle(handle);
|
|
12529
13058
|
if (this.closedErr)
|
|
12530
13059
|
return Promise.reject(this.closedErr);
|
|
12531
|
-
|
|
12532
|
-
|
|
12533
|
-
|
|
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);
|
|
12534
13070
|
return new Promise((resolve7, reject) => {
|
|
12535
13071
|
const ms = timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
12536
13072
|
const pending = {
|
|
12537
|
-
|
|
13073
|
+
handle,
|
|
12538
13074
|
resolve: resolve7,
|
|
12539
13075
|
reject,
|
|
12540
13076
|
onProgress,
|
|
12541
|
-
timer: null
|
|
13077
|
+
timer: null,
|
|
13078
|
+
acceptFrame,
|
|
13079
|
+
onLateResponse
|
|
12542
13080
|
};
|
|
12543
|
-
pending.timer = setTimeout(() =>
|
|
12544
|
-
this.arbitrateTimeout(key, pending, channel, corr, ms);
|
|
12545
|
-
}, ms);
|
|
13081
|
+
pending.timer = setTimeout(() => this.arbitrateTimeout(key, pending, channel, corr, ms), ms);
|
|
12546
13082
|
this.pending.set(key, pending);
|
|
12547
|
-
this.sock.write(encodeFrame(frame), Date.now() + ms).catch((
|
|
12548
|
-
const
|
|
12549
|
-
if (
|
|
12550
|
-
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)));
|
|
12551
13087
|
});
|
|
12552
13088
|
});
|
|
12553
13089
|
}
|
|
12554
13090
|
arbitrateTimeout(key, pending, channel, corr, ms) {
|
|
12555
13091
|
const settleAsTimeout = () => {
|
|
13092
|
+
if (pending.onLateResponse)
|
|
13093
|
+
this.lateResponses.set(key, pending.onLateResponse);
|
|
12556
13094
|
this.rejectPending(key, pending, new SubcError(this.timeoutMessage(channel, corr, ms), REQUEST_DEADLINE_MARKER));
|
|
12557
13095
|
};
|
|
12558
13096
|
const graceDeadline = Date.now() + this.opts.timeoutArbitrationGraceMs;
|
|
@@ -12568,59 +13106,67 @@ class SubcClient {
|
|
|
12568
13106
|
};
|
|
12569
13107
|
setImmediate(arbitrate);
|
|
12570
13108
|
}
|
|
12571
|
-
async managedRequest(
|
|
13109
|
+
async managedRequest(handle, body, opts) {
|
|
12572
13110
|
const bytes = body instanceof Uint8Array ? body : this.encode(body);
|
|
12573
13111
|
const priority = opts.priority ?? Priority.Interactive;
|
|
13112
|
+
const admission = opts.admissionClass ?? AdmissionClass.Normal;
|
|
12574
13113
|
try {
|
|
12575
|
-
const reply = await this.sendManaged(
|
|
13114
|
+
const reply = await this.sendManaged(handle, bytes, priority, admission, opts.timeoutMs, opts.onProgress);
|
|
12576
13115
|
return this.parseJson(reply);
|
|
12577
|
-
} catch (
|
|
12578
|
-
if (
|
|
12579
|
-
throw
|
|
12580
|
-
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);
|
|
12581
13120
|
}
|
|
12582
13121
|
}
|
|
12583
|
-
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
|
+
}
|
|
12584
13128
|
if (this.closedErr) {
|
|
12585
13129
|
return Promise.reject(this.notSentCallError("request was not sent because the subc connection was already closed", this.closedErr));
|
|
12586
13130
|
}
|
|
12587
|
-
|
|
12588
|
-
|
|
12589
|
-
|
|
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);
|
|
12590
13139
|
let handedToSocket = false;
|
|
12591
|
-
const classifyFailure = (
|
|
12592
|
-
if (!handedToSocket)
|
|
12593
|
-
return this.notSentCallError("request bytes were not queued to the subc socket",
|
|
12594
|
-
|
|
12595
|
-
|
|
12596
|
-
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);
|
|
12597
13145
|
}
|
|
12598
|
-
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);
|
|
12599
13147
|
};
|
|
12600
13148
|
return new Promise((resolve7, reject) => {
|
|
12601
13149
|
const ms = timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
12602
13150
|
const pending = {
|
|
12603
|
-
|
|
13151
|
+
handle,
|
|
12604
13152
|
resolve: resolve7,
|
|
12605
13153
|
reject,
|
|
12606
13154
|
onProgress,
|
|
12607
13155
|
timer: null,
|
|
12608
13156
|
classifyFailure
|
|
12609
13157
|
};
|
|
12610
|
-
pending.timer = setTimeout(() =>
|
|
12611
|
-
this.arbitrateTimeout(key, pending, channel, corr, ms);
|
|
12612
|
-
}, ms);
|
|
13158
|
+
pending.timer = setTimeout(() => this.arbitrateTimeout(key, pending, handle.channel, corr, ms), ms);
|
|
12613
13159
|
this.pending.set(key, pending);
|
|
12614
13160
|
const write = this.sock.writeTracked(encodeFrame(frame), Date.now() + ms);
|
|
12615
13161
|
handedToSocket = write.queued;
|
|
12616
|
-
write.completed.catch((
|
|
12617
|
-
const
|
|
12618
|
-
if (
|
|
12619
|
-
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)));
|
|
12620
13166
|
});
|
|
12621
13167
|
});
|
|
12622
13168
|
}
|
|
12623
|
-
async
|
|
13169
|
+
async cachedRouteHandle(moduleId, opts) {
|
|
12624
13170
|
const identity = opts.identity ?? this.opts.identity;
|
|
12625
13171
|
if (!identity) {
|
|
12626
13172
|
throw new SubcCallError("terminal", "managed call requires a BindIdentity in SubcClient.connect({ identity }) or call(..., { identity })", "missing_identity");
|
|
@@ -12636,15 +13182,13 @@ class SubcClient {
|
|
|
12636
13182
|
target,
|
|
12637
13183
|
identity,
|
|
12638
13184
|
consumerIdentity,
|
|
12639
|
-
|
|
12640
|
-
generation: 0,
|
|
13185
|
+
handle: null,
|
|
12641
13186
|
opening: null
|
|
12642
13187
|
};
|
|
12643
13188
|
this.routes.set(key, cached);
|
|
12644
13189
|
}
|
|
12645
|
-
if (cached.
|
|
12646
|
-
return cached.
|
|
12647
|
-
}
|
|
13190
|
+
if (cached.handle && this.isLiveHandle(cached.handle))
|
|
13191
|
+
return cached.handle;
|
|
12648
13192
|
if (!cached.opening) {
|
|
12649
13193
|
cached.opening = this.openCachedRoute(cached).finally(() => {
|
|
12650
13194
|
cached.opening = null;
|
|
@@ -12661,44 +13205,43 @@ class SubcClient {
|
|
|
12661
13205
|
throw this.routeClosedDuringOpen();
|
|
12662
13206
|
try {
|
|
12663
13207
|
await this.ensureConnectedForManaged();
|
|
12664
|
-
} catch (
|
|
12665
|
-
throw this.notSentRecoveryError("route.open could not run because reconnect failed",
|
|
12666
|
-
}
|
|
12667
|
-
if (cached.channel !== null && cached.generation === this.generation && !this.closedErr) {
|
|
12668
|
-
return cached.channel;
|
|
13208
|
+
} catch (error2) {
|
|
13209
|
+
throw this.notSentRecoveryError("route.open could not run because reconnect failed", error2);
|
|
12669
13210
|
}
|
|
13211
|
+
if (cached.handle && this.isLiveHandle(cached.handle))
|
|
13212
|
+
return cached.handle;
|
|
12670
13213
|
try {
|
|
12671
|
-
const
|
|
13214
|
+
const handle = await this.routeOpen(cached.target, cached.identity, {
|
|
12672
13215
|
consumerIdentity: cached.consumerIdentity ?? null
|
|
12673
13216
|
});
|
|
12674
13217
|
if (cached.closed) {
|
|
12675
|
-
this.
|
|
13218
|
+
this.liveRoutes.delete(handle.channel);
|
|
13219
|
+
this.sendRouteGoodbye(handle);
|
|
12676
13220
|
throw this.routeClosedDuringOpen();
|
|
12677
13221
|
}
|
|
12678
|
-
cached.
|
|
12679
|
-
|
|
12680
|
-
|
|
12681
|
-
|
|
12682
|
-
|
|
12683
|
-
|
|
12684
|
-
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)) {
|
|
12685
13228
|
try {
|
|
12686
|
-
await this.reconnectAfterDrop(
|
|
12687
|
-
} catch (
|
|
12688
|
-
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);
|
|
12689
13232
|
}
|
|
12690
13233
|
continue;
|
|
12691
13234
|
}
|
|
12692
|
-
if (!this.closeStarted &&
|
|
13235
|
+
if (!this.closeStarted && error2 instanceof SubcError && isRetryableRouteOpenCode(error2.code)) {
|
|
12693
13236
|
routeRetryAttempt += 1;
|
|
12694
13237
|
if (routeRetryAttempt < this.opts.reconnectBackoff.maxAttempts && Date.now() < routeRetryDeadline) {
|
|
12695
13238
|
await this.opts.sleep(routeRetryDelay);
|
|
12696
13239
|
routeRetryDelay = Math.min(routeRetryDelay * 2, this.opts.reconnectBackoff.capMs);
|
|
12697
13240
|
continue;
|
|
12698
13241
|
}
|
|
12699
|
-
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);
|
|
12700
13243
|
}
|
|
12701
|
-
throw this.terminalCallError(`route.open failed for module ${cached.moduleId}`,
|
|
13244
|
+
throw this.terminalCallError(`route.open failed for module ${cached.moduleId}`, error2);
|
|
12702
13245
|
}
|
|
12703
13246
|
}
|
|
12704
13247
|
}
|
|
@@ -12761,25 +13304,27 @@ class SubcClient {
|
|
|
12761
13304
|
this.currentConn = opened.conn;
|
|
12762
13305
|
this.closedErr = null;
|
|
12763
13306
|
this.generation += 1;
|
|
13307
|
+
this.connectionToken = newConnectionToken();
|
|
13308
|
+
this.liveRoutes.clear();
|
|
13309
|
+
this.lateResponses.clear();
|
|
13310
|
+
this.nextCorr = 1n;
|
|
12764
13311
|
this.readLoop(opened.sock, this.generation);
|
|
12765
13312
|
}
|
|
12766
13313
|
async reopenCachedRoutes() {
|
|
12767
|
-
for (const cached of this.routes.values())
|
|
12768
|
-
cached.
|
|
12769
|
-
cached.generation = 0;
|
|
12770
|
-
}
|
|
13314
|
+
for (const cached of this.routes.values())
|
|
13315
|
+
cached.handle = null;
|
|
12771
13316
|
for (const cached of this.routes.values()) {
|
|
12772
13317
|
if (cached.closed)
|
|
12773
13318
|
continue;
|
|
12774
|
-
const
|
|
13319
|
+
const handle = await this.routeOpen(cached.target, cached.identity, {
|
|
12775
13320
|
consumerIdentity: cached.consumerIdentity ?? null
|
|
12776
13321
|
});
|
|
12777
13322
|
if (cached.closed) {
|
|
12778
|
-
this.
|
|
13323
|
+
this.liveRoutes.delete(handle.channel);
|
|
13324
|
+
this.sendRouteGoodbye(handle);
|
|
12779
13325
|
continue;
|
|
12780
13326
|
}
|
|
12781
|
-
cached.
|
|
12782
|
-
cached.generation = this.generation;
|
|
13327
|
+
cached.handle = handle;
|
|
12783
13328
|
}
|
|
12784
13329
|
}
|
|
12785
13330
|
timeoutMessage(channel, corr, ms) {
|
|
@@ -12793,28 +13338,37 @@ class SubcClient {
|
|
|
12793
13338
|
async readLoop(sock, generation) {
|
|
12794
13339
|
try {
|
|
12795
13340
|
for (;; ) {
|
|
12796
|
-
|
|
12797
|
-
|
|
13341
|
+
this.readerActive = false;
|
|
13342
|
+
const frame = await sock.readFrame(Number.POSITIVE_INFINITY, { afterHeaderMs: BODY_READ_TIMEOUT_MS }, () => {
|
|
13343
|
+
this.readerActive = true;
|
|
13344
|
+
});
|
|
12798
13345
|
try {
|
|
12799
|
-
|
|
12800
|
-
|
|
12801
|
-
if (this.sock === sock && this.generation === generation) {
|
|
12802
|
-
this.dispatch({ header, body });
|
|
12803
|
-
}
|
|
13346
|
+
if (this.sock === sock && this.generation === generation)
|
|
13347
|
+
this.dispatch(frame);
|
|
12804
13348
|
} finally {
|
|
12805
13349
|
this.readerActive = false;
|
|
12806
13350
|
}
|
|
12807
13351
|
}
|
|
12808
|
-
} catch (
|
|
13352
|
+
} catch (error2) {
|
|
12809
13353
|
if (this.sock === sock && this.generation === generation) {
|
|
12810
|
-
this.fail(
|
|
13354
|
+
this.fail(error2 instanceof Error ? error2 : new SubcError(String(error2)));
|
|
12811
13355
|
}
|
|
12812
13356
|
}
|
|
12813
13357
|
}
|
|
12814
13358
|
dispatch(frame) {
|
|
12815
|
-
|
|
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);
|
|
12816
13368
|
const pending = this.pending.get(key);
|
|
12817
13369
|
if (pending) {
|
|
13370
|
+
if (pending.acceptFrame && !pending.acceptFrame(frame))
|
|
13371
|
+
return;
|
|
12818
13372
|
switch (frame.header.ty) {
|
|
12819
13373
|
case FrameType.Push:
|
|
12820
13374
|
case FrameType.StreamData:
|
|
@@ -12831,15 +13385,22 @@ class SubcClient {
|
|
|
12831
13385
|
return;
|
|
12832
13386
|
}
|
|
12833
13387
|
}
|
|
12834
|
-
|
|
12835
|
-
|
|
12836
|
-
this.
|
|
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);
|
|
12837
13392
|
return;
|
|
12838
13393
|
}
|
|
12839
|
-
if (frame.header.ty === FrameType.
|
|
12840
|
-
|
|
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);
|
|
12841
13399
|
return;
|
|
12842
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
|
+
}
|
|
12843
13404
|
}
|
|
12844
13405
|
settle(key, pending, run) {
|
|
12845
13406
|
if (this.pending.get(key) !== pending)
|
|
@@ -12862,18 +13423,16 @@ class SubcClient {
|
|
|
12862
13423
|
return new SubcError(Buffer.from(frame.body).toString("utf8") || "subc error");
|
|
12863
13424
|
}
|
|
12864
13425
|
}
|
|
12865
|
-
|
|
13426
|
+
evictRouteHandle(handle) {
|
|
12866
13427
|
for (const cached of this.routes.values()) {
|
|
12867
|
-
if (cached.
|
|
12868
|
-
cached.
|
|
12869
|
-
}
|
|
13428
|
+
if (cached.handle && sameRouteHandle(cached.handle, handle))
|
|
13429
|
+
cached.handle = null;
|
|
12870
13430
|
}
|
|
12871
13431
|
}
|
|
12872
|
-
|
|
13432
|
+
failHandle(handle, error2) {
|
|
12873
13433
|
for (const [key, pending] of this.pending) {
|
|
12874
|
-
if (pending.
|
|
12875
|
-
this.rejectPending(key, pending,
|
|
12876
|
-
}
|
|
13434
|
+
if (pending.handle && sameRouteHandle(pending.handle, handle))
|
|
13435
|
+
this.rejectPending(key, pending, error2);
|
|
12877
13436
|
}
|
|
12878
13437
|
}
|
|
12879
13438
|
fail(err) {
|
|
@@ -12901,6 +13460,44 @@ class SubcClient {
|
|
|
12901
13460
|
return this.notSentCallError(message, cause);
|
|
12902
13461
|
return this.terminalCallError(message, cause);
|
|
12903
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
|
+
}
|
|
12904
13501
|
encode(value) {
|
|
12905
13502
|
return new Uint8Array(Buffer.from(JSON.stringify(value), "utf8"));
|
|
12906
13503
|
}
|
|
@@ -12970,7 +13567,10 @@ function causeMessage(cause) {
|
|
|
12970
13567
|
return "";
|
|
12971
13568
|
return `: ${cause instanceof Error ? cause.message : String(cause)}`;
|
|
12972
13569
|
}
|
|
12973
|
-
|
|
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
|
|
12974
13574
|
import { Buffer as Buffer2 } from "node:buffer";
|
|
12975
13575
|
var DEFAULT_HANDSHAKE_TIMEOUT_MS2 = 1e4;
|
|
12976
13576
|
var BODY_READ_TIMEOUT_MS2 = 30000;
|
|
@@ -13033,6 +13633,11 @@ class SubcProvider {
|
|
|
13033
13633
|
closeStarted = false;
|
|
13034
13634
|
closedErr = null;
|
|
13035
13635
|
inflight = new Map;
|
|
13636
|
+
pending = new Map;
|
|
13637
|
+
liveRoutes = new Map;
|
|
13638
|
+
connectionToken = newConnectionToken();
|
|
13639
|
+
nextCorr = 1n;
|
|
13640
|
+
ingressEpochDropCount = 0;
|
|
13036
13641
|
requestGate = new AsyncPermitPool(DEFAULT_PROVIDER_HANDLER_CAPACITY);
|
|
13037
13642
|
reconnecting = null;
|
|
13038
13643
|
generation = 1;
|
|
@@ -13052,12 +13657,55 @@ class SubcProvider {
|
|
|
13052
13657
|
this.readLoop(sock, this.generation);
|
|
13053
13658
|
this.enqueueConnectionState({ state: "connected", epoch: this.connectionEpoch });
|
|
13054
13659
|
}
|
|
13660
|
+
get droppedIngressFrames() {
|
|
13661
|
+
return this.ingressEpochDropCount;
|
|
13662
|
+
}
|
|
13055
13663
|
get conn() {
|
|
13056
13664
|
return this.currentConn;
|
|
13057
13665
|
}
|
|
13058
13666
|
currentEpoch() {
|
|
13059
13667
|
return this.connectionEpoch;
|
|
13060
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
|
+
}
|
|
13061
13709
|
static async connect(opts) {
|
|
13062
13710
|
if (opts.manifest.protocol_ver !== PROTOCOL_VERSION) {
|
|
13063
13711
|
throw new SubcProviderError(`manifest protocol_ver ${opts.manifest.protocol_ver} does not match client protocol ${PROTOCOL_VERSION}`, "invalid_manifest");
|
|
@@ -13072,7 +13720,7 @@ class SubcProvider {
|
|
|
13072
13720
|
this.cancelRestoredDebounce();
|
|
13073
13721
|
const sock = this.sock;
|
|
13074
13722
|
try {
|
|
13075
|
-
await sendFrame(sock, buildFrame(FrameType.Goodbye, controlFlags(), 0, 0n, new Uint8Array(0)));
|
|
13723
|
+
await sendFrame(sock, buildFrame(FrameType.Goodbye, controlFlags(), 0, 0, 0n, new Uint8Array(0)));
|
|
13076
13724
|
} catch {} finally {
|
|
13077
13725
|
sock.close();
|
|
13078
13726
|
this.finishClosed();
|
|
@@ -13080,12 +13728,13 @@ class SubcProvider {
|
|
|
13080
13728
|
}
|
|
13081
13729
|
await this.closed;
|
|
13082
13730
|
}
|
|
13083
|
-
static async openConnection(opts) {
|
|
13731
|
+
static async openConnection(opts, onSocket) {
|
|
13084
13732
|
const conn = await readConnectionFile(opts.connectionFile);
|
|
13085
13733
|
const deadline = Date.now() + (opts.handshakeTimeoutMs ?? DEFAULT_HANDSHAKE_TIMEOUT_MS2);
|
|
13086
13734
|
const endpoint = conn.endpoints[0];
|
|
13087
13735
|
const sock = await SubcSocket.connect(endpoint.host, endpoint.port, deadline);
|
|
13088
13736
|
try {
|
|
13737
|
+
onSocket?.(sock);
|
|
13089
13738
|
await authenticateClient(sock, conn, deadline);
|
|
13090
13739
|
await sendFrame(sock, buildHelloFrame(opts));
|
|
13091
13740
|
const ack = await expectHelloAck(sock, deadline);
|
|
@@ -13098,19 +13747,17 @@ class SubcProvider {
|
|
|
13098
13747
|
async readLoop(sock, generation) {
|
|
13099
13748
|
try {
|
|
13100
13749
|
for (;; ) {
|
|
13101
|
-
const
|
|
13102
|
-
const
|
|
13103
|
-
const body = header.len === 0 ? new Uint8Array(0) : await sock.readExact(header.len, Date.now() + BODY_READ_TIMEOUT_MS2);
|
|
13104
|
-
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);
|
|
13105
13752
|
if (!keepGoing) {
|
|
13106
13753
|
if (this.sock === sock && this.generation === generation)
|
|
13107
13754
|
this.closeStarted = true;
|
|
13108
13755
|
break;
|
|
13109
13756
|
}
|
|
13110
13757
|
}
|
|
13111
|
-
} catch (
|
|
13758
|
+
} catch (error2) {
|
|
13112
13759
|
if (this.sock === sock && this.generation === generation && !this.closeStarted) {
|
|
13113
|
-
this.handleUnexpectedDrop(sock, generation,
|
|
13760
|
+
this.handleUnexpectedDrop(sock, generation, error2 instanceof Error ? error2 : new SubcProviderError(String(error2)));
|
|
13114
13761
|
return;
|
|
13115
13762
|
}
|
|
13116
13763
|
} finally {
|
|
@@ -13122,28 +13769,58 @@ class SubcProvider {
|
|
|
13122
13769
|
}
|
|
13123
13770
|
}
|
|
13124
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
|
+
}
|
|
13125
13800
|
switch (frame.header.ty) {
|
|
13126
13801
|
case FrameType.Ping:
|
|
13127
13802
|
if (frame.header.channel === 0) {
|
|
13128
|
-
await this.sendOn(sock, generation, buildFrameWithVersion(frame.header.ver, FrameType.Pong, frame.header.flags, 0, frame.header.corr, new Uint8Array(0)));
|
|
13803
|
+
await this.sendOn(sock, generation, buildFrameWithVersion(frame.header.ver, FrameType.Pong, frame.header.flags, 0, 0, frame.header.corr, new Uint8Array(0)));
|
|
13129
13804
|
}
|
|
13130
13805
|
return true;
|
|
13131
13806
|
case FrameType.Goodbye:
|
|
13132
|
-
if (
|
|
13807
|
+
if (!handle)
|
|
13133
13808
|
return false;
|
|
13134
|
-
this.
|
|
13135
|
-
|
|
13809
|
+
this.liveRoutes.delete(handle.channel);
|
|
13810
|
+
this.abortHandle(handle);
|
|
13811
|
+
await this.opts.onRouteGone?.(handle);
|
|
13136
13812
|
return true;
|
|
13137
13813
|
case FrameType.Cancel:
|
|
13138
|
-
|
|
13814
|
+
if (handle)
|
|
13815
|
+
this.inflight.get(routeKey(handle, frame.header.corr))?.abort();
|
|
13139
13816
|
return true;
|
|
13140
13817
|
case FrameType.Request:
|
|
13141
13818
|
if (frame.header.channel === 0) {
|
|
13142
13819
|
await this.handleControlRequest(frame, sock, generation);
|
|
13143
|
-
} else {
|
|
13144
|
-
this.handleDataRequest(frame, sock, generation).catch((
|
|
13820
|
+
} else if (handle) {
|
|
13821
|
+
this.handleDataRequest(frame, handle, sock, generation).catch((error2) => {
|
|
13145
13822
|
if (!this.closeStarted && this.sock === sock && this.generation === generation) {
|
|
13146
|
-
console.warn("SubcProvider handler failed after its request was dispatched",
|
|
13823
|
+
console.warn("SubcProvider handler failed after its request was dispatched", error2);
|
|
13147
13824
|
}
|
|
13148
13825
|
});
|
|
13149
13826
|
}
|
|
@@ -13152,19 +13829,28 @@ class SubcProvider {
|
|
|
13152
13829
|
return true;
|
|
13153
13830
|
}
|
|
13154
13831
|
}
|
|
13155
|
-
|
|
13156
|
-
const prefix = `${
|
|
13832
|
+
abortHandle(handle) {
|
|
13833
|
+
const prefix = `${handle.channel}:${handle.epoch}:`;
|
|
13157
13834
|
for (const [key, controller] of this.inflight) {
|
|
13158
13835
|
if (key.startsWith(prefix))
|
|
13159
13836
|
controller.abort();
|
|
13160
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
|
+
}
|
|
13161
13845
|
}
|
|
13162
|
-
abortGeneration(
|
|
13163
|
-
|
|
13164
|
-
for (const [key,
|
|
13165
|
-
|
|
13166
|
-
|
|
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"));
|
|
13167
13852
|
}
|
|
13853
|
+
this.liveRoutes.clear();
|
|
13168
13854
|
}
|
|
13169
13855
|
abortAllInflight() {
|
|
13170
13856
|
for (const controller of this.inflight.values())
|
|
@@ -13173,9 +13859,9 @@ class SubcProvider {
|
|
|
13173
13859
|
async handleControlRequest(frame, sock, generation) {
|
|
13174
13860
|
const request = parseJson(frame.body);
|
|
13175
13861
|
if (request.op === HEALTH_CHECK_OP) {
|
|
13176
|
-
this.handleHealthRequest(frame, sock, generation).catch((
|
|
13862
|
+
this.handleHealthRequest(frame, sock, generation).catch((error2) => {
|
|
13177
13863
|
if (!this.closeStarted && this.sock === sock && this.generation === generation) {
|
|
13178
|
-
console.warn("SubcProvider health handler failed after its request was dispatched",
|
|
13864
|
+
console.warn("SubcProvider health handler failed after its request was dispatched", error2);
|
|
13179
13865
|
}
|
|
13180
13866
|
});
|
|
13181
13867
|
return;
|
|
@@ -13183,47 +13869,93 @@ class SubcProvider {
|
|
|
13183
13869
|
if (request.op !== "route.bind") {
|
|
13184
13870
|
throw new SubcProviderError(`unsupported module control request ${request.op ?? "<missing op>"}`);
|
|
13185
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);
|
|
13186
13885
|
const bindRequest = {
|
|
13187
|
-
|
|
13886
|
+
handle: tentative,
|
|
13188
13887
|
target: request.target,
|
|
13189
13888
|
identity: request.identity,
|
|
13190
|
-
principal: request.principal
|
|
13889
|
+
principal: request.principal,
|
|
13890
|
+
consumer_capabilities: request.consumer_capabilities
|
|
13191
13891
|
};
|
|
13192
|
-
|
|
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
|
+
}
|
|
13193
13903
|
const rejection = bindRejection(decision);
|
|
13194
13904
|
if (rejection) {
|
|
13195
|
-
|
|
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);
|
|
13196
13920
|
return;
|
|
13197
13921
|
}
|
|
13198
|
-
|
|
13922
|
+
this.liveRoutes.set(tentative.channel, tentative);
|
|
13923
|
+
await this.opts.onBound?.(tentative);
|
|
13199
13924
|
}
|
|
13200
|
-
async handleDataRequest(frame, sock, generation) {
|
|
13201
|
-
const {
|
|
13202
|
-
const key = routeKey(
|
|
13925
|
+
async handleDataRequest(frame, handle, sock, generation) {
|
|
13926
|
+
const { corr, ver } = frame.header;
|
|
13927
|
+
const key = routeKey(handle, corr);
|
|
13203
13928
|
const controller = new AbortController;
|
|
13204
13929
|
this.inflight.set(key, controller);
|
|
13205
|
-
const
|
|
13206
|
-
|
|
13930
|
+
const context = {
|
|
13931
|
+
handle,
|
|
13207
13932
|
signal: controller.signal,
|
|
13208
13933
|
currentEpoch: () => this.connectionEpoch,
|
|
13209
|
-
emit: async (eventBody) => {
|
|
13934
|
+
emit: async (eventBody, options = {}) => {
|
|
13935
|
+
this.assertLiveHandle(handle);
|
|
13210
13936
|
if (controller.signal.aborted)
|
|
13211
13937
|
return;
|
|
13212
|
-
await this.sendOn(sock, generation, buildFrameWithVersion(ver, FrameType.StreamData,
|
|
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));
|
|
13213
13939
|
}
|
|
13214
13940
|
};
|
|
13215
13941
|
const releasePermit = await (this.requestGate ?? new AsyncPermitPool(DEFAULT_PROVIDER_HANDLER_CAPACITY)).acquire();
|
|
13942
|
+
const dataFlags = buildFlags(false, Priority.Interactive, false);
|
|
13216
13943
|
try {
|
|
13217
|
-
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);
|
|
13218
13948
|
if (body === undefined) {
|
|
13219
|
-
await this.sendOn(sock, generation, buildFrameWithVersion(ver, FrameType.StreamEnd, dataFlags, channel, corr, new Uint8Array(0)));
|
|
13949
|
+
await this.sendOn(sock, generation, buildFrameWithVersion(ver, FrameType.StreamEnd, dataFlags, handle.channel, handle.epoch, corr, new Uint8Array(0)));
|
|
13220
13950
|
} else if (body instanceof Uint8Array) {
|
|
13221
|
-
await this.sendOn(sock, generation, buildFrameWithVersion(ver, FrameType.Response, dataFlags, channel, corr, body));
|
|
13951
|
+
await this.sendOn(sock, generation, buildFrameWithVersion(ver, FrameType.Response, dataFlags, handle.channel, handle.epoch, corr, body));
|
|
13222
13952
|
} else {
|
|
13223
13953
|
throw new SubcProviderError("provider handler must return a Uint8Array or void", "invalid_handler_response");
|
|
13224
13954
|
}
|
|
13225
|
-
} catch (
|
|
13226
|
-
|
|
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);
|
|
13227
13959
|
} finally {
|
|
13228
13960
|
releasePermit();
|
|
13229
13961
|
if (this.inflight.get(key) === controller)
|
|
@@ -13231,8 +13963,8 @@ class SubcProvider {
|
|
|
13231
13963
|
}
|
|
13232
13964
|
}
|
|
13233
13965
|
async handleHealthRequest(frame, sock, generation) {
|
|
13234
|
-
const {
|
|
13235
|
-
const key =
|
|
13966
|
+
const { corr, ver } = frame.header;
|
|
13967
|
+
const key = `control:${generation}:${corr}`;
|
|
13236
13968
|
const controller = new AbortController;
|
|
13237
13969
|
this.inflight.set(key, controller);
|
|
13238
13970
|
const releasePermit = await (this.requestGate ?? new AsyncPermitPool(DEFAULT_PROVIDER_HANDLER_CAPACITY)).acquire();
|
|
@@ -13240,14 +13972,14 @@ class SubcProvider {
|
|
|
13240
13972
|
if (controller.signal.aborted)
|
|
13241
13973
|
return;
|
|
13242
13974
|
const report = await this.opts.health();
|
|
13243
|
-
await this.sendOn(sock, generation, buildFrameWithVersion(ver, FrameType.Response, controlFlags(),
|
|
13975
|
+
await this.sendOn(sock, generation, buildFrameWithVersion(ver, FrameType.Response, controlFlags(), 0, 0, corr, encodeJson({
|
|
13244
13976
|
op: HEALTH_CHECK_OP,
|
|
13245
13977
|
status: report.status,
|
|
13246
13978
|
...report.detail === undefined ? {} : { detail: report.detail },
|
|
13247
13979
|
...report.metrics === undefined ? {} : { metrics: report.metrics }
|
|
13248
13980
|
})));
|
|
13249
|
-
} catch (
|
|
13250
|
-
await this.sendError(frame,
|
|
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);
|
|
13251
13983
|
} finally {
|
|
13252
13984
|
releasePermit();
|
|
13253
13985
|
if (this.inflight.get(key) === controller)
|
|
@@ -13255,7 +13987,7 @@ class SubcProvider {
|
|
|
13255
13987
|
}
|
|
13256
13988
|
}
|
|
13257
13989
|
async sendError(frame, code, message, flags, sock, generation) {
|
|
13258
|
-
await this.sendOn(sock, generation, buildFrameWithVersion(frame.header.ver, FrameType.Error, flags, frame.header.channel, frame.header.corr, encodeJson({ code, message })));
|
|
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 })));
|
|
13259
13991
|
}
|
|
13260
13992
|
async sendOn(sock, generation, frame) {
|
|
13261
13993
|
if (this.sock !== sock || this.generation !== generation || this.closeStarted || this.closedErr)
|
|
@@ -13263,42 +13995,79 @@ class SubcProvider {
|
|
|
13263
13995
|
await sendFrame(sock, frame);
|
|
13264
13996
|
}
|
|
13265
13997
|
handleUnexpectedDrop(sock, generation, cause) {
|
|
13998
|
+
if (this.closeStarted || this.sock !== sock || this.generation !== generation)
|
|
13999
|
+
return;
|
|
13266
14000
|
this.cancelRestoredDebounce();
|
|
13267
14001
|
this.abortGeneration(generation);
|
|
13268
14002
|
this.generation += 1;
|
|
13269
14003
|
sock.close();
|
|
13270
|
-
this.
|
|
13271
|
-
this.scheduleReconnectAfterDrop(cause);
|
|
14004
|
+
this.scheduleReconnectAfterDrop(cause, this.generation, sock);
|
|
13272
14005
|
}
|
|
13273
|
-
scheduleReconnectAfterDrop(
|
|
13274
|
-
if (this.closeStarted
|
|
14006
|
+
scheduleReconnectAfterDrop(cause, generation, droppedSocket) {
|
|
14007
|
+
if (this.closeStarted)
|
|
13275
14008
|
return;
|
|
13276
|
-
const
|
|
13277
|
-
|
|
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) {
|
|
13278
14026
|
this.failFatal(err instanceof Error ? err : new SubcProviderError(String(err)));
|
|
14027
|
+
}
|
|
13279
14028
|
}).finally(() => {
|
|
13280
|
-
if (this.
|
|
14029
|
+
if (this.isCurrentReconnect(cycle))
|
|
13281
14030
|
this.reconnecting = null;
|
|
13282
14031
|
});
|
|
13283
|
-
this.reconnecting = promise;
|
|
13284
14032
|
}
|
|
13285
|
-
async reconnectWithRetry(
|
|
14033
|
+
async reconnectWithRetry(cycle) {
|
|
13286
14034
|
let attempt = 0;
|
|
13287
14035
|
let delay = this.opts.reconnectBackoff.baseMs;
|
|
13288
14036
|
for (;; ) {
|
|
14037
|
+
if (!this.isCurrentReconnect(cycle))
|
|
14038
|
+
return;
|
|
13289
14039
|
if (this.closeStarted)
|
|
13290
14040
|
throw new SubcProviderError("provider closed");
|
|
14041
|
+
cycle.socket = null;
|
|
14042
|
+
cycle.socketDied = false;
|
|
13291
14043
|
attempt += 1;
|
|
13292
|
-
this.
|
|
14044
|
+
this.enqueueReconnectState(cycle, attempt);
|
|
13293
14045
|
try {
|
|
13294
|
-
const opened = await SubcProvider.openConnection(this.opts)
|
|
13295
|
-
|
|
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) {
|
|
13296
14054
|
opened.sock.close();
|
|
13297
|
-
|
|
14055
|
+
if (this.closeStarted)
|
|
14056
|
+
throw new SubcProviderError("provider closed");
|
|
14057
|
+
return;
|
|
13298
14058
|
}
|
|
13299
|
-
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);
|
|
13300
14065
|
return;
|
|
13301
14066
|
} catch (err) {
|
|
14067
|
+
if (!this.isCurrentReconnect(cycle))
|
|
14068
|
+
return;
|
|
14069
|
+
if (cycle.socket)
|
|
14070
|
+
cycle.socketDied = true;
|
|
13302
14071
|
if (this.closeStarted)
|
|
13303
14072
|
throw err;
|
|
13304
14073
|
if (!isProviderReconnectTransient(err))
|
|
@@ -13308,16 +14077,29 @@ class SubcProvider {
|
|
|
13308
14077
|
}
|
|
13309
14078
|
}
|
|
13310
14079
|
}
|
|
13311
|
-
|
|
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) {
|
|
13312
14092
|
this.sock.close();
|
|
13313
14093
|
this.sock = opened.sock;
|
|
13314
14094
|
this.currentConn = opened.conn;
|
|
13315
14095
|
this.storage = opened.ack.storage;
|
|
13316
14096
|
this.closedErr = null;
|
|
13317
14097
|
this.connectionEpoch += 1;
|
|
13318
|
-
|
|
14098
|
+
this.connectionToken = newConnectionToken();
|
|
14099
|
+
this.liveRoutes.clear();
|
|
14100
|
+
this.nextCorr = 1n;
|
|
13319
14101
|
this.readLoop(opened.sock, generation);
|
|
13320
|
-
|
|
14102
|
+
return this.connectionEpoch;
|
|
13321
14103
|
}
|
|
13322
14104
|
scheduleRestored(generation, epoch) {
|
|
13323
14105
|
if (!this.opts.onConnectionState)
|
|
@@ -13325,7 +14107,7 @@ class SubcProvider {
|
|
|
13325
14107
|
const token = ++this.restoredDebounceToken;
|
|
13326
14108
|
this.opts.sleep(this.opts.restoredDebounceMs).then(() => {
|
|
13327
14109
|
if (token === this.restoredDebounceToken && !this.closeStarted && this.sock && this.generation === generation && this.connectionEpoch === epoch) {
|
|
13328
|
-
this.enqueueConnectionState({ state: "restored", epoch });
|
|
14110
|
+
this.enqueueConnectionState({ state: "restored", epoch }, generation);
|
|
13329
14111
|
}
|
|
13330
14112
|
}).catch((err) => {
|
|
13331
14113
|
if (token === this.restoredDebounceToken && !this.closeStarted) {
|
|
@@ -13336,10 +14118,10 @@ class SubcProvider {
|
|
|
13336
14118
|
cancelRestoredDebounce() {
|
|
13337
14119
|
this.restoredDebounceToken += 1;
|
|
13338
14120
|
}
|
|
13339
|
-
enqueueConnectionState(event) {
|
|
14121
|
+
enqueueConnectionState(event, generation, reconnect) {
|
|
13340
14122
|
if (!this.opts.onConnectionState)
|
|
13341
14123
|
return;
|
|
13342
|
-
this.stateQueue.push(event);
|
|
14124
|
+
this.stateQueue.push({ event, generation, reconnect });
|
|
13343
14125
|
if (!this.drainingStateQueue)
|
|
13344
14126
|
this.drainConnectionStateQueue();
|
|
13345
14127
|
}
|
|
@@ -13349,7 +14131,12 @@ class SubcProvider {
|
|
|
13349
14131
|
this.drainingStateQueue = true;
|
|
13350
14132
|
try {
|
|
13351
14133
|
while (this.stateQueue.length > 0) {
|
|
13352
|
-
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;
|
|
13353
14140
|
try {
|
|
13354
14141
|
await this.opts.onConnectionState?.(event);
|
|
13355
14142
|
this.stateQueue.shift();
|
|
@@ -13369,6 +14156,24 @@ class SubcProvider {
|
|
|
13369
14156
|
this.drainConnectionStateQueue();
|
|
13370
14157
|
}
|
|
13371
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
|
+
}
|
|
13372
14177
|
failFatal(err) {
|
|
13373
14178
|
if (!this.closedErr)
|
|
13374
14179
|
this.closedErr = err;
|
|
@@ -13382,8 +14187,16 @@ class SubcProvider {
|
|
|
13382
14187
|
this.resolveClosed();
|
|
13383
14188
|
}
|
|
13384
14189
|
}
|
|
13385
|
-
function routeKey(
|
|
13386
|
-
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
|
+
}
|
|
13387
14200
|
}
|
|
13388
14201
|
function launchNonce(opts) {
|
|
13389
14202
|
const nonce = opts.launchNonce ?? process.env[SUBC_LAUNCH_NONCE_ENV2];
|
|
@@ -13399,6 +14212,7 @@ function normalizeProviderConnectOptions(opts) {
|
|
|
13399
14212
|
handshakeTimeoutMs: opts.handshakeTimeoutMs,
|
|
13400
14213
|
controlOps: opts.controlOps,
|
|
13401
14214
|
onBind: opts.onBind,
|
|
14215
|
+
onBound: opts.onBound,
|
|
13402
14216
|
onRouteGone: opts.onRouteGone,
|
|
13403
14217
|
reconnectBackoff: opts.reconnectBackoff ?? DEFAULT_RECONNECT_BACKOFF,
|
|
13404
14218
|
sleep: opts.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms))),
|
|
@@ -13416,7 +14230,7 @@ function normalizedControlOps(controlOps) {
|
|
|
13416
14230
|
}
|
|
13417
14231
|
function buildHelloFrame(opts) {
|
|
13418
14232
|
const nonce = launchNonce(opts);
|
|
13419
|
-
return buildFrame(FrameType.Hello, controlFlags(), 0, HELLO_CORR, encodeJson({
|
|
14233
|
+
return buildFrame(FrameType.Hello, controlFlags(), 0, 0, HELLO_CORR, encodeJson({
|
|
13420
14234
|
manifest: normalizeManifest(opts.manifest),
|
|
13421
14235
|
protocol_ver: PROTOCOL_VERSION,
|
|
13422
14236
|
control_ops: normalizedControlOps(opts.controlOps),
|
|
@@ -13455,14 +14269,17 @@ async function sendFrame(sock, frame) {
|
|
|
13455
14269
|
await sock.write(encodeFrame(frame), Date.now() + WRITE_TIMEOUT_MS);
|
|
13456
14270
|
}
|
|
13457
14271
|
async function expectHelloAck(sock, deadline) {
|
|
13458
|
-
const
|
|
13459
|
-
|
|
13460
|
-
|
|
13461
|
-
|
|
13462
|
-
|
|
13463
|
-
|
|
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
|
+
}
|
|
13464
14281
|
case FrameType.Error: {
|
|
13465
|
-
const error2 = parseJson(body);
|
|
14282
|
+
const error2 = parseJson(frame.body);
|
|
13466
14283
|
throw new SubcProviderError(`subc rejected HELLO: ${error2.code ?? "unknown"} — ${error2.message ?? "subc error"}`, error2.code);
|
|
13467
14284
|
}
|
|
13468
14285
|
default:
|
|
@@ -13617,15 +14434,17 @@ class BgSubscription {
|
|
|
13617
14434
|
identity;
|
|
13618
14435
|
acquireClient;
|
|
13619
14436
|
dropClient;
|
|
14437
|
+
consumerIdentity;
|
|
13620
14438
|
onNudge;
|
|
13621
14439
|
sleep;
|
|
13622
14440
|
stopped = false;
|
|
13623
14441
|
current = null;
|
|
13624
14442
|
loop;
|
|
13625
|
-
constructor(identity, acquireClient, dropClient, onNudge, sleep2) {
|
|
14443
|
+
constructor(identity, acquireClient, dropClient, consumerIdentity, onNudge, sleep2) {
|
|
13626
14444
|
this.identity = identity;
|
|
13627
14445
|
this.acquireClient = acquireClient;
|
|
13628
14446
|
this.dropClient = dropClient;
|
|
14447
|
+
this.consumerIdentity = consumerIdentity;
|
|
13629
14448
|
this.onNudge = onNudge;
|
|
13630
14449
|
this.sleep = sleep2;
|
|
13631
14450
|
this.loop = this.run();
|
|
@@ -13654,9 +14473,9 @@ class BgSubscription {
|
|
|
13654
14473
|
}
|
|
13655
14474
|
if (this.stopped)
|
|
13656
14475
|
return;
|
|
13657
|
-
let
|
|
14476
|
+
let route;
|
|
13658
14477
|
try {
|
|
13659
|
-
|
|
14478
|
+
route = await client.routeOpen({ kind: "tool_provider", module_id: AFT_MODULE_ID }, this.identity, { consumerIdentity: this.consumerIdentity });
|
|
13660
14479
|
} catch (err) {
|
|
13661
14480
|
if (isConsumerReconnectTransient(err))
|
|
13662
14481
|
this.dropClient(client);
|
|
@@ -13664,12 +14483,12 @@ class BgSubscription {
|
|
|
13664
14483
|
continue;
|
|
13665
14484
|
}
|
|
13666
14485
|
if (this.stopped) {
|
|
13667
|
-
safeCloseRoute(client,
|
|
14486
|
+
safeCloseRoute(client, route);
|
|
13668
14487
|
return;
|
|
13669
14488
|
}
|
|
13670
14489
|
const subscribedAt = Date.now();
|
|
13671
14490
|
try {
|
|
13672
|
-
const sub = client.subscribe(
|
|
14491
|
+
const sub = client.subscribe(route, { op: "bg_events" }, () => {
|
|
13673
14492
|
if (!this.stopped)
|
|
13674
14493
|
this.onNudge();
|
|
13675
14494
|
});
|
|
@@ -13689,7 +14508,7 @@ class BgSubscription {
|
|
|
13689
14508
|
attempt = 0;
|
|
13690
14509
|
} finally {
|
|
13691
14510
|
this.current = null;
|
|
13692
|
-
safeCloseRoute(client,
|
|
14511
|
+
safeCloseRoute(client, route);
|
|
13693
14512
|
}
|
|
13694
14513
|
await this.backoff(attempt++);
|
|
13695
14514
|
}
|
|
@@ -13702,12 +14521,12 @@ class BgSubscription {
|
|
|
13702
14521
|
function isRecord(value) {
|
|
13703
14522
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13704
14523
|
}
|
|
13705
|
-
function
|
|
13706
|
-
return err instanceof SubcError && err.code === "unknown_channel";
|
|
14524
|
+
function isRouteProvenAbsentError(err) {
|
|
14525
|
+
return err instanceof SubcError && err.code === "unknown_channel" || err instanceof StaleRouteHandleError;
|
|
13707
14526
|
}
|
|
13708
|
-
function safeCloseRoute(client,
|
|
14527
|
+
function safeCloseRoute(client, route) {
|
|
13709
14528
|
try {
|
|
13710
|
-
client.closeRouteChannel(
|
|
14529
|
+
client.closeRouteChannel(route).catch(() => {
|
|
13711
14530
|
return;
|
|
13712
14531
|
});
|
|
13713
14532
|
} catch {}
|
|
@@ -13794,6 +14613,7 @@ class SubcTransportPool {
|
|
|
13794
14613
|
harness;
|
|
13795
14614
|
connectionFile;
|
|
13796
14615
|
handshakeTimeoutMs;
|
|
14616
|
+
consumerIdentity;
|
|
13797
14617
|
connectFn;
|
|
13798
14618
|
onBgEventsNudge;
|
|
13799
14619
|
bgBackoffSleep;
|
|
@@ -13807,6 +14627,7 @@ class SubcTransportPool {
|
|
|
13807
14627
|
this.connectionFile = options.connectionFile;
|
|
13808
14628
|
this.harness = options.harness;
|
|
13809
14629
|
this.handshakeTimeoutMs = options.handshakeTimeoutMs;
|
|
14630
|
+
this.consumerIdentity = options.consumerIdentity;
|
|
13810
14631
|
this.connectFn = options.connect ?? ((opts) => SubcClient.connect(opts));
|
|
13811
14632
|
this.onBgEventsNudge = options.onBgEventsNudge;
|
|
13812
14633
|
this.bgBackoffSleep = options.bgBackoffSleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
|
|
@@ -13829,6 +14650,11 @@ class SubcTransportPool {
|
|
|
13829
14650
|
return null;
|
|
13830
14651
|
return this.transports.get(key) ?? null;
|
|
13831
14652
|
}
|
|
14653
|
+
activeBridges() {
|
|
14654
|
+
if (!this.client || this.shuttingDown)
|
|
14655
|
+
return [];
|
|
14656
|
+
return [...this.transports.values()];
|
|
14657
|
+
}
|
|
13832
14658
|
async toolCall(projectRoot, runtime, name, rawArgs = {}, options) {
|
|
13833
14659
|
return this.getBridge(projectRoot).toolCall(runtime.sessionID, name, rawArgs, options);
|
|
13834
14660
|
}
|
|
@@ -13859,7 +14685,7 @@ class SubcTransportPool {
|
|
|
13859
14685
|
throw new RouteTornDownError("subc session closed");
|
|
13860
14686
|
}
|
|
13861
14687
|
try {
|
|
13862
|
-
const opened = await this.
|
|
14688
|
+
const opened = await this.routeHandle(client, identity, record);
|
|
13863
14689
|
if (!this.isCurrentSession(key, record)) {
|
|
13864
14690
|
throw new RouteTornDownError("subc session closed");
|
|
13865
14691
|
}
|
|
@@ -13885,29 +14711,29 @@ class SubcTransportPool {
|
|
|
13885
14711
|
if (isConsumerReconnectTransient(err)) {
|
|
13886
14712
|
this.transportFailures = 0;
|
|
13887
14713
|
this.dropClient(client);
|
|
13888
|
-
} else if (!
|
|
14714
|
+
} else if (!isRouteProvenAbsentError(err) && ++this.transportFailures >= MAX_CONSECUTIVE_TRANSPORT_FAILURES) {
|
|
13889
14715
|
this.transportFailures = 0;
|
|
13890
14716
|
this.dropClient(client);
|
|
13891
14717
|
}
|
|
13892
14718
|
}
|
|
13893
14719
|
};
|
|
13894
|
-
const requestOnRoute = async (
|
|
13895
|
-
const reply = await client.request(
|
|
14720
|
+
const requestOnRoute = async (route2) => {
|
|
14721
|
+
const reply = await client.request(route2, body, { timeoutMs, onProgress });
|
|
13896
14722
|
if (this.isCurrentSession(key, record) && this.client === client) {
|
|
13897
14723
|
this.transportFailures = 0;
|
|
13898
14724
|
}
|
|
13899
14725
|
this.ensureBgSubscription(identity, record);
|
|
13900
14726
|
return reply;
|
|
13901
14727
|
};
|
|
13902
|
-
let {
|
|
14728
|
+
let { route, entry } = await openRoute();
|
|
13903
14729
|
try {
|
|
13904
|
-
return await requestOnRoute(
|
|
14730
|
+
return await requestOnRoute(route);
|
|
13905
14731
|
} catch (err) {
|
|
13906
|
-
if (
|
|
14732
|
+
if (isRouteProvenAbsentError(err) && this.isCurrentSession(key, record) && this.client === client) {
|
|
13907
14733
|
clearRouteEntry(entry);
|
|
13908
|
-
({
|
|
14734
|
+
({ route, entry } = await openRoute());
|
|
13909
14735
|
try {
|
|
13910
|
-
return await requestOnRoute(
|
|
14736
|
+
return await requestOnRoute(route);
|
|
13911
14737
|
} catch (retryErr) {
|
|
13912
14738
|
handleRequestFailure(retryErr, entry);
|
|
13913
14739
|
throw retryErr;
|
|
@@ -13949,24 +14775,26 @@ class SubcTransportPool {
|
|
|
13949
14775
|
});
|
|
13950
14776
|
return this.connecting;
|
|
13951
14777
|
}
|
|
13952
|
-
async
|
|
14778
|
+
async routeHandle(client, identity, record) {
|
|
13953
14779
|
const key = identityKey(identity);
|
|
13954
14780
|
const existing = record.routeEntry;
|
|
13955
|
-
if (existing?.
|
|
13956
|
-
return {
|
|
14781
|
+
if (existing?.handle != null)
|
|
14782
|
+
return { route: existing.handle, entry: existing };
|
|
13957
14783
|
if (existing?.opening)
|
|
13958
|
-
return {
|
|
13959
|
-
const entry = { client, opening: null,
|
|
13960
|
-
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) => {
|
|
13961
14789
|
if (!this.isCurrentSession(key, record) || record.routeEntry !== entry || entry.closed || this.client !== client) {
|
|
13962
|
-
safeCloseRoute(client,
|
|
14790
|
+
safeCloseRoute(client, route);
|
|
13963
14791
|
if (record.routeEntry === entry)
|
|
13964
14792
|
record.routeEntry = null;
|
|
13965
14793
|
throw new RouteTornDownError("subc route opened after teardown");
|
|
13966
14794
|
}
|
|
13967
|
-
entry.
|
|
14795
|
+
entry.handle = route;
|
|
13968
14796
|
entry.opening = null;
|
|
13969
|
-
return
|
|
14797
|
+
return route;
|
|
13970
14798
|
}).catch((err) => {
|
|
13971
14799
|
const current = this.isCurrentSession(key, record);
|
|
13972
14800
|
if (record.routeEntry === entry) {
|
|
@@ -13980,7 +14808,7 @@ class SubcTransportPool {
|
|
|
13980
14808
|
});
|
|
13981
14809
|
entry.opening = opening;
|
|
13982
14810
|
record.routeEntry = entry;
|
|
13983
|
-
return {
|
|
14811
|
+
return { route: await opening, entry };
|
|
13984
14812
|
}
|
|
13985
14813
|
ensureBgSubscription(identity, record) {
|
|
13986
14814
|
if (this.shuttingDown || !this.onBgEventsNudge)
|
|
@@ -13991,7 +14819,7 @@ class SubcTransportPool {
|
|
|
13991
14819
|
if (record.bgSub)
|
|
13992
14820
|
return;
|
|
13993
14821
|
const onNudge = () => this.onBgEventsNudge?.(identity.project_root, identity.session);
|
|
13994
|
-
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);
|
|
13995
14823
|
record.bgSub = sub;
|
|
13996
14824
|
}
|
|
13997
14825
|
dropClient(client) {
|
|
@@ -14012,9 +14840,13 @@ class SubcTransportPool {
|
|
|
14012
14840
|
}
|
|
14013
14841
|
}
|
|
14014
14842
|
setConfigureOverride(_key, _value) {}
|
|
14843
|
+
async reconfigure(_projectRoot, _overrides) {}
|
|
14015
14844
|
async replaceBinary(path2) {
|
|
14016
14845
|
return path2;
|
|
14017
14846
|
}
|
|
14847
|
+
isShutdown() {
|
|
14848
|
+
return this.shuttingDown;
|
|
14849
|
+
}
|
|
14018
14850
|
async shutdown() {
|
|
14019
14851
|
this.shuttingDown = true;
|
|
14020
14852
|
const subs = [];
|
|
@@ -14038,10 +14870,10 @@ class SubcTransportPool {
|
|
|
14038
14870
|
this.transports.clear();
|
|
14039
14871
|
await Promise.allSettled(subs.map((sub) => sub.stop()));
|
|
14040
14872
|
await Promise.allSettled(entries.map(async (entry) => {
|
|
14041
|
-
if (entry.
|
|
14873
|
+
if (entry.handle == null)
|
|
14042
14874
|
return;
|
|
14043
14875
|
try {
|
|
14044
|
-
await entry.client.closeRouteChannel(entry.
|
|
14876
|
+
await entry.client.closeRouteChannel(entry.handle);
|
|
14045
14877
|
} catch {}
|
|
14046
14878
|
}));
|
|
14047
14879
|
if (client) {
|
|
@@ -14070,9 +14902,9 @@ class SubcTransportPool {
|
|
|
14070
14902
|
entry.closed = true;
|
|
14071
14903
|
if (sub)
|
|
14072
14904
|
await sub.stop();
|
|
14073
|
-
if (entry?.
|
|
14905
|
+
if (entry?.handle != null) {
|
|
14074
14906
|
try {
|
|
14075
|
-
await entry.client.closeRouteChannel(entry.
|
|
14907
|
+
await entry.client.closeRouteChannel(entry.handle);
|
|
14076
14908
|
} catch {}
|
|
14077
14909
|
}
|
|
14078
14910
|
}
|
|
@@ -14149,18 +14981,25 @@ function collectStructuredExtras(response) {
|
|
|
14149
14981
|
return stringifyData(extras);
|
|
14150
14982
|
}
|
|
14151
14983
|
// ../aft-bridge/dist/transport-factory.js
|
|
14152
|
-
import { homedir as
|
|
14153
|
-
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";
|
|
14154
14986
|
function resolveConnectionFilePath(raw) {
|
|
14155
14987
|
const trimmed = raw.trim();
|
|
14156
14988
|
if (trimmed.startsWith("~")) {
|
|
14157
|
-
return
|
|
14989
|
+
return join10(homedir11(), trimmed.slice(1).replace(/^[/\\]/, ""));
|
|
14158
14990
|
}
|
|
14159
14991
|
if (isAbsolute5(trimmed))
|
|
14160
14992
|
return trimmed;
|
|
14161
|
-
return
|
|
14993
|
+
return join10(homedir11(), trimmed);
|
|
14162
14994
|
}
|
|
14163
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) {
|
|
14164
15003
|
const raw = opts.subcConnectionFile?.trim();
|
|
14165
15004
|
if (raw && raw.length > 0) {
|
|
14166
15005
|
const connectionFile = resolveConnectionFilePath(raw);
|
|
@@ -14171,18 +15010,17 @@ async function createAftTransportPool(opts) {
|
|
|
14171
15010
|
return new SubcTransportPool({
|
|
14172
15011
|
connectionFile,
|
|
14173
15012
|
harness: opts.harness,
|
|
15013
|
+
consumerIdentity: opts.subcConsumerIdentity,
|
|
14174
15014
|
onBgEventsNudge: opts.onBgEventsNudge
|
|
14175
15015
|
});
|
|
14176
15016
|
}
|
|
14177
15017
|
return new BridgePool(opts.binaryPath, opts.poolOptions, opts.configOverrides);
|
|
14178
15018
|
}
|
|
14179
15019
|
// src/logger.ts
|
|
14180
|
-
import * as fs3 from "node:fs";
|
|
14181
|
-
import * as os2 from "node:os";
|
|
14182
|
-
import * as path2 from "node:path";
|
|
14183
15020
|
var TAG = "[aft-pi]";
|
|
14184
15021
|
var isTestEnv = process.env.BUN_TEST === "1" || false;
|
|
14185
|
-
var logFile =
|
|
15022
|
+
var logFile = resolveAftLogPath(isTestEnv ? "aft-plugin-test.log" : "aft-plugin.log");
|
|
15023
|
+
var fileSink = new RotatingLogSink(logFile);
|
|
14186
15024
|
var useStderr = process.env.AFT_LOG_STDERR === "1";
|
|
14187
15025
|
var buffer = [];
|
|
14188
15026
|
var flushTimer = null;
|
|
@@ -14197,7 +15035,7 @@ function flush() {
|
|
|
14197
15035
|
if (useStderr) {
|
|
14198
15036
|
process.stderr.write(data);
|
|
14199
15037
|
} else {
|
|
14200
|
-
|
|
15038
|
+
fileSink.append(data);
|
|
14201
15039
|
}
|
|
14202
15040
|
} catch {}
|
|
14203
15041
|
}
|
|
@@ -14910,7 +15748,7 @@ import {
|
|
|
14910
15748
|
// package.json
|
|
14911
15749
|
var package_default = {
|
|
14912
15750
|
name: "@cortexkit/aft-pi",
|
|
14913
|
-
version: "0.
|
|
15751
|
+
version: "0.47.0",
|
|
14914
15752
|
type: "module",
|
|
14915
15753
|
description: "Pi coding agent extension for Agent File Tools (AFT) — tree-sitter and LSP-powered code analysis",
|
|
14916
15754
|
main: "dist/index.js",
|
|
@@ -14933,19 +15771,19 @@ var package_default = {
|
|
|
14933
15771
|
"test:unit": "bun test src/__tests__ --path-ignore-patterns 'src/__tests__/e2e/**'"
|
|
14934
15772
|
},
|
|
14935
15773
|
dependencies: {
|
|
14936
|
-
"@cortexkit/aft-bridge": "0.
|
|
15774
|
+
"@cortexkit/aft-bridge": "0.47.0",
|
|
14937
15775
|
"comment-json": "^5.0.0",
|
|
14938
15776
|
diff: "^8.0.4",
|
|
14939
15777
|
typebox: "1.1.38",
|
|
14940
15778
|
zod: "^4.4.3"
|
|
14941
15779
|
},
|
|
14942
15780
|
optionalDependencies: {
|
|
14943
|
-
"@cortexkit/aft-darwin-arm64": "0.
|
|
14944
|
-
"@cortexkit/aft-darwin-x64": "0.
|
|
14945
|
-
"@cortexkit/aft-linux-arm64": "0.
|
|
14946
|
-
"@cortexkit/aft-linux-x64": "0.
|
|
14947
|
-
"@cortexkit/aft-win32-arm64": "0.
|
|
14948
|
-
"@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"
|
|
14949
15787
|
},
|
|
14950
15788
|
devDependencies: {
|
|
14951
15789
|
"@earendil-works/pi-coding-agent": "^0.80.2",
|
|
@@ -15176,9 +16014,15 @@ function formatStatusDialogMessage(status) {
|
|
|
15176
16014
|
}
|
|
15177
16015
|
|
|
15178
16016
|
// src/tools/_shared.ts
|
|
16017
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
15179
16018
|
import { Type } from "typebox";
|
|
15180
|
-
var optionalInt = (
|
|
16019
|
+
var optionalInt = (min, max, description = "(integer)") => Type.Optional(Type.Union([Type.Integer({ minimum: min, maximum: max }), Type.String()], {
|
|
16020
|
+
description
|
|
16021
|
+
}));
|
|
15181
16022
|
function bridgeFor(ctx, cwd) {
|
|
16023
|
+
if (!existsSync7(cwd)) {
|
|
16024
|
+
throw new Error(`project directory no longer exists: ${cwd} (stale restored session?)`);
|
|
16025
|
+
}
|
|
15182
16026
|
return ctx.pool.getBridge(cwd);
|
|
15183
16027
|
}
|
|
15184
16028
|
function resolveSessionId(extCtx) {
|
|
@@ -15534,7 +16378,7 @@ function registerStatusCommand(pi, ctx) {
|
|
|
15534
16378
|
}
|
|
15535
16379
|
|
|
15536
16380
|
// src/config.ts
|
|
15537
|
-
import { existsSync as
|
|
16381
|
+
import { existsSync as existsSync8, readFileSync as readFileSync7, renameSync as renameSync5, unlinkSync as unlinkSync5, writeFileSync as writeFileSync4 } from "node:fs";
|
|
15538
16382
|
var import_comment_json = __toESM(require_src2(), 1);
|
|
15539
16383
|
|
|
15540
16384
|
// ../../node_modules/.bun/zod@4.4.3/node_modules/zod/v4/classic/external.js
|
|
@@ -16301,10 +17145,10 @@ function mergeDefs(...defs) {
|
|
|
16301
17145
|
function cloneDef(schema) {
|
|
16302
17146
|
return mergeDefs(schema._zod.def);
|
|
16303
17147
|
}
|
|
16304
|
-
function getElementAtPath(obj,
|
|
16305
|
-
if (!
|
|
17148
|
+
function getElementAtPath(obj, path2) {
|
|
17149
|
+
if (!path2)
|
|
16306
17150
|
return obj;
|
|
16307
|
-
return
|
|
17151
|
+
return path2.reduce((acc, key) => acc?.[key], obj);
|
|
16308
17152
|
}
|
|
16309
17153
|
function promiseAllObject(promisesObj) {
|
|
16310
17154
|
const keys = Object.keys(promisesObj);
|
|
@@ -16712,11 +17556,11 @@ function explicitlyAborted(x, startIndex = 0) {
|
|
|
16712
17556
|
}
|
|
16713
17557
|
return false;
|
|
16714
17558
|
}
|
|
16715
|
-
function prefixIssues(
|
|
17559
|
+
function prefixIssues(path2, issues) {
|
|
16716
17560
|
return issues.map((iss) => {
|
|
16717
17561
|
var _a2;
|
|
16718
17562
|
(_a2 = iss).path ?? (_a2.path = []);
|
|
16719
|
-
iss.path.unshift(
|
|
17563
|
+
iss.path.unshift(path2);
|
|
16720
17564
|
return iss;
|
|
16721
17565
|
});
|
|
16722
17566
|
}
|
|
@@ -16863,16 +17707,16 @@ function flattenError(error3, mapper = (issue2) => issue2.message) {
|
|
|
16863
17707
|
}
|
|
16864
17708
|
function formatError(error3, mapper = (issue2) => issue2.message) {
|
|
16865
17709
|
const fieldErrors = { _errors: [] };
|
|
16866
|
-
const processError = (error4,
|
|
17710
|
+
const processError = (error4, path2 = []) => {
|
|
16867
17711
|
for (const issue2 of error4.issues) {
|
|
16868
17712
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
16869
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
17713
|
+
issue2.errors.map((issues) => processError({ issues }, [...path2, ...issue2.path]));
|
|
16870
17714
|
} else if (issue2.code === "invalid_key") {
|
|
16871
|
-
processError({ issues: issue2.issues }, [...
|
|
17715
|
+
processError({ issues: issue2.issues }, [...path2, ...issue2.path]);
|
|
16872
17716
|
} else if (issue2.code === "invalid_element") {
|
|
16873
|
-
processError({ issues: issue2.issues }, [...
|
|
17717
|
+
processError({ issues: issue2.issues }, [...path2, ...issue2.path]);
|
|
16874
17718
|
} else {
|
|
16875
|
-
const fullpath = [...
|
|
17719
|
+
const fullpath = [...path2, ...issue2.path];
|
|
16876
17720
|
if (fullpath.length === 0) {
|
|
16877
17721
|
fieldErrors._errors.push(mapper(issue2));
|
|
16878
17722
|
} else {
|
|
@@ -16899,17 +17743,17 @@ function formatError(error3, mapper = (issue2) => issue2.message) {
|
|
|
16899
17743
|
}
|
|
16900
17744
|
function treeifyError(error3, mapper = (issue2) => issue2.message) {
|
|
16901
17745
|
const result = { errors: [] };
|
|
16902
|
-
const processError = (error4,
|
|
17746
|
+
const processError = (error4, path2 = []) => {
|
|
16903
17747
|
var _a2, _b;
|
|
16904
17748
|
for (const issue2 of error4.issues) {
|
|
16905
17749
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
16906
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
17750
|
+
issue2.errors.map((issues) => processError({ issues }, [...path2, ...issue2.path]));
|
|
16907
17751
|
} else if (issue2.code === "invalid_key") {
|
|
16908
|
-
processError({ issues: issue2.issues }, [...
|
|
17752
|
+
processError({ issues: issue2.issues }, [...path2, ...issue2.path]);
|
|
16909
17753
|
} else if (issue2.code === "invalid_element") {
|
|
16910
|
-
processError({ issues: issue2.issues }, [...
|
|
17754
|
+
processError({ issues: issue2.issues }, [...path2, ...issue2.path]);
|
|
16911
17755
|
} else {
|
|
16912
|
-
const fullpath = [...
|
|
17756
|
+
const fullpath = [...path2, ...issue2.path];
|
|
16913
17757
|
if (fullpath.length === 0) {
|
|
16914
17758
|
result.errors.push(mapper(issue2));
|
|
16915
17759
|
continue;
|
|
@@ -16941,8 +17785,8 @@ function treeifyError(error3, mapper = (issue2) => issue2.message) {
|
|
|
16941
17785
|
}
|
|
16942
17786
|
function toDotPath(_path) {
|
|
16943
17787
|
const segs = [];
|
|
16944
|
-
const
|
|
16945
|
-
for (const seg of
|
|
17788
|
+
const path2 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
17789
|
+
for (const seg of path2) {
|
|
16946
17790
|
if (typeof seg === "number")
|
|
16947
17791
|
segs.push(`[${seg}]`);
|
|
16948
17792
|
else if (typeof seg === "symbol")
|
|
@@ -29401,13 +30245,13 @@ function resolveRef(ref, ctx) {
|
|
|
29401
30245
|
if (!ref.startsWith("#")) {
|
|
29402
30246
|
throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
|
|
29403
30247
|
}
|
|
29404
|
-
const
|
|
29405
|
-
if (
|
|
30248
|
+
const path2 = ref.slice(1).split("/").filter(Boolean);
|
|
30249
|
+
if (path2.length === 0) {
|
|
29406
30250
|
return ctx.rootSchema;
|
|
29407
30251
|
}
|
|
29408
30252
|
const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
|
|
29409
|
-
if (
|
|
29410
|
-
const key =
|
|
30253
|
+
if (path2[0] === defsKey) {
|
|
30254
|
+
const key = path2[1];
|
|
29411
30255
|
if (!key || !ctx.defs[key]) {
|
|
29412
30256
|
throw new Error(`Reference not found: ${ref}`);
|
|
29413
30257
|
}
|
|
@@ -29893,6 +30737,7 @@ var SemanticConfigSchema = exports_external.object({
|
|
|
29893
30737
|
base_url: exports_external.string().trim().min(1).optional(),
|
|
29894
30738
|
api_key_env: exports_external.string().trim().min(1).optional(),
|
|
29895
30739
|
timeout_ms: exports_external.number().int().positive().optional(),
|
|
30740
|
+
query_timeout_ms: exports_external.number().int().positive().optional(),
|
|
29896
30741
|
max_batch_size: exports_external.number().int().positive().optional(),
|
|
29897
30742
|
max_files: exports_external.number().int().positive().optional()
|
|
29898
30743
|
});
|
|
@@ -29958,7 +30803,7 @@ var BackupConfigSchema = exports_external.object({
|
|
|
29958
30803
|
max_depth: exports_external.number().int().positive().optional(),
|
|
29959
30804
|
max_file_size: exports_external.number().int().positive().optional()
|
|
29960
30805
|
});
|
|
29961
|
-
var AftConfigSchema = exports_external.object({
|
|
30806
|
+
var AftConfigSchema = exports_external.preprocess((value) => stripHarnessSpecificConfigKeys(value, OPENCODE_ONLY_KEYS), exports_external.object({
|
|
29962
30807
|
$schema: exports_external.string().optional(),
|
|
29963
30808
|
enabled: exports_external.boolean().optional(),
|
|
29964
30809
|
format_on_edit: exports_external.boolean().optional(),
|
|
@@ -29983,7 +30828,7 @@ var AftConfigSchema = exports_external.object({
|
|
|
29983
30828
|
semantic: SemanticConfigSchema.optional(),
|
|
29984
30829
|
bridge: BridgeConfigSchema.optional(),
|
|
29985
30830
|
subc: SubcConfigSchema.optional()
|
|
29986
|
-
}).strict();
|
|
30831
|
+
}).strict());
|
|
29987
30832
|
var CONFIG_MIGRATIONS = [
|
|
29988
30833
|
{ oldKey: "experimental_search_index", newPath: ["search_index"] },
|
|
29989
30834
|
{ oldKey: "experimental_semantic_search", newPath: ["semantic_search"] },
|
|
@@ -30008,9 +30853,9 @@ function extractCommentsForPreservation(content) {
|
|
|
30008
30853
|
}
|
|
30009
30854
|
return comments;
|
|
30010
30855
|
}
|
|
30011
|
-
function ensureRecordAtPath(root,
|
|
30856
|
+
function ensureRecordAtPath(root, path2) {
|
|
30012
30857
|
let current = root;
|
|
30013
|
-
for (const segment of
|
|
30858
|
+
for (const segment of path2) {
|
|
30014
30859
|
const existing = current[segment];
|
|
30015
30860
|
if (!existing || typeof existing !== "object" || Array.isArray(existing)) {
|
|
30016
30861
|
current[segment] = {};
|
|
@@ -30019,9 +30864,9 @@ function ensureRecordAtPath(root, path3) {
|
|
|
30019
30864
|
}
|
|
30020
30865
|
return current;
|
|
30021
30866
|
}
|
|
30022
|
-
function hasPath(root,
|
|
30867
|
+
function hasPath(root, path2) {
|
|
30023
30868
|
let current = root;
|
|
30024
|
-
for (const segment of
|
|
30869
|
+
for (const segment of path2) {
|
|
30025
30870
|
if (!current || typeof current !== "object" || Array.isArray(current))
|
|
30026
30871
|
return false;
|
|
30027
30872
|
const record2 = current;
|
|
@@ -30031,9 +30876,9 @@ function hasPath(root, path3) {
|
|
|
30031
30876
|
}
|
|
30032
30877
|
return true;
|
|
30033
30878
|
}
|
|
30034
|
-
function setPath(root,
|
|
30035
|
-
const parent = ensureRecordAtPath(root,
|
|
30036
|
-
parent[
|
|
30879
|
+
function setPath(root, path2, value) {
|
|
30880
|
+
const parent = ensureRecordAtPath(root, path2.slice(0, -1));
|
|
30881
|
+
parent[path2[path2.length - 1]] = value;
|
|
30037
30882
|
}
|
|
30038
30883
|
function migrateRawConfig(rawConfig, configPath, logger) {
|
|
30039
30884
|
const oldKeys = [];
|
|
@@ -30094,7 +30939,7 @@ function migrateExperimentalBashBlock(rawConfig, configPath, logger) {
|
|
|
30094
30939
|
return movedKeys;
|
|
30095
30940
|
}
|
|
30096
30941
|
function migrateAftConfigFile2(configPath, logger = { log: log2, warn: warn2 }) {
|
|
30097
|
-
if (!
|
|
30942
|
+
if (!existsSync8(configPath)) {
|
|
30098
30943
|
return { migrated: false, oldKeys: [] };
|
|
30099
30944
|
}
|
|
30100
30945
|
let tmpPath = null;
|
|
@@ -30147,7 +30992,7 @@ function recordConfigParseFailure(configPath, errorMessage) {
|
|
|
30147
30992
|
}
|
|
30148
30993
|
function loadConfigFromPath(configPath) {
|
|
30149
30994
|
try {
|
|
30150
|
-
if (!
|
|
30995
|
+
if (!existsSync8(configPath))
|
|
30151
30996
|
return null;
|
|
30152
30997
|
const content = readFileSync7(configPath, "utf-8");
|
|
30153
30998
|
const rawConfig = import_comment_json.parse(content);
|
|
@@ -30428,7 +31273,7 @@ import { spawn as spawn2 } from "node:child_process";
|
|
|
30428
31273
|
import { createHash as createHash4 } from "node:crypto";
|
|
30429
31274
|
import {
|
|
30430
31275
|
createReadStream,
|
|
30431
|
-
existsSync as
|
|
31276
|
+
existsSync as existsSync10,
|
|
30432
31277
|
mkdirSync as mkdirSync7,
|
|
30433
31278
|
readFileSync as readFileSync10,
|
|
30434
31279
|
renameSync as renameSync6,
|
|
@@ -30448,7 +31293,7 @@ import {
|
|
|
30448
31293
|
unlinkSync as unlinkSync6,
|
|
30449
31294
|
writeFileSync as writeFileSync5
|
|
30450
31295
|
} from "node:fs";
|
|
30451
|
-
import { homedir as
|
|
31296
|
+
import { homedir as homedir12 } from "node:os";
|
|
30452
31297
|
import { join as join11 } from "node:path";
|
|
30453
31298
|
function aftCacheBase() {
|
|
30454
31299
|
const override = process.env.AFT_CACHE_DIR;
|
|
@@ -30456,10 +31301,10 @@ function aftCacheBase() {
|
|
|
30456
31301
|
return override;
|
|
30457
31302
|
if (process.platform === "win32") {
|
|
30458
31303
|
const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
|
|
30459
|
-
const base2 = localAppData || join11(
|
|
31304
|
+
const base2 = localAppData || join11(homedir12(), "AppData", "Local");
|
|
30460
31305
|
return join11(base2, "aft");
|
|
30461
31306
|
}
|
|
30462
|
-
const base = process.env.XDG_CACHE_HOME || join11(
|
|
31307
|
+
const base = process.env.XDG_CACHE_HOME || join11(homedir12(), ".cache");
|
|
30463
31308
|
return join11(base, "aft");
|
|
30464
31309
|
}
|
|
30465
31310
|
function lspCacheRoot() {
|
|
@@ -30503,11 +31348,11 @@ function writeInstalledMetaIn(installDir, version2, sha256) {
|
|
|
30503
31348
|
}
|
|
30504
31349
|
}
|
|
30505
31350
|
function readInstalledMetaIn(installDir) {
|
|
30506
|
-
const
|
|
31351
|
+
const path2 = join11(installDir, INSTALLED_META_FILE);
|
|
30507
31352
|
try {
|
|
30508
|
-
if (!statSync5(
|
|
31353
|
+
if (!statSync5(path2).isFile())
|
|
30509
31354
|
return null;
|
|
30510
|
-
const raw = readFileSync8(
|
|
31355
|
+
const raw = readFileSync8(path2, "utf8");
|
|
30511
31356
|
const parsed = JSON.parse(raw);
|
|
30512
31357
|
if (typeof parsed.version !== "string" || parsed.version.length === 0)
|
|
30513
31358
|
return null;
|
|
@@ -30813,7 +31658,7 @@ var NPM_LSP_TABLE = [
|
|
|
30813
31658
|
];
|
|
30814
31659
|
|
|
30815
31660
|
// src/lsp-project-relevance.ts
|
|
30816
|
-
import { existsSync as
|
|
31661
|
+
import { existsSync as existsSync9, readdirSync as readdirSync3, readFileSync as readFileSync9 } from "node:fs";
|
|
30817
31662
|
import { join as join12 } from "node:path";
|
|
30818
31663
|
var MAX_WALK_DIRS = 200;
|
|
30819
31664
|
var MAX_WALK_DEPTH = 4;
|
|
@@ -30831,7 +31676,7 @@ function hasRootMarker(projectRoot, rootMarkers) {
|
|
|
30831
31676
|
if (!rootMarkers)
|
|
30832
31677
|
return false;
|
|
30833
31678
|
for (const marker of rootMarkers) {
|
|
30834
|
-
if (
|
|
31679
|
+
if (existsSync9(join12(projectRoot, marker)))
|
|
30835
31680
|
return true;
|
|
30836
31681
|
}
|
|
30837
31682
|
return false;
|
|
@@ -31013,7 +31858,7 @@ async function resolveTargetVersion(spec, config2, fetchImpl = fetch) {
|
|
|
31013
31858
|
function ensureInstallAnchor(cwd) {
|
|
31014
31859
|
try {
|
|
31015
31860
|
const stub = join13(cwd, "package.json");
|
|
31016
|
-
if (!
|
|
31861
|
+
if (!existsSync10(stub)) {
|
|
31017
31862
|
writeFileSync6(stub, `${JSON.stringify({ name: "aft-lsp-cache", version: "0.0.0", private: true })}
|
|
31018
31863
|
`);
|
|
31019
31864
|
}
|
|
@@ -31199,8 +32044,8 @@ function installedBinaryPath(spec) {
|
|
|
31199
32044
|
}
|
|
31200
32045
|
return null;
|
|
31201
32046
|
}
|
|
31202
|
-
function sha256OfFileSync(
|
|
31203
|
-
return createHash4("sha256").update(readFileSync10(
|
|
32047
|
+
function sha256OfFileSync(path2) {
|
|
32048
|
+
return createHash4("sha256").update(readFileSync10(path2)).digest("hex");
|
|
31204
32049
|
}
|
|
31205
32050
|
function quarantineCachedNpmInstall(spec, reason) {
|
|
31206
32051
|
const packageDir = lspPackageDir(spec.npm);
|
|
@@ -31278,9 +32123,15 @@ function runAutoInstall(projectRoot, config2, fetchImpl = fetch) {
|
|
|
31278
32123
|
return installsStarted;
|
|
31279
32124
|
},
|
|
31280
32125
|
skipped,
|
|
31281
|
-
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))
|
|
31282
32128
|
};
|
|
31283
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
|
+
}
|
|
31284
32135
|
|
|
31285
32136
|
// src/lsp-github-install.ts
|
|
31286
32137
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
@@ -31289,7 +32140,7 @@ import {
|
|
|
31289
32140
|
copyFileSync as copyFileSync4,
|
|
31290
32141
|
createReadStream as createReadStream2,
|
|
31291
32142
|
createWriteStream as createWriteStream3,
|
|
31292
|
-
existsSync as
|
|
32143
|
+
existsSync as existsSync11,
|
|
31293
32144
|
lstatSync as lstatSync2,
|
|
31294
32145
|
mkdirSync as mkdirSync8,
|
|
31295
32146
|
readdirSync as readdirSync4,
|
|
@@ -31302,7 +32153,7 @@ import {
|
|
|
31302
32153
|
unlinkSync as unlinkSync7,
|
|
31303
32154
|
writeFileSync as writeFileSync7
|
|
31304
32155
|
} from "node:fs";
|
|
31305
|
-
import { dirname as
|
|
32156
|
+
import { dirname as dirname6, join as join14, relative as relative3, resolve as resolve7 } from "node:path";
|
|
31306
32157
|
import { Readable as Readable3 } from "node:stream";
|
|
31307
32158
|
import { pipeline as pipeline3 } from "node:stream/promises";
|
|
31308
32159
|
|
|
@@ -31424,10 +32275,10 @@ function ghBinaryCandidates(spec, platform) {
|
|
|
31424
32275
|
}
|
|
31425
32276
|
function readGithubInstalledMetaIn(installDir) {
|
|
31426
32277
|
try {
|
|
31427
|
-
const
|
|
31428
|
-
if (!statSync7(
|
|
32278
|
+
const path2 = join14(installDir, INSTALLED_META_FILE2);
|
|
32279
|
+
if (!statSync7(path2).isFile())
|
|
31429
32280
|
return null;
|
|
31430
|
-
const parsed = JSON.parse(readFileSync11(
|
|
32281
|
+
const parsed = JSON.parse(readFileSync11(path2, "utf8"));
|
|
31431
32282
|
if (typeof parsed.version !== "string" || parsed.version.length === 0)
|
|
31432
32283
|
return null;
|
|
31433
32284
|
return {
|
|
@@ -31458,17 +32309,17 @@ function writeGithubInstalledMetaIn(installDir, version2, binarySha256, archiveS
|
|
|
31458
32309
|
}
|
|
31459
32310
|
var MAX_DOWNLOAD_BYTES3 = 256 * 1024 * 1024;
|
|
31460
32311
|
var MAX_EXTRACT_BYTES2 = 1024 * 1024 * 1024;
|
|
31461
|
-
function sha256OfFile(
|
|
32312
|
+
function sha256OfFile(path2) {
|
|
31462
32313
|
return new Promise((resolve8, reject) => {
|
|
31463
32314
|
const hash2 = createHash5("sha256");
|
|
31464
|
-
const stream = createReadStream2(
|
|
32315
|
+
const stream = createReadStream2(path2);
|
|
31465
32316
|
stream.on("error", reject);
|
|
31466
32317
|
stream.on("data", (chunk) => hash2.update(chunk));
|
|
31467
32318
|
stream.on("end", () => resolve8(hash2.digest("hex")));
|
|
31468
32319
|
});
|
|
31469
32320
|
}
|
|
31470
|
-
function sha256OfFileSync2(
|
|
31471
|
-
return createHash5("sha256").update(readFileSync11(
|
|
32321
|
+
function sha256OfFileSync2(path2) {
|
|
32322
|
+
return createHash5("sha256").update(readFileSync11(path2)).digest("hex");
|
|
31472
32323
|
}
|
|
31473
32324
|
async function fetchReleaseByTag(githubRepo, tag, fetchImpl, signal) {
|
|
31474
32325
|
const candidates = [];
|
|
@@ -31644,7 +32495,7 @@ async function downloadFile(url2, destPath, fetchImpl, assetSize, signal) {
|
|
|
31644
32495
|
if (Number.isFinite(advertised) && advertised > MAX_DOWNLOAD_BYTES3) {
|
|
31645
32496
|
throw new Error(`Content-Length ${advertised} exceeds max ${MAX_DOWNLOAD_BYTES3}`);
|
|
31646
32497
|
}
|
|
31647
|
-
mkdirSync8(
|
|
32498
|
+
mkdirSync8(dirname6(destPath), { recursive: true });
|
|
31648
32499
|
let bytesWritten = 0;
|
|
31649
32500
|
const guard = new TransformStream({
|
|
31650
32501
|
transform(chunk, controller) {
|
|
@@ -31791,7 +32642,7 @@ function quarantineCachedGithubInstall(spec, reason) {
|
|
|
31791
32642
|
const dest = join14(ghCacheRoot(), ".quarantine", encodeURIComponent(spec.id), `${Date.now()}`);
|
|
31792
32643
|
warn2(`[lsp] tofu_mismatch ${spec.id}: ${reason}; quarantining ${packageDir} -> ${dest}`);
|
|
31793
32644
|
try {
|
|
31794
|
-
mkdirSync8(
|
|
32645
|
+
mkdirSync8(dirname6(dest), { recursive: true });
|
|
31795
32646
|
rmSync5(dest, { recursive: true, force: true });
|
|
31796
32647
|
renameSync7(packageDir, dest);
|
|
31797
32648
|
} catch (err) {
|
|
@@ -31920,12 +32771,12 @@ async function downloadAndInstall(spec, tag, assets, platform, arch, fetchImpl,
|
|
|
31920
32771
|
} catch {}
|
|
31921
32772
|
}
|
|
31922
32773
|
const innerBinaryPath = join14(extractDir, spec.binaryPathInArchive(platform, arch, version2));
|
|
31923
|
-
if (!
|
|
32774
|
+
if (!existsSync11(innerBinaryPath)) {
|
|
31924
32775
|
error2(`[lsp] ${spec.id}: extracted binary not found at ${innerBinaryPath}`);
|
|
31925
32776
|
return null;
|
|
31926
32777
|
}
|
|
31927
32778
|
const targetBinary = ghBinaryPath(spec, platform);
|
|
31928
|
-
mkdirSync8(
|
|
32779
|
+
mkdirSync8(dirname6(targetBinary), { recursive: true });
|
|
31929
32780
|
try {
|
|
31930
32781
|
copyFileSync4(innerBinaryPath, targetBinary);
|
|
31931
32782
|
if (platform !== "win32") {
|
|
@@ -32004,7 +32855,7 @@ function runGithubAutoInstall(relevantServers, config2, fetchImpl = fetch) {
|
|
|
32004
32855
|
if (!host) {
|
|
32005
32856
|
for (const spec of GITHUB_LSP_TABLE) {
|
|
32006
32857
|
try {
|
|
32007
|
-
if (
|
|
32858
|
+
if (existsSync11(ghBinDir(spec))) {
|
|
32008
32859
|
cachedBinDirs.push(ghBinDir(spec));
|
|
32009
32860
|
}
|
|
32010
32861
|
} catch {}
|
|
@@ -32014,7 +32865,8 @@ function runGithubAutoInstall(relevantServers, config2, fetchImpl = fetch) {
|
|
|
32014
32865
|
installsStarted: 0,
|
|
32015
32866
|
installingBinaries: [],
|
|
32016
32867
|
skipped,
|
|
32017
|
-
installsComplete: Promise.resolve()
|
|
32868
|
+
installsComplete: Promise.resolve(),
|
|
32869
|
+
getCachedBinDirs: () => [...cachedBinDirs]
|
|
32018
32870
|
};
|
|
32019
32871
|
}
|
|
32020
32872
|
for (const spec of GITHUB_LSP_TABLE) {
|
|
@@ -32056,7 +32908,13 @@ function runGithubAutoInstall(relevantServers, config2, fetchImpl = fetch) {
|
|
|
32056
32908
|
return installsStarted;
|
|
32057
32909
|
},
|
|
32058
32910
|
skipped,
|
|
32059
|
-
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
|
+
}
|
|
32060
32918
|
};
|
|
32061
32919
|
}
|
|
32062
32920
|
function discoverRelevantGithubServers(projectRoot) {
|
|
@@ -32242,6 +33100,27 @@ function statusBarBlockForSession(sessionID, counts, force = false) {
|
|
|
32242
33100
|
return shouldEmitStatusBar(state, counts) ? formatStatusBar(counts) : undefined;
|
|
32243
33101
|
}
|
|
32244
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
|
+
|
|
32245
33124
|
// src/shutdown-hooks.ts
|
|
32246
33125
|
var GLOBAL_KEY = "__aftPiShutdownHooks__";
|
|
32247
33126
|
function getState() {
|
|
@@ -32338,8 +33217,8 @@ import { StringEnum } from "@earendil-works/pi-ai";
|
|
|
32338
33217
|
import { Type as Type3 } from "typebox";
|
|
32339
33218
|
|
|
32340
33219
|
// src/tools/hoisted.ts
|
|
32341
|
-
import { stat } from "node:fs/promises";
|
|
32342
|
-
import { homedir as
|
|
33220
|
+
import { stat as stat2 } from "node:fs/promises";
|
|
33221
|
+
import { homedir as homedir13 } from "node:os";
|
|
32343
33222
|
import { isAbsolute as isAbsolute6, relative as relative4, resolve as resolve8, sep } from "node:path";
|
|
32344
33223
|
import {
|
|
32345
33224
|
renderDiff
|
|
@@ -32483,15 +33362,15 @@ function containsPath(parent, child) {
|
|
|
32483
33362
|
const rel = relative4(parent, child);
|
|
32484
33363
|
return rel === "" || !rel.startsWith("..") && !isAbsolute6(rel);
|
|
32485
33364
|
}
|
|
32486
|
-
function expandTilde2(
|
|
32487
|
-
if (!
|
|
32488
|
-
return
|
|
32489
|
-
if (
|
|
32490
|
-
return
|
|
32491
|
-
if (
|
|
32492
|
-
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));
|
|
32493
33372
|
}
|
|
32494
|
-
return
|
|
33373
|
+
return path2;
|
|
32495
33374
|
}
|
|
32496
33375
|
function absoluteSearchPath(cwd, target) {
|
|
32497
33376
|
const expanded = expandTilde2(target);
|
|
@@ -32499,7 +33378,7 @@ function absoluteSearchPath(cwd, target) {
|
|
|
32499
33378
|
}
|
|
32500
33379
|
async function searchPathExists(cwd, target) {
|
|
32501
33380
|
try {
|
|
32502
|
-
await
|
|
33381
|
+
await stat2(absoluteSearchPath(cwd, target));
|
|
32503
33382
|
return true;
|
|
32504
33383
|
} catch {
|
|
32505
33384
|
return false;
|
|
@@ -32556,6 +33435,8 @@ async function assertExternalDirectoryPermission(extCtx, target, options = {}) {
|
|
|
32556
33435
|
return;
|
|
32557
33436
|
if (options.restrictToProjectRoot === false)
|
|
32558
33437
|
return;
|
|
33438
|
+
if (options.serverValidatedRead === true)
|
|
33439
|
+
return;
|
|
32559
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.");
|
|
32560
33441
|
}
|
|
32561
33442
|
var ReadParams = Type2.Object({
|
|
@@ -32565,8 +33446,8 @@ var ReadParams = Type2.Object({
|
|
|
32565
33446
|
filePath: Type2.Optional(Type2.String({
|
|
32566
33447
|
description: "Alias for `path` — provide one of the two."
|
|
32567
33448
|
})),
|
|
32568
|
-
offset: optionalInt(1, Number.MAX_SAFE_INTEGER),
|
|
32569
|
-
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")
|
|
32570
33451
|
});
|
|
32571
33452
|
var WriteParams = Type2.Object({
|
|
32572
33453
|
filePath: Type2.Optional(Type2.String({
|
|
@@ -32580,8 +33461,10 @@ var WriteParams = Type2.Object({
|
|
|
32580
33461
|
var BatchEditParams = Type2.Object({
|
|
32581
33462
|
oldString: Type2.Optional(Type2.String({ description: "Text to find for a batch find/replace edit" })),
|
|
32582
33463
|
newString: Type2.Optional(Type2.String({ description: "Replacement text for a batch find/replace edit" })),
|
|
32583
|
-
|
|
32584
|
-
|
|
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"),
|
|
32585
33468
|
content: Type2.Optional(Type2.String({
|
|
32586
33469
|
description: "Replacement text for a batch line-range edit (empty string deletes the lines)"
|
|
32587
33470
|
}))
|
|
@@ -32596,12 +33479,12 @@ var EditParams = Type2.Object({
|
|
|
32596
33479
|
oldString: Type2.Optional(Type2.String({ description: "Text to find (exact match, fuzzy fallback)" })),
|
|
32597
33480
|
newString: Type2.Optional(Type2.String({ description: "Replacement text (omit to delete match)" })),
|
|
32598
33481
|
replaceAll: Type2.Optional(Type2.Boolean({ description: "Replace every occurrence" })),
|
|
32599
|
-
occurrence: optionalInt(0, Number.MAX_SAFE_INTEGER),
|
|
33482
|
+
occurrence: optionalInt(0, Number.MAX_SAFE_INTEGER, "0-based occurrence to replace when multiple matches exist"),
|
|
32600
33483
|
appendContent: Type2.Optional(Type2.String({
|
|
32601
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."
|
|
32602
33485
|
})),
|
|
32603
33486
|
edits: Type2.Optional(Type2.Array(BatchEditParams, {
|
|
32604
|
-
description: "Batch edits — array of { oldString, newString } or { startLine, endLine, content } objects applied atomically
|
|
33487
|
+
description: "Batch edits — array of { oldString, newString }, { oldString, newString, replaceAll: true }, or { startLine, endLine, content } objects applied atomically."
|
|
32605
33488
|
}))
|
|
32606
33489
|
});
|
|
32607
33490
|
var GrepParams = Type2.Object({
|
|
@@ -32685,7 +33568,8 @@ function registerHoistedTools(pi, ctx, surface) {
|
|
|
32685
33568
|
const limit = coerceOptionalInt(params.limit, "limit", 1, Number.MAX_SAFE_INTEGER);
|
|
32686
33569
|
const filePath = await resolvePathArg(extCtx.cwd, pathArg);
|
|
32687
33570
|
await assertExternalDirectoryPermission(extCtx, filePath, {
|
|
32688
|
-
restrictToProjectRoot: surface.restrictToProjectRoot
|
|
33571
|
+
restrictToProjectRoot: surface.restrictToProjectRoot,
|
|
33572
|
+
serverValidatedRead: true
|
|
32689
33573
|
});
|
|
32690
33574
|
const rawArgs = { filePath: pathArg };
|
|
32691
33575
|
if (offset !== undefined)
|
|
@@ -32766,7 +33650,7 @@ PDFs aren't supported on the Pi harness yet.`, response);
|
|
|
32766
33650
|
pi.registerTool({
|
|
32767
33651
|
name: "edit",
|
|
32768
33652
|
label: "edit",
|
|
32769
|
-
description: "Edit part of a file via `appendContent`, batch `edits[]`, or `oldString`/`newString` find-and-replace.
|
|
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.",
|
|
32770
33654
|
promptSnippet: "Partial file edits via appendContent, edits[], or oldString/newString (mode priority: appendContent > edits > oldString).",
|
|
32771
33655
|
promptGuidelines: [
|
|
32772
33656
|
"Prefer edit over write when changing part of an existing file.",
|
|
@@ -32792,10 +33676,20 @@ PDFs aren't supported on the Pi harness yet.`, response);
|
|
|
32792
33676
|
});
|
|
32793
33677
|
const bridge = bridgeFor(ctx, extCtx.cwd);
|
|
32794
33678
|
const rawArgs = { filePath: filePathArg };
|
|
32795
|
-
for (const key of ["appendContent", "
|
|
33679
|
+
for (const key of ["appendContent", "oldString", "newString"]) {
|
|
32796
33680
|
if (argsRecord[key] !== undefined)
|
|
32797
33681
|
rawArgs[key] = argsRecord[key];
|
|
32798
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;
|
|
33692
|
+
}
|
|
32799
33693
|
if (params.replaceAll !== undefined)
|
|
32800
33694
|
rawArgs.replaceAll = coerceBoolean(params.replaceAll);
|
|
32801
33695
|
const occurrence = coerceOptionalInt(params.occurrence, "occurrence", 0, Number.MAX_SAFE_INTEGER);
|
|
@@ -32998,17 +33892,17 @@ ${summary}${suffix}`);
|
|
|
32998
33892
|
container.addChild(new Text(renderDiff(diff), 1, 0));
|
|
32999
33893
|
return container;
|
|
33000
33894
|
}
|
|
33001
|
-
function shortenPath2(
|
|
33002
|
-
const home =
|
|
33003
|
-
if (
|
|
33004
|
-
return `~${
|
|
33005
|
-
return
|
|
33895
|
+
function shortenPath2(path2) {
|
|
33896
|
+
const home = homedir13();
|
|
33897
|
+
if (path2.startsWith(home))
|
|
33898
|
+
return `~${path2.slice(home.length)}`;
|
|
33899
|
+
return path2;
|
|
33006
33900
|
}
|
|
33007
|
-
async function resolvePathArg(cwd,
|
|
33008
|
-
const expanded = expandTilde2(
|
|
33009
|
-
const abs = absoluteSearchPath(cwd,
|
|
33901
|
+
async function resolvePathArg(cwd, path2) {
|
|
33902
|
+
const expanded = expandTilde2(path2);
|
|
33903
|
+
const abs = absoluteSearchPath(cwd, path2);
|
|
33010
33904
|
try {
|
|
33011
|
-
await
|
|
33905
|
+
await stat2(abs);
|
|
33012
33906
|
return abs;
|
|
33013
33907
|
} catch {
|
|
33014
33908
|
return expanded;
|
|
@@ -33016,7 +33910,7 @@ async function resolvePathArg(cwd, path3) {
|
|
|
33016
33910
|
}
|
|
33017
33911
|
|
|
33018
33912
|
// src/tools/render-helpers.ts
|
|
33019
|
-
import { homedir as
|
|
33913
|
+
import { homedir as homedir14 } from "node:os";
|
|
33020
33914
|
import { renderDiff as renderDiff2 } from "@earendil-works/pi-coding-agent";
|
|
33021
33915
|
import { Container as Container2, Spacer as Spacer2, Text as Text2 } from "@earendil-works/pi-tui";
|
|
33022
33916
|
function reuseText2(last) {
|
|
@@ -33025,11 +33919,11 @@ function reuseText2(last) {
|
|
|
33025
33919
|
function reuseContainer2(last) {
|
|
33026
33920
|
return last instanceof Container2 ? last : new Container2;
|
|
33027
33921
|
}
|
|
33028
|
-
function shortenPath3(
|
|
33029
|
-
const home =
|
|
33030
|
-
if (
|
|
33031
|
-
return `~${
|
|
33032
|
-
return
|
|
33922
|
+
function shortenPath3(path2) {
|
|
33923
|
+
const home = homedir14();
|
|
33924
|
+
if (path2.startsWith(home))
|
|
33925
|
+
return `~${path2.slice(home.length)}`;
|
|
33926
|
+
return path2;
|
|
33033
33927
|
}
|
|
33034
33928
|
function renderToolCall(toolName, summary, theme, context) {
|
|
33035
33929
|
const text = reuseText2(context.lastComponent);
|
|
@@ -33037,10 +33931,10 @@ function renderToolCall(toolName, summary, theme, context) {
|
|
|
33037
33931
|
text.setText(`${theme.fg("toolTitle", theme.bold(toolName))}${suffix}`);
|
|
33038
33932
|
return text;
|
|
33039
33933
|
}
|
|
33040
|
-
function accentPath(theme,
|
|
33041
|
-
if (!
|
|
33934
|
+
function accentPath(theme, path2) {
|
|
33935
|
+
if (!path2)
|
|
33042
33936
|
return theme.fg("toolOutput", "...");
|
|
33043
|
-
return theme.fg("accent", shortenPath3(
|
|
33937
|
+
return theme.fg("accent", shortenPath3(path2));
|
|
33044
33938
|
}
|
|
33045
33939
|
function collectTextContent(result) {
|
|
33046
33940
|
return result.content.filter((part) => part.type === "text").map((part) => part.text ?? "").join(`
|
|
@@ -33225,17 +34119,17 @@ ${warningStrings.map((w) => ` ${w}`).join(`
|
|
|
33225
34119
|
async function resolveAstPaths(extCtx, paths) {
|
|
33226
34120
|
if (isEmptyParam(paths) || !Array.isArray(paths))
|
|
33227
34121
|
return;
|
|
33228
|
-
return Promise.all(paths.filter((
|
|
34122
|
+
return Promise.all(paths.filter((path2) => typeof path2 === "string").map((path2) => resolvePathArg(extCtx.cwd, path2)));
|
|
33229
34123
|
}
|
|
33230
34124
|
async function assertAstPathsPermission(extCtx, paths, restrictToProjectRoot) {
|
|
33231
34125
|
if (paths === undefined || paths.length === 0)
|
|
33232
34126
|
return;
|
|
33233
34127
|
const checked = new Set;
|
|
33234
|
-
for (const
|
|
33235
|
-
if (checked.has(
|
|
34128
|
+
for (const path2 of paths) {
|
|
34129
|
+
if (checked.has(path2))
|
|
33236
34130
|
continue;
|
|
33237
|
-
checked.add(
|
|
33238
|
-
await assertExternalDirectoryPermission(extCtx,
|
|
34131
|
+
checked.add(path2);
|
|
34132
|
+
await assertExternalDirectoryPermission(extCtx, path2, { restrictToProjectRoot });
|
|
33239
34133
|
}
|
|
33240
34134
|
}
|
|
33241
34135
|
function buildAstSearchSections(payload, theme) {
|
|
@@ -33417,7 +34311,7 @@ function registerAstTools(pi, ctx, surface) {
|
|
|
33417
34311
|
}
|
|
33418
34312
|
|
|
33419
34313
|
// src/tools/bash.ts
|
|
33420
|
-
import * as
|
|
34314
|
+
import * as fs3 from "node:fs/promises";
|
|
33421
34315
|
import { Container as Container3, Spacer as Spacer3, Text as Text3 } from "@earendil-works/pi-tui";
|
|
33422
34316
|
import { Type as Type4 } from "typebox";
|
|
33423
34317
|
var BASH_WAIT_POLL_INTERVAL_MS = 100;
|
|
@@ -33433,7 +34327,7 @@ function resolveForegroundWaitMs(configured) {
|
|
|
33433
34327
|
}
|
|
33434
34328
|
return configured;
|
|
33435
34329
|
}
|
|
33436
|
-
var
|
|
34330
|
+
var BASH_TRANSPORT_TIMEOUT_MS2 = 30000;
|
|
33437
34331
|
var DEFAULT_HARD_TIMEOUT_MS = 30 * 60 * 1000;
|
|
33438
34332
|
var BASH_TRANSPORT_MARGIN_MS = 1e4;
|
|
33439
34333
|
function orchestratedTransportTimeoutMs(blockToCompletion, wait, effectiveTimeout, foregroundWaitMs) {
|
|
@@ -33444,7 +34338,7 @@ var BashBaseParams = {
|
|
|
33444
34338
|
command: Type4.String({
|
|
33445
34339
|
description: "Shell command to execute. Supports pipes, redirections, and shell syntax."
|
|
33446
34340
|
}),
|
|
33447
|
-
timeout: optionalInt(1, Number.MAX_SAFE_INTEGER),
|
|
34341
|
+
timeout: optionalInt(1, Number.MAX_SAFE_INTEGER, "Hard kill timeout in milliseconds"),
|
|
33448
34342
|
workdir: Type4.Optional(Type4.String({
|
|
33449
34343
|
description: "Working directory for command execution. Relative paths resolve against the project root. Defaults to the current session's working directory."
|
|
33450
34344
|
})),
|
|
@@ -33452,7 +34346,7 @@ var BashBaseParams = {
|
|
|
33452
34346
|
description: "Human-readable description shown in UI logs. Helps users understand what the command does without reading shell syntax."
|
|
33453
34347
|
})),
|
|
33454
34348
|
wait: Type4.Optional(Type4.Boolean({
|
|
33455
|
-
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."
|
|
33456
34350
|
}))
|
|
33457
34351
|
};
|
|
33458
34352
|
var BashBackgroundFlagParam = {
|
|
@@ -33469,8 +34363,8 @@ var BashPtyParams = {
|
|
|
33469
34363
|
pty: Type4.Optional(Type4.Boolean({
|
|
33470
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.'
|
|
33471
34365
|
})),
|
|
33472
|
-
ptyRows: optionalInt(1, 60),
|
|
33473
|
-
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)")
|
|
33474
34368
|
};
|
|
33475
34369
|
var BashParams = Type4.Object({
|
|
33476
34370
|
...BashBaseParams,
|
|
@@ -33504,7 +34398,7 @@ var BashWatchParams = Type4.Object({
|
|
|
33504
34398
|
}),
|
|
33505
34399
|
pattern: Type4.Optional(Type4.Union([Type4.String(), Type4.Object({ regex: Type4.String() })])),
|
|
33506
34400
|
background: Type4.Optional(Type4.Boolean()),
|
|
33507
|
-
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"),
|
|
33508
34402
|
once: Type4.Optional(Type4.Boolean())
|
|
33509
34403
|
});
|
|
33510
34404
|
var BashWriteParams = Type4.Object({
|
|
@@ -33530,7 +34424,7 @@ function unavailableSnapshot() {
|
|
|
33530
34424
|
}
|
|
33531
34425
|
async function callBashBridge(bridge, command, params = {}, extCtx, options) {
|
|
33532
34426
|
return await callBridge(bridge, command, params, extCtx, {
|
|
33533
|
-
transportTimeoutMs:
|
|
34427
|
+
transportTimeoutMs: BASH_TRANSPORT_TIMEOUT_MS2,
|
|
33534
34428
|
...options,
|
|
33535
34429
|
keepBridgeOnTimeout: true
|
|
33536
34430
|
});
|
|
@@ -33561,7 +34455,7 @@ function registerBashTool(pi, ctx, aftSearchRegistered = false) {
|
|
|
33561
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";
|
|
33562
34456
|
const bashCfg = resolveBashConfig(ctx.config);
|
|
33563
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`." : "";
|
|
33564
|
-
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).";
|
|
33565
34459
|
pi.registerTool({
|
|
33566
34460
|
name: "bash",
|
|
33567
34461
|
label: "bash",
|
|
@@ -33835,7 +34729,7 @@ async function waitForBashStatus(ctx, bridge, extCtx, taskId, outputMode, waitFo
|
|
|
33835
34729
|
let scanBaseOffset = 0;
|
|
33836
34730
|
const bridgeOptions = {
|
|
33837
34731
|
keepBridgeOnTimeout: true,
|
|
33838
|
-
transportTimeoutMs:
|
|
34732
|
+
transportTimeoutMs: BASH_TRANSPORT_TIMEOUT_MS2
|
|
33839
34733
|
};
|
|
33840
34734
|
if (waitFor?.kind === "regex") {
|
|
33841
34735
|
await validateWaitRegex(bridge, extCtx, waitFor);
|
|
@@ -33946,7 +34840,7 @@ async function readNewTaskOutput(data, cursor) {
|
|
|
33946
34840
|
};
|
|
33947
34841
|
}
|
|
33948
34842
|
async function readFileBytesFrom(outputPath, cursor) {
|
|
33949
|
-
const handle = await
|
|
34843
|
+
const handle = await fs3.open(outputPath, "r");
|
|
33950
34844
|
try {
|
|
33951
34845
|
const chunks = [];
|
|
33952
34846
|
let offset = cursor;
|
|
@@ -34075,7 +34969,7 @@ async function formatPtyStatus(_extCtx, taskId, details, requestedOutputMode) {
|
|
|
34075
34969
|
return `
|
|
34076
34970
|
[PTY output path unavailable]`;
|
|
34077
34971
|
const outputMode = requestedOutputMode ?? "screen";
|
|
34078
|
-
const raw = outputMode === "raw" || outputMode === "both" ? await
|
|
34972
|
+
const raw = outputMode === "raw" || outputMode === "both" ? await fs3.readFile(details.output_path) : undefined;
|
|
34079
34973
|
let suffix = "";
|
|
34080
34974
|
if (outputMode === "raw") {
|
|
34081
34975
|
suffix = raw && raw.length > 0 ? `
|
|
@@ -34196,12 +35090,12 @@ function registerConflictsTool(pi, ctx) {
|
|
|
34196
35090
|
async execute(_toolCallId, params, _signal, _onUpdate, extCtx) {
|
|
34197
35091
|
const bridge = bridgeFor(ctx, extCtx.cwd);
|
|
34198
35092
|
const reqParams = {};
|
|
34199
|
-
const
|
|
34200
|
-
if (typeof
|
|
34201
|
-
await assertExternalDirectoryPermission(extCtx,
|
|
35093
|
+
const path2 = params?.path;
|
|
35094
|
+
if (typeof path2 === "string" && path2.trim() !== "") {
|
|
35095
|
+
await assertExternalDirectoryPermission(extCtx, path2, {
|
|
34202
35096
|
restrictToProjectRoot: ctx.config.restrict_to_project_root ?? false
|
|
34203
35097
|
});
|
|
34204
|
-
reqParams.path = await resolvePathArg(extCtx.cwd,
|
|
35098
|
+
reqParams.path = await resolvePathArg(extCtx.cwd, path2);
|
|
34205
35099
|
}
|
|
34206
35100
|
const response = await callToolCall(bridge, "conflicts", reqParams, extCtx);
|
|
34207
35101
|
if (response.success === false) {
|
|
@@ -34613,8 +35507,8 @@ function tier2SummaryPart(summary, key, label) {
|
|
|
34613
35507
|
return `${label} ${status ?? "unavailable"}`;
|
|
34614
35508
|
}
|
|
34615
35509
|
function shortDupOccurrence(entry) {
|
|
34616
|
-
const [
|
|
34617
|
-
return
|
|
35510
|
+
const [path2] = entry.split(":");
|
|
35511
|
+
return path2?.split("/").pop() ?? entry;
|
|
34618
35512
|
}
|
|
34619
35513
|
function tier2TopPreview(summary, theme) {
|
|
34620
35514
|
const lines = [];
|
|
@@ -34798,7 +35692,7 @@ function navigateParamsSchema() {
|
|
|
34798
35692
|
description: "Source file containing the symbol (absolute or relative to project root)"
|
|
34799
35693
|
}),
|
|
34800
35694
|
symbol: Type9.String({ description: "Name of the symbol to analyze" }),
|
|
34801
|
-
depth: optionalInt(1, Number.MAX_SAFE_INTEGER),
|
|
35695
|
+
depth: optionalInt(1, Number.MAX_SAFE_INTEGER, "Maximum call-graph depth to traverse"),
|
|
34802
35696
|
expression: Type9.Optional(Type9.String({ description: "Expression to track (required for trace_data)" })),
|
|
34803
35697
|
toSymbol: Type9.Optional(Type9.String({
|
|
34804
35698
|
description: "Target symbol for trace_to_symbol; the returned path ends here"
|
|
@@ -35235,9 +36129,9 @@ var RefactorParams = Type11.Object({
|
|
|
35235
36129
|
destination: Type11.Optional(Type11.String({ description: "Target file (for move)" })),
|
|
35236
36130
|
scope: Type11.Optional(Type11.String({ description: "Disambiguation scope for move op" })),
|
|
35237
36131
|
name: Type11.Optional(Type11.String({ description: "New function name (for extract)" })),
|
|
35238
|
-
startLine: optionalInt(1, Number.MAX_SAFE_INTEGER),
|
|
35239
|
-
endLine: optionalInt(1, Number.MAX_SAFE_INTEGER),
|
|
35240
|
-
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")
|
|
35241
36135
|
});
|
|
35242
36136
|
function buildRefactorSections(args, payload, theme) {
|
|
35243
36137
|
const response = asRecord3(payload);
|
|
@@ -35356,7 +36250,7 @@ function registerRefactorTool(pi, ctx) {
|
|
|
35356
36250
|
import { StringEnum as StringEnum5 } from "@earendil-works/pi-ai";
|
|
35357
36251
|
import { Type as Type12 } from "typebox";
|
|
35358
36252
|
function responsePaths(response) {
|
|
35359
|
-
return Array.isArray(response.paths) ? response.paths.filter((
|
|
36253
|
+
return Array.isArray(response.paths) ? response.paths.filter((path2) => typeof path2 === "string" && path2.length > 0) : [];
|
|
35360
36254
|
}
|
|
35361
36255
|
var SafetyParams = Type12.Object({
|
|
35362
36256
|
op: StringEnum5(["undo", "history", "checkpoint", "restore", "list"], {
|
|
@@ -35738,7 +36632,7 @@ function buildWorkflowHints(opts) {
|
|
|
35738
36632
|
}
|
|
35739
36633
|
if (hasBgBash) {
|
|
35740
36634
|
sections.push([
|
|
35741
|
-
`**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.`,
|
|
35742
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.",
|
|
35743
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."
|
|
35744
36638
|
].join(`
|
|
@@ -35809,18 +36703,18 @@ function createVersionMismatchHandler(getPool, ensureCompatibleBinary = ensureBi
|
|
|
35809
36703
|
const upgradePromise = (async () => {
|
|
35810
36704
|
warn2(`WARNING: aft binary v${binaryVersion} is older than plugin v${minVersion}. ` + "Some features may not work. Attempting to download a compatible binary...");
|
|
35811
36705
|
try {
|
|
35812
|
-
const
|
|
35813
|
-
if (!
|
|
36706
|
+
const path2 = await ensureCompatibleBinary(`v${minVersion}`);
|
|
36707
|
+
if (!path2) {
|
|
35814
36708
|
warn2(`Could not find or download v${minVersion}. Continuing with v${binaryVersion}.`);
|
|
35815
36709
|
return null;
|
|
35816
36710
|
}
|
|
35817
36711
|
const pool = getPool();
|
|
35818
36712
|
if (!pool) {
|
|
35819
|
-
warn2(`Found/downloaded compatible binary at ${
|
|
36713
|
+
warn2(`Found/downloaded compatible binary at ${path2}, but bridge pool is not ready.`);
|
|
35820
36714
|
return null;
|
|
35821
36715
|
}
|
|
35822
|
-
log2(`Found/downloaded compatible binary at ${
|
|
35823
|
-
const replaced = await pool.replaceBinary(
|
|
36716
|
+
log2(`Found/downloaded compatible binary at ${path2}. Replacing running bridges...`);
|
|
36717
|
+
const replaced = await pool.replaceBinary(path2);
|
|
35824
36718
|
log2("Binary replaced successfully. New bridges will use the updated binary.");
|
|
35825
36719
|
return replaced;
|
|
35826
36720
|
} catch (err) {
|
|
@@ -35842,12 +36736,14 @@ var PLUGIN_VERSION = (() => {
|
|
|
35842
36736
|
return "0.0.0";
|
|
35843
36737
|
}
|
|
35844
36738
|
})();
|
|
35845
|
-
var ANNOUNCEMENT_VERSION = "0.
|
|
36739
|
+
var ANNOUNCEMENT_VERSION = "0.47.0";
|
|
35846
36740
|
var ANNOUNCEMENT_FEATURES = [
|
|
35847
|
-
"
|
|
35848
|
-
"
|
|
35849
|
-
"
|
|
35850
|
-
"
|
|
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."
|
|
35851
36747
|
];
|
|
35852
36748
|
var ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/DSa65w8wuf";
|
|
35853
36749
|
var ALL_ONLY_TOOLS = new Set(["aft_callgraph", "aft_delete", "aft_move", "aft_refactor"]);
|
|
@@ -36044,6 +36940,7 @@ async function src_default(pi) {
|
|
|
36044
36940
|
const configOverrides = buildConfigTierConfigureParams(projectRoot, {
|
|
36045
36941
|
storage_dir: storageDir
|
|
36046
36942
|
});
|
|
36943
|
+
let lspInstallCompletion = null;
|
|
36047
36944
|
try {
|
|
36048
36945
|
const lspAutoInstall = config2.lsp?.auto_install ?? true;
|
|
36049
36946
|
const lspGraceDays = config2.lsp?.grace_days ?? 7;
|
|
@@ -36073,10 +36970,24 @@ async function src_default(pi) {
|
|
|
36073
36970
|
if (lspInflightInstalls.length > 0) {
|
|
36074
36971
|
configOverrides.lsp_inflight_installs = lspInflightInstalls;
|
|
36075
36972
|
}
|
|
36076
|
-
|
|
36973
|
+
const installsWereStarted = npmResult.installsStarted > 0 || ghResult.installsStarted > 0;
|
|
36974
|
+
if (installsWereStarted) {
|
|
36077
36975
|
log2(`[lsp] auto-install: ${npmResult.installsStarted} npm + ${ghResult.installsStarted} github install(s) running in background`);
|
|
36078
36976
|
}
|
|
36079
|
-
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) => {
|
|
36080
36991
|
const actionable = [...npmResult.skipped, ...ghResult.skipped].filter((s) => {
|
|
36081
36992
|
const r = s.reason.toLowerCase();
|
|
36082
36993
|
if (r === "auto_install: false")
|
|
@@ -36091,16 +37002,20 @@ async function src_default(pi) {
|
|
|
36091
37002
|
return false;
|
|
36092
37003
|
return true;
|
|
36093
37004
|
});
|
|
36094
|
-
if (actionable.length
|
|
36095
|
-
|
|
36096
|
-
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(`
|
|
36097
37007
|
`);
|
|
36098
|
-
|
|
37008
|
+
warn2(`[lsp] skipped or failed to install ${actionable.length} server(s):
|
|
36099
37009
|
${lines}
|
|
36100
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;
|
|
36101
37013
|
}).catch((err) => {
|
|
36102
37014
|
warn2(`[lsp] install-summary aggregation failed: ${err}`);
|
|
37015
|
+
return null;
|
|
36103
37016
|
});
|
|
37017
|
+
if (installsWereStarted)
|
|
37018
|
+
lspInstallCompletion = installCompletion;
|
|
36104
37019
|
} catch (err) {
|
|
36105
37020
|
warn2(`[lsp] auto-install setup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
36106
37021
|
}
|
|
@@ -36171,6 +37086,17 @@ ${lines}
|
|
|
36171
37086
|
});
|
|
36172
37087
|
}
|
|
36173
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
|
+
}
|
|
36174
37100
|
pool.setConfigureOverride("harness", "pi");
|
|
36175
37101
|
pool.setConfigureOverride("aft_search_registered", resolveToolSurface(config2).semantic);
|
|
36176
37102
|
const ctx = { pool, config: config2, storageDir };
|
|
@@ -36283,7 +37209,9 @@ ${lines}
|
|
|
36283
37209
|
}
|
|
36284
37210
|
});
|
|
36285
37211
|
pi.on("input", (_event, extCtx) => {
|
|
36286
|
-
|
|
37212
|
+
const sessionId = resolveSessionId(extCtx);
|
|
37213
|
+
signalSyncWatchAbort(sessionId);
|
|
37214
|
+
signalBashWaitDetachForProject(pool, extCtx.cwd, sessionId);
|
|
36287
37215
|
});
|
|
36288
37216
|
const unregisterShutdownCleanup = registerShutdownCleanup(async () => {
|
|
36289
37217
|
try {
|