@blue-soda/dsh-remote 0.4.28 → 0.4.29
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 +9 -9
- package/dist/atomic-file.d.ts +16 -0
- package/dist/atomic-file.d.ts.map +1 -0
- package/dist/atomic-file.js +81 -0
- package/dist/atomic-file.js.map +1 -0
- package/dist/identity-store.d.ts.map +1 -1
- package/dist/identity-store.js +6 -3
- package/dist/identity-store.js.map +1 -1
- package/dist/index.js +142 -83
- package/dist/index.js.map +4 -4
- package/dist/server-credentials.d.ts.map +1 -1
- package/dist/server-credentials.js +6 -3
- package/dist/server-credentials.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dsh-plugin.json +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -18354,8 +18354,63 @@ var AsyncValueQueue2 = class {
|
|
|
18354
18354
|
import { platform } from "node:os";
|
|
18355
18355
|
|
|
18356
18356
|
// src/server-credentials.ts
|
|
18357
|
-
import { chmod, mkdir, readFile as readFile2,
|
|
18358
|
-
import { dirname as dirname2, join as
|
|
18357
|
+
import { chmod, mkdir, readFile as readFile2, rm as rm2, stat as stat2, writeFile } from "node:fs/promises";
|
|
18358
|
+
import { dirname as dirname2, join as join3 } from "node:path";
|
|
18359
|
+
|
|
18360
|
+
// src/atomic-file.ts
|
|
18361
|
+
import { readdir, rename, rm, stat } from "node:fs/promises";
|
|
18362
|
+
import { join as join2 } from "node:path";
|
|
18363
|
+
var TRANSIENT_REPLACE_CODES = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES"]);
|
|
18364
|
+
var REPLACE_RETRY_DELAYS_MS = [20, 40, 80, 160, 320];
|
|
18365
|
+
var TEMPORARY_NAME = /\.\d+\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.tmp$/iu;
|
|
18366
|
+
var STALE_TEMPORARY_AGE_MS = 10 * 60 * 1e3;
|
|
18367
|
+
async function replaceFile(temporary, target2, options = {}) {
|
|
18368
|
+
const delays = options.delaysMs ?? REPLACE_RETRY_DELAYS_MS;
|
|
18369
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
18370
|
+
try {
|
|
18371
|
+
await rename(temporary, target2);
|
|
18372
|
+
return;
|
|
18373
|
+
} catch (error) {
|
|
18374
|
+
const delay = delays[attempt];
|
|
18375
|
+
if (delay === void 0 || !TRANSIENT_REPLACE_CODES.has(errorCode2(error) ?? "")) throw error;
|
|
18376
|
+
await sleep2(delay);
|
|
18377
|
+
}
|
|
18378
|
+
}
|
|
18379
|
+
}
|
|
18380
|
+
async function sweepStaleTemporaries(directory, options = {}) {
|
|
18381
|
+
const maxAgeMs = options.maxAgeMs ?? STALE_TEMPORARY_AGE_MS;
|
|
18382
|
+
const removed = [];
|
|
18383
|
+
let entries;
|
|
18384
|
+
try {
|
|
18385
|
+
entries = await readdir(directory);
|
|
18386
|
+
} catch {
|
|
18387
|
+
return removed;
|
|
18388
|
+
}
|
|
18389
|
+
for (const entry of entries) {
|
|
18390
|
+
if (!TEMPORARY_NAME.test(entry)) continue;
|
|
18391
|
+
const path = join2(directory, entry);
|
|
18392
|
+
try {
|
|
18393
|
+
const info = await stat(path);
|
|
18394
|
+
if (!info.isFile() || Date.now() - info.mtimeMs < maxAgeMs) continue;
|
|
18395
|
+
await rm(path, { force: true });
|
|
18396
|
+
removed.push(path);
|
|
18397
|
+
} catch {
|
|
18398
|
+
}
|
|
18399
|
+
}
|
|
18400
|
+
return removed;
|
|
18401
|
+
}
|
|
18402
|
+
function errorCode2(error) {
|
|
18403
|
+
if (typeof error !== "object" || error === null || !("code" in error)) return void 0;
|
|
18404
|
+
const code = error.code;
|
|
18405
|
+
return typeof code === "string" ? code : void 0;
|
|
18406
|
+
}
|
|
18407
|
+
function sleep2(ms) {
|
|
18408
|
+
return new Promise((resolve4) => {
|
|
18409
|
+
setTimeout(resolve4, ms);
|
|
18410
|
+
});
|
|
18411
|
+
}
|
|
18412
|
+
|
|
18413
|
+
// src/server-credentials.ts
|
|
18359
18414
|
var credentialSchema = external_exports.object({
|
|
18360
18415
|
schemaVersion: external_exports.literal(1),
|
|
18361
18416
|
serverUrl: external_exports.string().url(),
|
|
@@ -18370,7 +18425,7 @@ var credentialSchema = external_exports.object({
|
|
|
18370
18425
|
var ServerCredentialStore = class {
|
|
18371
18426
|
path;
|
|
18372
18427
|
constructor(directory) {
|
|
18373
|
-
this.path =
|
|
18428
|
+
this.path = join3(directory, "server-credentials.json");
|
|
18374
18429
|
}
|
|
18375
18430
|
/** Serialize the complete read/refresh/write transaction across processes.
|
|
18376
18431
|
* Never steal an old lock: a suspended owner may still consume a one-use token.
|
|
@@ -18393,7 +18448,7 @@ var ServerCredentialStore = class {
|
|
|
18393
18448
|
try {
|
|
18394
18449
|
return await operation();
|
|
18395
18450
|
} finally {
|
|
18396
|
-
await
|
|
18451
|
+
await rm2(lock, { recursive: true });
|
|
18397
18452
|
}
|
|
18398
18453
|
}
|
|
18399
18454
|
async load(serverUrl, deviceId) {
|
|
@@ -18414,7 +18469,7 @@ var ServerCredentialStore = class {
|
|
|
18414
18469
|
return record7;
|
|
18415
18470
|
}
|
|
18416
18471
|
async clear() {
|
|
18417
|
-
await
|
|
18472
|
+
await rm2(this.path, { force: true });
|
|
18418
18473
|
}
|
|
18419
18474
|
};
|
|
18420
18475
|
var ServerCredentialsInvalidError = class extends Error {
|
|
@@ -18427,21 +18482,23 @@ var ServerCredentialsBusyError = class extends Error {
|
|
|
18427
18482
|
}
|
|
18428
18483
|
};
|
|
18429
18484
|
async function atomicWrite(path, contents) {
|
|
18430
|
-
|
|
18485
|
+
const directory = dirname2(path);
|
|
18486
|
+
await mkdir(directory, { recursive: true, mode: 448 });
|
|
18487
|
+
await sweepStaleTemporaries(directory);
|
|
18431
18488
|
const temporary = `${path}.${process.pid}.${uuidV7()}.tmp`;
|
|
18432
18489
|
await writeFile(temporary, contents, { encoding: "utf8", mode: 384, flag: "wx" });
|
|
18433
18490
|
await chmod(temporary, 384);
|
|
18434
|
-
await
|
|
18491
|
+
await replaceFile(temporary, path);
|
|
18435
18492
|
await chmod(path, 384);
|
|
18436
18493
|
}
|
|
18437
18494
|
async function assertPrivateMode(path) {
|
|
18438
18495
|
if (process.platform === "win32") return;
|
|
18439
|
-
const mode = (await
|
|
18496
|
+
const mode = (await stat2(path)).mode & 511;
|
|
18440
18497
|
if ((mode & 63) !== 0) throw new ServerCredentialsInvalidError("server credentials permissions must be 0600");
|
|
18441
18498
|
}
|
|
18442
18499
|
async function exists(path) {
|
|
18443
18500
|
try {
|
|
18444
|
-
await
|
|
18501
|
+
await stat2(path);
|
|
18445
18502
|
return true;
|
|
18446
18503
|
} catch (error) {
|
|
18447
18504
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") return false;
|
|
@@ -18572,7 +18629,7 @@ function normalizeServerUrl(value) {
|
|
|
18572
18629
|
}
|
|
18573
18630
|
|
|
18574
18631
|
// src/version.ts
|
|
18575
|
-
var PLUGIN_VERSION = "0.4.
|
|
18632
|
+
var PLUGIN_VERSION = "0.4.29";
|
|
18576
18633
|
|
|
18577
18634
|
// src/server-api.ts
|
|
18578
18635
|
var ENABLED_QR_PROVIDERS = ["github"];
|
|
@@ -19317,7 +19374,7 @@ import { networkInterfaces } from "node:os";
|
|
|
19317
19374
|
// src/native-rtc-helper.ts
|
|
19318
19375
|
import { spawn, spawnSync } from "node:child_process";
|
|
19319
19376
|
import { existsSync, readdirSync, realpathSync, statSync } from "node:fs";
|
|
19320
|
-
import { delimiter, dirname as dirname3, join as
|
|
19377
|
+
import { delimiter, dirname as dirname3, join as join4 } from "node:path";
|
|
19321
19378
|
import { fileURLToPath } from "node:url";
|
|
19322
19379
|
var cachedExternalFactory;
|
|
19323
19380
|
var cachedExternalFactoryResolved = false;
|
|
@@ -19368,21 +19425,21 @@ function nodeBinaryCandidates() {
|
|
|
19368
19425
|
add3(process.env.DSH_REMOTE_NODE);
|
|
19369
19426
|
add3(process.env.NODE);
|
|
19370
19427
|
if (process.versions.electron === void 0) add3(process.execPath);
|
|
19371
|
-
for (const part of (process.env.PATH ?? "").split(delimiter)) add3(
|
|
19428
|
+
for (const part of (process.env.PATH ?? "").split(delimiter)) add3(join4(part, process.platform === "win32" ? "node.exe" : "node"));
|
|
19372
19429
|
add3("/opt/homebrew/bin/node");
|
|
19373
19430
|
add3("/usr/local/bin/node");
|
|
19374
19431
|
add3("/usr/bin/node");
|
|
19375
|
-
add3(
|
|
19376
|
-
add3(
|
|
19377
|
-
add3(
|
|
19432
|
+
add3(join4(process.env.HOME ?? "", ".volta", "bin", process.platform === "win32" ? "node.exe" : "node"));
|
|
19433
|
+
add3(join4(process.env.HOME ?? "", ".asdf", "shims", process.platform === "win32" ? "node.exe" : "node"));
|
|
19434
|
+
add3(join4(process.env.HOME ?? "", ".local", "bin", process.platform === "win32" ? "node.exe" : "node"));
|
|
19378
19435
|
for (const nvmNode of nvmNodeCandidates()) add3(nvmNode);
|
|
19379
19436
|
return candidates;
|
|
19380
19437
|
}
|
|
19381
19438
|
function nvmNodeCandidates() {
|
|
19382
|
-
const root =
|
|
19439
|
+
const root = join4(process.env.HOME ?? "", ".nvm", "versions", "node");
|
|
19383
19440
|
if (root === "" || !existsSync(root)) return [];
|
|
19384
19441
|
try {
|
|
19385
|
-
return readdirSync(root).map((version) =>
|
|
19442
|
+
return readdirSync(root).map((version) => join4(root, version, "bin", process.platform === "win32" ? "node.exe" : "node")).sort((left, right) => right.localeCompare(left, "en", { numeric: true }));
|
|
19386
19443
|
} catch {
|
|
19387
19444
|
return [];
|
|
19388
19445
|
}
|
|
@@ -19413,10 +19470,10 @@ function isUsableExternalNode(candidate, requireFrom) {
|
|
|
19413
19470
|
}
|
|
19414
19471
|
function isExecutableFile(candidate) {
|
|
19415
19472
|
try {
|
|
19416
|
-
const
|
|
19417
|
-
if (!
|
|
19473
|
+
const stat8 = statSync(candidate);
|
|
19474
|
+
if (!stat8.isFile()) return false;
|
|
19418
19475
|
if (process.platform === "win32") return true;
|
|
19419
|
-
return (
|
|
19476
|
+
return (stat8.mode & 73) !== 0;
|
|
19420
19477
|
} catch {
|
|
19421
19478
|
return false;
|
|
19422
19479
|
}
|
|
@@ -21613,9 +21670,9 @@ import { execFileSync } from "node:child_process";
|
|
|
21613
21670
|
|
|
21614
21671
|
// src/identity-store.ts
|
|
21615
21672
|
import { createHash } from "node:crypto";
|
|
21616
|
-
import { chmod as chmod2, mkdir as mkdir2, readFile as readFile3,
|
|
21673
|
+
import { chmod as chmod2, mkdir as mkdir2, readFile as readFile3, rm as rm3, stat as stat3, writeFile as writeFile2 } from "node:fs/promises";
|
|
21617
21674
|
import { homedir } from "node:os";
|
|
21618
|
-
import { dirname as dirname4, join as
|
|
21675
|
+
import { dirname as dirname4, join as join5 } from "node:path";
|
|
21619
21676
|
var identitySchema = external_exports.object({
|
|
21620
21677
|
schemaVersion: external_exports.literal(1),
|
|
21621
21678
|
deviceId: external_exports.string().uuid(),
|
|
@@ -21644,14 +21701,14 @@ var IdentityStore = class {
|
|
|
21644
21701
|
peers = /* @__PURE__ */ new Map();
|
|
21645
21702
|
constructor(options = {}) {
|
|
21646
21703
|
const env = options.env ?? process.env;
|
|
21647
|
-
const dshHome = env.DSH_HOME ||
|
|
21648
|
-
this.directory = options.directory ??
|
|
21704
|
+
const dshHome = env.DSH_HOME || join5(options.homeDirectory ?? homedir(), ".dsh");
|
|
21705
|
+
this.directory = options.directory ?? join5(dshHome, "remote");
|
|
21649
21706
|
}
|
|
21650
21707
|
async loadOrCreate(deviceName) {
|
|
21651
21708
|
await mkdir2(this.directory, { recursive: true, mode: 448 });
|
|
21652
21709
|
await chmod2(this.directory, 448);
|
|
21653
|
-
const devicePath =
|
|
21654
|
-
const keyPath =
|
|
21710
|
+
const devicePath = join5(this.directory, "device.json");
|
|
21711
|
+
const keyPath = join5(this.directory, "device.key");
|
|
21655
21712
|
const [hasDevice, hasKey] = await Promise.all([exists2(devicePath), exists2(keyPath)]);
|
|
21656
21713
|
if (hasDevice !== hasKey) {
|
|
21657
21714
|
throw new IdentityInvalidError("device identity is incomplete; repair it explicitly before reconnecting");
|
|
@@ -21688,7 +21745,7 @@ var IdentityStore = class {
|
|
|
21688
21745
|
return this.identity;
|
|
21689
21746
|
}
|
|
21690
21747
|
async reset(deviceName) {
|
|
21691
|
-
await
|
|
21748
|
+
await rm3(this.directory, { recursive: true, force: true });
|
|
21692
21749
|
this.identity = void 0;
|
|
21693
21750
|
this.peers.clear();
|
|
21694
21751
|
return this.loadOrCreate(deviceName);
|
|
@@ -21720,7 +21777,7 @@ var IdentityStore = class {
|
|
|
21720
21777
|
return removed;
|
|
21721
21778
|
}
|
|
21722
21779
|
async loadPeers() {
|
|
21723
|
-
const path =
|
|
21780
|
+
const path = join5(this.directory, "trusted-peers.json");
|
|
21724
21781
|
if (!await exists2(path)) {
|
|
21725
21782
|
await atomicJsonWrite(path, { schemaVersion: 1, peers: [] }, 384);
|
|
21726
21783
|
}
|
|
@@ -21736,7 +21793,7 @@ var IdentityStore = class {
|
|
|
21736
21793
|
this.peers = peers;
|
|
21737
21794
|
}
|
|
21738
21795
|
async savePeers() {
|
|
21739
|
-
await atomicJsonWrite(
|
|
21796
|
+
await atomicJsonWrite(join5(this.directory, "trusted-peers.json"), {
|
|
21740
21797
|
schemaVersion: 1,
|
|
21741
21798
|
peers: [...this.peers.values()]
|
|
21742
21799
|
}, 384);
|
|
@@ -21745,7 +21802,7 @@ var IdentityStore = class {
|
|
|
21745
21802
|
function serverStorageDirectory(root, serverUrl, role) {
|
|
21746
21803
|
const origin = new URL(serverUrl).origin;
|
|
21747
21804
|
const scope = createHash("sha256").update(origin).digest("hex").slice(0, 24);
|
|
21748
|
-
return
|
|
21805
|
+
return join5(root, "servers", scope, role);
|
|
21749
21806
|
}
|
|
21750
21807
|
function fingerprint(publicKey) {
|
|
21751
21808
|
const compact = createHash("sha256").update(fromBase64Url2(publicKey)).digest("hex").slice(0, 12).toUpperCase();
|
|
@@ -21753,7 +21810,7 @@ function fingerprint(publicKey) {
|
|
|
21753
21810
|
}
|
|
21754
21811
|
async function assertPrivateMode2(path) {
|
|
21755
21812
|
if (process.platform === "win32") return;
|
|
21756
|
-
const mode = (await
|
|
21813
|
+
const mode = (await stat3(path)).mode & 511;
|
|
21757
21814
|
if ((mode & 63) !== 0) {
|
|
21758
21815
|
throw new IdentityInvalidError(`private key permissions must be 0600, got ${mode.toString(8).padStart(3, "0")}`);
|
|
21759
21816
|
}
|
|
@@ -21763,16 +21820,18 @@ async function atomicJsonWrite(path, value, mode) {
|
|
|
21763
21820
|
`, mode);
|
|
21764
21821
|
}
|
|
21765
21822
|
async function atomicTextWrite(path, value, mode) {
|
|
21766
|
-
|
|
21823
|
+
const directory = dirname4(path);
|
|
21824
|
+
await mkdir2(directory, { recursive: true, mode: 448 });
|
|
21825
|
+
await sweepStaleTemporaries(directory);
|
|
21767
21826
|
const temporary = `${path}.${process.pid}.${uuidV7()}.tmp`;
|
|
21768
21827
|
await writeFile2(temporary, value, { encoding: "utf8", mode, flag: "wx" });
|
|
21769
21828
|
await chmod2(temporary, mode);
|
|
21770
|
-
await
|
|
21829
|
+
await replaceFile(temporary, path);
|
|
21771
21830
|
await chmod2(path, mode);
|
|
21772
21831
|
}
|
|
21773
21832
|
async function exists2(path) {
|
|
21774
21833
|
try {
|
|
21775
|
-
await
|
|
21834
|
+
await stat3(path);
|
|
21776
21835
|
return true;
|
|
21777
21836
|
} catch (error) {
|
|
21778
21837
|
if (isNodeError(error) && error.code === "ENOENT") return false;
|
|
@@ -21932,9 +21991,9 @@ var ControlStatusStream = class {
|
|
|
21932
21991
|
// src/codex/domain.ts
|
|
21933
21992
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
21934
21993
|
import { accessSync, constants, existsSync as existsSync2, readFileSync, readdirSync as readdirSync2, statSync as statSync2 } from "node:fs";
|
|
21935
|
-
import { readdir, realpath, stat as
|
|
21994
|
+
import { readdir as readdir2, realpath, stat as stat4 } from "node:fs/promises";
|
|
21936
21995
|
import { homedir as homedir2 } from "node:os";
|
|
21937
|
-
import { basename as basename2, isAbsolute as isAbsolute2, join as
|
|
21996
|
+
import { basename as basename2, isAbsolute as isAbsolute2, join as join6, relative, resolve } from "node:path";
|
|
21938
21997
|
|
|
21939
21998
|
// src/codex/app-server.ts
|
|
21940
21999
|
import { spawn as spawn2 } from "node:child_process";
|
|
@@ -22489,7 +22548,7 @@ var CodexRemoteDomain = class {
|
|
|
22489
22548
|
} catch (error) {
|
|
22490
22549
|
this.available = false;
|
|
22491
22550
|
this.state = "unavailable";
|
|
22492
|
-
this.unavailableCode =
|
|
22551
|
+
this.unavailableCode = errorCode3(error);
|
|
22493
22552
|
await this.disposeAppServer(this.appServer);
|
|
22494
22553
|
this.logger.warn("Codex Remote domain unavailable", { code: this.unavailableCode });
|
|
22495
22554
|
}
|
|
@@ -22734,7 +22793,7 @@ var CodexRemoteDomain = class {
|
|
|
22734
22793
|
this.appServer = appServer;
|
|
22735
22794
|
this.unsubscribeInbound = appServer.onInbound((message) => {
|
|
22736
22795
|
void this.handleInbound(message).catch((error) => {
|
|
22737
|
-
this.logger.warn("Codex inbound handling failed", { code:
|
|
22796
|
+
this.logger.warn("Codex inbound handling failed", { code: errorCode3(error) });
|
|
22738
22797
|
});
|
|
22739
22798
|
});
|
|
22740
22799
|
this.unsubscribeUnavailable = appServer.onUnavailable((code) => {
|
|
@@ -22800,7 +22859,7 @@ var CodexRemoteDomain = class {
|
|
|
22800
22859
|
} catch (error) {
|
|
22801
22860
|
this.available = false;
|
|
22802
22861
|
this.state = "restarting";
|
|
22803
|
-
this.unavailableCode =
|
|
22862
|
+
this.unavailableCode = errorCode3(error);
|
|
22804
22863
|
this.logger.warn("Codex App Server restart failed", {
|
|
22805
22864
|
attempt: this.restartAttempt,
|
|
22806
22865
|
code: this.unavailableCode
|
|
@@ -23013,7 +23072,7 @@ var CodexRemoteDomain = class {
|
|
|
23013
23072
|
this.logger.warn("Codex history read fallback", {
|
|
23014
23073
|
connectionId: maskId(connectionId),
|
|
23015
23074
|
stage,
|
|
23016
|
-
code:
|
|
23075
|
+
code: errorCode3(error)
|
|
23017
23076
|
});
|
|
23018
23077
|
}
|
|
23019
23078
|
async assertResultThreadAllowed(result) {
|
|
@@ -23084,7 +23143,7 @@ var CodexRemoteDomain = class {
|
|
|
23084
23143
|
this.approvalExpiryTimer = setTimeout(() => {
|
|
23085
23144
|
this.approvalExpiryTimer = void 0;
|
|
23086
23145
|
void this.expireApprovals().catch((error) => {
|
|
23087
|
-
this.logger.warn("Codex approval expiry failed", { code:
|
|
23146
|
+
this.logger.warn("Codex approval expiry failed", { code: errorCode3(error) });
|
|
23088
23147
|
});
|
|
23089
23148
|
}, Math.max(0, nextExpiry - Date.now()));
|
|
23090
23149
|
this.approvalExpiryTimer.unref?.();
|
|
@@ -23174,7 +23233,7 @@ var CodexRemoteDomain = class {
|
|
|
23174
23233
|
let candidate;
|
|
23175
23234
|
try {
|
|
23176
23235
|
candidate = await realpath(path);
|
|
23177
|
-
if (!(await
|
|
23236
|
+
if (!(await stat4(candidate)).isDirectory()) throw new Error("not a directory");
|
|
23178
23237
|
} catch {
|
|
23179
23238
|
throw new RpcError("CODEX_PATH_NOT_ALLOWED", "The CodeX working directory is not available as a Workspace.");
|
|
23180
23239
|
}
|
|
@@ -23194,7 +23253,7 @@ var CodexRemoteDomain = class {
|
|
|
23194
23253
|
}
|
|
23195
23254
|
try {
|
|
23196
23255
|
const canonical = await realpath(resolve(path));
|
|
23197
|
-
if (!(await
|
|
23256
|
+
if (!(await stat4(canonical)).isDirectory()) throw new Error("not a directory");
|
|
23198
23257
|
return canonical;
|
|
23199
23258
|
} catch {
|
|
23200
23259
|
throw new RpcError("CODEX_PATH_NOT_ALLOWED", "The CodeX project directory must be an existing absolute directory.");
|
|
@@ -23202,7 +23261,7 @@ var CodexRemoteDomain = class {
|
|
|
23202
23261
|
}
|
|
23203
23262
|
async listCodexDirectory(path) {
|
|
23204
23263
|
const target2 = await this.resolveCodexDirectory(path);
|
|
23205
|
-
const rows = await
|
|
23264
|
+
const rows = await readdir2(target2.path, { withFileTypes: true }).catch(() => void 0);
|
|
23206
23265
|
if (rows === void 0) {
|
|
23207
23266
|
throw new RpcError("CODEX_PATH_NOT_ALLOWED", "The CodeX working directory is not available as a Workspace.");
|
|
23208
23267
|
}
|
|
@@ -23211,12 +23270,12 @@ var CodexRemoteDomain = class {
|
|
|
23211
23270
|
const child = resolve(target2.path, row.name);
|
|
23212
23271
|
let directory = row.isDirectory();
|
|
23213
23272
|
if (!directory && row.isSymbolicLink()) {
|
|
23214
|
-
directory = await
|
|
23273
|
+
directory = await stat4(child).then((value) => value.isDirectory()).catch(() => false);
|
|
23215
23274
|
}
|
|
23216
23275
|
if (!directory) continue;
|
|
23217
23276
|
try {
|
|
23218
23277
|
const canonicalChild = await realpath(child);
|
|
23219
|
-
if (!(await
|
|
23278
|
+
if (!(await stat4(canonicalChild)).isDirectory()) continue;
|
|
23220
23279
|
if (!containsCodexPath(target2.canonicalRoot, canonicalChild)) continue;
|
|
23221
23280
|
} catch {
|
|
23222
23281
|
continue;
|
|
@@ -23245,7 +23304,7 @@ var CodexRemoteDomain = class {
|
|
|
23245
23304
|
let canonicalCandidate;
|
|
23246
23305
|
try {
|
|
23247
23306
|
canonicalCandidate = await realpath(path);
|
|
23248
|
-
if (!(await
|
|
23307
|
+
if (!(await stat4(canonicalCandidate)).isDirectory()) throw new Error("not a directory");
|
|
23249
23308
|
} catch {
|
|
23250
23309
|
throw new RpcError("CODEX_PATH_NOT_ALLOWED", "The CodeX working directory is not available as a Workspace.");
|
|
23251
23310
|
}
|
|
@@ -23275,15 +23334,15 @@ function codexBinaryCandidates(configured, hostPlatform = process.platform, user
|
|
|
23275
23334
|
if (hostPlatform !== "darwin") return [configured];
|
|
23276
23335
|
const bundledCandidates = [
|
|
23277
23336
|
"/Applications/ChatGPT.app",
|
|
23278
|
-
|
|
23337
|
+
join6(userHome, "Applications", "ChatGPT.app")
|
|
23279
23338
|
].flatMap((chatGptApp) => {
|
|
23280
|
-
const codexCli =
|
|
23339
|
+
const codexCli = join6(chatGptApp, "Contents", "Resources", "codex-cli");
|
|
23281
23340
|
try {
|
|
23282
|
-
const manifest = JSON.parse(readFileSync(
|
|
23341
|
+
const manifest = JSON.parse(readFileSync(join6(codexCli, "codex-package.json"), "utf8"));
|
|
23283
23342
|
if (!isRecord11(manifest) || typeof manifest.entrypoint !== "string" || manifest.entrypoint.length === 0) {
|
|
23284
23343
|
return [];
|
|
23285
23344
|
}
|
|
23286
|
-
const candidate =
|
|
23345
|
+
const candidate = join6(codexCli, manifest.entrypoint);
|
|
23287
23346
|
if (!existsSync2(candidate)) return [];
|
|
23288
23347
|
accessSync(candidate, constants.X_OK);
|
|
23289
23348
|
return [candidate];
|
|
@@ -23294,14 +23353,14 @@ function codexBinaryCandidates(configured, hostPlatform = process.platform, user
|
|
|
23294
23353
|
return [.../* @__PURE__ */ new Set([
|
|
23295
23354
|
...bundledCandidates,
|
|
23296
23355
|
"/Applications/ChatGPT.app/Contents/Resources/codex",
|
|
23297
|
-
|
|
23356
|
+
join6(userHome, "Applications", "ChatGPT.app", "Contents", "Resources", "codex"),
|
|
23298
23357
|
configured
|
|
23299
23358
|
])];
|
|
23300
23359
|
}
|
|
23301
23360
|
function bundledWindowsCodex(userHome) {
|
|
23302
|
-
const bin =
|
|
23361
|
+
const bin = join6(userHome, "AppData", "Local", "OpenAI", "Codex", "bin");
|
|
23303
23362
|
try {
|
|
23304
|
-
const newest = readdirSync2(bin, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) =>
|
|
23363
|
+
const newest = readdirSync2(bin, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => join6(bin, entry.name, "codex.exe")).filter((candidate2) => existsSync2(candidate2)).map((candidate2) => ({ candidate: candidate2, modified: statSync2(candidate2).mtimeMs })).sort((left, right) => right.modified - left.modified);
|
|
23305
23364
|
const candidate = newest[0]?.candidate;
|
|
23306
23365
|
return candidate === void 0 ? [] : [candidate];
|
|
23307
23366
|
} catch {
|
|
@@ -23511,7 +23570,7 @@ function mapAppServerError(error) {
|
|
|
23511
23570
|
}
|
|
23512
23571
|
return new RpcError("CODEX_UPSTREAM_ERROR", "Codex App Server could not complete the request.");
|
|
23513
23572
|
}
|
|
23514
|
-
function
|
|
23573
|
+
function errorCode3(error) {
|
|
23515
23574
|
if (error instanceof RpcError || error instanceof CodexAppServerError) return error.code;
|
|
23516
23575
|
return "CODEX_START_FAILED";
|
|
23517
23576
|
}
|
|
@@ -24714,7 +24773,7 @@ var HostServerConnection = class {
|
|
|
24714
24773
|
await this.connectOnce();
|
|
24715
24774
|
delayMs = this.config.reconnect.initialDelayMs;
|
|
24716
24775
|
} catch (error) {
|
|
24717
|
-
const code =
|
|
24776
|
+
const code = errorCode4(error);
|
|
24718
24777
|
if (code === "CREDENTIALS_REFRESHED") continue;
|
|
24719
24778
|
this.terminalError = code;
|
|
24720
24779
|
this.logger.warn("server control connection failed", {
|
|
@@ -24729,7 +24788,7 @@ var HostServerConnection = class {
|
|
|
24729
24788
|
try {
|
|
24730
24789
|
await this.api.clearAuthorization();
|
|
24731
24790
|
} catch (clearError) {
|
|
24732
|
-
this.logger.error("failed to clear revoked Host authorization", { code:
|
|
24791
|
+
this.logger.error("failed to clear revoked Host authorization", { code: errorCode4(clearError) });
|
|
24733
24792
|
}
|
|
24734
24793
|
}
|
|
24735
24794
|
if (TERMINAL_AUTH_ERRORS.has(code) || !this.config.reconnect.enabled) return;
|
|
@@ -24809,7 +24868,7 @@ var HostServerConnection = class {
|
|
|
24809
24868
|
if (!acknowledged) throw new ControlConnectionError("INVALID_MESSAGE", "Server sent a frame before hello.ack.");
|
|
24810
24869
|
await this.handleFrame(frame);
|
|
24811
24870
|
}).catch((error) => {
|
|
24812
|
-
const code =
|
|
24871
|
+
const code = errorCode4(error);
|
|
24813
24872
|
this.terminalError = code;
|
|
24814
24873
|
this.logger.error("server control frame failed", {
|
|
24815
24874
|
code,
|
|
@@ -24921,7 +24980,7 @@ var HostServerConnection = class {
|
|
|
24921
24980
|
this.sendControl("connect.rejected", { connectionId: payload.connectionId });
|
|
24922
24981
|
this.logger.warn("connection rejected by account authorization", {
|
|
24923
24982
|
clientDeviceId: shortId3(payload.clientDeviceId),
|
|
24924
|
-
code:
|
|
24983
|
+
code: errorCode4(error)
|
|
24925
24984
|
});
|
|
24926
24985
|
return;
|
|
24927
24986
|
}
|
|
@@ -25070,7 +25129,7 @@ var HostServerConnection = class {
|
|
|
25070
25129
|
} catch (error) {
|
|
25071
25130
|
this.logger.warn("TURN credentials unavailable; trying direct candidates", {
|
|
25072
25131
|
connectionId: shortId3(tunnel.connectionId),
|
|
25073
|
-
code:
|
|
25132
|
+
code: errorCode4(error)
|
|
25074
25133
|
});
|
|
25075
25134
|
}
|
|
25076
25135
|
if (!tunnel.preferredTransports.includes("turn")) iceServers = stunOnlyIceServers(iceServers);
|
|
@@ -25507,11 +25566,11 @@ function rtcDiagnostics(rtc) {
|
|
|
25507
25566
|
return void 0;
|
|
25508
25567
|
}
|
|
25509
25568
|
}
|
|
25510
|
-
function
|
|
25569
|
+
function errorCode4(error) {
|
|
25511
25570
|
return error instanceof ServerApiError || error instanceof ControlConnectionError ? error.code : "CONNECTION_FAILED";
|
|
25512
25571
|
}
|
|
25513
25572
|
function isRetryable(error) {
|
|
25514
|
-
return !TERMINAL_AUTH_ERRORS.has(
|
|
25573
|
+
return !TERMINAL_AUTH_ERRORS.has(errorCode4(error)) && (!(error instanceof ServerApiError) || error.retryable);
|
|
25515
25574
|
}
|
|
25516
25575
|
function closeCode(code) {
|
|
25517
25576
|
if (code === 4002) return "AUTH_INVALID";
|
|
@@ -25523,7 +25582,7 @@ function closeCode(code) {
|
|
|
25523
25582
|
}
|
|
25524
25583
|
|
|
25525
25584
|
// src/remote-directory-browser.ts
|
|
25526
|
-
import { readdir as
|
|
25585
|
+
import { readdir as readdir3, stat as stat5 } from "node:fs/promises";
|
|
25527
25586
|
import { homedir as homedir3, platform as platform2 } from "node:os";
|
|
25528
25587
|
import { basename as basename3, dirname as dirname5, isAbsolute as isAbsolute3, parse, resolve as resolve2 } from "node:path";
|
|
25529
25588
|
var MAX_ENTRIES = 500;
|
|
@@ -25532,13 +25591,13 @@ async function listRemoteDirectory(path, signal) {
|
|
|
25532
25591
|
const home = resolve2(homedir3());
|
|
25533
25592
|
const target2 = path === void 0 || path.trim() === "" ? home : resolve2(path);
|
|
25534
25593
|
if (!isAbsolute3(target2)) throw new Error("The remote directory path must be absolute.");
|
|
25535
|
-
const rows = await
|
|
25594
|
+
const rows = await readdir3(target2, { withFileTypes: true });
|
|
25536
25595
|
const directories = [];
|
|
25537
25596
|
for (const row of rows) {
|
|
25538
25597
|
signal?.throwIfAborted();
|
|
25539
25598
|
const child = resolve2(target2, row.name);
|
|
25540
25599
|
let directory = row.isDirectory();
|
|
25541
|
-
if (!directory && row.isSymbolicLink()) directory = await
|
|
25600
|
+
if (!directory && row.isSymbolicLink()) directory = await stat5(child).then((value) => value.isDirectory()).catch(() => false);
|
|
25542
25601
|
if (!directory) continue;
|
|
25543
25602
|
directories.push({ name: row.name, path: child, hidden: platform2() !== "win32" && row.name.startsWith(".") });
|
|
25544
25603
|
}
|
|
@@ -26963,8 +27022,8 @@ import { execFileSync as execFileSync2 } from "node:child_process";
|
|
|
26963
27022
|
|
|
26964
27023
|
// src/codex-workspace-bridge.ts
|
|
26965
27024
|
import { spawn as spawn4 } from "node:child_process";
|
|
26966
|
-
import { realpath as realpath2, lstat, readdir as
|
|
26967
|
-
import { isAbsolute as isAbsolute4, join as
|
|
27025
|
+
import { realpath as realpath2, lstat, readdir as readdir4, readFile as readFile4, stat as stat6, watch } from "node:fs/promises";
|
|
27026
|
+
import { isAbsolute as isAbsolute4, join as join7, relative as relative2, resolve as resolve3 } from "node:path";
|
|
26968
27027
|
var MAX_READ_BYTES = 4 * 1024 * 1024;
|
|
26969
27028
|
var MAX_INPUT_BYTES = 64 * 1024;
|
|
26970
27029
|
var MAX_COLS = 240;
|
|
@@ -27051,17 +27110,17 @@ var CodexWorkspaceBridge = class {
|
|
|
27051
27110
|
const target2 = await this.safePath(root, path, endpoint === "workspaceFiles/list");
|
|
27052
27111
|
try {
|
|
27053
27112
|
if (endpoint === "workspaceFiles/list") {
|
|
27054
|
-
const entries = await
|
|
27113
|
+
const entries = await readdir4(target2, { withFileTypes: true });
|
|
27055
27114
|
const result = [];
|
|
27056
27115
|
for (const entry of entries.slice(0, 500)) {
|
|
27057
|
-
const item =
|
|
27116
|
+
const item = join7(target2, entry.name);
|
|
27058
27117
|
const info2 = await lstat(item);
|
|
27059
27118
|
if (info2.isSymbolicLink()) continue;
|
|
27060
27119
|
result.push({ name: entry.name, type: info2.isDirectory() ? "directory" : info2.isFile() ? "file" : "other", ...info2.isFile() ? { size: info2.size } : {} });
|
|
27061
27120
|
}
|
|
27062
27121
|
return { ok: true, value: { path, entries: result, truncated: entries.length > 500 } };
|
|
27063
27122
|
}
|
|
27064
|
-
const info = await
|
|
27123
|
+
const info = await stat6(target2);
|
|
27065
27124
|
const absolutePath = target2;
|
|
27066
27125
|
const version = `${info.mtimeMs}:${info.size}`;
|
|
27067
27126
|
if (endpoint === "workspaceFiles/stat") {
|
|
@@ -27268,7 +27327,7 @@ var CodexWorkspaceBridge = class {
|
|
|
27268
27327
|
if (!cwd) throw new RpcError("CODEX_WORKSPACE_UNAVAILABLE", "The CodeX thread has no available workspace.");
|
|
27269
27328
|
try {
|
|
27270
27329
|
const root = await realpath2(cwd);
|
|
27271
|
-
const info = await
|
|
27330
|
+
const info = await stat6(root);
|
|
27272
27331
|
if (!info.isDirectory()) throw new Error();
|
|
27273
27332
|
return root;
|
|
27274
27333
|
} catch {
|
|
@@ -27757,12 +27816,12 @@ var HostPluginRuntime = class {
|
|
|
27757
27816
|
}
|
|
27758
27817
|
async readHarnessVersion() {
|
|
27759
27818
|
let reportedVersion;
|
|
27760
|
-
let
|
|
27819
|
+
let errorCode6;
|
|
27761
27820
|
try {
|
|
27762
27821
|
const response = await this.apiProxy?.host.describe({ rpcId: randomUUID3(), payload: {} });
|
|
27763
27822
|
if (response === void 0) throw new Error("ApiProxy is unavailable");
|
|
27764
27823
|
if (!response.result.ok) {
|
|
27765
|
-
|
|
27824
|
+
errorCode6 = response.result.error.code;
|
|
27766
27825
|
} else {
|
|
27767
27826
|
reportedVersion = normalizeHarnessVersion(response.result.value.version);
|
|
27768
27827
|
}
|
|
@@ -27771,7 +27830,7 @@ var HostPluginRuntime = class {
|
|
|
27771
27830
|
const distributionVersion = reportedVersion === void 0 || reportedVersion === "0.0.1" ? await readHarnessDistributionVersion() : void 0;
|
|
27772
27831
|
const version = selectHarnessVersion(reportedVersion, distributionVersion);
|
|
27773
27832
|
if (version !== void 0) return version;
|
|
27774
|
-
this.logger.warn("Harness version is unavailable",
|
|
27833
|
+
this.logger.warn("Harness version is unavailable", errorCode6 === void 0 ? void 0 : { code: errorCode6 });
|
|
27775
27834
|
return void 0;
|
|
27776
27835
|
}
|
|
27777
27836
|
hostCapabilities() {
|
|
@@ -27827,9 +27886,9 @@ function isPlainRecord(value) {
|
|
|
27827
27886
|
}
|
|
27828
27887
|
|
|
27829
27888
|
// src/cli.ts
|
|
27830
|
-
import { stat as
|
|
27889
|
+
import { stat as stat7 } from "node:fs/promises";
|
|
27831
27890
|
import { hostname as hostname3 } from "node:os";
|
|
27832
|
-
import { join as
|
|
27891
|
+
import { join as join8 } from "node:path";
|
|
27833
27892
|
var QR_POLL_INTERVAL_MS = 2e3;
|
|
27834
27893
|
var TERMINAL_QR_MARGIN = 4;
|
|
27835
27894
|
async function runCli(args = process.argv.slice(2), dependencies = {}) {
|
|
@@ -27917,7 +27976,7 @@ async function status(args, runtime) {
|
|
|
27917
27976
|
`Server: ${serverUrl}`,
|
|
27918
27977
|
"Host control: enabled (dsh-TUI default)"
|
|
27919
27978
|
];
|
|
27920
|
-
if (!await exists3(
|
|
27979
|
+
if (!await exists3(join8(directory, "device.json"))) {
|
|
27921
27980
|
lines.push("Device: not initialized", "Authorization: logged out", "Credential: unavailable");
|
|
27922
27981
|
write(runtime.stdout, `${lines.join("\n")}
|
|
27923
27982
|
|
|
@@ -27961,7 +28020,7 @@ async function logout(args, runtime) {
|
|
|
27961
28020
|
const serverUrl = selectedServer();
|
|
27962
28021
|
const root = new IdentityStore({ env: runtime.env }).directory;
|
|
27963
28022
|
const directory = serverStorageDirectory(root, serverUrl, "host");
|
|
27964
|
-
if (!await exists3(
|
|
28023
|
+
if (!await exists3(join8(directory, "device.json"))) {
|
|
27965
28024
|
await new ServerCredentialStore(directory).clear();
|
|
27966
28025
|
write(runtime.stdout, "This Host is already logged out.\n");
|
|
27967
28026
|
return 0;
|
|
@@ -28081,7 +28140,7 @@ function terminalLink(url, target2) {
|
|
|
28081
28140
|
}
|
|
28082
28141
|
async function exists3(path) {
|
|
28083
28142
|
try {
|
|
28084
|
-
await
|
|
28143
|
+
await stat7(path);
|
|
28085
28144
|
return true;
|
|
28086
28145
|
} catch (error) {
|
|
28087
28146
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") return false;
|
|
@@ -28148,7 +28207,7 @@ function installTuiRemoteCommand(ctx, resolveTarget) {
|
|
|
28148
28207
|
} catch (error) {
|
|
28149
28208
|
return {
|
|
28150
28209
|
kind: "error",
|
|
28151
|
-
text: `Local Remote Host credentials were cleared, but Server revocation failed (${
|
|
28210
|
+
text: `Local Remote Host credentials were cleared, but Server revocation failed (${errorCode5(error)}).`
|
|
28152
28211
|
};
|
|
28153
28212
|
}
|
|
28154
28213
|
}
|
|
@@ -28204,7 +28263,7 @@ function registerOptional(ctx, feature, register2) {
|
|
|
28204
28263
|
return register2() ?? (() => {
|
|
28205
28264
|
});
|
|
28206
28265
|
} catch (error) {
|
|
28207
|
-
ctx.logger.warn(`dsh-TUI Remote ${feature} is unavailable`, { code:
|
|
28266
|
+
ctx.logger.warn(`dsh-TUI Remote ${feature} is unavailable`, { code: errorCode5(error) });
|
|
28208
28267
|
return () => {
|
|
28209
28268
|
};
|
|
28210
28269
|
}
|
|
@@ -28317,7 +28376,7 @@ var RemoteLoginController = class {
|
|
|
28317
28376
|
await this.poll(attempt, provider, session, target2.runtime);
|
|
28318
28377
|
} catch (error) {
|
|
28319
28378
|
if (attempt !== this.attempt) return;
|
|
28320
|
-
this.update({ phase: "error", provider, error:
|
|
28379
|
+
this.update({ phase: "error", provider, error: errorCode5(error) });
|
|
28321
28380
|
}
|
|
28322
28381
|
}
|
|
28323
28382
|
async poll(attempt, provider, session, runtime) {
|
|
@@ -28435,7 +28494,7 @@ function terminalLink2(url) {
|
|
|
28435
28494
|
function printableWidth(value) {
|
|
28436
28495
|
return value.replace(/\u001B\[[0-9;]*m/gu, "").length;
|
|
28437
28496
|
}
|
|
28438
|
-
function
|
|
28497
|
+
function errorCode5(error) {
|
|
28439
28498
|
if (error instanceof ServerApiError) return error.code;
|
|
28440
28499
|
if (error instanceof Error && "code" in error && typeof error.code === "string") return error.code;
|
|
28441
28500
|
return "CONNECTION_FAILED";
|