ds-harness-remote 0.4.26 → 0.4.27
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 +6 -6
- package/dist/client-runtime.d.ts +11 -0
- package/dist/client-runtime.d.ts.map +1 -1
- package/dist/client-runtime.js +62 -0
- package/dist/client-runtime.js.map +1 -1
- package/dist/client.github.js +18 -51
- package/dist/client.js +18 -51
- package/dist/client.js.map +1 -1
- package/dist/codex/domain.d.ts.map +1 -1
- package/dist/codex/domain.js +22 -0
- package/dist/codex/domain.js.map +1 -1
- package/dist/config.d.ts +6 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +4 -1
- package/dist/config.js.map +1 -1
- package/dist/control-runtime.d.ts.map +1 -1
- package/dist/control-runtime.js +16 -1
- package/dist/control-runtime.js.map +1 -1
- package/dist/harness-version.d.ts.map +1 -1
- package/dist/harness-version.js +16 -3
- package/dist/harness-version.js.map +1 -1
- package/dist/index.js +154 -31
- package/dist/index.js.map +3 -3
- package/dist/loopback-host.d.ts +2 -2
- package/dist/loopback-host.d.ts.map +1 -1
- package/dist/loopback-host.js +41 -16
- package/dist/loopback-host.js.map +1 -1
- package/dist/server-api.d.ts +2 -0
- package/dist/server-api.d.ts.map +1 -1
- package/dist/server-api.js +5 -0
- package/dist/server-api.js.map +1 -1
- package/dist/server-connection.d.ts.map +1 -1
- package/dist/server-connection.js +8 -0
- package/dist/server-connection.js.map +1 -1
- package/dist/service.d.ts +4 -1
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +19 -5
- package/dist/service.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 +1 -1
package/dist/index.js
CHANGED
|
@@ -4646,22 +4646,22 @@ function headerPairs(headers) {
|
|
|
4646
4646
|
return Object.entries(headers).flatMap(([name2, value]) => value === void 0 ? [] : (Array.isArray(value) ? value : [value]).map((item) => [name2, item]));
|
|
4647
4647
|
}
|
|
4648
4648
|
var LoopbackHost = class {
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
timer;
|
|
4653
|
-
constructor(ports) {
|
|
4654
|
-
this.ports = [...ports];
|
|
4649
|
+
constructor(getPorts, onClose) {
|
|
4650
|
+
this.onClose = onClose;
|
|
4651
|
+
this.ports = [...getPorts()];
|
|
4655
4652
|
this.timer = setInterval(() => {
|
|
4656
4653
|
for (const [id4, handle] of this.handles) if (Date.now() - handle.touched > 6e4) this.close(id4);
|
|
4657
4654
|
}, 1e4);
|
|
4658
4655
|
this.timer.unref();
|
|
4659
4656
|
}
|
|
4657
|
+
handles = /* @__PURE__ */ new Map();
|
|
4658
|
+
ports;
|
|
4659
|
+
closed = false;
|
|
4660
|
+
timer;
|
|
4660
4661
|
setPorts(ports) {
|
|
4661
4662
|
this.ports = [...ports];
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
return this.ports.length > 0;
|
|
4663
|
+
const allowed = new Set(ports);
|
|
4664
|
+
for (const [id4, handle] of this.handles) if (!allowed.has(handle.port)) this.close(id4);
|
|
4665
4665
|
}
|
|
4666
4666
|
async call(input2) {
|
|
4667
4667
|
if (this.closed) throw new RpcError("TRANSPORT_CLOSED", "Preview connection closed.");
|
|
@@ -4678,10 +4678,13 @@ var LoopbackHost = class {
|
|
|
4678
4678
|
);
|
|
4679
4679
|
if (this.handles.has(value.id)) throw new RpcError("REQUEST_CONFLICT", "Preview handle is already in use.");
|
|
4680
4680
|
if (this.handles.size >= LOOPBACK_MAX_CONNECTIONS) throw new RpcError("RATE_LIMITED", "Too many active preview requests.");
|
|
4681
|
+
let openedHandle;
|
|
4681
4682
|
try {
|
|
4682
|
-
|
|
4683
|
+
const opening = value.op === "http.open" ? this.openHttp(value) : this.openWs(value);
|
|
4684
|
+
openedHandle = this.handles.get(value.id);
|
|
4685
|
+
return await opening;
|
|
4683
4686
|
} catch {
|
|
4684
|
-
this.close(value.id);
|
|
4687
|
+
this.close(value.id, openedHandle);
|
|
4685
4688
|
throw new RpcError("LOOPBACK_UNAVAILABLE", "The allowed loopback service did not respond. Check that it is running on the Host.");
|
|
4686
4689
|
}
|
|
4687
4690
|
}
|
|
@@ -4712,6 +4715,7 @@ var LoopbackHost = class {
|
|
|
4712
4715
|
response.once("close", ready);
|
|
4713
4716
|
response.once("error", fail4);
|
|
4714
4717
|
});
|
|
4718
|
+
if (this.handles.get(value.id) !== handle) throw new Error("closed");
|
|
4715
4719
|
chunk = response.read(Math.min(response.readableLength || LOOPBACK_CHUNK_BYTES, LOOPBACK_CHUNK_BYTES));
|
|
4716
4720
|
if (chunk === null && response.readableLength > 0) chunk = response.read(Math.min(response.readableLength, LOOPBACK_CHUNK_BYTES));
|
|
4717
4721
|
}
|
|
@@ -4719,10 +4723,10 @@ var LoopbackHost = class {
|
|
|
4719
4723
|
if (done && !response.complete) throw new Error("truncated");
|
|
4720
4724
|
handle.total += chunk?.length ?? 0;
|
|
4721
4725
|
if (handle.total > 64 * 1024 * 1024) throw new Error("size");
|
|
4722
|
-
if (done) this.close(value.id);
|
|
4726
|
+
if (done) this.close(value.id, handle);
|
|
4723
4727
|
return { data: chunk?.toString("base64") ?? "", done };
|
|
4724
4728
|
} catch {
|
|
4725
|
-
this.close(value.id);
|
|
4729
|
+
this.close(value.id, handle);
|
|
4726
4730
|
throw new RpcError("LOOPBACK_READ_FAILED", "Preview response ended or exceeded its limit; reload to retry.");
|
|
4727
4731
|
} finally {
|
|
4728
4732
|
handle.reading = false;
|
|
@@ -4762,7 +4766,7 @@ var LoopbackHost = class {
|
|
|
4762
4766
|
}
|
|
4763
4767
|
handle.bytes -= bytes;
|
|
4764
4768
|
const closed = handle.closed && handle.messages.length === 0;
|
|
4765
|
-
if (closed) this.close(value.id);
|
|
4769
|
+
if (closed) this.close(value.id, handle);
|
|
4766
4770
|
return { messages, closed };
|
|
4767
4771
|
} finally {
|
|
4768
4772
|
handle.reading = false;
|
|
@@ -4772,12 +4776,15 @@ var LoopbackHost = class {
|
|
|
4772
4776
|
throw new RpcError("INVALID_MESSAGE", "Preview handle kind does not match the operation.");
|
|
4773
4777
|
}
|
|
4774
4778
|
closeAll() {
|
|
4779
|
+
if (this.closed) return;
|
|
4775
4780
|
this.closed = true;
|
|
4776
4781
|
clearInterval(this.timer);
|
|
4777
4782
|
for (const id4 of this.handles.keys()) this.close(id4);
|
|
4783
|
+
this.onClose?.();
|
|
4778
4784
|
}
|
|
4779
|
-
close(id4) {
|
|
4785
|
+
close(id4, expected) {
|
|
4780
4786
|
const handle = this.handles.get(id4);
|
|
4787
|
+
if (expected !== void 0 && handle !== expected) return;
|
|
4781
4788
|
this.handles.delete(id4);
|
|
4782
4789
|
if (handle?.kind === "http") {
|
|
4783
4790
|
handle.response?.destroy();
|
|
@@ -4785,6 +4792,8 @@ var LoopbackHost = class {
|
|
|
4785
4792
|
}
|
|
4786
4793
|
if (handle?.kind === "ws") {
|
|
4787
4794
|
handle.closed = true;
|
|
4795
|
+
handle.messages = [];
|
|
4796
|
+
handle.bytes = 0;
|
|
4788
4797
|
handle.wake?.();
|
|
4789
4798
|
handle.socket.terminate();
|
|
4790
4799
|
}
|
|
@@ -4794,6 +4803,7 @@ var LoopbackHost = class {
|
|
|
4794
4803
|
if ((body?.length ?? 0) > LOOPBACK_MAX_BODY_BYTES) throw new Error("body limit");
|
|
4795
4804
|
return new Promise((resolve4, reject) => {
|
|
4796
4805
|
const headers = Object.fromEntries(cleanHeaders(value.headers));
|
|
4806
|
+
let handle;
|
|
4797
4807
|
const request = httpRequest({
|
|
4798
4808
|
hostname: "127.0.0.1",
|
|
4799
4809
|
port: value.port,
|
|
@@ -4803,8 +4813,7 @@ var LoopbackHost = class {
|
|
|
4803
4813
|
agent: false,
|
|
4804
4814
|
maxHeaderSize: 32 * 1024
|
|
4805
4815
|
}, (response) => {
|
|
4806
|
-
|
|
4807
|
-
if (handle?.kind !== "http") {
|
|
4816
|
+
if (this.handles.get(value.id) !== handle) {
|
|
4808
4817
|
response.destroy();
|
|
4809
4818
|
return;
|
|
4810
4819
|
}
|
|
@@ -4821,7 +4830,8 @@ var LoopbackHost = class {
|
|
|
4821
4830
|
clearTimeout(timer);
|
|
4822
4831
|
reject(new Error("upstream"));
|
|
4823
4832
|
});
|
|
4824
|
-
|
|
4833
|
+
handle = { kind: "http", port: value.port, request, reading: false, total: 0, touched: Date.now() };
|
|
4834
|
+
this.handles.set(value.id, handle);
|
|
4825
4835
|
request.end(body);
|
|
4826
4836
|
});
|
|
4827
4837
|
}
|
|
@@ -4834,13 +4844,14 @@ var LoopbackHost = class {
|
|
|
4834
4844
|
maxPayload: LOOPBACK_MAX_WS_BYTES,
|
|
4835
4845
|
perMessageDeflate: false
|
|
4836
4846
|
});
|
|
4837
|
-
const handle = { kind: "ws", socket, messages: [], bytes: 0, reading: false, closed: false, touched: Date.now() };
|
|
4847
|
+
const handle = { kind: "ws", port: value.port, socket, messages: [], bytes: 0, reading: false, closed: false, touched: Date.now() };
|
|
4838
4848
|
this.handles.set(value.id, handle);
|
|
4839
4849
|
socket.once("open", () => resolve4({ protocol: socket.protocol }));
|
|
4840
4850
|
socket.on("message", (data2, binary) => {
|
|
4851
|
+
if (handle.closed) return;
|
|
4841
4852
|
const message = { data: Buffer.from(data2).toString("base64"), binary };
|
|
4842
4853
|
if (handle.bytes + message.data.length > 1024 * 1024 || handle.messages.length >= 256) {
|
|
4843
|
-
this.close(value.id);
|
|
4854
|
+
this.close(value.id, handle);
|
|
4844
4855
|
return;
|
|
4845
4856
|
}
|
|
4846
4857
|
handle.messages.push(message);
|
|
@@ -14745,7 +14756,7 @@ function uuidV7(now = Date.now()) {
|
|
|
14745
14756
|
}
|
|
14746
14757
|
|
|
14747
14758
|
// src/harness-version.ts
|
|
14748
|
-
import { readFile } from "node:fs/promises";
|
|
14759
|
+
import { readFile, realpath as realpathEntrypoint } from "node:fs/promises";
|
|
14749
14760
|
import { createRequire } from "node:module";
|
|
14750
14761
|
import { dirname, isAbsolute, join } from "node:path";
|
|
14751
14762
|
var LEGACY_PLACEHOLDER_VERSION = "0.0.1";
|
|
@@ -14780,7 +14791,13 @@ async function readHarnessManifestVersion(manifestPath) {
|
|
|
14780
14791
|
}
|
|
14781
14792
|
async function readHarnessDistributionVersion(entrypoint = process.argv[1]) {
|
|
14782
14793
|
if (entrypoint === void 0 || !isAbsolute(entrypoint)) return void 0;
|
|
14783
|
-
let
|
|
14794
|
+
let resolvedEntrypoint = entrypoint;
|
|
14795
|
+
try {
|
|
14796
|
+
resolvedEntrypoint = await realpathEntrypoint(entrypoint);
|
|
14797
|
+
} catch {
|
|
14798
|
+
}
|
|
14799
|
+
if (!isAbsolute(resolvedEntrypoint)) return void 0;
|
|
14800
|
+
let directory = dirname(resolvedEntrypoint);
|
|
14784
14801
|
for (let depth = 0; depth < 8; depth += 1) {
|
|
14785
14802
|
const version = await readHarnessManifestVersion(join(directory, "package.json"));
|
|
14786
14803
|
if (version !== void 0) return version;
|
|
@@ -14789,7 +14806,7 @@ async function readHarnessDistributionVersion(entrypoint = process.argv[1]) {
|
|
|
14789
14806
|
directory = parent;
|
|
14790
14807
|
}
|
|
14791
14808
|
try {
|
|
14792
|
-
return await readHarnessManifestVersion(createRequire(
|
|
14809
|
+
return await readHarnessManifestVersion(createRequire(resolvedEntrypoint).resolve(`${HARNESS_PACKAGE_NAME}/package.json`));
|
|
14793
14810
|
} catch {
|
|
14794
14811
|
return void 0;
|
|
14795
14812
|
}
|
|
@@ -18286,6 +18303,7 @@ var entryConfigSchema = s.object({
|
|
|
18286
18303
|
serverUrl: s.string(),
|
|
18287
18304
|
deviceName: s.string(),
|
|
18288
18305
|
terminal: s.object({ enabled: s.boolean() }),
|
|
18306
|
+
hostControl: s.object({ enabled: s.boolean() }),
|
|
18289
18307
|
loopback: s.object({ ports: s.array(s.number()) }),
|
|
18290
18308
|
forceRelay: s.boolean(),
|
|
18291
18309
|
logLevel: s.union(["debug", "info", "warn", "error"]),
|
|
@@ -18322,6 +18340,7 @@ var configSchema = external_exports.object({
|
|
|
18322
18340
|
serverUrl: external_exports.string().url().optional(),
|
|
18323
18341
|
deviceName: external_exports.string().trim().min(1).max(80).optional(),
|
|
18324
18342
|
terminal: external_exports.object({ enabled: external_exports.boolean().optional() }).strict().optional(),
|
|
18343
|
+
hostControl: external_exports.object({ enabled: external_exports.boolean().optional() }).strict().optional(),
|
|
18325
18344
|
loopback: external_exports.object({ ports: external_exports.array(external_exports.number().int().min(1024).max(65535)).max(16).optional() }).strict().optional(),
|
|
18326
18345
|
forceRelay: external_exports.boolean().optional(),
|
|
18327
18346
|
logLevel: external_exports.enum(["debug", "info", "warn", "error"]).optional(),
|
|
@@ -18347,7 +18366,8 @@ function resolveConfig(input2 = {}, env = process.env) {
|
|
|
18347
18366
|
role: parsed.role ?? "host",
|
|
18348
18367
|
...serverUrl === void 0 ? {} : { serverUrl },
|
|
18349
18368
|
deviceName: parsed.deviceName ?? hostname(),
|
|
18350
|
-
|
|
18369
|
+
hostControl: { enabled: parsed.hostControl?.enabled ?? true },
|
|
18370
|
+
terminal: { enabled: parsed.terminal?.enabled ?? (env.DSH_REMOTE_TERMINAL_ENABLED === void 0 || env.DSH_REMOTE_TERMINAL_ENABLED === "true") },
|
|
18351
18371
|
loopback: { ports: [...new Set(parsed.loopback?.ports ?? [])] },
|
|
18352
18372
|
forceRelay: parsed.forceRelay ?? false,
|
|
18353
18373
|
logLevel: parsed.logLevel ?? "info",
|
|
@@ -18387,7 +18407,7 @@ function normalizeServerUrl(value) {
|
|
|
18387
18407
|
}
|
|
18388
18408
|
|
|
18389
18409
|
// src/version.ts
|
|
18390
|
-
var PLUGIN_VERSION = "0.4.
|
|
18410
|
+
var PLUGIN_VERSION = "0.4.27";
|
|
18391
18411
|
|
|
18392
18412
|
// src/server-api.ts
|
|
18393
18413
|
var TERMINAL_CONTROL_CHARACTERS = /[\u0000-\u001f\u007f-\u009f]/u;
|
|
@@ -18416,6 +18436,11 @@ var HostServerApi = class {
|
|
|
18416
18436
|
...this.credentials.account === void 0 ? {} : { account: this.credentials.account }
|
|
18417
18437
|
};
|
|
18418
18438
|
}
|
|
18439
|
+
/** Check the persisted device credential without issuing or refreshing one. */
|
|
18440
|
+
async hasStoredAuthorization() {
|
|
18441
|
+
const identity = this.requireIdentity();
|
|
18442
|
+
return await this.store.load(this.baseUrl, identity.deviceId) !== void 0;
|
|
18443
|
+
}
|
|
18419
18444
|
async clearAuthorization() {
|
|
18420
18445
|
this.credentials = void 0;
|
|
18421
18446
|
this.credentialsPromise = void 0;
|
|
@@ -20061,6 +20086,13 @@ var REMOTE_COMMAND_LIST_MIN_VERSION = [0, 3, 16];
|
|
|
20061
20086
|
var REMOTE_FILE_VIEWER_MIN_VERSION = [0, 3, 17];
|
|
20062
20087
|
var DIRECT_WEBRTC_NEGOTIATE_TIMEOUT_MS = 12e3;
|
|
20063
20088
|
var DIRECT_LAN_PROGRESS_DISPLAY_MS = 1400;
|
|
20089
|
+
var HOST_AUTHORIZATION_ERRORS = /* @__PURE__ */ new Set([
|
|
20090
|
+
"ACCOUNT_AUTH_REQUIRED",
|
|
20091
|
+
"AUTH_INVALID",
|
|
20092
|
+
"DEVICE_OWNERSHIP_REQUIRED",
|
|
20093
|
+
"DEVICE_REVOKED",
|
|
20094
|
+
"TOKEN_EXPIRED"
|
|
20095
|
+
]);
|
|
20064
20096
|
var ClientModeRuntime = class {
|
|
20065
20097
|
constructor(config, identities, server, apiProxy, typertGateway, logger, host, rtcFactoryProvider = loadNodeRtcFactory) {
|
|
20066
20098
|
this.config = config;
|
|
@@ -20093,6 +20125,27 @@ var ClientModeRuntime = class {
|
|
|
20093
20125
|
deviceId: shortId(this.identity.deviceId),
|
|
20094
20126
|
fingerprint: this.identity.fingerprint
|
|
20095
20127
|
});
|
|
20128
|
+
if (this.config.hostControl?.enabled !== false && this.host !== void 0 && this.server.hasStoredAuthorization !== void 0) {
|
|
20129
|
+
try {
|
|
20130
|
+
if (await this.server.hasStoredAuthorization() && (this.host.hasStoredAuthorization === void 0 || !await this.host.hasStoredAuthorization())) {
|
|
20131
|
+
await this.authorizeHostByDefault();
|
|
20132
|
+
}
|
|
20133
|
+
} catch (error) {
|
|
20134
|
+
this.logger.warn("automatic Host authorization failed", { code: safeErrorCode(error) });
|
|
20135
|
+
}
|
|
20136
|
+
}
|
|
20137
|
+
}
|
|
20138
|
+
async authorizeHostByDefault() {
|
|
20139
|
+
try {
|
|
20140
|
+
if (this.host === void 0) return;
|
|
20141
|
+
if (this.config.hostControl?.enabled === false) return;
|
|
20142
|
+
if (this.host.hostStatus().authorized) return;
|
|
20143
|
+
if (this.host.hasStoredAuthorization !== void 0 && await this.host.hasStoredAuthorization()) return;
|
|
20144
|
+
const credentials = await this.server.authenticate(this.requireIdentity());
|
|
20145
|
+
await this.host.authorizeHostAsOwned(credentials.accessToken, credentials.account);
|
|
20146
|
+
} catch (error) {
|
|
20147
|
+
this.logger.warn("automatic Host authorization failed", { code: safeErrorCode(error) });
|
|
20148
|
+
}
|
|
20096
20149
|
}
|
|
20097
20150
|
registerControl(connection, webServer) {
|
|
20098
20151
|
return registerControlRoute(connection, (endpoint, payload, signal) => this.handleControl(endpoint, payload, signal), webServer);
|
|
@@ -20152,6 +20205,7 @@ var ClientModeRuntime = class {
|
|
|
20152
20205
|
};
|
|
20153
20206
|
}
|
|
20154
20207
|
async devices() {
|
|
20208
|
+
this.assertHostAuthorizationForDeviceDiscovery();
|
|
20155
20209
|
this.requireIdentity();
|
|
20156
20210
|
const serverDevices = await this.server.listDevices();
|
|
20157
20211
|
const remoteDevices = serverDevices.filter((device) => device.deviceId !== this.host?.hostStatus().deviceId);
|
|
@@ -20161,6 +20215,20 @@ var ClientModeRuntime = class {
|
|
|
20161
20215
|
return { ...device, ...presence };
|
|
20162
20216
|
}));
|
|
20163
20217
|
}
|
|
20218
|
+
/**
|
|
20219
|
+
* Device discovery is exposed through the local app control route. When this
|
|
20220
|
+
* installation also runs a Host, keep that route closed after the Host's
|
|
20221
|
+
* Server credential has become terminally invalid. The Client credential can
|
|
20222
|
+
* remain usable for a short time after a revoke, so checking only
|
|
20223
|
+
* `ClientServerApi.listDevices()` would otherwise leak the device directory
|
|
20224
|
+
* from a Host that the user has already been told to re-authorize.
|
|
20225
|
+
*/
|
|
20226
|
+
assertHostAuthorizationForDeviceDiscovery() {
|
|
20227
|
+
const status2 = this.host?.hostStatus();
|
|
20228
|
+
if (status2 === void 0 || status2.error === void 0 || !HOST_AUTHORIZATION_ERRORS.has(status2.error)) return;
|
|
20229
|
+
const message = status2.error === "DEVICE_REVOKED" ? "The local Host was revoked on the Server. Sign out and authorize this Host again." : "The local Host authorization is no longer valid. Sign out and authorize this Host again.";
|
|
20230
|
+
throw new ClientModeError(status2.error, message);
|
|
20231
|
+
}
|
|
20164
20232
|
async authorizeClientWithAccount(email, password) {
|
|
20165
20233
|
let authorization;
|
|
20166
20234
|
try {
|
|
@@ -20171,6 +20239,7 @@ var ClientModeRuntime = class {
|
|
|
20171
20239
|
this.server.bindIdentity(this.identity);
|
|
20172
20240
|
authorization = await this.server.authorizeWithAccount(this.identity, email, password);
|
|
20173
20241
|
}
|
|
20242
|
+
await this.authorizeHostByDefault();
|
|
20174
20243
|
this.logger.info("Client account authorized");
|
|
20175
20244
|
return authorization;
|
|
20176
20245
|
}
|
|
@@ -20185,6 +20254,7 @@ var ClientModeRuntime = class {
|
|
|
20185
20254
|
return this.identity;
|
|
20186
20255
|
});
|
|
20187
20256
|
if (result.status === "complete") this.logger.info("Client account authorized with QR login");
|
|
20257
|
+
if (result.status === "complete") await this.authorizeHostByDefault();
|
|
20188
20258
|
return result;
|
|
20189
20259
|
}
|
|
20190
20260
|
async clearClientAuthorization() {
|
|
@@ -21518,6 +21588,16 @@ var PluginControlRuntime = class {
|
|
|
21518
21588
|
if (endpoint === "settings.acp.add") return ok3(await this.addAcp(payload));
|
|
21519
21589
|
if (endpoint === "settings.acp.remove") return ok3(await this.removeAcp(payload));
|
|
21520
21590
|
if (endpoint === "settings.logout") return ok3(await this.logout());
|
|
21591
|
+
if (endpoint === "host.authorization.set" && this.client !== void 0) {
|
|
21592
|
+
const value = record5(payload);
|
|
21593
|
+
if (typeof value.enabled !== "boolean") throw new ClientModeError("INVALID_MESSAGE", "Host authorization state is required.");
|
|
21594
|
+
const status2 = await this.client.setHostAuthorization(value.enabled);
|
|
21595
|
+
if (this.settings !== void 0) {
|
|
21596
|
+
const current = resolveConfig(this.settings.get());
|
|
21597
|
+
await this.settings.replace(editableConfig({ ...current, hostControl: { enabled: value.enabled } }));
|
|
21598
|
+
}
|
|
21599
|
+
return ok3(status2);
|
|
21600
|
+
}
|
|
21521
21601
|
if (endpoint === "host.reconnect") {
|
|
21522
21602
|
if (this.host === void 0) throw new ClientModeError("METHOD_NOT_ALLOWED", "This plugin is not running as a Host.");
|
|
21523
21603
|
this.host.reconnectHost();
|
|
@@ -21575,6 +21655,9 @@ var PluginControlRuntime = class {
|
|
|
21575
21655
|
}
|
|
21576
21656
|
authorization = await api.authorizeWithAccount(identity, value.email, value.password);
|
|
21577
21657
|
}
|
|
21658
|
+
if (value.role === "client" && resolveConfig(this.settings.get()).hostControl?.enabled !== false) {
|
|
21659
|
+
await this.client?.authorizeHostByDefault();
|
|
21660
|
+
}
|
|
21578
21661
|
await this.settings.replace(editableConfig(next));
|
|
21579
21662
|
return {
|
|
21580
21663
|
status: "authorized",
|
|
@@ -21621,7 +21704,7 @@ var PluginControlRuntime = class {
|
|
|
21621
21704
|
const current = editableConfig(resolveConfig(this.settings.get()));
|
|
21622
21705
|
const next = resolveConfig({
|
|
21623
21706
|
...current,
|
|
21624
|
-
terminal: { enabled: value.terminalEnabled === void 0 ? current.terminal?.enabled ??
|
|
21707
|
+
terminal: { enabled: value.terminalEnabled === void 0 ? current.terminal?.enabled ?? true : value.terminalEnabled },
|
|
21625
21708
|
loopback: { ports: value.ports === void 0 ? current.loopback?.ports ?? [] : value.ports }
|
|
21626
21709
|
});
|
|
21627
21710
|
await this.settings.replace(editableConfig(next));
|
|
@@ -21769,6 +21852,7 @@ function editableConfig(config) {
|
|
|
21769
21852
|
role: config.role,
|
|
21770
21853
|
...config.serverUrl === void 0 ? {} : { serverUrl: config.serverUrl },
|
|
21771
21854
|
terminal: config.terminal,
|
|
21855
|
+
hostControl: config.hostControl ?? { enabled: true },
|
|
21772
21856
|
loopback: config.loopback,
|
|
21773
21857
|
forceRelay: config.forceRelay,
|
|
21774
21858
|
logLevel: config.logLevel,
|
|
@@ -22549,6 +22633,13 @@ var HostServerConnection = class {
|
|
|
22549
22633
|
if (TERMINAL_AUTH_ERRORS.has(code)) {
|
|
22550
22634
|
this.logger.warn(code === "CONNECTION_REPLACED" ? "Another instance is using this Host identity. Stop it or use a separate DSH_HOME; automatic reconnect is paused." : code === "SERVER_CREDENTIALS_BUSY" ? "Credential refresh is locked. Stop other instances; after a crash, stop all instances before removing server-credentials.json.refresh-lock and authorizing again." : "Host authorization failed. Run /remote login or authorize this Host again in Remote settings.");
|
|
22551
22635
|
}
|
|
22636
|
+
if (code === "DEVICE_REVOKED") {
|
|
22637
|
+
try {
|
|
22638
|
+
await this.api.clearAuthorization();
|
|
22639
|
+
} catch (clearError) {
|
|
22640
|
+
this.logger.error("failed to clear revoked Host authorization", { code: errorCode2(clearError) });
|
|
22641
|
+
}
|
|
22642
|
+
}
|
|
22552
22643
|
if (TERMINAL_AUTH_ERRORS.has(code) || !this.config.reconnect.enabled) return;
|
|
22553
22644
|
}
|
|
22554
22645
|
if (this.stopped) return;
|
|
@@ -24666,6 +24757,7 @@ function isRecord10(value) {
|
|
|
24666
24757
|
|
|
24667
24758
|
// src/codex/domain.ts
|
|
24668
24759
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
24760
|
+
import { accessSync, constants, existsSync as existsSync2, readFileSync } from "node:fs";
|
|
24669
24761
|
import { readdir as readdir2, realpath, stat as stat4 } from "node:fs/promises";
|
|
24670
24762
|
import { homedir as homedir3 } from "node:os";
|
|
24671
24763
|
import { basename as basename3, isAbsolute as isAbsolute3, join as join5, relative, resolve as resolve2 } from "node:path";
|
|
@@ -26114,7 +26206,26 @@ var CodexRemoteDomain = class {
|
|
|
26114
26206
|
};
|
|
26115
26207
|
function codexBinaryCandidates(configured, hostPlatform = process.platform, userHome = homedir3()) {
|
|
26116
26208
|
if (configured !== "codex" || hostPlatform !== "darwin") return [configured];
|
|
26209
|
+
const bundledCandidates = [
|
|
26210
|
+
"/Applications/ChatGPT.app",
|
|
26211
|
+
join5(userHome, "Applications", "ChatGPT.app")
|
|
26212
|
+
].flatMap((chatGptApp) => {
|
|
26213
|
+
const codexCli = join5(chatGptApp, "Contents", "Resources", "codex-cli");
|
|
26214
|
+
try {
|
|
26215
|
+
const manifest = JSON.parse(readFileSync(join5(codexCli, "codex-package.json"), "utf8"));
|
|
26216
|
+
if (!isRecord13(manifest) || typeof manifest.entrypoint !== "string" || manifest.entrypoint.length === 0) {
|
|
26217
|
+
return [];
|
|
26218
|
+
}
|
|
26219
|
+
const candidate = join5(codexCli, manifest.entrypoint);
|
|
26220
|
+
if (!existsSync2(candidate)) return [];
|
|
26221
|
+
accessSync(candidate, constants.X_OK);
|
|
26222
|
+
return [candidate];
|
|
26223
|
+
} catch {
|
|
26224
|
+
return [];
|
|
26225
|
+
}
|
|
26226
|
+
});
|
|
26117
26227
|
return [.../* @__PURE__ */ new Set([
|
|
26228
|
+
...bundledCandidates,
|
|
26118
26229
|
"/Applications/ChatGPT.app/Contents/Resources/codex",
|
|
26119
26230
|
join5(userHome, "Applications", "ChatGPT.app", "Contents", "Resources", "codex"),
|
|
26120
26231
|
configured
|
|
@@ -26935,7 +27046,7 @@ var HostPluginRuntime = class {
|
|
|
26935
27046
|
this.fileViewerHost = fileViewerHost;
|
|
26936
27047
|
this.terminalSpawner = terminalSpawner;
|
|
26937
27048
|
this.terminalEnabled = config.terminal.enabled;
|
|
26938
|
-
this.
|
|
27049
|
+
this.loopbackPorts = [...config.loopback.ports];
|
|
26939
27050
|
this.codex = new CodexRemoteDomain(config.codex, logger);
|
|
26940
27051
|
this.connections = new ConnectionController(this.identities, (context, send) => {
|
|
26941
27052
|
const harnessApi = this.apiProxy === void 0 ? void 0 : new HarnessApiBridge(
|
|
@@ -26978,7 +27089,8 @@ var HostPluginRuntime = class {
|
|
|
26978
27089
|
() => this.hostCapabilities(),
|
|
26979
27090
|
codex,
|
|
26980
27091
|
acp,
|
|
26981
|
-
this.
|
|
27092
|
+
// Handles and their lifetime belong to this connection; only policy is shared.
|
|
27093
|
+
this.createLoopbackHost()
|
|
26982
27094
|
);
|
|
26983
27095
|
}, this.logger);
|
|
26984
27096
|
if (config.serverUrl !== void 0) {
|
|
@@ -26987,8 +27099,9 @@ var HostPluginRuntime = class {
|
|
|
26987
27099
|
}
|
|
26988
27100
|
connections;
|
|
26989
27101
|
terminalOwners = /* @__PURE__ */ new Map();
|
|
27102
|
+
loopbackHosts = /* @__PURE__ */ new Set();
|
|
26990
27103
|
terminalEnabled;
|
|
26991
|
-
|
|
27104
|
+
loopbackPorts;
|
|
26992
27105
|
identity;
|
|
26993
27106
|
serverApi;
|
|
26994
27107
|
serverConnection;
|
|
@@ -27002,7 +27115,14 @@ var HostPluginRuntime = class {
|
|
|
27002
27115
|
this.terminalEnabled = enabled;
|
|
27003
27116
|
}
|
|
27004
27117
|
setLoopbackPorts(ports) {
|
|
27005
|
-
this.
|
|
27118
|
+
this.loopbackPorts = [...ports];
|
|
27119
|
+
for (const loopback of this.loopbackHosts) loopback.setPorts(this.loopbackPorts);
|
|
27120
|
+
}
|
|
27121
|
+
createLoopbackHost() {
|
|
27122
|
+
let loopback;
|
|
27123
|
+
loopback = new LoopbackHost(() => this.loopbackPorts, () => this.loopbackHosts.delete(loopback));
|
|
27124
|
+
this.loopbackHosts.add(loopback);
|
|
27125
|
+
return loopback;
|
|
27006
27126
|
}
|
|
27007
27127
|
async start() {
|
|
27008
27128
|
if (this.closed) throw new Error("remote runtime is closed");
|
|
@@ -27045,6 +27165,9 @@ var HostPluginRuntime = class {
|
|
|
27045
27165
|
connectedClients: this.listConnectedClients()
|
|
27046
27166
|
};
|
|
27047
27167
|
}
|
|
27168
|
+
async hasStoredAuthorization() {
|
|
27169
|
+
return this.serverApi?.hasStoredAuthorization() ?? false;
|
|
27170
|
+
}
|
|
27048
27171
|
listConnectedClients() {
|
|
27049
27172
|
return this.connections.connectedPeers().map((peer) => {
|
|
27050
27173
|
const trusted = this.identities.trustedPeer(peer.deviceId);
|
|
@@ -27228,7 +27351,7 @@ var HostPluginRuntime = class {
|
|
|
27228
27351
|
}
|
|
27229
27352
|
hostCapabilities() {
|
|
27230
27353
|
const capabilities = [];
|
|
27231
|
-
if (this.
|
|
27354
|
+
if (this.loopbackPorts.length > 0) capabilities.push("loopback.http-ws.v1");
|
|
27232
27355
|
if (this.localGateway?.supportsCarrier === true) {
|
|
27233
27356
|
capabilities.push(
|
|
27234
27357
|
harnessSessionGeneration(this.harnessVersion) === "v3" ? "harness.remote.v3" : "harness.remote.v1",
|