@blue-soda/dsh-remote 0.4.29 → 0.4.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client-runtime.d.ts +137 -1
- package/dist/client-runtime.d.ts.map +1 -1
- package/dist/client-runtime.js +399 -22
- package/dist/client-runtime.js.map +1 -1
- package/dist/client-secure-transport.d.ts.map +1 -1
- package/dist/client-secure-transport.js +2 -1
- package/dist/client-secure-transport.js.map +1 -1
- package/dist/client-target-store.d.ts +40 -0
- package/dist/client-target-store.d.ts.map +1 -0
- package/dist/client-target-store.js +79 -0
- package/dist/client-target-store.js.map +1 -0
- package/dist/client.github.js +63 -15
- package/dist/client.js +63 -15
- package/dist/codex/domain.d.ts +9 -0
- package/dist/codex/domain.d.ts.map +1 -1
- package/dist/codex/domain.js +48 -7
- package/dist/codex/domain.js.map +1 -1
- package/dist/codex/virtual-harness.d.ts.map +1 -1
- package/dist/codex/virtual-harness.js +35 -1
- package/dist/codex/virtual-harness.js.map +1 -1
- package/dist/harness-api-bridge.d.ts +2 -0
- package/dist/harness-api-bridge.d.ts.map +1 -1
- package/dist/harness-api-bridge.js +10 -0
- package/dist/harness-api-bridge.js.map +1 -1
- package/dist/harness-remote-bridge.d.ts +8 -0
- package/dist/harness-remote-bridge.d.ts.map +1 -1
- package/dist/harness-remote-bridge.js +12 -1
- package/dist/harness-remote-bridge.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +795 -184
- package/dist/index.js.map +4 -4
- package/dist/remote-api-proxy.d.ts.map +1 -1
- package/dist/remote-api-proxy.js +52 -1
- package/dist/remote-api-proxy.js.map +1 -1
- package/dist/remote-typert-gateway.d.ts.map +1 -1
- package/dist/remote-typert-gateway.js +5 -3
- package/dist/remote-typert-gateway.js.map +1 -1
- package/dist/rpc-binary-attachments.d.ts +33 -0
- package/dist/rpc-binary-attachments.d.ts.map +1 -0
- package/dist/rpc-binary-attachments.js +142 -0
- package/dist/rpc-binary-attachments.js.map +1 -0
- package/dist/service.d.ts +10 -0
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +17 -1
- package/dist/service.js.map +1 -1
- package/dist/tui-command.js +7 -5
- package/dist/tui-command.js.map +1 -1
- package/dist/typert-gateway-switch.d.ts.map +1 -1
- package/dist/typert-gateway-switch.js +148 -15
- package/dist/typert-gateway-switch.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
|
@@ -14579,7 +14579,7 @@ var ClientSecureTransport = class {
|
|
|
14579
14579
|
}
|
|
14580
14580
|
requireNoise() {
|
|
14581
14581
|
if (this.noise === void 0 || !this.noise.complete || this.closed) {
|
|
14582
|
-
throw new Error("The authenticated Noise channel is not connected.");
|
|
14582
|
+
throw Object.assign(new Error("The authenticated Noise channel is not connected."), { code: "TRANSPORT_CLOSED" });
|
|
14583
14583
|
}
|
|
14584
14584
|
return this.noise;
|
|
14585
14585
|
}
|
|
@@ -14812,6 +14812,100 @@ async function readHarnessDistributionVersion(entrypoint = process.argv[1]) {
|
|
|
14812
14812
|
}
|
|
14813
14813
|
}
|
|
14814
14814
|
|
|
14815
|
+
// src/rpc-binary-attachments.ts
|
|
14816
|
+
function hydrateRpcAttachments(result) {
|
|
14817
|
+
if (!isRecord2(result)) return result;
|
|
14818
|
+
const envelope = result;
|
|
14819
|
+
if (!Array.isArray(envelope.attachments) || envelope.attachments.length === 0) return result;
|
|
14820
|
+
const { attachments, ...rest } = result;
|
|
14821
|
+
const base = Object.hasOwn(rest, "value") ? rest.value : void 0;
|
|
14822
|
+
for (const attachment of attachments) {
|
|
14823
|
+
const entry = attachment;
|
|
14824
|
+
const path = Array.isArray(entry.path) ? entry.path : void 0;
|
|
14825
|
+
if (path === void 0 || path.length === 0) {
|
|
14826
|
+
throw new Error("The remote Host returned a byte attachment without a path.");
|
|
14827
|
+
}
|
|
14828
|
+
assignAtPath(base, path, decodeAttachmentBytes(entry.bytes));
|
|
14829
|
+
}
|
|
14830
|
+
return rest;
|
|
14831
|
+
}
|
|
14832
|
+
function assignAtPath(base, path, bytes) {
|
|
14833
|
+
if (base === null || typeof base !== "object") {
|
|
14834
|
+
throw new Error("The remote Host returned a byte attachment that its result cannot hold.");
|
|
14835
|
+
}
|
|
14836
|
+
let cursor2 = base;
|
|
14837
|
+
for (let index = 0; index < path.length - 1; index += 1) {
|
|
14838
|
+
const key = pathKey(path[index]);
|
|
14839
|
+
const next = cursor2[key];
|
|
14840
|
+
if (next === null || typeof next !== "object") {
|
|
14841
|
+
throw new Error("The remote Host returned a byte attachment at an unknown result path.");
|
|
14842
|
+
}
|
|
14843
|
+
cursor2 = next;
|
|
14844
|
+
}
|
|
14845
|
+
cursor2[pathKey(path[path.length - 1])] = bytes;
|
|
14846
|
+
}
|
|
14847
|
+
function pathKey(segment) {
|
|
14848
|
+
if (typeof segment === "string") return segment;
|
|
14849
|
+
if (typeof segment === "number" && Number.isInteger(segment) && segment >= 0) return String(segment);
|
|
14850
|
+
throw new Error("The remote Host returned a byte attachment with an invalid path segment.");
|
|
14851
|
+
}
|
|
14852
|
+
function decodeAttachmentBytes(value) {
|
|
14853
|
+
if (value instanceof Uint8Array) return value;
|
|
14854
|
+
if (typeof value === "string") return decodeBase64(value);
|
|
14855
|
+
if (Array.isArray(value)) return Uint8Array.from(value);
|
|
14856
|
+
if (!isRecord2(value)) throw new Error("The remote Host returned invalid byte attachment data.");
|
|
14857
|
+
if (Array.isArray(value.data)) {
|
|
14858
|
+
return Uint8Array.from(value.data);
|
|
14859
|
+
}
|
|
14860
|
+
const keys = Object.keys(value);
|
|
14861
|
+
if (keys.every((key) => /^\d+$/u.test(key))) {
|
|
14862
|
+
const ordered = keys.map(Number).sort((left, right) => left - right);
|
|
14863
|
+
return Uint8Array.from(ordered.map((key) => value[String(key)]));
|
|
14864
|
+
}
|
|
14865
|
+
throw new Error("The remote Host returned invalid byte attachment data.");
|
|
14866
|
+
}
|
|
14867
|
+
function decodeBase64(value) {
|
|
14868
|
+
const binary = atob(value);
|
|
14869
|
+
const bytes = new Uint8Array(binary.length);
|
|
14870
|
+
for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);
|
|
14871
|
+
return bytes;
|
|
14872
|
+
}
|
|
14873
|
+
function isRecord2(value) {
|
|
14874
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14875
|
+
}
|
|
14876
|
+
function collectRpcAttachments(result) {
|
|
14877
|
+
if (!isRecord2(result) || !Object.hasOwn(result, "value")) return result;
|
|
14878
|
+
const attachments = [];
|
|
14879
|
+
const value = replaceBytes(result.value, [], attachments);
|
|
14880
|
+
if (attachments.length === 0) return result;
|
|
14881
|
+
return { ...result, value, attachments };
|
|
14882
|
+
}
|
|
14883
|
+
function replaceBytes(value, path, attachments) {
|
|
14884
|
+
if (value instanceof Uint8Array) {
|
|
14885
|
+
attachments.push({ path: [...path], bytes: encodeBase64(value) });
|
|
14886
|
+
return null;
|
|
14887
|
+
}
|
|
14888
|
+
if (Array.isArray(value)) return value.map((item, index) => replaceBytes(item, [...path, index], attachments));
|
|
14889
|
+
if (!isRecord2(value)) return value;
|
|
14890
|
+
const clone = {};
|
|
14891
|
+
for (const [key, item] of Object.entries(value)) clone[key] = replaceBytes(item, [...path, key], attachments);
|
|
14892
|
+
return clone;
|
|
14893
|
+
}
|
|
14894
|
+
function encodeBase64(bytes) {
|
|
14895
|
+
let binary = "";
|
|
14896
|
+
for (let offset = 0; offset < bytes.byteLength; offset += 32768) {
|
|
14897
|
+
binary += String.fromCharCode(...bytes.subarray(offset, offset + 32768));
|
|
14898
|
+
}
|
|
14899
|
+
return btoa(binary);
|
|
14900
|
+
}
|
|
14901
|
+
function decodeByteValue(value) {
|
|
14902
|
+
try {
|
|
14903
|
+
return decodeAttachmentBytes(value);
|
|
14904
|
+
} catch {
|
|
14905
|
+
return void 0;
|
|
14906
|
+
}
|
|
14907
|
+
}
|
|
14908
|
+
|
|
14815
14909
|
// src/remote-api-proxy.ts
|
|
14816
14910
|
var DIRECT_API_CALL_BYTES = 2 * 1024 * 1024;
|
|
14817
14911
|
var AGENT_PRESET_SETTINGS_NS = "agent-presets";
|
|
@@ -14919,12 +15013,13 @@ var RemoteHarnessApiProxy = class {
|
|
|
14919
15013
|
if (String(response.rpcId) !== String(request.rpcId) || typeof response.result !== "object" || response.result === null) {
|
|
14920
15014
|
throw new Error("The remote Host returned an invalid Harness API response.");
|
|
14921
15015
|
}
|
|
14922
|
-
const
|
|
15016
|
+
const hydrated = { ...response, result: hydrateRpcAttachments(response.result) };
|
|
15017
|
+
const normalized = normalizeLegacyResponse(method, normalizeByteResult(method, hydrated));
|
|
14923
15018
|
return this.normalizeLegacyWelcomeSettings(method, params.payload, normalized);
|
|
14924
15019
|
}
|
|
14925
15020
|
normalizeLegacyWelcomeSettings(method, payload, response) {
|
|
14926
15021
|
if (!isLegacyRemoteHost(this.harnessVersion)) return response;
|
|
14927
|
-
if (method === "settings.describe" && response.result.ok &&
|
|
15022
|
+
if (method === "settings.describe" && response.result.ok && isRecord3(response.result.value)) {
|
|
14928
15023
|
this.settingsDescribeValue = response.result.value;
|
|
14929
15024
|
return this.legacyWelcomeAcknowledged ? patchWelcomeDescribe(response) : response;
|
|
14930
15025
|
}
|
|
@@ -15036,7 +15131,7 @@ function normalizeLegacyResponse(method, response) {
|
|
|
15036
15131
|
};
|
|
15037
15132
|
}
|
|
15038
15133
|
function normalizeLegacyRequest(method, payload) {
|
|
15039
|
-
if (!
|
|
15134
|
+
if (!isRecord3(payload)) return payload;
|
|
15040
15135
|
if (method === "agentPreset.read") return replaceAgentPreset(payload, "agentPreset");
|
|
15041
15136
|
if (method === "agentPreset.select") return replaceAgentPreset(payload, "agentPreset");
|
|
15042
15137
|
if (method === "agentPreset.copy") return replaceAgentPreset(payload, "from");
|
|
@@ -15051,7 +15146,7 @@ function replaceAgentPreset(payload, key) {
|
|
|
15051
15146
|
return replacement === void 0 ? payload : { ...payload, [key]: replacement };
|
|
15052
15147
|
}
|
|
15053
15148
|
function replaceAgentPresetSettings(payload) {
|
|
15054
|
-
if (payload.ns !== AGENT_PRESET_SETTINGS_NS || !
|
|
15149
|
+
if (payload.ns !== AGENT_PRESET_SETTINGS_NS || !isRecord3(payload.patch)) return payload;
|
|
15055
15150
|
const patch = replaceAgentPreset(payload.patch, "default");
|
|
15056
15151
|
return patch === payload.patch ? payload : { ...payload, patch };
|
|
15057
15152
|
}
|
|
@@ -15061,11 +15156,11 @@ function isLegacyRemoteHost(version) {
|
|
|
15061
15156
|
return match !== null && Number(match[1]) === 0 && Number(match[2]) === 1 && Number(match[3]) < 7;
|
|
15062
15157
|
}
|
|
15063
15158
|
function isWelcomeNoticeRequest(payload) {
|
|
15064
|
-
if (!
|
|
15065
|
-
return payload.ops.some((operation) =>
|
|
15159
|
+
if (!isRecord3(payload) || payload.ns !== WELCOME_NOTICE_NAMESPACE || !Array.isArray(payload.ops)) return false;
|
|
15160
|
+
return payload.ops.some((operation) => isRecord3(operation) && operation.op === "set" && Array.isArray(operation.path) && operation.path.length === 1 && operation.path[0] === WELCOME_NOTICE_FIELD && operation.value === WELCOME_NOTICE_VERSION);
|
|
15066
15161
|
}
|
|
15067
15162
|
function patchWelcomeDescribe(response) {
|
|
15068
|
-
if (!response.result.ok || !
|
|
15163
|
+
if (!response.result.ok || !isRecord3(response.result.value) || !Array.isArray(response.result.value.namespaces)) return response;
|
|
15069
15164
|
return {
|
|
15070
15165
|
...response,
|
|
15071
15166
|
result: {
|
|
@@ -15073,8 +15168,8 @@ function patchWelcomeDescribe(response) {
|
|
|
15073
15168
|
value: {
|
|
15074
15169
|
...response.result.value,
|
|
15075
15170
|
namespaces: response.result.value.namespaces.map((namespace) => {
|
|
15076
|
-
if (!
|
|
15077
|
-
const value =
|
|
15171
|
+
if (!isRecord3(namespace) || namespace.ns !== WELCOME_NOTICE_NAMESPACE) return namespace;
|
|
15172
|
+
const value = isRecord3(namespace.value) ? namespace.value : {};
|
|
15078
15173
|
return { ...namespace, value: { ...value, [WELCOME_NOTICE_FIELD]: WELCOME_NOTICE_VERSION } };
|
|
15079
15174
|
})
|
|
15080
15175
|
}
|
|
@@ -15088,8 +15183,8 @@ function patchWelcomeMutate(value, rpcId2) {
|
|
|
15088
15183
|
};
|
|
15089
15184
|
const namespaces = Array.isArray(value.namespaces) ? value.namespaces : [];
|
|
15090
15185
|
response.result.value.namespaces = namespaces.map((namespace) => {
|
|
15091
|
-
if (!
|
|
15092
|
-
const current =
|
|
15186
|
+
if (!isRecord3(namespace) || namespace.ns !== WELCOME_NOTICE_NAMESPACE) return namespace;
|
|
15187
|
+
const current = isRecord3(namespace.value) ? namespace.value : {};
|
|
15093
15188
|
return { ...namespace, value: { ...current, [WELCOME_NOTICE_FIELD]: WELCOME_NOTICE_VERSION } };
|
|
15094
15189
|
});
|
|
15095
15190
|
return response;
|
|
@@ -15097,7 +15192,7 @@ function patchWelcomeMutate(value, rpcId2) {
|
|
|
15097
15192
|
function isRemoteDisconnect(error) {
|
|
15098
15193
|
return error instanceof RemoteClientError && (error.code === "TRANSPORT_CLOSED" || error.code === "CLIENT_CLOSED");
|
|
15099
15194
|
}
|
|
15100
|
-
function
|
|
15195
|
+
function isRecord3(value) {
|
|
15101
15196
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15102
15197
|
}
|
|
15103
15198
|
var AsyncFrameQueue = class {
|
|
@@ -15161,6 +15256,24 @@ function base64ToBytes2(value) {
|
|
|
15161
15256
|
}
|
|
15162
15257
|
return bytes;
|
|
15163
15258
|
}
|
|
15259
|
+
function normalizeByteResult(method, response) {
|
|
15260
|
+
if (method !== "workspaceFiles.readBytes") return response;
|
|
15261
|
+
const result = response.result;
|
|
15262
|
+
if (result === void 0 || result.ok !== true) return response;
|
|
15263
|
+
const value = result.value;
|
|
15264
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return response;
|
|
15265
|
+
const raw = value.data;
|
|
15266
|
+
const data2 = raw instanceof Uint8Array ? raw : decodeByteValue(raw);
|
|
15267
|
+
if (data2 === void 0) return response;
|
|
15268
|
+
return {
|
|
15269
|
+
...response,
|
|
15270
|
+
result: {
|
|
15271
|
+
...result,
|
|
15272
|
+
value: { ...value, data: null },
|
|
15273
|
+
attachments: [{ path: ["data"], bytes: data2 }]
|
|
15274
|
+
}
|
|
15275
|
+
};
|
|
15276
|
+
}
|
|
15164
15277
|
|
|
15165
15278
|
// src/session-format-compat.ts
|
|
15166
15279
|
function normalizeLegacySessionGatewayValue(endpoint, value) {
|
|
@@ -15169,7 +15282,7 @@ function normalizeLegacySessionGatewayValue(endpoint, value) {
|
|
|
15169
15282
|
return value;
|
|
15170
15283
|
}
|
|
15171
15284
|
function normalizeFollowFrame(value) {
|
|
15172
|
-
if (!
|
|
15285
|
+
if (!isRecord4(value)) return value;
|
|
15173
15286
|
if (value.type === "snapshot") {
|
|
15174
15287
|
return {
|
|
15175
15288
|
...value,
|
|
@@ -15181,7 +15294,7 @@ function normalizeFollowFrame(value) {
|
|
|
15181
15294
|
return normalizeEntry(value);
|
|
15182
15295
|
}
|
|
15183
15296
|
function normalizePage(value) {
|
|
15184
|
-
if (!
|
|
15297
|
+
if (!isRecord4(value)) return value;
|
|
15185
15298
|
return { ...value, records: normalizeRecords(value.records) };
|
|
15186
15299
|
}
|
|
15187
15300
|
function normalizeRecords(value) {
|
|
@@ -15226,10 +15339,10 @@ function eventTime(value) {
|
|
|
15226
15339
|
return typeof value.event.time === "number" && Number.isSafeInteger(value.event.time) ? value.event.time : 0;
|
|
15227
15340
|
}
|
|
15228
15341
|
function isEventEntry(value) {
|
|
15229
|
-
return
|
|
15342
|
+
return isRecord4(value) && value.type === "event" && isRecord4(value.event);
|
|
15230
15343
|
}
|
|
15231
15344
|
function normalizeHeader(value) {
|
|
15232
|
-
if (!
|
|
15345
|
+
if (!isRecord4(value)) return value;
|
|
15233
15346
|
const next = {
|
|
15234
15347
|
...value,
|
|
15235
15348
|
version: 3,
|
|
@@ -15238,7 +15351,7 @@ function normalizeHeader(value) {
|
|
|
15238
15351
|
return next.agentPreset === "code" ? { ...next, agentPreset: "ptc" } : next;
|
|
15239
15352
|
}
|
|
15240
15353
|
function normalizeEvent(value) {
|
|
15241
|
-
if (!
|
|
15354
|
+
if (!isRecord4(value)) return value;
|
|
15242
15355
|
let next = value;
|
|
15243
15356
|
const type = normalizeEventType(value.type);
|
|
15244
15357
|
if (type !== value.type) next = { ...next, type };
|
|
@@ -15258,7 +15371,7 @@ function normalizeEventType(value) {
|
|
|
15258
15371
|
return value;
|
|
15259
15372
|
}
|
|
15260
15373
|
function normalizeEventData(type, value) {
|
|
15261
|
-
if (!
|
|
15374
|
+
if (!isRecord4(value)) return value;
|
|
15262
15375
|
if (type === "request/header") return normalizeRequestHeaderData(value);
|
|
15263
15376
|
if (type === "agent-preset/selected" && value.agentPreset === "code") return { ...value, agentPreset: "ptc" };
|
|
15264
15377
|
if (type === "user/message") return normalizeMessage(value);
|
|
@@ -15271,7 +15384,7 @@ function normalizeEventData(type, value) {
|
|
|
15271
15384
|
return value;
|
|
15272
15385
|
}
|
|
15273
15386
|
function normalizeRequestHeaderData(value) {
|
|
15274
|
-
if (!
|
|
15387
|
+
if (!isRecord4(value.header)) return value;
|
|
15275
15388
|
const header = normalizeRequestHeader(value.header);
|
|
15276
15389
|
return header === value.header ? value : { ...value, header };
|
|
15277
15390
|
}
|
|
@@ -15287,7 +15400,7 @@ function normalizeRequestHeader(value) {
|
|
|
15287
15400
|
changed = true;
|
|
15288
15401
|
continue;
|
|
15289
15402
|
}
|
|
15290
|
-
if (key === "adapterDefaults" &&
|
|
15403
|
+
if (key === "adapterDefaults" && isRecord4(field) && Object.keys(field).length === 0) {
|
|
15291
15404
|
changed = true;
|
|
15292
15405
|
continue;
|
|
15293
15406
|
}
|
|
@@ -15296,12 +15409,12 @@ function normalizeRequestHeader(value) {
|
|
|
15296
15409
|
return changed ? next : value;
|
|
15297
15410
|
}
|
|
15298
15411
|
function normalizeMessage(value) {
|
|
15299
|
-
if (!
|
|
15412
|
+
if (!isRecord4(value) || !isRecord4(value.source)) return value;
|
|
15300
15413
|
if (value.source.kind !== "plugin" || value.source.plugin !== "tools-code-mode") return value;
|
|
15301
15414
|
return { ...value, source: { ...value.source, plugin: "tools-ptc" } };
|
|
15302
15415
|
}
|
|
15303
15416
|
function normalizeSurfaceOp(value) {
|
|
15304
|
-
if (!
|
|
15417
|
+
if (!isRecord4(value) || value.op !== "replace") return value;
|
|
15305
15418
|
const { start, end, ...rest } = value;
|
|
15306
15419
|
if (start === void 0 && end === void 0) return value;
|
|
15307
15420
|
return {
|
|
@@ -15311,7 +15424,7 @@ function normalizeSurfaceOp(value) {
|
|
|
15311
15424
|
...end === void 0 ? {} : { endSeq: end }
|
|
15312
15425
|
};
|
|
15313
15426
|
}
|
|
15314
|
-
function
|
|
15427
|
+
function isRecord4(value) {
|
|
15315
15428
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15316
15429
|
}
|
|
15317
15430
|
function isEventSeq(value) {
|
|
@@ -15352,7 +15465,7 @@ var RemoteTypertGateway2 = class {
|
|
|
15352
15465
|
response = await this.callTransferred(encoded, signal);
|
|
15353
15466
|
}
|
|
15354
15467
|
}
|
|
15355
|
-
const result = parseRpcResult(response);
|
|
15468
|
+
const result = parseRpcResult(hydrateRpcAttachments(response));
|
|
15356
15469
|
const settingsResult = this.normalizeLegacyWelcomeSettings(endpoint, payload, result);
|
|
15357
15470
|
if (settingsResult !== void 0) return settingsResult;
|
|
15358
15471
|
if (result.ok && this.compatibility === "legacy-to-v3") {
|
|
@@ -15365,11 +15478,11 @@ var RemoteTypertGateway2 = class {
|
|
|
15365
15478
|
/** DSH <=0.1.6 cannot persist the 0.1.7 welcome acknowledgement remotely. */
|
|
15366
15479
|
normalizeLegacyWelcomeSettings(endpoint, payload, result) {
|
|
15367
15480
|
if (!isLegacyRemoteHost2(this.harnessVersion)) return void 0;
|
|
15368
|
-
if (endpoint === "settings/describe" && result.ok &&
|
|
15481
|
+
if (endpoint === "settings/describe" && result.ok && isRecord5(result.value)) {
|
|
15369
15482
|
this.settingsDescribeValue = result.value;
|
|
15370
15483
|
return this.legacyWelcomeAcknowledged ? { ...result, value: patchWelcomeDescribe2(result.value) } : void 0;
|
|
15371
15484
|
}
|
|
15372
|
-
if (endpoint !== "settings/mutate" || !
|
|
15485
|
+
if (endpoint !== "settings/mutate" || !isRecord5(payload) || !isRecord5(payload.args)) return void 0;
|
|
15373
15486
|
const args = payload.args;
|
|
15374
15487
|
if (args.ns !== WELCOME_NOTICE_NAMESPACE2 || !isWelcomeNoticeOperation(args.ops)) return void 0;
|
|
15375
15488
|
const value = patchWelcomeDescribe2(this.settingsDescribeValue ?? { namespaces: [] });
|
|
@@ -15398,7 +15511,8 @@ var RemoteTypertGateway2 = class {
|
|
|
15398
15511
|
async *iterate(streamId, endpoint, queue, unsubscribe, unsubscribeClose, signal, onAbort) {
|
|
15399
15512
|
try {
|
|
15400
15513
|
for await (const value of queue) {
|
|
15401
|
-
|
|
15514
|
+
const hydrated = hydrateRpcAttachments(value);
|
|
15515
|
+
yield this.compatibility === "legacy-to-v3" ? normalizeLegacySessionGatewayValue(endpoint, hydrated) : hydrated;
|
|
15402
15516
|
}
|
|
15403
15517
|
} finally {
|
|
15404
15518
|
signal.removeEventListener("abort", onAbort);
|
|
@@ -15533,16 +15647,16 @@ function routeStreamEvent2(event, streamId, queue) {
|
|
|
15533
15647
|
queue.fail(remoteFailure({
|
|
15534
15648
|
code: typeof failure2?.code === "string" ? failure2.code : "internal",
|
|
15535
15649
|
message: typeof failure2?.message === "string" ? failure2.message : "The remote Harness stream failed.",
|
|
15536
|
-
details:
|
|
15650
|
+
details: isRecord5(failure2?.details) ? failure2.details : {}
|
|
15537
15651
|
}));
|
|
15538
15652
|
} else {
|
|
15539
15653
|
queue.close();
|
|
15540
15654
|
}
|
|
15541
15655
|
}
|
|
15542
15656
|
function parseRpcResult(value) {
|
|
15543
|
-
if (!
|
|
15657
|
+
if (!isRecord5(value)) throw new Error("The remote Host returned an invalid Gateway result.");
|
|
15544
15658
|
if (value.ok === true) return Object.hasOwn(value, "value") ? { ok: true, value: value.value } : { ok: true };
|
|
15545
|
-
if (value.ok !== false || !
|
|
15659
|
+
if (value.ok !== false || !isRecord5(value.error) || typeof value.error.code !== "string" || typeof value.error.message !== "string" || !isRecord5(value.error.details)) {
|
|
15546
15660
|
throw new Error("The remote Host returned an invalid Gateway failure.");
|
|
15547
15661
|
}
|
|
15548
15662
|
return {
|
|
@@ -15576,7 +15690,7 @@ function base64ToBytes3(value) {
|
|
|
15576
15690
|
if (bytesToBase643(bytes) !== value) throw new Error("The remote Host returned non-canonical Harness Remote transfer data.");
|
|
15577
15691
|
return bytes;
|
|
15578
15692
|
}
|
|
15579
|
-
function
|
|
15693
|
+
function isRecord5(value) {
|
|
15580
15694
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15581
15695
|
}
|
|
15582
15696
|
function hasErrorCode(error, code) {
|
|
@@ -15589,13 +15703,13 @@ function isLegacyRemoteHost2(version) {
|
|
|
15589
15703
|
return Number(match[1]) === 0 && Number(match[2]) === 1 && Number(match[3]) < 7;
|
|
15590
15704
|
}
|
|
15591
15705
|
function isWelcomeNoticeOperation(value) {
|
|
15592
|
-
return Array.isArray(value) && value.some((operation) =>
|
|
15706
|
+
return Array.isArray(value) && value.some((operation) => isRecord5(operation) && operation.op === "set" && Array.isArray(operation.path) && operation.path.length === 1 && operation.path[0] === WELCOME_NOTICE_FIELD2 && operation.value === WELCOME_NOTICE_VERSION2);
|
|
15593
15707
|
}
|
|
15594
15708
|
function patchWelcomeDescribe2(value) {
|
|
15595
15709
|
if (!Array.isArray(value.namespaces)) return value;
|
|
15596
15710
|
const namespaces = value.namespaces.map((namespace) => {
|
|
15597
|
-
if (!
|
|
15598
|
-
const current =
|
|
15711
|
+
if (!isRecord5(namespace) || namespace.ns !== WELCOME_NOTICE_NAMESPACE2) return namespace;
|
|
15712
|
+
const current = isRecord5(namespace.value) ? namespace.value : {};
|
|
15599
15713
|
return { ...namespace, value: { ...current, [WELCOME_NOTICE_FIELD2]: WELCOME_NOTICE_VERSION2 } };
|
|
15600
15714
|
});
|
|
15601
15715
|
return { ...value, namespaces };
|
|
@@ -15757,11 +15871,11 @@ var DATA_IMAGE_URL2 = /^data:(image\/(?:png|jpeg|webp|gif));base64,([A-Za-z0-9+/
|
|
|
15757
15871
|
function string(value) {
|
|
15758
15872
|
return typeof value === "string" ? value : void 0;
|
|
15759
15873
|
}
|
|
15760
|
-
function
|
|
15874
|
+
function isRecord6(value) {
|
|
15761
15875
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15762
15876
|
}
|
|
15763
15877
|
function record2(value) {
|
|
15764
|
-
return
|
|
15878
|
+
return isRecord6(value) ? value : {};
|
|
15765
15879
|
}
|
|
15766
15880
|
function toolOutputImageBlocks(item) {
|
|
15767
15881
|
const candidates = [
|
|
@@ -15851,7 +15965,7 @@ function collectImageBlocks(value, output = []) {
|
|
|
15851
15965
|
for (const item of value) collectImageBlocks(item, output);
|
|
15852
15966
|
return output;
|
|
15853
15967
|
}
|
|
15854
|
-
if (!
|
|
15968
|
+
if (!isRecord6(value)) return output;
|
|
15855
15969
|
if (value.type === "image") output.push(value);
|
|
15856
15970
|
for (const [key, child] of Object.entries(value)) {
|
|
15857
15971
|
if (key === "attachment") continue;
|
|
@@ -16110,7 +16224,7 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
|
|
|
16110
16224
|
try {
|
|
16111
16225
|
if (isHostWorkspaceEndpoint(endpoint)) {
|
|
16112
16226
|
if (this.hostCarrier === void 0) return fail("method-not-found", `CodeX virtual Harness does not implement ${endpoint}.`);
|
|
16113
|
-
return await this.hostCarrier.dispatch(endpoint, payload, signal);
|
|
16227
|
+
return normalizeByteResult2(endpoint, await this.hostCarrier.dispatch(endpoint, payload, signal));
|
|
16114
16228
|
}
|
|
16115
16229
|
const args = carrierArgs(payload);
|
|
16116
16230
|
switch (endpoint) {
|
|
@@ -16932,7 +17046,7 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
|
|
|
16932
17046
|
this.broadcastRcHost({ type: "host/session-status", sessionId, running: args[1] });
|
|
16933
17047
|
} else if (event === "api-session/removed" && sessionId !== void 0) {
|
|
16934
17048
|
this.broadcastRcHost({ type: "host/session-removed", sessionId });
|
|
16935
|
-
} else if (event === "api-session/added" &&
|
|
17049
|
+
} else if (event === "api-session/added" && isRecord7(args[0])) {
|
|
16936
17050
|
const summary = args[0];
|
|
16937
17051
|
this.broadcastRcHost({
|
|
16938
17052
|
type: "host/session-added",
|
|
@@ -17322,7 +17436,7 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
|
|
|
17322
17436
|
}
|
|
17323
17437
|
createApiProxy() {
|
|
17324
17438
|
const call = (endpoint) => async (request, signal) => {
|
|
17325
|
-
const payload = endpoint === "session.prompt" &&
|
|
17439
|
+
const payload = endpoint === "session.prompt" && isRecord7(request.payload) ? { ...request.payload, requestId: String(request.rpcId) } : request.payload;
|
|
17326
17440
|
const result = await this.dispatch(rcEndpoint(endpoint), { args: { request: payload } }, signal ?? new AbortController().signal);
|
|
17327
17441
|
return {
|
|
17328
17442
|
rpcId: request.rpcId,
|
|
@@ -17977,7 +18091,7 @@ function codexPromptInput(content) {
|
|
|
17977
18091
|
if (content.length === 0 || content.length > MAX_CODEX_PROMPT_PARTS) return void 0;
|
|
17978
18092
|
const input2 = [];
|
|
17979
18093
|
for (const value of content) {
|
|
17980
|
-
if (!
|
|
18094
|
+
if (!isRecord7(value)) return void 0;
|
|
17981
18095
|
if (value.type === "text") {
|
|
17982
18096
|
if (typeof value.text !== "string" || value.text.length === 0 || value.text.length > MAX_CODEX_PROMPT_TEXT) return void 0;
|
|
17983
18097
|
input2.push({ type: "text", text: value.text });
|
|
@@ -18227,12 +18341,12 @@ function errorCode(error) {
|
|
|
18227
18341
|
return "code" in error && typeof error.code === "string" ? error.code : void 0;
|
|
18228
18342
|
}
|
|
18229
18343
|
function errorDetails(error) {
|
|
18230
|
-
return "details" in error &&
|
|
18344
|
+
return "details" in error && isRecord7(error.details) ? error.details : {};
|
|
18231
18345
|
}
|
|
18232
18346
|
function record3(value) {
|
|
18233
|
-
return
|
|
18347
|
+
return isRecord7(value) ? value : {};
|
|
18234
18348
|
}
|
|
18235
|
-
function
|
|
18349
|
+
function isRecord7(value) {
|
|
18236
18350
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
18237
18351
|
}
|
|
18238
18352
|
function array(value) {
|
|
@@ -18349,6 +18463,19 @@ var AsyncValueQueue2 = class {
|
|
|
18349
18463
|
}
|
|
18350
18464
|
}
|
|
18351
18465
|
};
|
|
18466
|
+
function normalizeByteResult2(endpoint, result) {
|
|
18467
|
+
if (endpoint !== "workspaceFiles/readBytes" || !result.ok) return result;
|
|
18468
|
+
const value = result.value;
|
|
18469
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return result;
|
|
18470
|
+
const raw = value.data;
|
|
18471
|
+
const data2 = raw instanceof Uint8Array ? raw : decodeByteValue(raw);
|
|
18472
|
+
if (data2 === void 0) return result;
|
|
18473
|
+
return {
|
|
18474
|
+
...result,
|
|
18475
|
+
value: { ...value, data: null },
|
|
18476
|
+
attachments: [{ path: ["data"], bytes: data2 }]
|
|
18477
|
+
};
|
|
18478
|
+
}
|
|
18352
18479
|
|
|
18353
18480
|
// src/server-api.ts
|
|
18354
18481
|
import { platform } from "node:os";
|
|
@@ -18629,7 +18756,7 @@ function normalizeServerUrl(value) {
|
|
|
18629
18756
|
}
|
|
18630
18757
|
|
|
18631
18758
|
// src/version.ts
|
|
18632
|
-
var PLUGIN_VERSION = "0.4.
|
|
18759
|
+
var PLUGIN_VERSION = "0.4.31";
|
|
18633
18760
|
|
|
18634
18761
|
// src/server-api.ts
|
|
18635
18762
|
var ENABLED_QR_PROVIDERS = ["github"];
|
|
@@ -19104,6 +19231,17 @@ function requireRecord(value, name2) {
|
|
|
19104
19231
|
// src/typert-gateway-switch.ts
|
|
19105
19232
|
var REMOTE_COMMAND_METHODS = ["execute", "list"];
|
|
19106
19233
|
var LOCAL_ONLY_NAMESPACES = /* @__PURE__ */ new Set(["dynamicCordisRunner"]);
|
|
19234
|
+
var LOCAL_FALLBACK_NAMESPACES = /* @__PURE__ */ new Set(["$events", "settings", "credentials", "dynamicCordisRunner"]);
|
|
19235
|
+
var PEER_GONE_CODES = /* @__PURE__ */ new Set([
|
|
19236
|
+
"TRANSPORT_CLOSED",
|
|
19237
|
+
"CLIENT_CLOSED",
|
|
19238
|
+
"RPC_TIMEOUT",
|
|
19239
|
+
"CONNECTION_FAILED",
|
|
19240
|
+
"CONNECTION_REPLACED",
|
|
19241
|
+
"NOT_CONNECTED",
|
|
19242
|
+
"UNAVAILABLE",
|
|
19243
|
+
"internal"
|
|
19244
|
+
]);
|
|
19107
19245
|
var ALL_REMOTE_COMMANDS = { execute: true, list: true };
|
|
19108
19246
|
var TypertGatewaySwitch = class {
|
|
19109
19247
|
runtime;
|
|
@@ -19165,25 +19303,39 @@ var TypertGatewaySwitch = class {
|
|
|
19165
19303
|
install() {
|
|
19166
19304
|
if (this.installed) return;
|
|
19167
19305
|
this.runtime.invoke = (request) => this.selectInvoke(request);
|
|
19168
|
-
|
|
19169
|
-
|
|
19306
|
+
const previousStream = this.runtime.stream?.bind(this.runtime);
|
|
19307
|
+
const previousDispatch = this.runtime.dispatchRpc?.bind(this.runtime);
|
|
19308
|
+
const previousOpen = this.runtime.openWireStream;
|
|
19309
|
+
const forwards = (endpoint) => this.remoteTarget === void 0 && this.remoteAvailability() && !isLocalOnlyEndpoint(endpoint);
|
|
19310
|
+
if (previousStream !== void 0) {
|
|
19311
|
+
this.runtime.stream = (request) => forwards(endpointOf(request)) ? previousStream(request) : !this.routesToRemote(endpointOf(request)) ? this.localStream(request) : this.withLocalFallback(
|
|
19170
19312
|
endpointOf(request),
|
|
19171
19313
|
() => this.remoteTarget.open(endpointOf(request), { args: request.args }, request.signal ?? new AbortController().signal),
|
|
19172
19314
|
() => this.localStream(request)
|
|
19173
19315
|
);
|
|
19174
19316
|
}
|
|
19175
|
-
if (
|
|
19176
|
-
this.runtime.dispatchRpc = (endpoint, payload, signal) =>
|
|
19317
|
+
if (previousDispatch !== void 0) {
|
|
19318
|
+
this.runtime.dispatchRpc = (endpoint, payload, signal) => forwards(endpoint) ? Promise.resolve(previousDispatch(endpoint, payload, signal)).catch((error) => {
|
|
19319
|
+
console.warn("[dsh-remote] local call failed with no remote target selected", {
|
|
19320
|
+
endpoint,
|
|
19321
|
+
code: typeof error === "object" && error !== null && "code" in error ? String(error.code) : void 0,
|
|
19322
|
+
message: error instanceof Error ? error.message : String(error)
|
|
19323
|
+
});
|
|
19324
|
+
throw error;
|
|
19325
|
+
}) : !this.routesToRemote(endpoint) ? this.localDispatch(endpoint, payload, signal) : this.withLocalFallback(
|
|
19177
19326
|
endpoint,
|
|
19178
19327
|
() => Promise.resolve(this.remoteTarget.dispatch(endpoint, payload, signal)),
|
|
19179
19328
|
() => this.localDispatch(endpoint, payload, signal)
|
|
19180
|
-
);
|
|
19329
|
+
).then((result) => normalizeByteResult3(endpoint, result));
|
|
19181
19330
|
}
|
|
19182
|
-
if (
|
|
19183
|
-
const open =
|
|
19331
|
+
if (previousOpen !== void 0) {
|
|
19332
|
+
const open = previousOpen;
|
|
19184
19333
|
const rc1 = usesRc1Arity(open);
|
|
19185
19334
|
this.runtime.openWireStream = (...callArgs) => {
|
|
19186
19335
|
const endpoint = callArgs[0];
|
|
19336
|
+
if (forwards(endpoint)) {
|
|
19337
|
+
return rc1 ? Reflect.apply(open, this.runtime, callArgs) : open.call(this.runtime, endpoint, callArgs[1], callArgs[2]);
|
|
19338
|
+
}
|
|
19187
19339
|
if (!this.routesToRemote(endpoint)) {
|
|
19188
19340
|
return rc1 ? Reflect.apply(open, this.runtime, callArgs) : open.call(this.runtime, endpoint, callArgs[1], callArgs[2]);
|
|
19189
19341
|
}
|
|
@@ -19254,8 +19406,8 @@ var TypertGatewaySwitch = class {
|
|
|
19254
19406
|
selectInvoke(request) {
|
|
19255
19407
|
if (isLocalOnlyEndpoint(endpointOf(request))) return this.localInvoke(request);
|
|
19256
19408
|
if (this.remoteTarget !== void 0 && this.remoteAvailability()) {
|
|
19257
|
-
return this.remoteTarget.invoke(request).catch((error) => {
|
|
19258
|
-
if (!
|
|
19409
|
+
return this.remoteTarget.invoke(request).then((result) => normalizeByteResult3(endpointOf(request), result)).catch((error) => {
|
|
19410
|
+
if (!localFallbackAllowed(endpointOf(request), error)) throw error;
|
|
19259
19411
|
console.warn(`[dsh-remote] serving ${endpointOf(request)} locally: the peer did not answer it`, error);
|
|
19260
19412
|
return this.localInvoke(request);
|
|
19261
19413
|
});
|
|
@@ -19282,7 +19434,7 @@ var TypertGatewaySwitch = class {
|
|
|
19282
19434
|
*/
|
|
19283
19435
|
withLocalFallback(endpoint, remote, local) {
|
|
19284
19436
|
return remote().catch((error) => {
|
|
19285
|
-
if (!
|
|
19437
|
+
if (!localFallbackAllowed(endpoint, error)) throw error;
|
|
19286
19438
|
console.warn(`[dsh-remote] serving ${endpoint} locally: the peer did not answer it`, error);
|
|
19287
19439
|
return local();
|
|
19288
19440
|
});
|
|
@@ -19292,7 +19444,7 @@ var TypertGatewaySwitch = class {
|
|
|
19292
19444
|
if (normalized !== void 0) return normalized;
|
|
19293
19445
|
const source = error instanceof Error ? error : new Error("The Harness Gateway rejected the request.");
|
|
19294
19446
|
const code = "code" in source && typeof source.code === "string" ? source.code : "internal";
|
|
19295
|
-
const details = "details" in source &&
|
|
19447
|
+
const details = "details" in source && isRecord8(source.details) ? source.details : {};
|
|
19296
19448
|
return { code, message: source.message, details };
|
|
19297
19449
|
}
|
|
19298
19450
|
};
|
|
@@ -19332,7 +19484,7 @@ function requestFromCarrier(endpoint, payload, signal) {
|
|
|
19332
19484
|
if (segments.length !== 2 || segments.some((segment) => segment.length === 0)) {
|
|
19333
19485
|
throw new Error("The Harness Gateway endpoint is invalid.");
|
|
19334
19486
|
}
|
|
19335
|
-
if (!
|
|
19487
|
+
if (!isRecord8(payload) || !isRecord8(payload.args)) {
|
|
19336
19488
|
throw new Error("The Harness Gateway payload is invalid.");
|
|
19337
19489
|
}
|
|
19338
19490
|
return { namespace: segments[0], method: segments[1], args: payload.args, signal };
|
|
@@ -19340,15 +19492,27 @@ function requestFromCarrier(endpoint, payload, signal) {
|
|
|
19340
19492
|
function endpointOf(request) {
|
|
19341
19493
|
return `${request.namespace}/${request.method}`;
|
|
19342
19494
|
}
|
|
19343
|
-
function
|
|
19495
|
+
function namespaceOf(endpoint) {
|
|
19344
19496
|
const separator = endpoint.indexOf("/");
|
|
19345
|
-
return separator > 0
|
|
19497
|
+
return separator > 0 ? endpoint.slice(0, separator) : void 0;
|
|
19498
|
+
}
|
|
19499
|
+
function isLocalOnlyEndpoint(endpoint) {
|
|
19500
|
+
const namespace = namespaceOf(endpoint);
|
|
19501
|
+
return namespace !== void 0 && LOCAL_ONLY_NAMESPACES.has(namespace);
|
|
19502
|
+
}
|
|
19503
|
+
function localFallbackAllowed(endpoint, error) {
|
|
19504
|
+
if (!isUnansweredByPeer(error)) return false;
|
|
19505
|
+
if (!isRecord8(error)) return true;
|
|
19506
|
+
const code = typeof error.code === "string" ? error.code : "";
|
|
19507
|
+
if (!PEER_GONE_CODES.has(code)) return true;
|
|
19508
|
+
const namespace = namespaceOf(endpoint);
|
|
19509
|
+
return namespace !== void 0 && LOCAL_FALLBACK_NAMESPACES.has(namespace);
|
|
19346
19510
|
}
|
|
19347
19511
|
function isRemoteCommandMethod(method) {
|
|
19348
19512
|
return REMOTE_COMMAND_METHODS.includes(method);
|
|
19349
19513
|
}
|
|
19350
19514
|
function isUnansweredByPeer(error) {
|
|
19351
|
-
if (!
|
|
19515
|
+
if (!isRecord8(error)) return error instanceof Error;
|
|
19352
19516
|
const code = typeof error.code === "string" ? error.code : "";
|
|
19353
19517
|
return code === "" || UNANSWERED_BY_PEER_CODES.has(code);
|
|
19354
19518
|
}
|
|
@@ -19361,11 +19525,32 @@ var UNANSWERED_BY_PEER_CODES = /* @__PURE__ */ new Set([
|
|
|
19361
19525
|
"CONNECTION_REPLACED",
|
|
19362
19526
|
"NOT_CONNECTED",
|
|
19363
19527
|
"UNAVAILABLE",
|
|
19528
|
+
// The client's own transport codes: the peer cannot answer a call it never received, so these are
|
|
19529
|
+
// "unanswered" rather than a refusal - and PEER_GONE_CODES above is what decides whether the local
|
|
19530
|
+
// shell may answer for it.
|
|
19531
|
+
"TRANSPORT_CLOSED",
|
|
19532
|
+
"CLIENT_CLOSED",
|
|
19533
|
+
"RPC_TIMEOUT",
|
|
19364
19534
|
"internal"
|
|
19365
19535
|
]);
|
|
19366
|
-
function
|
|
19536
|
+
function isRecord8(value) {
|
|
19367
19537
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
19368
19538
|
}
|
|
19539
|
+
function normalizeByteResult3(endpoint, result) {
|
|
19540
|
+
if (endpoint !== "workspaceFiles/readBytes") return result;
|
|
19541
|
+
if (typeof result !== "object" || result === null || Array.isArray(result)) return result;
|
|
19542
|
+
const envelope = result;
|
|
19543
|
+
const value = envelope.value;
|
|
19544
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return result;
|
|
19545
|
+
const raw = value.data;
|
|
19546
|
+
const data2 = raw instanceof Uint8Array ? raw : decodeByteValue(raw);
|
|
19547
|
+
if (data2 === void 0) return result;
|
|
19548
|
+
return {
|
|
19549
|
+
...envelope,
|
|
19550
|
+
value: { ...value, data: null },
|
|
19551
|
+
attachments: [{ path: ["data"], bytes: data2 }]
|
|
19552
|
+
};
|
|
19553
|
+
}
|
|
19369
19554
|
|
|
19370
19555
|
// src/werift-rtc.ts
|
|
19371
19556
|
import { createSocket } from "node:dgram";
|
|
@@ -20442,14 +20627,34 @@ var HOST_AUTHORIZATION_ERRORS = /* @__PURE__ */ new Set([
|
|
|
20442
20627
|
"DEVICE_REVOKED",
|
|
20443
20628
|
"TOKEN_EXPIRED"
|
|
20444
20629
|
]);
|
|
20630
|
+
var CREDENTIAL_FAILURE_CODES = /* @__PURE__ */ new Set([
|
|
20631
|
+
"AUTH_REQUIRED",
|
|
20632
|
+
"ACCOUNT_AUTH_REQUIRED",
|
|
20633
|
+
"AUTH_INVALID",
|
|
20634
|
+
"TOKEN_EXPIRED",
|
|
20635
|
+
"DEVICE_NOT_FOUND",
|
|
20636
|
+
"DEVICE_REVOKED",
|
|
20637
|
+
"MEMBERSHIP_REQUIRED"
|
|
20638
|
+
]);
|
|
20639
|
+
var LIVENESS_INTERVAL_MS = 3e4;
|
|
20640
|
+
var FAST_RECONNECT_INTERVAL_MS = 5e3;
|
|
20641
|
+
var LIVENESS_TIMEOUT_MS = 4e3;
|
|
20642
|
+
var LIVENESS_TOLERATED_FAILURES = 2;
|
|
20643
|
+
var NO_ANSWER_CODES = /* @__PURE__ */ new Set(["RPC_TIMEOUT", "CLIENT_CLOSED", "TRANSPORT_CLOSED", "RPC_ABORTED"]);
|
|
20644
|
+
function livenessProbeLost(error) {
|
|
20645
|
+
const code = typeof error === "object" && error !== null && "code" in error ? error.code : void 0;
|
|
20646
|
+
if (typeof code !== "string") return true;
|
|
20647
|
+
return NO_ANSWER_CODES.has(code);
|
|
20648
|
+
}
|
|
20445
20649
|
var ClientModeRuntime = class {
|
|
20446
|
-
constructor(config, identities, server, apiProxy, typertGateway, logger, host, rtcFactoryProvider = loadNodeRtcFactory) {
|
|
20650
|
+
constructor(config, identities, server, apiProxy, typertGateway, logger, host, rtcFactoryProvider = loadNodeRtcFactory, targetStore) {
|
|
20447
20651
|
this.config = config;
|
|
20448
20652
|
this.identities = identities;
|
|
20449
20653
|
this.server = server;
|
|
20450
20654
|
this.logger = logger;
|
|
20451
20655
|
this.host = host;
|
|
20452
20656
|
this.rtcFactoryProvider = rtcFactoryProvider;
|
|
20657
|
+
this.targetStore = targetStore;
|
|
20453
20658
|
this.proxySwitch = apiProxy === void 0 ? void 0 : new ApiProxySwitch(apiProxy);
|
|
20454
20659
|
this.gatewaySwitch = new TypertGatewaySwitch(typertGateway);
|
|
20455
20660
|
this.gatewaySwitch.setRemoteAvailability(() => this.connected !== void 0);
|
|
@@ -20474,12 +20679,56 @@ var ClientModeRuntime = class {
|
|
|
20474
20679
|
*/
|
|
20475
20680
|
remoteReconnectRun = 0;
|
|
20476
20681
|
pendingWorkspaceSelection;
|
|
20682
|
+
/**
|
|
20683
|
+
* Transport a running fast reconnect is replacing.
|
|
20684
|
+
*
|
|
20685
|
+
* The rebuild opens its own control connection and the Server closes the older one for the same
|
|
20686
|
+
* device, so that close belongs to our own replacement. Reading it as a lost peer is what made the
|
|
20687
|
+
* fast level escalate itself into the fallback before it could ever succeed.
|
|
20688
|
+
*/
|
|
20689
|
+
supersededClient;
|
|
20690
|
+
/**
|
|
20691
|
+
* True while a fast reconnect is rebuilding the link.
|
|
20692
|
+
*
|
|
20693
|
+
* The probe must not judge the link in the middle of its own replacement: the Server closes the
|
|
20694
|
+
* older connection for the device, so a check that ran then would read our replacement's side effect
|
|
20695
|
+
* as a second miss and escalate the level that is busy recovering. The rebuild's own outcome decides
|
|
20696
|
+
* first; only once it has settled does the cadence resume judging.
|
|
20697
|
+
*/
|
|
20698
|
+
fastRebuildInFlight = false;
|
|
20699
|
+
/**
|
|
20700
|
+
* The last workspace the user opened for a Host.
|
|
20701
|
+
*
|
|
20702
|
+
* Kept so a reconnect can republish it: re-selecting the workspace is what makes the native UI
|
|
20703
|
+
* re-read its session list, and without it a list poisoned by the outage stays wrong even after
|
|
20704
|
+
* the link is back.
|
|
20705
|
+
*/
|
|
20706
|
+
lastWorkspaceSelection;
|
|
20477
20707
|
codexVirtual;
|
|
20478
20708
|
proxySwitch;
|
|
20479
20709
|
gatewaySwitch;
|
|
20480
20710
|
codexStreams = /* @__PURE__ */ new Map();
|
|
20481
20711
|
connectionProgress;
|
|
20482
20712
|
connectionProgressRun = 0;
|
|
20713
|
+
/**
|
|
20714
|
+
* Host a boot is trying to restore, reported to the UI while the retry loop runs.
|
|
20715
|
+
*
|
|
20716
|
+
* Nothing is connected yet, so without this the window would show the local shell while the
|
|
20717
|
+
* user is still looking at the remote workspace they left.
|
|
20718
|
+
*/
|
|
20719
|
+
/**
|
|
20720
|
+
* Recovery in progress, and which kind.
|
|
20721
|
+
*
|
|
20722
|
+
* `restore` is a start that is reconnecting to the recorded target, `fast` keeps the session on
|
|
20723
|
+
* screen while its link is rebuilt, and `fallback` is the last resort that returns the user to
|
|
20724
|
+
* the local shell. The UI shows all three, so a reconnect is never invisible, and the retry loop
|
|
20725
|
+
* stops as soon as the user asks for local.
|
|
20726
|
+
*/
|
|
20727
|
+
reconnecting;
|
|
20728
|
+
/** Periodic proof of life for the live remote session; absent while nothing is connected. */
|
|
20729
|
+
livenessTimer;
|
|
20730
|
+
livenessInFlight = false;
|
|
20731
|
+
livenessFailures = 0;
|
|
20483
20732
|
closed = false;
|
|
20484
20733
|
async start() {
|
|
20485
20734
|
if (this.closed) throw new Error("client remote-mode runtime is closed");
|
|
@@ -20540,6 +20789,7 @@ var ClientModeRuntime = class {
|
|
|
20540
20789
|
...targetStatus,
|
|
20541
20790
|
connected: this.connected !== void 0,
|
|
20542
20791
|
fellBackToLocal: this.fellBackToLocal,
|
|
20792
|
+
...this.reconnecting === void 0 ? {} : { reconnecting: { ...this.reconnecting } },
|
|
20543
20793
|
transport: this.connected?.client.getStats().mode ?? "Disconnected",
|
|
20544
20794
|
connectedTargetDeviceId: this.connected?.target.deviceId,
|
|
20545
20795
|
preferredTransports: this.config.forceRelay ? ["relay"] : ["lan", "p2p", "turn", "relay"],
|
|
@@ -20686,10 +20936,14 @@ var ClientModeRuntime = class {
|
|
|
20686
20936
|
this.connected = void 0;
|
|
20687
20937
|
this.connectionProgress = void 0;
|
|
20688
20938
|
this.pendingWorkspaceSelection = void 0;
|
|
20939
|
+
this.lastWorkspaceSelection = void 0;
|
|
20689
20940
|
await this.closeCodexStreams(previous2?.client);
|
|
20690
20941
|
await previous2?.client.close().catch(() => void 0);
|
|
20691
20942
|
this.fellBackToLocal = false;
|
|
20943
|
+
this.reconnecting = void 0;
|
|
20944
|
+
this.stopLivenessWatch();
|
|
20692
20945
|
this.remoteReconnectRun += 1;
|
|
20946
|
+
await this.rememberTarget({ mode: "local" });
|
|
20693
20947
|
this.logger.info("Harness target switched", { mode: "local" });
|
|
20694
20948
|
return this.status();
|
|
20695
20949
|
}
|
|
@@ -20712,11 +20966,191 @@ var ClientModeRuntime = class {
|
|
|
20712
20966
|
await this.closeCodexVirtual();
|
|
20713
20967
|
this.selectRemoteTarget(next);
|
|
20714
20968
|
this.fellBackToLocal = false;
|
|
20969
|
+
this.reconnecting = void 0;
|
|
20970
|
+
await this.rememberTarget({ mode: "remote", hostDeviceId: next.target.deviceId, hostName: next.target.name });
|
|
20715
20971
|
await this.closeCodexStreams(previous?.client);
|
|
20716
20972
|
await previous?.client.close().catch(() => void 0);
|
|
20717
20973
|
this.logger.info("Harness target switched", { mode: "remote", targetDeviceId: shortId(next.target.deviceId) });
|
|
20718
20974
|
return this.status();
|
|
20719
20975
|
}
|
|
20976
|
+
/**
|
|
20977
|
+
* Prove the live remote link still answers, and act on the result.
|
|
20978
|
+
*
|
|
20979
|
+
* Only a missing answer counts as loss: the peer's own error (a refusal, an unknown method) came
|
|
20980
|
+
* back over the same link and therefore proves it is there.
|
|
20981
|
+
*
|
|
20982
|
+
* The two recovery levels differ in what the user keeps. The first miss rebuilds the link in
|
|
20983
|
+
* place, so the session, its workspace selection and the remote carriers all stay; the second
|
|
20984
|
+
* gives up and returns to the local shell.
|
|
20985
|
+
*/
|
|
20986
|
+
async verifyRemoteLiveness() {
|
|
20987
|
+
const connected = this.connected;
|
|
20988
|
+
if (connected === void 0 || this.livenessInFlight || this.fastRebuildInFlight) return;
|
|
20989
|
+
this.livenessInFlight = true;
|
|
20990
|
+
try {
|
|
20991
|
+
await connected.client.rpc("harness.transport.describe", {}, void 0, { timeoutMs: LIVENESS_TIMEOUT_MS });
|
|
20992
|
+
this.livenessFailures = 0;
|
|
20993
|
+
if (this.reconnecting?.phase === "fast") this.finishReconnect("the link answered again");
|
|
20994
|
+
} catch (error) {
|
|
20995
|
+
if (!livenessProbeLost(error)) {
|
|
20996
|
+
this.livenessFailures = 0;
|
|
20997
|
+
return;
|
|
20998
|
+
}
|
|
20999
|
+
this.livenessFailures += 1;
|
|
21000
|
+
this.logger.warn("remote Harness liveness check found no answer", {
|
|
21001
|
+
targetDeviceId: shortId(connected.target.deviceId),
|
|
21002
|
+
attempt: this.livenessFailures
|
|
21003
|
+
});
|
|
21004
|
+
if (this.livenessFailures === 1) {
|
|
21005
|
+
await this.enterFastReconnect(connected.target.deviceId, connected.target.name);
|
|
21006
|
+
return;
|
|
21007
|
+
}
|
|
21008
|
+
if (this.livenessFailures >= LIVENESS_TOLERATED_FAILURES) {
|
|
21009
|
+
this.handleRemoteTransportLost(connected.client, connected.target.deviceId, "unanswered-twice");
|
|
21010
|
+
}
|
|
21011
|
+
} finally {
|
|
21012
|
+
this.livenessInFlight = false;
|
|
21013
|
+
}
|
|
21014
|
+
}
|
|
21015
|
+
/**
|
|
21016
|
+
* Rebuild the link while the session stays on screen.
|
|
21017
|
+
*
|
|
21018
|
+
* This is what separates the two recovery levels: nothing is handed back to the local shell, so a
|
|
21019
|
+
* link that recovers does not cost the user the view they were working in.
|
|
21020
|
+
* @param targetDeviceId - the Host to rebuild the link to.
|
|
21021
|
+
*/
|
|
21022
|
+
async enterFastReconnect(targetDeviceId, targetName) {
|
|
21023
|
+
if (this.reconnecting !== void 0) return;
|
|
21024
|
+
this.reconnecting = { targetDeviceId, ...targetName === void 0 ? {} : { targetName }, phase: "fast" };
|
|
21025
|
+
this.logger.warn("remote Harness link stopped answering; reconnecting in place", {
|
|
21026
|
+
targetDeviceId: shortId(targetDeviceId)
|
|
21027
|
+
});
|
|
21028
|
+
this.armLivenessWatch(FAST_RECONNECT_INTERVAL_MS);
|
|
21029
|
+
await this.reestablish(targetDeviceId);
|
|
21030
|
+
}
|
|
21031
|
+
/**
|
|
21032
|
+
* Build a new transport for the session already on screen.
|
|
21033
|
+
*
|
|
21034
|
+
* The carriers hold the previous client, so they are rebound to the new one before the old client
|
|
21035
|
+
* is closed; leaving it open would keep pointing remote calls at a dead transport.
|
|
21036
|
+
* @param targetDeviceId - the Host to reconnect to.
|
|
21037
|
+
* @returns true when a new session is in place.
|
|
21038
|
+
*/
|
|
21039
|
+
async reestablish(targetDeviceId) {
|
|
21040
|
+
const previous = this.connected;
|
|
21041
|
+
this.supersededClient = previous?.client;
|
|
21042
|
+
this.fastRebuildInFlight = true;
|
|
21043
|
+
try {
|
|
21044
|
+
const next = await this.connect(targetDeviceId);
|
|
21045
|
+
if (previous === void 0) {
|
|
21046
|
+
this.supersededClient = void 0;
|
|
21047
|
+
await next.client.close().catch(() => void 0);
|
|
21048
|
+
return false;
|
|
21049
|
+
}
|
|
21050
|
+
await this.closePreview();
|
|
21051
|
+
this.connected = next;
|
|
21052
|
+
this.clearConnectionProgress(next.progressRunId);
|
|
21053
|
+
this.selectRemoteTarget(next);
|
|
21054
|
+
await this.closeCodexStreams(previous.client);
|
|
21055
|
+
await previous.client.close().catch(() => void 0);
|
|
21056
|
+
this.supersededClient = void 0;
|
|
21057
|
+
this.finishReconnect("link re-established");
|
|
21058
|
+
return true;
|
|
21059
|
+
} catch (error) {
|
|
21060
|
+
this.supersededClient = void 0;
|
|
21061
|
+
this.logger.warn("fast reconnect attempt failed", {
|
|
21062
|
+
targetDeviceId: shortId(targetDeviceId),
|
|
21063
|
+
code: safeErrorCode(error)
|
|
21064
|
+
});
|
|
21065
|
+
return false;
|
|
21066
|
+
} finally {
|
|
21067
|
+
this.fastRebuildInFlight = false;
|
|
21068
|
+
}
|
|
21069
|
+
}
|
|
21070
|
+
rememberWorkspaceSelection(selection) {
|
|
21071
|
+
this.pendingWorkspaceSelection = selection;
|
|
21072
|
+
this.lastWorkspaceSelection = { ...selection };
|
|
21073
|
+
}
|
|
21074
|
+
/**
|
|
21075
|
+
* Republish the workspace selection so the native UI re-reads its remote session list.
|
|
21076
|
+
*
|
|
21077
|
+
* The client half consumes status.workspaceSelection and reconnects that workspace; that refresh is
|
|
21078
|
+
* what replaces a list the outage had filled with local answers.
|
|
21079
|
+
* @param targetDeviceId - the Host the reconnect finished against.
|
|
21080
|
+
*/
|
|
21081
|
+
restoreWorkspaceSelection(targetDeviceId) {
|
|
21082
|
+
const selection = this.lastWorkspaceSelection;
|
|
21083
|
+
if (selection === void 0 || selection.targetDeviceId !== targetDeviceId) return;
|
|
21084
|
+
this.pendingWorkspaceSelection = { ...selection };
|
|
21085
|
+
this.logger.info("republishing the workspace selection to re-read the remote session list", {
|
|
21086
|
+
targetDeviceId: shortId(targetDeviceId),
|
|
21087
|
+
workspaceId: selection.workspaceId
|
|
21088
|
+
});
|
|
21089
|
+
}
|
|
21090
|
+
finishReconnect(reason) {
|
|
21091
|
+
const target2 = this.reconnecting?.targetDeviceId;
|
|
21092
|
+
if (target2 === void 0) return;
|
|
21093
|
+
this.reconnecting = void 0;
|
|
21094
|
+
this.livenessFailures = 0;
|
|
21095
|
+
this.armLivenessWatch(LIVENESS_INTERVAL_MS);
|
|
21096
|
+
this.restoreWorkspaceSelection(target2);
|
|
21097
|
+
this.logger.info("remote Harness reconnect finished", { targetDeviceId: shortId(target2), reason });
|
|
21098
|
+
}
|
|
21099
|
+
/**
|
|
21100
|
+
* Check the live session now, outside the cadence.
|
|
21101
|
+
*
|
|
21102
|
+
* Used when the page becomes visible again, which is when a suspended client is most likely to be
|
|
21103
|
+
* holding a link that already ended.
|
|
21104
|
+
* @returns the status after the check.
|
|
21105
|
+
*/
|
|
21106
|
+
async verifyRemoteConnection() {
|
|
21107
|
+
await this.verifyRemoteLiveness();
|
|
21108
|
+
return this.status();
|
|
21109
|
+
}
|
|
21110
|
+
armLivenessWatch(intervalMs) {
|
|
21111
|
+
if (this.livenessTimer !== void 0) clearInterval(this.livenessTimer);
|
|
21112
|
+
const timer = setInterval(() => {
|
|
21113
|
+
void this.verifyRemoteLiveness();
|
|
21114
|
+
}, intervalMs);
|
|
21115
|
+
timer.unref?.();
|
|
21116
|
+
this.livenessTimer = timer;
|
|
21117
|
+
}
|
|
21118
|
+
stopLivenessWatch() {
|
|
21119
|
+
if (this.livenessTimer !== void 0) clearInterval(this.livenessTimer);
|
|
21120
|
+
this.livenessTimer = void 0;
|
|
21121
|
+
this.livenessFailures = 0;
|
|
21122
|
+
this.livenessInFlight = false;
|
|
21123
|
+
}
|
|
21124
|
+
/**
|
|
21125
|
+
* Shared cleanup for a session whose transport is gone.
|
|
21126
|
+
*
|
|
21127
|
+
* A close event and an exhausted liveness check must leave exactly the same state behind, so both
|
|
21128
|
+
* paths run this. The session is gone for good here, which is why the phase becomes 'fallback' and
|
|
21129
|
+
* the retry loop keeps the UI saying that it is reconnecting.
|
|
21130
|
+
* @param client - the client that was connected.
|
|
21131
|
+
* @param targetDeviceId - the Host it was bound to.
|
|
21132
|
+
*/
|
|
21133
|
+
handleRemoteTransportLost(client, targetDeviceId, reason) {
|
|
21134
|
+
if (this.connected?.client !== client) return;
|
|
21135
|
+
this.supersededClient = void 0;
|
|
21136
|
+
const targetName = this.connected.target.name;
|
|
21137
|
+
this.stopLivenessWatch();
|
|
21138
|
+
void this.closePreview();
|
|
21139
|
+
this.connected = void 0;
|
|
21140
|
+
this.connectionProgress = void 0;
|
|
21141
|
+
this.pendingWorkspaceSelection = void 0;
|
|
21142
|
+
void this.closeCodexVirtual();
|
|
21143
|
+
this.proxySwitch?.selectLocal();
|
|
21144
|
+
this.gatewaySwitch.selectLocal();
|
|
21145
|
+
this.fellBackToLocal = true;
|
|
21146
|
+
this.reconnecting = { targetDeviceId, ...targetName === void 0 ? {} : { targetName }, phase: "fallback" };
|
|
21147
|
+
void client.close().catch(() => void 0);
|
|
21148
|
+
this.logger.warn("remote Harness transport lost; reconnecting", {
|
|
21149
|
+
targetDeviceId: shortId(targetDeviceId),
|
|
21150
|
+
reason
|
|
21151
|
+
});
|
|
21152
|
+
void this.reconnectRemoteSession(targetDeviceId);
|
|
21153
|
+
}
|
|
20720
21154
|
/**
|
|
20721
21155
|
* Re-establish a remote session whose transport closed.
|
|
20722
21156
|
*
|
|
@@ -20740,20 +21174,79 @@ var ClientModeRuntime = class {
|
|
|
20740
21174
|
if (this.connected !== void 0) return;
|
|
20741
21175
|
try {
|
|
20742
21176
|
await this.setMode("remote", targetDeviceId);
|
|
21177
|
+
this.restoreWorkspaceSelection(targetDeviceId);
|
|
20743
21178
|
this.logger.info("remote Harness session reconnected", { targetDeviceId: shortId(targetDeviceId) });
|
|
20744
21179
|
return;
|
|
20745
21180
|
} catch (error) {
|
|
21181
|
+
const code = safeErrorCode(error);
|
|
21182
|
+
if (code !== void 0 && CREDENTIAL_FAILURE_CODES.has(code)) {
|
|
21183
|
+
this.logger.warn("remote Harness reconnect stopped: authorization is required", {
|
|
21184
|
+
targetDeviceId: shortId(targetDeviceId),
|
|
21185
|
+
code
|
|
21186
|
+
});
|
|
21187
|
+
this.reconnecting = void 0;
|
|
21188
|
+
return;
|
|
21189
|
+
}
|
|
20746
21190
|
if (attempt < 3 || attempt % 10 === 0) {
|
|
20747
21191
|
this.logger.warn("remote Harness reconnect attempt failed", {
|
|
20748
21192
|
targetDeviceId: shortId(targetDeviceId),
|
|
20749
21193
|
attempt,
|
|
20750
|
-
code
|
|
21194
|
+
code
|
|
20751
21195
|
});
|
|
20752
21196
|
}
|
|
20753
21197
|
}
|
|
20754
21198
|
attempt += 1;
|
|
20755
21199
|
}
|
|
20756
21200
|
}
|
|
21201
|
+
/**
|
|
21202
|
+
* Persist the target a later boot may have to restore.
|
|
21203
|
+
*
|
|
21204
|
+
* A failure here must not fail a connect: the only cost is that a killed process comes back to
|
|
21205
|
+
* the local shell, which is where it would have been without this record.
|
|
21206
|
+
* @param target - the target to record.
|
|
21207
|
+
*/
|
|
21208
|
+
async rememberTarget(target2) {
|
|
21209
|
+
if (this.targetStore === void 0) return;
|
|
21210
|
+
try {
|
|
21211
|
+
await this.targetStore.save({
|
|
21212
|
+
...target2,
|
|
21213
|
+
...this.config.serverUrl === void 0 ? {} : { serverUrl: this.config.serverUrl }
|
|
21214
|
+
});
|
|
21215
|
+
} catch (error) {
|
|
21216
|
+
this.logger.warn("could not record the remote target", { code: safeErrorCode(error) });
|
|
21217
|
+
}
|
|
21218
|
+
}
|
|
21219
|
+
/**
|
|
21220
|
+
* Reconnect to the target this device was last using, with the usual backoff.
|
|
21221
|
+
*
|
|
21222
|
+
* A resumed app can miss the transport close entirely - Android may reclaim the process - so
|
|
21223
|
+
* nothing would start the retry loop and the user would face a local shell behind a remote
|
|
21224
|
+
* workspace view. Restoring the recorded target gives that case the same loop a dropped
|
|
21225
|
+
* transport gets. A target recorded for another Server is left alone: it is not reachable
|
|
21226
|
+
* through the configured one.
|
|
21227
|
+
* @returns true when a retry loop was started.
|
|
21228
|
+
*/
|
|
21229
|
+
async restoreLastTarget() {
|
|
21230
|
+
if (this.closed || this.connected !== void 0) return false;
|
|
21231
|
+
const record7 = await this.targetStore?.load();
|
|
21232
|
+
if (record7 === void 0 || record7.mode !== "remote" || record7.hostDeviceId === void 0) return false;
|
|
21233
|
+
if (record7.serverUrl !== void 0 && record7.serverUrl !== this.config.serverUrl) {
|
|
21234
|
+
this.logger.info("recorded remote target belongs to another Server; not restoring", {
|
|
21235
|
+
targetDeviceId: shortId(record7.hostDeviceId)
|
|
21236
|
+
});
|
|
21237
|
+
return false;
|
|
21238
|
+
}
|
|
21239
|
+
this.reconnecting = {
|
|
21240
|
+
targetDeviceId: record7.hostDeviceId,
|
|
21241
|
+
...record7.hostName === void 0 ? {} : { targetName: record7.hostName },
|
|
21242
|
+
phase: "restore"
|
|
21243
|
+
};
|
|
21244
|
+
this.logger.info("restoring the remote target of the previous run", {
|
|
21245
|
+
targetDeviceId: shortId(record7.hostDeviceId)
|
|
21246
|
+
});
|
|
21247
|
+
void this.reconnectRemoteSession(record7.hostDeviceId);
|
|
21248
|
+
return true;
|
|
21249
|
+
}
|
|
20757
21250
|
async listRemoteDirectory(targetDeviceId, path, signal) {
|
|
20758
21251
|
const remote = await this.ensureConnected(targetDeviceId, signal);
|
|
20759
21252
|
if (remote.features.remoteGateway) {
|
|
@@ -20808,7 +21301,7 @@ var ClientModeRuntime = class {
|
|
|
20808
21301
|
await this.closeCodexVirtual();
|
|
20809
21302
|
this.selectRemoteTarget(remote, transport);
|
|
20810
21303
|
const workspaceId = workspaceRecordId(workspace.workspace);
|
|
20811
|
-
this.
|
|
21304
|
+
this.rememberWorkspaceSelection({ targetDeviceId: remote.target.deviceId, workspaceId });
|
|
20812
21305
|
this.logger.info("Remote workspace opened", { targetDeviceId: shortId(remote.target.deviceId) });
|
|
20813
21306
|
return { ...this.status(), workspace };
|
|
20814
21307
|
}
|
|
@@ -20842,12 +21335,12 @@ var ClientModeRuntime = class {
|
|
|
20842
21335
|
this.codexVirtual = virtual;
|
|
20843
21336
|
this.selectCodexTarget(virtual, remote);
|
|
20844
21337
|
const preferredSessionId = await virtual.preferredSessionId(signal);
|
|
20845
|
-
this.
|
|
21338
|
+
this.rememberWorkspaceSelection({
|
|
20846
21339
|
targetDeviceId: remote.target.deviceId,
|
|
20847
21340
|
workspaceId,
|
|
20848
21341
|
backend: "codex",
|
|
20849
21342
|
...preferredSessionId === void 0 ? {} : { sessionId: preferredSessionId }
|
|
20850
|
-
};
|
|
21343
|
+
});
|
|
20851
21344
|
this.logger.info("CodeX virtual workspace opened", { targetDeviceId: shortId(remote.target.deviceId) });
|
|
20852
21345
|
return { ...this.status(), workspace };
|
|
20853
21346
|
}
|
|
@@ -20872,6 +21365,7 @@ var ClientModeRuntime = class {
|
|
|
20872
21365
|
return this.openCodexWorkspace(targetDeviceId, codexProjectWorkspaceId(project.id), signal);
|
|
20873
21366
|
}
|
|
20874
21367
|
consumeWorkspaceSelection(selection) {
|
|
21368
|
+
this.lastWorkspaceSelection = { ...selection };
|
|
20875
21369
|
const pending = this.pendingWorkspaceSelection;
|
|
20876
21370
|
if (pending?.targetDeviceId === selection.targetDeviceId && pending.workspaceId === selection.workspaceId && (pending.backend ?? "harness") === (selection.backend ?? "harness")) {
|
|
20877
21371
|
this.pendingWorkspaceSelection = void 0;
|
|
@@ -20882,6 +21376,7 @@ var ClientModeRuntime = class {
|
|
|
20882
21376
|
await this.closePreview();
|
|
20883
21377
|
if (this.closed) return;
|
|
20884
21378
|
this.closed = true;
|
|
21379
|
+
this.stopLivenessWatch();
|
|
20885
21380
|
this.proxySwitch?.selectLocal();
|
|
20886
21381
|
await this.closePreview();
|
|
20887
21382
|
this.gatewaySwitch.selectLocal();
|
|
@@ -20949,10 +21444,10 @@ var ClientModeRuntime = class {
|
|
|
20949
21444
|
}
|
|
20950
21445
|
}
|
|
20951
21446
|
stream.unsubscribe = remote.client.onEvent((event) => {
|
|
20952
|
-
if (event.event === "codex.app.frame" &&
|
|
21447
|
+
if (event.event === "codex.app.frame" && isRecord9(event.data) && event.data.streamId === value.streamId) {
|
|
20953
21448
|
this.appendCodexFrame(stream, event.data);
|
|
20954
21449
|
}
|
|
20955
|
-
if (event.event === "codex.app.stream.closed" &&
|
|
21450
|
+
if (event.event === "codex.app.stream.closed" && isRecord9(event.data) && event.data.streamId === value.streamId) {
|
|
20956
21451
|
stream.closed = typeof event.data.reason === "string" ? event.data.reason : "closed";
|
|
20957
21452
|
stream.wake();
|
|
20958
21453
|
}
|
|
@@ -20979,7 +21474,7 @@ var ClientModeRuntime = class {
|
|
|
20979
21474
|
stream.wake();
|
|
20980
21475
|
};
|
|
20981
21476
|
appendCodexFrame(stream, data2) {
|
|
20982
|
-
if (!
|
|
21477
|
+
if (!isRecord9(data2) || !isRecord9(data2.frame) || typeof data2.frame.method !== "string") return;
|
|
20983
21478
|
if (stream.frames.length >= 256) {
|
|
20984
21479
|
stream.closed = "overflow";
|
|
20985
21480
|
} else {
|
|
@@ -21185,21 +21680,15 @@ var ClientModeRuntime = class {
|
|
|
21185
21680
|
connectedPreference === void 0 ? void 0 : [connectedPreference]
|
|
21186
21681
|
);
|
|
21187
21682
|
connectedClient.onClose(() => {
|
|
21188
|
-
if (this.
|
|
21189
|
-
|
|
21190
|
-
|
|
21191
|
-
|
|
21192
|
-
|
|
21193
|
-
|
|
21194
|
-
this.
|
|
21195
|
-
this.gatewaySwitch.selectLocal();
|
|
21196
|
-
this.fellBackToLocal = true;
|
|
21197
|
-
void connectedClient.close().catch(() => void 0);
|
|
21198
|
-
this.logger.warn("remote Harness transport closed; reconnecting", {
|
|
21199
|
-
targetDeviceId: shortId(target2.deviceId)
|
|
21200
|
-
});
|
|
21201
|
-
void this.reconnectRemoteSession(target2.deviceId);
|
|
21683
|
+
if (this.supersededClient === connectedClient) {
|
|
21684
|
+
this.logger.info("ignoring the close of the transport a reconnect is replacing", {
|
|
21685
|
+
targetDeviceId: shortId(target2.deviceId)
|
|
21686
|
+
});
|
|
21687
|
+
return;
|
|
21688
|
+
}
|
|
21689
|
+
this.handleRemoteTransportLost(connectedClient, target2.deviceId, "transport-closed");
|
|
21202
21690
|
});
|
|
21691
|
+
this.armLivenessWatch(LIVENESS_INTERVAL_MS);
|
|
21203
21692
|
const connectionDetails = await connectedTransport.connectionDetails().catch(() => void 0);
|
|
21204
21693
|
this.logger.info("remote Harness transport ready", {
|
|
21205
21694
|
targetDeviceId: shortId(target2.deviceId),
|
|
@@ -21270,6 +21759,7 @@ var ClientModeRuntime = class {
|
|
|
21270
21759
|
async handleControl(endpoint, payload, signal) {
|
|
21271
21760
|
try {
|
|
21272
21761
|
if (endpoint === "status") return ok2(await this.detailedStatus());
|
|
21762
|
+
if (endpoint === "client.connection.verify") return ok2(await this.verifyRemoteConnection());
|
|
21273
21763
|
if (endpoint === "devices") return ok2(await this.devices());
|
|
21274
21764
|
if (endpoint === "client.account.login") {
|
|
21275
21765
|
const value = record4(payload);
|
|
@@ -21483,7 +21973,7 @@ function record4(value) {
|
|
|
21483
21973
|
}
|
|
21484
21974
|
return value;
|
|
21485
21975
|
}
|
|
21486
|
-
function
|
|
21976
|
+
function isRecord9(value) {
|
|
21487
21977
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
21488
21978
|
}
|
|
21489
21979
|
function ok2(value) {
|
|
@@ -21508,11 +21998,11 @@ async function readRemoteWorkspaceBaseline(gateway, signal) {
|
|
|
21508
21998
|
const iterator = source[Symbol.asyncIterator]();
|
|
21509
21999
|
try {
|
|
21510
22000
|
const first = await iterator.next();
|
|
21511
|
-
if (first.done || !
|
|
22001
|
+
if (first.done || !isRecord9(first.value) || first.value.type !== "baseline" || !isRecord9(first.value.value) || !Array.isArray(first.value.value.items)) {
|
|
21512
22002
|
throw new ClientModeError("INVALID_MESSAGE", "The remote Host returned an invalid Workspace baseline.");
|
|
21513
22003
|
}
|
|
21514
22004
|
return first.value.value.items.map((item) => {
|
|
21515
|
-
if (!
|
|
22005
|
+
if (!isRecord9(item) || typeof item.workspaceId !== "string" || typeof item.path !== "string" || typeof item.title !== "string") {
|
|
21516
22006
|
throw new ClientModeError("INVALID_MESSAGE", "The remote Host returned an invalid Workspace row.");
|
|
21517
22007
|
}
|
|
21518
22008
|
return { workspaceId: item.workspaceId, path: item.path, title: item.title };
|
|
@@ -21578,7 +22068,7 @@ async function probeRemoteHostFeatures(client, clientVersion) {
|
|
|
21578
22068
|
if (error instanceof Error && "code" in error && error.code === "METHOD_NOT_FOUND") return fallback;
|
|
21579
22069
|
throw error;
|
|
21580
22070
|
}
|
|
21581
|
-
if (!
|
|
22071
|
+
if (!isRecord9(value) || !Array.isArray(value.capabilities) || value.capabilities.some((capability) => typeof capability !== "string")) {
|
|
21582
22072
|
throw new ClientModeError("INVALID_MESSAGE", "The remote Host returned invalid transport capabilities.");
|
|
21583
22073
|
}
|
|
21584
22074
|
const capabilities = new Set(value.capabilities);
|
|
@@ -21663,6 +22153,67 @@ function iceServersForAttempt(attempt, iceServers) {
|
|
|
21663
22153
|
return attempt === "direct" ? stunOnlyIceServers(iceServers) : iceServers;
|
|
21664
22154
|
}
|
|
21665
22155
|
|
|
22156
|
+
// src/client-target-store.ts
|
|
22157
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
22158
|
+
import { chmod as chmod2, mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2 } from "node:fs/promises";
|
|
22159
|
+
import { join as join5 } from "node:path";
|
|
22160
|
+
var FILE_NAME = "client-target.json";
|
|
22161
|
+
var ClientTargetStore = class {
|
|
22162
|
+
constructor(directory) {
|
|
22163
|
+
this.directory = directory;
|
|
22164
|
+
}
|
|
22165
|
+
get file() {
|
|
22166
|
+
return join5(this.directory, FILE_NAME);
|
|
22167
|
+
}
|
|
22168
|
+
/** Read the record. A missing, unreadable or malformed file reads as "no record". */
|
|
22169
|
+
async load() {
|
|
22170
|
+
let raw;
|
|
22171
|
+
try {
|
|
22172
|
+
raw = await readFile3(this.file, "utf8");
|
|
22173
|
+
} catch {
|
|
22174
|
+
return void 0;
|
|
22175
|
+
}
|
|
22176
|
+
try {
|
|
22177
|
+
const value = JSON.parse(raw);
|
|
22178
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
22179
|
+
const record7 = value;
|
|
22180
|
+
if (record7.schemaVersion !== 1) return void 0;
|
|
22181
|
+
if (record7.mode !== "local" && record7.mode !== "remote") return void 0;
|
|
22182
|
+
if (record7.mode === "remote" && (typeof record7.hostDeviceId !== "string" || record7.hostDeviceId.length === 0)) {
|
|
22183
|
+
return void 0;
|
|
22184
|
+
}
|
|
22185
|
+
return {
|
|
22186
|
+
schemaVersion: 1,
|
|
22187
|
+
mode: record7.mode,
|
|
22188
|
+
...typeof record7.serverUrl === "string" && record7.serverUrl.length > 0 ? { serverUrl: record7.serverUrl } : {},
|
|
22189
|
+
...typeof record7.hostDeviceId === "string" && record7.hostDeviceId.length > 0 ? { hostDeviceId: record7.hostDeviceId } : {},
|
|
22190
|
+
...typeof record7.hostName === "string" && record7.hostName.length > 0 ? { hostName: record7.hostName } : {},
|
|
22191
|
+
savedAt: typeof record7.savedAt === "number" ? record7.savedAt : 0
|
|
22192
|
+
};
|
|
22193
|
+
} catch {
|
|
22194
|
+
return void 0;
|
|
22195
|
+
}
|
|
22196
|
+
}
|
|
22197
|
+
/** Record the current target. Failures reach the caller so a boot can log them. */
|
|
22198
|
+
async save(target2) {
|
|
22199
|
+
const value = {
|
|
22200
|
+
schemaVersion: 1,
|
|
22201
|
+
mode: target2.mode,
|
|
22202
|
+
...target2.serverUrl === void 0 ? {} : { serverUrl: target2.serverUrl },
|
|
22203
|
+
...target2.hostDeviceId === void 0 ? {} : { hostDeviceId: target2.hostDeviceId },
|
|
22204
|
+
...target2.hostName === void 0 ? {} : { hostName: target2.hostName },
|
|
22205
|
+
savedAt: Date.now()
|
|
22206
|
+
};
|
|
22207
|
+
await mkdir2(this.directory, { recursive: true, mode: 448 });
|
|
22208
|
+
await sweepStaleTemporaries(this.directory);
|
|
22209
|
+
const temporary = this.file + "." + String(process.pid) + "." + randomUUID2() + ".tmp";
|
|
22210
|
+
await writeFile2(temporary, JSON.stringify(value, null, 2) + "\n", { encoding: "utf8", mode: 384, flag: "wx" });
|
|
22211
|
+
await chmod2(temporary, 384);
|
|
22212
|
+
await replaceFile(temporary, this.file);
|
|
22213
|
+
await chmod2(this.file, 384);
|
|
22214
|
+
}
|
|
22215
|
+
};
|
|
22216
|
+
|
|
21666
22217
|
// src/control-runtime.ts
|
|
21667
22218
|
import { hostname as hostname2 } from "node:os";
|
|
21668
22219
|
import { existsSync as existsSync3 } from "node:fs";
|
|
@@ -21670,9 +22221,9 @@ import { execFileSync } from "node:child_process";
|
|
|
21670
22221
|
|
|
21671
22222
|
// src/identity-store.ts
|
|
21672
22223
|
import { createHash } from "node:crypto";
|
|
21673
|
-
import { chmod as
|
|
22224
|
+
import { chmod as chmod3, mkdir as mkdir3, readFile as readFile4, rm as rm3, stat as stat3, writeFile as writeFile3 } from "node:fs/promises";
|
|
21674
22225
|
import { homedir } from "node:os";
|
|
21675
|
-
import { dirname as dirname4, join as
|
|
22226
|
+
import { dirname as dirname4, join as join6 } from "node:path";
|
|
21676
22227
|
var identitySchema = external_exports.object({
|
|
21677
22228
|
schemaVersion: external_exports.literal(1),
|
|
21678
22229
|
deviceId: external_exports.string().uuid(),
|
|
@@ -21701,14 +22252,14 @@ var IdentityStore = class {
|
|
|
21701
22252
|
peers = /* @__PURE__ */ new Map();
|
|
21702
22253
|
constructor(options = {}) {
|
|
21703
22254
|
const env = options.env ?? process.env;
|
|
21704
|
-
const dshHome = env.DSH_HOME ||
|
|
21705
|
-
this.directory = options.directory ??
|
|
22255
|
+
const dshHome = env.DSH_HOME || join6(options.homeDirectory ?? homedir(), ".dsh");
|
|
22256
|
+
this.directory = options.directory ?? join6(dshHome, "remote");
|
|
21706
22257
|
}
|
|
21707
22258
|
async loadOrCreate(deviceName) {
|
|
21708
|
-
await
|
|
21709
|
-
await
|
|
21710
|
-
const devicePath =
|
|
21711
|
-
const keyPath =
|
|
22259
|
+
await mkdir3(this.directory, { recursive: true, mode: 448 });
|
|
22260
|
+
await chmod3(this.directory, 448);
|
|
22261
|
+
const devicePath = join6(this.directory, "device.json");
|
|
22262
|
+
const keyPath = join6(this.directory, "device.key");
|
|
21712
22263
|
const [hasDevice, hasKey] = await Promise.all([exists2(devicePath), exists2(keyPath)]);
|
|
21713
22264
|
if (hasDevice !== hasKey) {
|
|
21714
22265
|
throw new IdentityInvalidError("device identity is incomplete; repair it explicitly before reconnecting");
|
|
@@ -21722,8 +22273,8 @@ var IdentityStore = class {
|
|
|
21722
22273
|
}
|
|
21723
22274
|
await assertPrivateMode2(keyPath);
|
|
21724
22275
|
try {
|
|
21725
|
-
let record7 = identitySchema.parse(JSON.parse(await
|
|
21726
|
-
const privateKey = (await
|
|
22276
|
+
let record7 = identitySchema.parse(JSON.parse(await readFile4(devicePath, "utf8")));
|
|
22277
|
+
const privateKey = (await readFile4(keyPath, "utf8")).trim();
|
|
21727
22278
|
const regenerated = generateKeyPair(fromBase64Url2(privateKey));
|
|
21728
22279
|
if (regenerated.publicKey !== record7.publicKey) {
|
|
21729
22280
|
throw new IdentityInvalidError("device public and private keys do not match");
|
|
@@ -21777,11 +22328,11 @@ var IdentityStore = class {
|
|
|
21777
22328
|
return removed;
|
|
21778
22329
|
}
|
|
21779
22330
|
async loadPeers() {
|
|
21780
|
-
const path =
|
|
22331
|
+
const path = join6(this.directory, "trusted-peers.json");
|
|
21781
22332
|
if (!await exists2(path)) {
|
|
21782
22333
|
await atomicJsonWrite(path, { schemaVersion: 1, peers: [] }, 384);
|
|
21783
22334
|
}
|
|
21784
|
-
const parsed = trustedPeersSchema.parse(JSON.parse(await
|
|
22335
|
+
const parsed = trustedPeersSchema.parse(JSON.parse(await readFile4(path, "utf8")));
|
|
21785
22336
|
const peers = /* @__PURE__ */ new Map();
|
|
21786
22337
|
for (const peer of parsed.peers) {
|
|
21787
22338
|
if (peer.fingerprint !== fingerprint(peer.publicKey)) {
|
|
@@ -21793,7 +22344,7 @@ var IdentityStore = class {
|
|
|
21793
22344
|
this.peers = peers;
|
|
21794
22345
|
}
|
|
21795
22346
|
async savePeers() {
|
|
21796
|
-
await atomicJsonWrite(
|
|
22347
|
+
await atomicJsonWrite(join6(this.directory, "trusted-peers.json"), {
|
|
21797
22348
|
schemaVersion: 1,
|
|
21798
22349
|
peers: [...this.peers.values()]
|
|
21799
22350
|
}, 384);
|
|
@@ -21802,7 +22353,7 @@ var IdentityStore = class {
|
|
|
21802
22353
|
function serverStorageDirectory(root, serverUrl, role) {
|
|
21803
22354
|
const origin = new URL(serverUrl).origin;
|
|
21804
22355
|
const scope = createHash("sha256").update(origin).digest("hex").slice(0, 24);
|
|
21805
|
-
return
|
|
22356
|
+
return join6(root, "servers", scope, role);
|
|
21806
22357
|
}
|
|
21807
22358
|
function fingerprint(publicKey) {
|
|
21808
22359
|
const compact = createHash("sha256").update(fromBase64Url2(publicKey)).digest("hex").slice(0, 12).toUpperCase();
|
|
@@ -21821,13 +22372,13 @@ async function atomicJsonWrite(path, value, mode) {
|
|
|
21821
22372
|
}
|
|
21822
22373
|
async function atomicTextWrite(path, value, mode) {
|
|
21823
22374
|
const directory = dirname4(path);
|
|
21824
|
-
await
|
|
22375
|
+
await mkdir3(directory, { recursive: true, mode: 448 });
|
|
21825
22376
|
await sweepStaleTemporaries(directory);
|
|
21826
22377
|
const temporary = `${path}.${process.pid}.${uuidV7()}.tmp`;
|
|
21827
|
-
await
|
|
21828
|
-
await
|
|
22378
|
+
await writeFile3(temporary, value, { encoding: "utf8", mode, flag: "wx" });
|
|
22379
|
+
await chmod3(temporary, mode);
|
|
21829
22380
|
await replaceFile(temporary, path);
|
|
21830
|
-
await
|
|
22381
|
+
await chmod3(path, mode);
|
|
21831
22382
|
}
|
|
21832
22383
|
async function exists2(path) {
|
|
21833
22384
|
try {
|
|
@@ -21989,11 +22540,11 @@ var ControlStatusStream = class {
|
|
|
21989
22540
|
};
|
|
21990
22541
|
|
|
21991
22542
|
// src/codex/domain.ts
|
|
21992
|
-
import { randomUUID as
|
|
22543
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
21993
22544
|
import { accessSync, constants, existsSync as existsSync2, readFileSync, readdirSync as readdirSync2, statSync as statSync2 } from "node:fs";
|
|
21994
22545
|
import { readdir as readdir2, realpath, stat as stat4 } from "node:fs/promises";
|
|
21995
22546
|
import { homedir as homedir2 } from "node:os";
|
|
21996
|
-
import { basename as basename2, isAbsolute as isAbsolute2, join as
|
|
22547
|
+
import { basename as basename2, isAbsolute as isAbsolute2, join as join7, posix, relative, resolve } from "node:path";
|
|
21997
22548
|
|
|
21998
22549
|
// src/codex/app-server.ts
|
|
21999
22550
|
import { spawn as spawn2 } from "node:child_process";
|
|
@@ -22179,7 +22730,7 @@ var CodexAppServerClient = class {
|
|
|
22179
22730
|
this.handleProcessFailure("CODEX_INVALID_RESPONSE", new Error("Codex App Server emitted invalid JSON."));
|
|
22180
22731
|
return;
|
|
22181
22732
|
}
|
|
22182
|
-
if (!
|
|
22733
|
+
if (!isRecord10(value)) {
|
|
22183
22734
|
this.handleProcessFailure("CODEX_INVALID_RESPONSE", new Error("Codex App Server emitted an invalid message."));
|
|
22184
22735
|
return;
|
|
22185
22736
|
}
|
|
@@ -22224,12 +22775,12 @@ var CodexAppServerClient = class {
|
|
|
22224
22775
|
}
|
|
22225
22776
|
};
|
|
22226
22777
|
function safeUpstreamError(value) {
|
|
22227
|
-
if (!
|
|
22778
|
+
if (!isRecord10(value) || typeof value.message !== "string") return "Codex App Server rejected the request.";
|
|
22228
22779
|
const message = value.message.toLowerCase();
|
|
22229
22780
|
if (message.includes("active writer")) return "Codex thread already has an active writer.";
|
|
22230
22781
|
return message.includes("not initialized") ? "Codex App Server is not initialized." : "Codex App Server rejected the request.";
|
|
22231
22782
|
}
|
|
22232
|
-
function
|
|
22783
|
+
function isRecord10(value) {
|
|
22233
22784
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
22234
22785
|
}
|
|
22235
22786
|
|
|
@@ -22489,15 +23040,15 @@ function decodeCanonicalBase64(value) {
|
|
|
22489
23040
|
}
|
|
22490
23041
|
return decoded;
|
|
22491
23042
|
}
|
|
22492
|
-
function
|
|
23043
|
+
function isRecord11(value) {
|
|
22493
23044
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
22494
23045
|
}
|
|
22495
23046
|
function safeErrorCode2(error) {
|
|
22496
|
-
if (
|
|
23047
|
+
if (isRecord11(error) && typeof error.code === "string") return error.code;
|
|
22497
23048
|
return "UNKNOWN";
|
|
22498
23049
|
}
|
|
22499
23050
|
function safeMethod(input2) {
|
|
22500
|
-
return
|
|
23051
|
+
return isRecord11(input2) && typeof input2.method === "string" ? input2.method : "invalid";
|
|
22501
23052
|
}
|
|
22502
23053
|
function concatChunks(chunks, totalBytes) {
|
|
22503
23054
|
const output = new Uint8Array(totalBytes);
|
|
@@ -22512,6 +23063,7 @@ function concatChunks(chunks, totalBytes) {
|
|
|
22512
23063
|
// src/codex/domain.ts
|
|
22513
23064
|
var APPROVAL_TTL_MS = 5 * 6e4;
|
|
22514
23065
|
var DEFAULT_RESTART_DELAYS_MS = [1e3, 2e3, 4e3, 8e3, 15e3];
|
|
23066
|
+
var CODEX_START_BUDGET_MS = 5e3;
|
|
22515
23067
|
var CODEX_PAGE_LIMIT2 = 100;
|
|
22516
23068
|
var MAX_CODEX_PAGES2 = 32;
|
|
22517
23069
|
var CODEX_HISTORY_PAGE_LIMIT = 25;
|
|
@@ -22544,7 +23096,7 @@ var CodexRemoteDomain = class {
|
|
|
22544
23096
|
if (!this.config.enabled) return;
|
|
22545
23097
|
try {
|
|
22546
23098
|
this.state = "starting";
|
|
22547
|
-
await this.
|
|
23099
|
+
await this.launchWithinBudget();
|
|
22548
23100
|
} catch (error) {
|
|
22549
23101
|
this.available = false;
|
|
22550
23102
|
this.state = "unavailable";
|
|
@@ -22775,6 +23327,32 @@ var CodexRemoteDomain = class {
|
|
|
22775
23327
|
await this.appServer?.close();
|
|
22776
23328
|
this.appServer = void 0;
|
|
22777
23329
|
}
|
|
23330
|
+
/**
|
|
23331
|
+
* Launch the App Server within {@link CODEX_START_BUDGET_MS}.
|
|
23332
|
+
*
|
|
23333
|
+
* The losing side of the race keeps running until the disposal in start()'s catch stops it, so
|
|
23334
|
+
* it needs its own handler: an unhandled rejection here would surface as a process-level error
|
|
23335
|
+
* for what is only an optional feature being unavailable.
|
|
23336
|
+
* @returns nothing once a candidate is ready.
|
|
23337
|
+
*/
|
|
23338
|
+
async launchWithinBudget() {
|
|
23339
|
+
let timer;
|
|
23340
|
+
const attempt = this.launchAppServer();
|
|
23341
|
+
attempt.catch(() => void 0);
|
|
23342
|
+
try {
|
|
23343
|
+
await Promise.race([
|
|
23344
|
+
attempt,
|
|
23345
|
+
new Promise((_resolve, reject) => {
|
|
23346
|
+
timer = setTimeout(() => {
|
|
23347
|
+
reject(new RpcError("CODEX_START_TIMEOUT", `Codex App Server did not start within ${CODEX_START_BUDGET_MS}ms.`));
|
|
23348
|
+
}, CODEX_START_BUDGET_MS);
|
|
23349
|
+
timer.unref?.();
|
|
23350
|
+
})
|
|
23351
|
+
]);
|
|
23352
|
+
} finally {
|
|
23353
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
23354
|
+
}
|
|
23355
|
+
}
|
|
22778
23356
|
async launchAppServer() {
|
|
22779
23357
|
let lastError;
|
|
22780
23358
|
for (const binary of codexBinaryCandidates(this.config.binary)) {
|
|
@@ -22927,14 +23505,14 @@ var CodexRemoteDomain = class {
|
|
|
22927
23505
|
await this.assertResultThreadAllowed(result);
|
|
22928
23506
|
if (codexPermissionPresetFromResponse(result) !== params.permissionPreset) {
|
|
22929
23507
|
await this.callUpstream("thread/settings/update", { threadId, ...settings });
|
|
22930
|
-
result = { ...
|
|
23508
|
+
result = { ...isRecord12(result) ? result : {}, ...settings, sandbox: settings.sandboxPolicy };
|
|
22931
23509
|
}
|
|
22932
23510
|
}
|
|
22933
23511
|
await this.publishPermission(threadId, result);
|
|
22934
23512
|
return result;
|
|
22935
23513
|
}
|
|
22936
23514
|
async publishPermission(threadId, value) {
|
|
22937
|
-
const source =
|
|
23515
|
+
const source = isRecord12(value) ? value : {};
|
|
22938
23516
|
const threadSettings = { approvalPolicy: source.approvalPolicy, sandboxPolicy: source.sandboxPolicy ?? source.sandbox };
|
|
22939
23517
|
await this.handleInbound({ kind: "notification", method: "thread/settings/updated", params: { threadId, threadSettings } });
|
|
22940
23518
|
}
|
|
@@ -22952,7 +23530,7 @@ var CodexRemoteDomain = class {
|
|
|
22952
23530
|
await appServer.respond(message.id, { decision: "decline" });
|
|
22953
23531
|
return;
|
|
22954
23532
|
}
|
|
22955
|
-
const requestHandle =
|
|
23533
|
+
const requestHandle = randomUUID3();
|
|
22956
23534
|
this.approvals.set(requestHandle, {
|
|
22957
23535
|
upstreamId: message.id,
|
|
22958
23536
|
connectionId: owner.connectionId,
|
|
@@ -23030,9 +23608,9 @@ var CodexRemoteDomain = class {
|
|
|
23030
23608
|
itemsView,
|
|
23031
23609
|
...cursor2 === void 0 ? {} : { cursor: cursor2 }
|
|
23032
23610
|
});
|
|
23033
|
-
const pageResult =
|
|
23611
|
+
const pageResult = isRecord12(result) ? result : {};
|
|
23034
23612
|
for (const rawTurn of array2(pageResult.data)) {
|
|
23035
|
-
if (!
|
|
23613
|
+
if (!isRecord12(rawTurn)) continue;
|
|
23036
23614
|
const turnId = typeof rawTurn.id === "string" ? rawTurn.id : void 0;
|
|
23037
23615
|
const items = rawTurn.itemsView === "full" || turnId === void 0 ? array2(rawTurn.items) : await this.readThreadItems(connectionId, threadId, turnId, array2(rawTurn.items));
|
|
23038
23616
|
turns.push({ ...rawTurn, items });
|
|
@@ -23054,9 +23632,9 @@ var CodexRemoteDomain = class {
|
|
|
23054
23632
|
sortDirection: "asc",
|
|
23055
23633
|
...cursor2 === void 0 ? {} : { cursor: cursor2 }
|
|
23056
23634
|
});
|
|
23057
|
-
const pageResult =
|
|
23635
|
+
const pageResult = isRecord12(result) ? result : {};
|
|
23058
23636
|
for (const entry of array2(pageResult.data)) {
|
|
23059
|
-
if (
|
|
23637
|
+
if (isRecord12(entry) && entry.item !== void 0) items.push(entry.item);
|
|
23060
23638
|
}
|
|
23061
23639
|
cursor2 = typeof pageResult.nextCursor === "string" && pageResult.nextCursor.length > 0 ? pageResult.nextCursor : void 0;
|
|
23062
23640
|
if (cursor2 === void 0) break;
|
|
@@ -23120,7 +23698,7 @@ var CodexRemoteDomain = class {
|
|
|
23120
23698
|
return [...this.peers.values()].some((peer) => peer.hasThreadSubscription(threadId));
|
|
23121
23699
|
}
|
|
23122
23700
|
resolveUpstreamApproval(params) {
|
|
23123
|
-
if (!
|
|
23701
|
+
if (!isRecord12(params) || typeof params.requestId !== "string" && typeof params.requestId !== "number") return;
|
|
23124
23702
|
for (const [handle, approval] of this.approvals) {
|
|
23125
23703
|
if (approval.upstreamId === params.requestId) this.approvals.delete(handle);
|
|
23126
23704
|
}
|
|
@@ -23334,15 +23912,15 @@ function codexBinaryCandidates(configured, hostPlatform = process.platform, user
|
|
|
23334
23912
|
if (hostPlatform !== "darwin") return [configured];
|
|
23335
23913
|
const bundledCandidates = [
|
|
23336
23914
|
"/Applications/ChatGPT.app",
|
|
23337
|
-
|
|
23915
|
+
posix.join(userHome, "Applications", "ChatGPT.app")
|
|
23338
23916
|
].flatMap((chatGptApp) => {
|
|
23339
|
-
const codexCli =
|
|
23917
|
+
const codexCli = posix.join(chatGptApp, "Contents", "Resources", "codex-cli");
|
|
23340
23918
|
try {
|
|
23341
|
-
const manifest = JSON.parse(readFileSync(
|
|
23342
|
-
if (!
|
|
23919
|
+
const manifest = JSON.parse(readFileSync(posix.join(codexCli, "codex-package.json"), "utf8"));
|
|
23920
|
+
if (!isRecord12(manifest) || typeof manifest.entrypoint !== "string" || manifest.entrypoint.length === 0) {
|
|
23343
23921
|
return [];
|
|
23344
23922
|
}
|
|
23345
|
-
const candidate =
|
|
23923
|
+
const candidate = posix.join(codexCli, manifest.entrypoint);
|
|
23346
23924
|
if (!existsSync2(candidate)) return [];
|
|
23347
23925
|
accessSync(candidate, constants.X_OK);
|
|
23348
23926
|
return [candidate];
|
|
@@ -23353,14 +23931,14 @@ function codexBinaryCandidates(configured, hostPlatform = process.platform, user
|
|
|
23353
23931
|
return [.../* @__PURE__ */ new Set([
|
|
23354
23932
|
...bundledCandidates,
|
|
23355
23933
|
"/Applications/ChatGPT.app/Contents/Resources/codex",
|
|
23356
|
-
|
|
23934
|
+
posix.join(userHome, "Applications", "ChatGPT.app", "Contents", "Resources", "codex"),
|
|
23357
23935
|
configured
|
|
23358
23936
|
])];
|
|
23359
23937
|
}
|
|
23360
23938
|
function bundledWindowsCodex(userHome) {
|
|
23361
|
-
const bin =
|
|
23939
|
+
const bin = join7(userHome, "AppData", "Local", "OpenAI", "Codex", "bin");
|
|
23362
23940
|
try {
|
|
23363
|
-
const newest = readdirSync2(bin, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) =>
|
|
23941
|
+
const newest = readdirSync2(bin, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => join7(bin, entry.name, "codex.exe")).filter((candidate2) => existsSync2(candidate2)).map((candidate2) => ({ candidate: candidate2, modified: statSync2(candidate2).mtimeMs })).sort((left, right) => right.modified - left.modified);
|
|
23364
23942
|
const candidate = newest[0]?.candidate;
|
|
23365
23943
|
return candidate === void 0 ? [] : [candidate];
|
|
23366
23944
|
} catch {
|
|
@@ -23368,24 +23946,24 @@ function bundledWindowsCodex(userHome) {
|
|
|
23368
23946
|
}
|
|
23369
23947
|
}
|
|
23370
23948
|
function parseCallEnvelope(input2) {
|
|
23371
|
-
if (!
|
|
23949
|
+
if (!isRecord12(input2) || typeof input2.method !== "string" || !("params" in input2) || Object.keys(input2).some((key) => key !== "method" && key !== "params")) {
|
|
23372
23950
|
throw new RpcError("INVALID_MESSAGE", "The Codex call envelope is invalid.");
|
|
23373
23951
|
}
|
|
23374
23952
|
return { method: input2.method, params: input2.params };
|
|
23375
23953
|
}
|
|
23376
23954
|
function parseRespond(input2) {
|
|
23377
|
-
if (!
|
|
23955
|
+
if (!isRecord12(input2) || typeof input2.requestHandle !== "string" || !["accept", "decline", "cancel"].includes(String(input2.decision)) || Object.keys(input2).some((key) => key !== "requestHandle" && key !== "decision")) {
|
|
23378
23956
|
throw new RpcError("INVALID_MESSAGE", "The Codex approval response is invalid.");
|
|
23379
23957
|
}
|
|
23380
23958
|
return input2;
|
|
23381
23959
|
}
|
|
23382
23960
|
function accountCanRun(result) {
|
|
23383
|
-
if (!
|
|
23384
|
-
return result.requiresOpenaiAuth === false ||
|
|
23961
|
+
if (!isRecord12(result) || typeof result.requiresOpenaiAuth !== "boolean") return false;
|
|
23962
|
+
return result.requiresOpenaiAuth === false || isRecord12(result.account);
|
|
23385
23963
|
}
|
|
23386
23964
|
function sanitizeAccount(result) {
|
|
23387
|
-
if (!
|
|
23388
|
-
const account =
|
|
23965
|
+
if (!isRecord12(result)) throw new RpcError("CODEX_INVALID_RESPONSE", "Codex App Server returned invalid account state.");
|
|
23966
|
+
const account = isRecord12(result.account) ? result.account : void 0;
|
|
23389
23967
|
return {
|
|
23390
23968
|
authenticated: account !== void 0 || result.requiresOpenaiAuth === false,
|
|
23391
23969
|
requiresOpenaiAuth: result.requiresOpenaiAuth === true,
|
|
@@ -23398,11 +23976,11 @@ function sanitizeAccount(result) {
|
|
|
23398
23976
|
};
|
|
23399
23977
|
}
|
|
23400
23978
|
function sanitizeThreadList(result) {
|
|
23401
|
-
if (!
|
|
23979
|
+
if (!isRecord12(result) || !Array.isArray(result.data)) {
|
|
23402
23980
|
throw new RpcError("CODEX_INVALID_RESPONSE", "Codex App Server returned an invalid thread list.");
|
|
23403
23981
|
}
|
|
23404
23982
|
const data2 = result.data.flatMap((value) => {
|
|
23405
|
-
if (!
|
|
23983
|
+
if (!isRecord12(value) || typeof value.id !== "string") return [];
|
|
23406
23984
|
return [{
|
|
23407
23985
|
id: value.id,
|
|
23408
23986
|
...typeof value.sessionId === "string" ? { sessionId: value.sessionId } : {},
|
|
@@ -23414,7 +23992,7 @@ function sanitizeThreadList(result) {
|
|
|
23414
23992
|
...typeof value.updatedAt === "number" && Number.isFinite(value.updatedAt) ? { updatedAt: value.updatedAt } : {},
|
|
23415
23993
|
...typeof value.archived === "boolean" ? { archived: value.archived } : {},
|
|
23416
23994
|
...typeof value.isPinned === "boolean" ? { isPinned: value.isPinned } : {},
|
|
23417
|
-
...
|
|
23995
|
+
...isRecord12(value.status) ? { status: value.status } : {}
|
|
23418
23996
|
}];
|
|
23419
23997
|
});
|
|
23420
23998
|
return {
|
|
@@ -23424,18 +24002,18 @@ function sanitizeThreadList(result) {
|
|
|
23424
24002
|
};
|
|
23425
24003
|
}
|
|
23426
24004
|
function filterThreadListByWorkspaceAuthority(result, authority) {
|
|
23427
|
-
if (!
|
|
24005
|
+
if (!isRecord12(result) || !Array.isArray(result.data)) {
|
|
23428
24006
|
throw new RpcError("CODEX_INVALID_RESPONSE", "Codex App Server returned an invalid thread list.");
|
|
23429
24007
|
}
|
|
23430
24008
|
return {
|
|
23431
24009
|
...result,
|
|
23432
|
-
data: result.data.map((record7) =>
|
|
24010
|
+
data: result.data.map((record7) => isRecord12(record7) ? record7 : void 0).filter((thread) => thread !== void 0 && isThreadAllowedByWorkspaceAuthority(thread, authority))
|
|
23433
24011
|
};
|
|
23434
24012
|
}
|
|
23435
24013
|
function sanitizeProject(value) {
|
|
23436
|
-
if (!
|
|
24014
|
+
if (!isRecord12(value) || typeof value.id !== "string" || typeof value.name !== "string") return void 0;
|
|
23437
24015
|
const roots = Array.isArray(value.roots) ? value.roots.flatMap((root) => {
|
|
23438
|
-
const path =
|
|
24016
|
+
const path = isRecord12(root) && typeof root.path === "string" && root.path.length > 0 ? root.path : void 0;
|
|
23439
24017
|
return path === void 0 || !isAbsolute2(path) ? [] : [{ path }];
|
|
23440
24018
|
}) : [];
|
|
23441
24019
|
if (roots.length === 0) return void 0;
|
|
@@ -23449,7 +24027,7 @@ function sanitizeProject(value) {
|
|
|
23449
24027
|
};
|
|
23450
24028
|
}
|
|
23451
24029
|
function sanitizeProjectList(result) {
|
|
23452
|
-
if (!
|
|
24030
|
+
if (!isRecord12(result) || !Array.isArray(result.data)) {
|
|
23453
24031
|
throw new RpcError("CODEX_INVALID_RESPONSE", "Codex App Server returned an invalid project list.");
|
|
23454
24032
|
}
|
|
23455
24033
|
const data2 = result.data.flatMap((value) => sanitizeProject(value) ?? []);
|
|
@@ -23459,7 +24037,7 @@ function sanitizeProjectList(result) {
|
|
|
23459
24037
|
};
|
|
23460
24038
|
}
|
|
23461
24039
|
function sanitizeProjectCreate(result) {
|
|
23462
|
-
const project =
|
|
24040
|
+
const project = isRecord12(result) ? sanitizeProject(result.project) : void 0;
|
|
23463
24041
|
if (project === void 0) {
|
|
23464
24042
|
throw new RpcError("CODEX_INVALID_RESPONSE", "Codex App Server returned an invalid created project.");
|
|
23465
24043
|
}
|
|
@@ -23492,19 +24070,19 @@ function codexDirectoryCrumbs(root, path) {
|
|
|
23492
24070
|
return crumbs2;
|
|
23493
24071
|
}
|
|
23494
24072
|
function extractThread(result) {
|
|
23495
|
-
return
|
|
24073
|
+
return isRecord12(result) && isRecord12(result.thread) ? result.thread : void 0;
|
|
23496
24074
|
}
|
|
23497
24075
|
function extractTurnId(result) {
|
|
23498
|
-
if (!
|
|
24076
|
+
if (!isRecord12(result)) return void 0;
|
|
23499
24077
|
if (typeof result.turnId === "string" && result.turnId.length > 0) return result.turnId;
|
|
23500
|
-
if (
|
|
24078
|
+
if (isRecord12(result.turn) && typeof result.turn.id === "string" && result.turn.id.length > 0) return result.turn.id;
|
|
23501
24079
|
return void 0;
|
|
23502
24080
|
}
|
|
23503
24081
|
function extractThreadId(params) {
|
|
23504
|
-
if (!
|
|
24082
|
+
if (!isRecord12(params)) return void 0;
|
|
23505
24083
|
if (typeof params.threadId === "string") return params.threadId;
|
|
23506
|
-
if (
|
|
23507
|
-
if (
|
|
24084
|
+
if (isRecord12(params.thread) && typeof params.thread.id === "string") return params.thread.id;
|
|
24085
|
+
if (isRecord12(params.turn) && typeof params.turn.threadId === "string") return params.turn.threadId;
|
|
23508
24086
|
return void 0;
|
|
23509
24087
|
}
|
|
23510
24088
|
function optionalInteger2(value) {
|
|
@@ -23546,13 +24124,13 @@ function mapCodexImageInputs(params) {
|
|
|
23546
24124
|
return {
|
|
23547
24125
|
...params,
|
|
23548
24126
|
input: params.input.map((value) => {
|
|
23549
|
-
if (!
|
|
24127
|
+
if (!isRecord12(value) || value.type !== "image" || typeof value.mediaType !== "string" || typeof value.data !== "string") return value;
|
|
23550
24128
|
return { type: "image", url: `data:${value.mediaType};base64,${value.data}` };
|
|
23551
24129
|
})
|
|
23552
24130
|
};
|
|
23553
24131
|
}
|
|
23554
24132
|
function sanitizeApprovalParams(params, requestHandle) {
|
|
23555
|
-
if (!
|
|
24133
|
+
if (!isRecord12(params)) return { requestHandle };
|
|
23556
24134
|
const safe = { ...params };
|
|
23557
24135
|
delete safe.proposedExecpolicyAmendment;
|
|
23558
24136
|
delete safe.additionalPermissions;
|
|
@@ -23583,7 +24161,7 @@ function isProjectListFallbackError(error) {
|
|
|
23583
24161
|
function canTryNextBinary(error) {
|
|
23584
24162
|
return !(error instanceof RpcError) || !["CODEX_AUTH_REQUIRED", "CODEX_CLOSED"].includes(error.code);
|
|
23585
24163
|
}
|
|
23586
|
-
function
|
|
24164
|
+
function isRecord12(value) {
|
|
23587
24165
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
23588
24166
|
}
|
|
23589
24167
|
function array2(value) {
|
|
@@ -24019,11 +24597,11 @@ function editableConfig(config) {
|
|
|
24019
24597
|
...config.acp === void 0 ? {} : { acp: { enabled: config.acp.enabled, backends: config.acp.backends } }
|
|
24020
24598
|
};
|
|
24021
24599
|
}
|
|
24022
|
-
function
|
|
24600
|
+
function isRecord13(value) {
|
|
24023
24601
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
24024
24602
|
}
|
|
24025
24603
|
function record5(value) {
|
|
24026
|
-
if (!
|
|
24604
|
+
if (!isRecord13(value)) throw new ClientModeError("INVALID_MESSAGE", "The control request payload is invalid.");
|
|
24027
24605
|
return value;
|
|
24028
24606
|
}
|
|
24029
24607
|
function ok3(value) {
|
|
@@ -24148,7 +24726,7 @@ var TerminalPolicy = class {
|
|
|
24148
24726
|
};
|
|
24149
24727
|
|
|
24150
24728
|
// src/service.ts
|
|
24151
|
-
import { randomUUID as
|
|
24729
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
24152
24730
|
|
|
24153
24731
|
// src/connection-controller.ts
|
|
24154
24732
|
var ConnectionController = class {
|
|
@@ -25869,7 +26447,15 @@ var HarnessApiBridge = class {
|
|
|
25869
26447
|
mux;
|
|
25870
26448
|
host;
|
|
25871
26449
|
answer;
|
|
26450
|
+
/** Answer one ApiProxy call, sending bytes in the form DSH's own connection layer expects. */
|
|
25872
26451
|
async call(input2) {
|
|
26452
|
+
const response = await this.dispatchCall(input2);
|
|
26453
|
+
const result = response.result;
|
|
26454
|
+
if (result === void 0) return response;
|
|
26455
|
+
const tagged = collectRpcAttachments(result);
|
|
26456
|
+
return tagged === result ? response : { ...response, result: tagged };
|
|
26457
|
+
}
|
|
26458
|
+
async dispatchCall(input2) {
|
|
25873
26459
|
const params = callSchema2.parse(input2);
|
|
25874
26460
|
const signal = AbortSignal.timeout(NATIVE_CALL_TIMEOUT_MS);
|
|
25875
26461
|
const method = this.methods.get(params.method);
|
|
@@ -26580,7 +27166,17 @@ var HarnessRemoteBridge = class {
|
|
|
26580
27166
|
streams = /* @__PURE__ */ new Map();
|
|
26581
27167
|
incomingTransfers = /* @__PURE__ */ new Map();
|
|
26582
27168
|
outgoingTransfers = /* @__PURE__ */ new Map();
|
|
27169
|
+
/**
|
|
27170
|
+
* Answer one remote Gateway call.
|
|
27171
|
+
*
|
|
27172
|
+
* Bytes have to leave in DSH's own form: this result is JSON-encoded on its way to the client, and a
|
|
27173
|
+
* \`Uint8Array\` would arrive there as \`{"0":…}\` and fail the generated schema (the image-preview
|
|
27174
|
+
* failure). Tagging here, hydrating on the client, keeps one convention on both sides.
|
|
27175
|
+
*/
|
|
26583
27176
|
async call(input2) {
|
|
27177
|
+
return collectRpcAttachments(await this.dispatchCall(input2));
|
|
27178
|
+
}
|
|
27179
|
+
async dispatchCall(input2) {
|
|
26584
27180
|
const params = callSchema3.parse(input2);
|
|
26585
27181
|
this.assertAllowed(params.endpoint);
|
|
26586
27182
|
if (TERMINAL_STREAMS.has(params.endpoint)) throw new RpcError("METHOD_NOT_ALLOWED", "Use a stream for this terminal endpoint.");
|
|
@@ -26800,7 +27396,7 @@ var HarnessRemoteBridge = class {
|
|
|
26800
27396
|
await this.publish("harness.remote.frame", {
|
|
26801
27397
|
streamId,
|
|
26802
27398
|
hasValue: true,
|
|
26803
|
-
...value === void 0 ? {} : { value }
|
|
27399
|
+
...value === void 0 ? {} : { value: collectRpcAttachments(value) }
|
|
26804
27400
|
});
|
|
26805
27401
|
}
|
|
26806
27402
|
if (signal.aborted) reason = "cancelled";
|
|
@@ -26824,11 +27420,11 @@ var HarnessRemoteBridge = class {
|
|
|
26824
27420
|
};
|
|
26825
27421
|
function normalizeWorkspaceChangesPayload(endpoint, payload, harnessVersion) {
|
|
26826
27422
|
if (endpoint !== "workspaceFiles/changes" || !requiresWorkspaceChangePath(harnessVersion)) return payload;
|
|
26827
|
-
if (!
|
|
27423
|
+
if (!isRecord14(payload) || !isRecord14(payload.args) || Object.hasOwn(payload.args, "path")) return payload;
|
|
26828
27424
|
return { ...payload, args: { ...payload.args, path: "." } };
|
|
26829
27425
|
}
|
|
26830
27426
|
function normalizeWorkspaceRequestPayload(endpoint, payload, harnessVersion) {
|
|
26831
|
-
if (!
|
|
27427
|
+
if (!isRecord14(payload) || !isRecord14(payload.args)) return payload;
|
|
26832
27428
|
const args = payload.args;
|
|
26833
27429
|
if (endpoint === "workspaceFiles/readBytes" && requiresWorkspaceChangePath(harnessVersion)) {
|
|
26834
27430
|
if (!Object.hasOwn(args, "range") || Object.hasOwn(args, "options")) return payload;
|
|
@@ -26896,13 +27492,13 @@ function needsDirectoryFallback(result) {
|
|
|
26896
27492
|
}
|
|
26897
27493
|
function requestArgs(payload) {
|
|
26898
27494
|
const root = record6(payload);
|
|
26899
|
-
const args =
|
|
27495
|
+
const args = isRecord14(root.args) ? root.args : root;
|
|
26900
27496
|
return record6(args.request ?? args._request ?? args);
|
|
26901
27497
|
}
|
|
26902
27498
|
function record6(value) {
|
|
26903
|
-
return
|
|
27499
|
+
return isRecord14(value) ? value : {};
|
|
26904
27500
|
}
|
|
26905
|
-
function
|
|
27501
|
+
function isRecord14(value) {
|
|
26906
27502
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
26907
27503
|
}
|
|
26908
27504
|
|
|
@@ -27022,8 +27618,8 @@ import { execFileSync as execFileSync2 } from "node:child_process";
|
|
|
27022
27618
|
|
|
27023
27619
|
// src/codex-workspace-bridge.ts
|
|
27024
27620
|
import { spawn as spawn4 } from "node:child_process";
|
|
27025
|
-
import { realpath as realpath2, lstat, readdir as readdir4, readFile as
|
|
27026
|
-
import { isAbsolute as isAbsolute4, join as
|
|
27621
|
+
import { realpath as realpath2, lstat, readdir as readdir4, readFile as readFile5, stat as stat6, watch } from "node:fs/promises";
|
|
27622
|
+
import { isAbsolute as isAbsolute4, join as join8, relative as relative2, resolve as resolve3 } from "node:path";
|
|
27027
27623
|
var MAX_READ_BYTES = 4 * 1024 * 1024;
|
|
27028
27624
|
var MAX_INPUT_BYTES = 64 * 1024;
|
|
27029
27625
|
var MAX_COLS = 240;
|
|
@@ -27113,7 +27709,7 @@ var CodexWorkspaceBridge = class {
|
|
|
27113
27709
|
const entries = await readdir4(target2, { withFileTypes: true });
|
|
27114
27710
|
const result = [];
|
|
27115
27711
|
for (const entry of entries.slice(0, 500)) {
|
|
27116
|
-
const item =
|
|
27712
|
+
const item = join8(target2, entry.name);
|
|
27117
27713
|
const info2 = await lstat(item);
|
|
27118
27714
|
if (info2.isSymbolicLink()) continue;
|
|
27119
27715
|
result.push({ name: entry.name, type: info2.isDirectory() ? "directory" : info2.isFile() ? "file" : "other", ...info2.isFile() ? { size: info2.size } : {} });
|
|
@@ -27129,7 +27725,7 @@ var CodexWorkspaceBridge = class {
|
|
|
27129
27725
|
if (!info.isFile()) throw new RpcError("CODEX_WORKSPACE_INVALID_PATH", "The requested workspace path is not a file.");
|
|
27130
27726
|
const offset = readOffset(args);
|
|
27131
27727
|
const limit = readLimit(args);
|
|
27132
|
-
const bytes = await
|
|
27728
|
+
const bytes = await readFile5(target2);
|
|
27133
27729
|
if (bytes.byteLength > MAX_READ_BYTES) throw new RpcError("CODEX_WORKSPACE_TOO_LARGE", "The requested workspace file is too large.");
|
|
27134
27730
|
if (endpoint === "workspaceFiles/readBytes") {
|
|
27135
27731
|
const slice2 = bytes.subarray(offset, Math.min(offset + limit, bytes.length));
|
|
@@ -27253,7 +27849,7 @@ var CodexWorkspaceBridge = class {
|
|
|
27253
27849
|
throw new RpcError("METHOD_NOT_FOUND", "The requested terminal method does not exist.");
|
|
27254
27850
|
}
|
|
27255
27851
|
async createTerminal(sessionId, cwd, args) {
|
|
27256
|
-
const request =
|
|
27852
|
+
const request = isRecord15(args.request) ? args.request : {};
|
|
27257
27853
|
const id4 = stringId(request.id);
|
|
27258
27854
|
if ([...this.terminals.values()].some((item) => item.sessionId === sessionId && item.id === id4)) throw new RpcError("REQUEST_CONFLICT", "The terminal id is already active.");
|
|
27259
27855
|
if (this.terminals.size >= MAX_TERMINALS) throw new RpcError("RATE_LIMITED", "Too many remote terminals are active.", void 0, true);
|
|
@@ -27454,10 +28050,10 @@ function screenOf(terminal) {
|
|
|
27454
28050
|
return (terminal.truncated ? "\x1Bc" : "") + terminal.screen.join("");
|
|
27455
28051
|
}
|
|
27456
28052
|
function argsOf(payload) {
|
|
27457
|
-
const value =
|
|
27458
|
-
return
|
|
28053
|
+
const value = isRecord15(payload) ? payload : {};
|
|
28054
|
+
return isRecord15(value.args) ? value.args : value;
|
|
27459
28055
|
}
|
|
27460
|
-
function
|
|
28056
|
+
function isRecord15(value) {
|
|
27461
28057
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
27462
28058
|
}
|
|
27463
28059
|
function stringId(value) {
|
|
@@ -27468,9 +28064,9 @@ function bounded(value, fallback, max) {
|
|
|
27468
28064
|
return typeof value === "number" && Number.isInteger(value) && value > 0 ? Math.min(value, max) : fallback;
|
|
27469
28065
|
}
|
|
27470
28066
|
function byteOrLineRange(args) {
|
|
27471
|
-
const options =
|
|
27472
|
-
if (options !== void 0 &&
|
|
27473
|
-
return
|
|
28067
|
+
const options = isRecord15(args.options) ? args.options : void 0;
|
|
28068
|
+
if (options !== void 0 && isRecord15(options.range)) return options.range;
|
|
28069
|
+
return isRecord15(args.range) ? args.range : args;
|
|
27474
28070
|
}
|
|
27475
28071
|
function readOffset(args) {
|
|
27476
28072
|
const range = byteOrLineRange(args);
|
|
@@ -27561,6 +28157,14 @@ var HostPluginRuntime = class {
|
|
|
27561
28157
|
* device identity, unlike clearing the authorization.
|
|
27562
28158
|
*/
|
|
27563
28159
|
paused;
|
|
28160
|
+
/**
|
|
28161
|
+
* Whether start() has finished wiring the Server connection.
|
|
28162
|
+
*
|
|
28163
|
+
* The Codex domain is optional business that waits on an external binary, so it runs in the
|
|
28164
|
+
* background; without this flag a Host that is merely still starting would report itself as
|
|
28165
|
+
* offline and look broken to the user and to other clients.
|
|
28166
|
+
*/
|
|
28167
|
+
starting = true;
|
|
27564
28168
|
harnessVersion;
|
|
27565
28169
|
closed = false;
|
|
27566
28170
|
codex;
|
|
@@ -27588,7 +28192,6 @@ var HostPluginRuntime = class {
|
|
|
27588
28192
|
fingerprint: this.identity.fingerprint,
|
|
27589
28193
|
server: this.config.serverUrl ?? "not configured"
|
|
27590
28194
|
});
|
|
27591
|
-
await this.codex.start();
|
|
27592
28195
|
if (this.serverApi !== void 0) {
|
|
27593
28196
|
this.harnessVersion = await this.readHarnessVersion();
|
|
27594
28197
|
this.serverApi.setHarnessVersion(this.harnessVersion);
|
|
@@ -27596,6 +28199,9 @@ var HostPluginRuntime = class {
|
|
|
27596
28199
|
this.serverConnection = this.createServerConnection(this.identity);
|
|
27597
28200
|
if (!this.paused) this.serverConnection.start();
|
|
27598
28201
|
}
|
|
28202
|
+
this.starting = false;
|
|
28203
|
+
void this.codex.start().catch(() => {
|
|
28204
|
+
});
|
|
27599
28205
|
}
|
|
27600
28206
|
currentIdentity() {
|
|
27601
28207
|
if (this.identity === void 0) throw new Error("remote runtime has not started");
|
|
@@ -27613,6 +28219,7 @@ var HostPluginRuntime = class {
|
|
|
27613
28219
|
configured: this.serverApi !== void 0,
|
|
27614
28220
|
online: this.serverConnection?.isOnline() ?? false,
|
|
27615
28221
|
reconnecting: this.serverConnection?.isReconnecting() ?? false,
|
|
28222
|
+
starting: this.starting,
|
|
27616
28223
|
...this.serverConnection?.lastActivity() === void 0 ? {} : { lastActiveAt: this.serverConnection.lastActivity() },
|
|
27617
28224
|
...error === void 0 ? {} : { error },
|
|
27618
28225
|
...authorization?.account === void 0 ? {} : { account: authorization.account },
|
|
@@ -27818,7 +28425,7 @@ var HostPluginRuntime = class {
|
|
|
27818
28425
|
let reportedVersion;
|
|
27819
28426
|
let errorCode6;
|
|
27820
28427
|
try {
|
|
27821
|
-
const response = await this.apiProxy?.host.describe({ rpcId:
|
|
28428
|
+
const response = await this.apiProxy?.host.describe({ rpcId: randomUUID4(), payload: {} });
|
|
27822
28429
|
if (response === void 0) throw new Error("ApiProxy is unavailable");
|
|
27823
28430
|
if (!response.result.ok) {
|
|
27824
28431
|
errorCode6 = response.result.error.code;
|
|
@@ -27888,7 +28495,7 @@ function isPlainRecord(value) {
|
|
|
27888
28495
|
// src/cli.ts
|
|
27889
28496
|
import { stat as stat7 } from "node:fs/promises";
|
|
27890
28497
|
import { hostname as hostname3 } from "node:os";
|
|
27891
|
-
import { join as
|
|
28498
|
+
import { join as join9 } from "node:path";
|
|
27892
28499
|
var QR_POLL_INTERVAL_MS = 2e3;
|
|
27893
28500
|
var TERMINAL_QR_MARGIN = 4;
|
|
27894
28501
|
async function runCli(args = process.argv.slice(2), dependencies = {}) {
|
|
@@ -27976,7 +28583,7 @@ async function status(args, runtime) {
|
|
|
27976
28583
|
`Server: ${serverUrl}`,
|
|
27977
28584
|
"Host control: enabled (dsh-TUI default)"
|
|
27978
28585
|
];
|
|
27979
|
-
if (!await exists3(
|
|
28586
|
+
if (!await exists3(join9(directory, "device.json"))) {
|
|
27980
28587
|
lines.push("Device: not initialized", "Authorization: logged out", "Credential: unavailable");
|
|
27981
28588
|
write(runtime.stdout, `${lines.join("\n")}
|
|
27982
28589
|
|
|
@@ -28020,7 +28627,7 @@ async function logout(args, runtime) {
|
|
|
28020
28627
|
const serverUrl = selectedServer();
|
|
28021
28628
|
const root = new IdentityStore({ env: runtime.env }).directory;
|
|
28022
28629
|
const directory = serverStorageDirectory(root, serverUrl, "host");
|
|
28023
|
-
if (!await exists3(
|
|
28630
|
+
if (!await exists3(join9(directory, "device.json"))) {
|
|
28024
28631
|
await new ServerCredentialStore(directory).clear();
|
|
28025
28632
|
write(runtime.stdout, "This Host is already logged out.\n");
|
|
28026
28633
|
return 0;
|
|
@@ -28306,7 +28913,7 @@ function remoteStatusLines(target2) {
|
|
|
28306
28913
|
const diagnostics = runtime.diagnostics();
|
|
28307
28914
|
const codex = runtime.codexStatus();
|
|
28308
28915
|
const capabilities = new Set(diagnostics.capabilities);
|
|
28309
|
-
const connection = status2.online ? "online" : status2.reconnecting ? "reconnecting" : status2.accountRequired ? "authorization required" : "offline";
|
|
28916
|
+
const connection = status2.online ? "online" : status2.starting ? "starting" : status2.reconnecting ? "reconnecting" : status2.accountRequired ? "authorization required" : "offline";
|
|
28310
28917
|
return [
|
|
28311
28918
|
`Server: ${config.serverUrl ?? "not configured"}`,
|
|
28312
28919
|
"Host control: enabled",
|
|
@@ -28537,7 +29144,7 @@ function createRemoteFileContentProvider(call, options = {}) {
|
|
|
28537
29144
|
const offset = request.offset + received;
|
|
28538
29145
|
const range = await call("fileviewer.readRange", { path: locator, offset, length }, request.signal);
|
|
28539
29146
|
if (range.offset !== offset) throw new Error("The Remote Host returned a mismatched file range.");
|
|
28540
|
-
const bytes =
|
|
29147
|
+
const bytes = decodeBase642(range.data);
|
|
28541
29148
|
if (bytes.byteLength > length) throw new Error("The Remote Host returned more file bytes than requested.");
|
|
28542
29149
|
chunks.push(bytes);
|
|
28543
29150
|
received += bytes.byteLength;
|
|
@@ -28569,7 +29176,7 @@ function currentSaveAsAllowed(value) {
|
|
|
28569
29176
|
function currentSaveAsMaxBytes(value) {
|
|
28570
29177
|
return typeof value === "function" ? value() : value ?? REMOTE_FILE_SAVE_AS_MAX_BYTES;
|
|
28571
29178
|
}
|
|
28572
|
-
function
|
|
29179
|
+
function decodeBase642(value) {
|
|
28573
29180
|
const binary = atob(value);
|
|
28574
29181
|
const bytes = new Uint8Array(binary.length);
|
|
28575
29182
|
for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);
|
|
@@ -28722,7 +29329,10 @@ async function activate(ctx, readConfig, entryId, tuiBinding) {
|
|
|
28722
29329
|
apiProxy,
|
|
28723
29330
|
nativeTypertGateway,
|
|
28724
29331
|
logger,
|
|
28725
|
-
hostControl
|
|
29332
|
+
hostControl,
|
|
29333
|
+
// The RTC factory keeps its default; the target store lives beside the plugin state.
|
|
29334
|
+
void 0,
|
|
29335
|
+
new ClientTargetStore(defaultIdentityDirectory)
|
|
28726
29336
|
);
|
|
28727
29337
|
}
|
|
28728
29338
|
const controlRuntime = connection === void 0 ? void 0 : new PluginControlRuntime(config, defaultIdentityDirectory, settingsBinding, clientRuntime, hostControl, deepseekSession);
|
|
@@ -28737,6 +29347,7 @@ async function activate(ctx, readConfig, entryId, tuiBinding) {
|
|
|
28737
29347
|
await runtime.start();
|
|
28738
29348
|
if (clientRuntime !== void 0) {
|
|
28739
29349
|
await clientRuntime.start();
|
|
29350
|
+
await clientRuntime.restoreLastTarget();
|
|
28740
29351
|
} else {
|
|
28741
29352
|
logger.warn("client remote mode is unavailable", {
|
|
28742
29353
|
serverConfigured: config.serverUrl !== void 0,
|