@blue-soda/dsh-remote 0.4.28 → 0.4.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -9
- package/dist/atomic-file.d.ts +16 -0
- package/dist/atomic-file.d.ts.map +1 -0
- package/dist/atomic-file.js +81 -0
- package/dist/atomic-file.js.map +1 -0
- package/dist/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/identity-store.d.ts.map +1 -1
- package/dist/identity-store.js +6 -3
- package/dist/identity-store.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +779 -220
- package/dist/index.js.map +4 -4
- 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 +17 -0
- package/dist/rpc-binary-attachments.d.ts.map +1 -0
- package/dist/rpc-binary-attachments.js +89 -0
- package/dist/rpc-binary-attachments.js.map +1 -0
- package/dist/server-credentials.d.ts.map +1 -1
- package/dist/server-credentials.js +6 -3
- package/dist/server-credentials.js.map +1 -1
- package/dist/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 +56 -4
- 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 +2 -2
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
|
}
|
|
@@ -15162,6 +15162,68 @@ function base64ToBytes2(value) {
|
|
|
15162
15162
|
return bytes;
|
|
15163
15163
|
}
|
|
15164
15164
|
|
|
15165
|
+
// src/rpc-binary-attachments.ts
|
|
15166
|
+
function hydrateRpcAttachments(result) {
|
|
15167
|
+
if (!isRecord3(result)) return result;
|
|
15168
|
+
const envelope = result;
|
|
15169
|
+
if (!Array.isArray(envelope.attachments) || envelope.attachments.length === 0) return result;
|
|
15170
|
+
const { attachments, ...rest } = result;
|
|
15171
|
+
const base = Object.hasOwn(rest, "value") ? rest.value : void 0;
|
|
15172
|
+
for (const attachment of attachments) {
|
|
15173
|
+
const entry = attachment;
|
|
15174
|
+
const path = Array.isArray(entry.path) ? entry.path : void 0;
|
|
15175
|
+
if (path === void 0 || path.length === 0) {
|
|
15176
|
+
throw new Error("The remote Host returned a byte attachment without a path.");
|
|
15177
|
+
}
|
|
15178
|
+
assignAtPath(base, path, decodeAttachmentBytes(entry.bytes));
|
|
15179
|
+
}
|
|
15180
|
+
return rest;
|
|
15181
|
+
}
|
|
15182
|
+
function assignAtPath(base, path, bytes) {
|
|
15183
|
+
if (base === null || typeof base !== "object") {
|
|
15184
|
+
throw new Error("The remote Host returned a byte attachment that its result cannot hold.");
|
|
15185
|
+
}
|
|
15186
|
+
let cursor2 = base;
|
|
15187
|
+
for (let index = 0; index < path.length - 1; index += 1) {
|
|
15188
|
+
const key = pathKey(path[index]);
|
|
15189
|
+
const next = cursor2[key];
|
|
15190
|
+
if (next === null || typeof next !== "object") {
|
|
15191
|
+
throw new Error("The remote Host returned a byte attachment at an unknown result path.");
|
|
15192
|
+
}
|
|
15193
|
+
cursor2 = next;
|
|
15194
|
+
}
|
|
15195
|
+
cursor2[pathKey(path[path.length - 1])] = bytes;
|
|
15196
|
+
}
|
|
15197
|
+
function pathKey(segment) {
|
|
15198
|
+
if (typeof segment === "string") return segment;
|
|
15199
|
+
if (typeof segment === "number" && Number.isInteger(segment) && segment >= 0) return String(segment);
|
|
15200
|
+
throw new Error("The remote Host returned a byte attachment with an invalid path segment.");
|
|
15201
|
+
}
|
|
15202
|
+
function decodeAttachmentBytes(value) {
|
|
15203
|
+
if (value instanceof Uint8Array) return value;
|
|
15204
|
+
if (typeof value === "string") return decodeBase64(value);
|
|
15205
|
+
if (Array.isArray(value)) return Uint8Array.from(value);
|
|
15206
|
+
if (!isRecord3(value)) throw new Error("The remote Host returned invalid byte attachment data.");
|
|
15207
|
+
if (Array.isArray(value.data)) {
|
|
15208
|
+
return Uint8Array.from(value.data);
|
|
15209
|
+
}
|
|
15210
|
+
const keys = Object.keys(value);
|
|
15211
|
+
if (keys.every((key) => /^\d+$/u.test(key))) {
|
|
15212
|
+
const ordered = keys.map(Number).sort((left, right) => left - right);
|
|
15213
|
+
return Uint8Array.from(ordered.map((key) => value[String(key)]));
|
|
15214
|
+
}
|
|
15215
|
+
throw new Error("The remote Host returned invalid byte attachment data.");
|
|
15216
|
+
}
|
|
15217
|
+
function decodeBase64(value) {
|
|
15218
|
+
const binary = atob(value);
|
|
15219
|
+
const bytes = new Uint8Array(binary.length);
|
|
15220
|
+
for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);
|
|
15221
|
+
return bytes;
|
|
15222
|
+
}
|
|
15223
|
+
function isRecord3(value) {
|
|
15224
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15225
|
+
}
|
|
15226
|
+
|
|
15165
15227
|
// src/session-format-compat.ts
|
|
15166
15228
|
function normalizeLegacySessionGatewayValue(endpoint, value) {
|
|
15167
15229
|
if (endpoint === "session/page") return normalizePage(value);
|
|
@@ -15169,7 +15231,7 @@ function normalizeLegacySessionGatewayValue(endpoint, value) {
|
|
|
15169
15231
|
return value;
|
|
15170
15232
|
}
|
|
15171
15233
|
function normalizeFollowFrame(value) {
|
|
15172
|
-
if (!
|
|
15234
|
+
if (!isRecord4(value)) return value;
|
|
15173
15235
|
if (value.type === "snapshot") {
|
|
15174
15236
|
return {
|
|
15175
15237
|
...value,
|
|
@@ -15181,7 +15243,7 @@ function normalizeFollowFrame(value) {
|
|
|
15181
15243
|
return normalizeEntry(value);
|
|
15182
15244
|
}
|
|
15183
15245
|
function normalizePage(value) {
|
|
15184
|
-
if (!
|
|
15246
|
+
if (!isRecord4(value)) return value;
|
|
15185
15247
|
return { ...value, records: normalizeRecords(value.records) };
|
|
15186
15248
|
}
|
|
15187
15249
|
function normalizeRecords(value) {
|
|
@@ -15226,10 +15288,10 @@ function eventTime(value) {
|
|
|
15226
15288
|
return typeof value.event.time === "number" && Number.isSafeInteger(value.event.time) ? value.event.time : 0;
|
|
15227
15289
|
}
|
|
15228
15290
|
function isEventEntry(value) {
|
|
15229
|
-
return
|
|
15291
|
+
return isRecord4(value) && value.type === "event" && isRecord4(value.event);
|
|
15230
15292
|
}
|
|
15231
15293
|
function normalizeHeader(value) {
|
|
15232
|
-
if (!
|
|
15294
|
+
if (!isRecord4(value)) return value;
|
|
15233
15295
|
const next = {
|
|
15234
15296
|
...value,
|
|
15235
15297
|
version: 3,
|
|
@@ -15238,7 +15300,7 @@ function normalizeHeader(value) {
|
|
|
15238
15300
|
return next.agentPreset === "code" ? { ...next, agentPreset: "ptc" } : next;
|
|
15239
15301
|
}
|
|
15240
15302
|
function normalizeEvent(value) {
|
|
15241
|
-
if (!
|
|
15303
|
+
if (!isRecord4(value)) return value;
|
|
15242
15304
|
let next = value;
|
|
15243
15305
|
const type = normalizeEventType(value.type);
|
|
15244
15306
|
if (type !== value.type) next = { ...next, type };
|
|
@@ -15258,7 +15320,7 @@ function normalizeEventType(value) {
|
|
|
15258
15320
|
return value;
|
|
15259
15321
|
}
|
|
15260
15322
|
function normalizeEventData(type, value) {
|
|
15261
|
-
if (!
|
|
15323
|
+
if (!isRecord4(value)) return value;
|
|
15262
15324
|
if (type === "request/header") return normalizeRequestHeaderData(value);
|
|
15263
15325
|
if (type === "agent-preset/selected" && value.agentPreset === "code") return { ...value, agentPreset: "ptc" };
|
|
15264
15326
|
if (type === "user/message") return normalizeMessage(value);
|
|
@@ -15271,7 +15333,7 @@ function normalizeEventData(type, value) {
|
|
|
15271
15333
|
return value;
|
|
15272
15334
|
}
|
|
15273
15335
|
function normalizeRequestHeaderData(value) {
|
|
15274
|
-
if (!
|
|
15336
|
+
if (!isRecord4(value.header)) return value;
|
|
15275
15337
|
const header = normalizeRequestHeader(value.header);
|
|
15276
15338
|
return header === value.header ? value : { ...value, header };
|
|
15277
15339
|
}
|
|
@@ -15287,7 +15349,7 @@ function normalizeRequestHeader(value) {
|
|
|
15287
15349
|
changed = true;
|
|
15288
15350
|
continue;
|
|
15289
15351
|
}
|
|
15290
|
-
if (key === "adapterDefaults" &&
|
|
15352
|
+
if (key === "adapterDefaults" && isRecord4(field) && Object.keys(field).length === 0) {
|
|
15291
15353
|
changed = true;
|
|
15292
15354
|
continue;
|
|
15293
15355
|
}
|
|
@@ -15296,12 +15358,12 @@ function normalizeRequestHeader(value) {
|
|
|
15296
15358
|
return changed ? next : value;
|
|
15297
15359
|
}
|
|
15298
15360
|
function normalizeMessage(value) {
|
|
15299
|
-
if (!
|
|
15361
|
+
if (!isRecord4(value) || !isRecord4(value.source)) return value;
|
|
15300
15362
|
if (value.source.kind !== "plugin" || value.source.plugin !== "tools-code-mode") return value;
|
|
15301
15363
|
return { ...value, source: { ...value.source, plugin: "tools-ptc" } };
|
|
15302
15364
|
}
|
|
15303
15365
|
function normalizeSurfaceOp(value) {
|
|
15304
|
-
if (!
|
|
15366
|
+
if (!isRecord4(value) || value.op !== "replace") return value;
|
|
15305
15367
|
const { start, end, ...rest } = value;
|
|
15306
15368
|
if (start === void 0 && end === void 0) return value;
|
|
15307
15369
|
return {
|
|
@@ -15311,7 +15373,7 @@ function normalizeSurfaceOp(value) {
|
|
|
15311
15373
|
...end === void 0 ? {} : { endSeq: end }
|
|
15312
15374
|
};
|
|
15313
15375
|
}
|
|
15314
|
-
function
|
|
15376
|
+
function isRecord4(value) {
|
|
15315
15377
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15316
15378
|
}
|
|
15317
15379
|
function isEventSeq(value) {
|
|
@@ -15352,7 +15414,7 @@ var RemoteTypertGateway2 = class {
|
|
|
15352
15414
|
response = await this.callTransferred(encoded, signal);
|
|
15353
15415
|
}
|
|
15354
15416
|
}
|
|
15355
|
-
const result = parseRpcResult(response);
|
|
15417
|
+
const result = parseRpcResult(hydrateRpcAttachments(response));
|
|
15356
15418
|
const settingsResult = this.normalizeLegacyWelcomeSettings(endpoint, payload, result);
|
|
15357
15419
|
if (settingsResult !== void 0) return settingsResult;
|
|
15358
15420
|
if (result.ok && this.compatibility === "legacy-to-v3") {
|
|
@@ -15365,11 +15427,11 @@ var RemoteTypertGateway2 = class {
|
|
|
15365
15427
|
/** DSH <=0.1.6 cannot persist the 0.1.7 welcome acknowledgement remotely. */
|
|
15366
15428
|
normalizeLegacyWelcomeSettings(endpoint, payload, result) {
|
|
15367
15429
|
if (!isLegacyRemoteHost2(this.harnessVersion)) return void 0;
|
|
15368
|
-
if (endpoint === "settings/describe" && result.ok &&
|
|
15430
|
+
if (endpoint === "settings/describe" && result.ok && isRecord5(result.value)) {
|
|
15369
15431
|
this.settingsDescribeValue = result.value;
|
|
15370
15432
|
return this.legacyWelcomeAcknowledged ? { ...result, value: patchWelcomeDescribe2(result.value) } : void 0;
|
|
15371
15433
|
}
|
|
15372
|
-
if (endpoint !== "settings/mutate" || !
|
|
15434
|
+
if (endpoint !== "settings/mutate" || !isRecord5(payload) || !isRecord5(payload.args)) return void 0;
|
|
15373
15435
|
const args = payload.args;
|
|
15374
15436
|
if (args.ns !== WELCOME_NOTICE_NAMESPACE2 || !isWelcomeNoticeOperation(args.ops)) return void 0;
|
|
15375
15437
|
const value = patchWelcomeDescribe2(this.settingsDescribeValue ?? { namespaces: [] });
|
|
@@ -15398,7 +15460,8 @@ var RemoteTypertGateway2 = class {
|
|
|
15398
15460
|
async *iterate(streamId, endpoint, queue, unsubscribe, unsubscribeClose, signal, onAbort) {
|
|
15399
15461
|
try {
|
|
15400
15462
|
for await (const value of queue) {
|
|
15401
|
-
|
|
15463
|
+
const hydrated = hydrateRpcAttachments(value);
|
|
15464
|
+
yield this.compatibility === "legacy-to-v3" ? normalizeLegacySessionGatewayValue(endpoint, hydrated) : hydrated;
|
|
15402
15465
|
}
|
|
15403
15466
|
} finally {
|
|
15404
15467
|
signal.removeEventListener("abort", onAbort);
|
|
@@ -15533,16 +15596,16 @@ function routeStreamEvent2(event, streamId, queue) {
|
|
|
15533
15596
|
queue.fail(remoteFailure({
|
|
15534
15597
|
code: typeof failure2?.code === "string" ? failure2.code : "internal",
|
|
15535
15598
|
message: typeof failure2?.message === "string" ? failure2.message : "The remote Harness stream failed.",
|
|
15536
|
-
details:
|
|
15599
|
+
details: isRecord5(failure2?.details) ? failure2.details : {}
|
|
15537
15600
|
}));
|
|
15538
15601
|
} else {
|
|
15539
15602
|
queue.close();
|
|
15540
15603
|
}
|
|
15541
15604
|
}
|
|
15542
15605
|
function parseRpcResult(value) {
|
|
15543
|
-
if (!
|
|
15606
|
+
if (!isRecord5(value)) throw new Error("The remote Host returned an invalid Gateway result.");
|
|
15544
15607
|
if (value.ok === true) return Object.hasOwn(value, "value") ? { ok: true, value: value.value } : { ok: true };
|
|
15545
|
-
if (value.ok !== false || !
|
|
15608
|
+
if (value.ok !== false || !isRecord5(value.error) || typeof value.error.code !== "string" || typeof value.error.message !== "string" || !isRecord5(value.error.details)) {
|
|
15546
15609
|
throw new Error("The remote Host returned an invalid Gateway failure.");
|
|
15547
15610
|
}
|
|
15548
15611
|
return {
|
|
@@ -15576,7 +15639,7 @@ function base64ToBytes3(value) {
|
|
|
15576
15639
|
if (bytesToBase643(bytes) !== value) throw new Error("The remote Host returned non-canonical Harness Remote transfer data.");
|
|
15577
15640
|
return bytes;
|
|
15578
15641
|
}
|
|
15579
|
-
function
|
|
15642
|
+
function isRecord5(value) {
|
|
15580
15643
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15581
15644
|
}
|
|
15582
15645
|
function hasErrorCode(error, code) {
|
|
@@ -15589,13 +15652,13 @@ function isLegacyRemoteHost2(version) {
|
|
|
15589
15652
|
return Number(match[1]) === 0 && Number(match[2]) === 1 && Number(match[3]) < 7;
|
|
15590
15653
|
}
|
|
15591
15654
|
function isWelcomeNoticeOperation(value) {
|
|
15592
|
-
return Array.isArray(value) && value.some((operation) =>
|
|
15655
|
+
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
15656
|
}
|
|
15594
15657
|
function patchWelcomeDescribe2(value) {
|
|
15595
15658
|
if (!Array.isArray(value.namespaces)) return value;
|
|
15596
15659
|
const namespaces = value.namespaces.map((namespace) => {
|
|
15597
|
-
if (!
|
|
15598
|
-
const current =
|
|
15660
|
+
if (!isRecord5(namespace) || namespace.ns !== WELCOME_NOTICE_NAMESPACE2) return namespace;
|
|
15661
|
+
const current = isRecord5(namespace.value) ? namespace.value : {};
|
|
15599
15662
|
return { ...namespace, value: { ...current, [WELCOME_NOTICE_FIELD2]: WELCOME_NOTICE_VERSION2 } };
|
|
15600
15663
|
});
|
|
15601
15664
|
return { ...value, namespaces };
|
|
@@ -15757,11 +15820,11 @@ var DATA_IMAGE_URL2 = /^data:(image\/(?:png|jpeg|webp|gif));base64,([A-Za-z0-9+/
|
|
|
15757
15820
|
function string(value) {
|
|
15758
15821
|
return typeof value === "string" ? value : void 0;
|
|
15759
15822
|
}
|
|
15760
|
-
function
|
|
15823
|
+
function isRecord6(value) {
|
|
15761
15824
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15762
15825
|
}
|
|
15763
15826
|
function record2(value) {
|
|
15764
|
-
return
|
|
15827
|
+
return isRecord6(value) ? value : {};
|
|
15765
15828
|
}
|
|
15766
15829
|
function toolOutputImageBlocks(item) {
|
|
15767
15830
|
const candidates = [
|
|
@@ -15851,7 +15914,7 @@ function collectImageBlocks(value, output = []) {
|
|
|
15851
15914
|
for (const item of value) collectImageBlocks(item, output);
|
|
15852
15915
|
return output;
|
|
15853
15916
|
}
|
|
15854
|
-
if (!
|
|
15917
|
+
if (!isRecord6(value)) return output;
|
|
15855
15918
|
if (value.type === "image") output.push(value);
|
|
15856
15919
|
for (const [key, child] of Object.entries(value)) {
|
|
15857
15920
|
if (key === "attachment") continue;
|
|
@@ -16932,7 +16995,7 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
|
|
|
16932
16995
|
this.broadcastRcHost({ type: "host/session-status", sessionId, running: args[1] });
|
|
16933
16996
|
} else if (event === "api-session/removed" && sessionId !== void 0) {
|
|
16934
16997
|
this.broadcastRcHost({ type: "host/session-removed", sessionId });
|
|
16935
|
-
} else if (event === "api-session/added" &&
|
|
16998
|
+
} else if (event === "api-session/added" && isRecord7(args[0])) {
|
|
16936
16999
|
const summary = args[0];
|
|
16937
17000
|
this.broadcastRcHost({
|
|
16938
17001
|
type: "host/session-added",
|
|
@@ -17322,7 +17385,7 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
|
|
|
17322
17385
|
}
|
|
17323
17386
|
createApiProxy() {
|
|
17324
17387
|
const call = (endpoint) => async (request, signal) => {
|
|
17325
|
-
const payload = endpoint === "session.prompt" &&
|
|
17388
|
+
const payload = endpoint === "session.prompt" && isRecord7(request.payload) ? { ...request.payload, requestId: String(request.rpcId) } : request.payload;
|
|
17326
17389
|
const result = await this.dispatch(rcEndpoint(endpoint), { args: { request: payload } }, signal ?? new AbortController().signal);
|
|
17327
17390
|
return {
|
|
17328
17391
|
rpcId: request.rpcId,
|
|
@@ -17977,7 +18040,7 @@ function codexPromptInput(content) {
|
|
|
17977
18040
|
if (content.length === 0 || content.length > MAX_CODEX_PROMPT_PARTS) return void 0;
|
|
17978
18041
|
const input2 = [];
|
|
17979
18042
|
for (const value of content) {
|
|
17980
|
-
if (!
|
|
18043
|
+
if (!isRecord7(value)) return void 0;
|
|
17981
18044
|
if (value.type === "text") {
|
|
17982
18045
|
if (typeof value.text !== "string" || value.text.length === 0 || value.text.length > MAX_CODEX_PROMPT_TEXT) return void 0;
|
|
17983
18046
|
input2.push({ type: "text", text: value.text });
|
|
@@ -18227,12 +18290,12 @@ function errorCode(error) {
|
|
|
18227
18290
|
return "code" in error && typeof error.code === "string" ? error.code : void 0;
|
|
18228
18291
|
}
|
|
18229
18292
|
function errorDetails(error) {
|
|
18230
|
-
return "details" in error &&
|
|
18293
|
+
return "details" in error && isRecord7(error.details) ? error.details : {};
|
|
18231
18294
|
}
|
|
18232
18295
|
function record3(value) {
|
|
18233
|
-
return
|
|
18296
|
+
return isRecord7(value) ? value : {};
|
|
18234
18297
|
}
|
|
18235
|
-
function
|
|
18298
|
+
function isRecord7(value) {
|
|
18236
18299
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
18237
18300
|
}
|
|
18238
18301
|
function array(value) {
|
|
@@ -18354,8 +18417,63 @@ var AsyncValueQueue2 = class {
|
|
|
18354
18417
|
import { platform } from "node:os";
|
|
18355
18418
|
|
|
18356
18419
|
// src/server-credentials.ts
|
|
18357
|
-
import { chmod, mkdir, readFile as readFile2,
|
|
18358
|
-
import { dirname as dirname2, join as
|
|
18420
|
+
import { chmod, mkdir, readFile as readFile2, rm as rm2, stat as stat2, writeFile } from "node:fs/promises";
|
|
18421
|
+
import { dirname as dirname2, join as join3 } from "node:path";
|
|
18422
|
+
|
|
18423
|
+
// src/atomic-file.ts
|
|
18424
|
+
import { readdir, rename, rm, stat } from "node:fs/promises";
|
|
18425
|
+
import { join as join2 } from "node:path";
|
|
18426
|
+
var TRANSIENT_REPLACE_CODES = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES"]);
|
|
18427
|
+
var REPLACE_RETRY_DELAYS_MS = [20, 40, 80, 160, 320];
|
|
18428
|
+
var TEMPORARY_NAME = /\.\d+\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.tmp$/iu;
|
|
18429
|
+
var STALE_TEMPORARY_AGE_MS = 10 * 60 * 1e3;
|
|
18430
|
+
async function replaceFile(temporary, target2, options = {}) {
|
|
18431
|
+
const delays = options.delaysMs ?? REPLACE_RETRY_DELAYS_MS;
|
|
18432
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
18433
|
+
try {
|
|
18434
|
+
await rename(temporary, target2);
|
|
18435
|
+
return;
|
|
18436
|
+
} catch (error) {
|
|
18437
|
+
const delay = delays[attempt];
|
|
18438
|
+
if (delay === void 0 || !TRANSIENT_REPLACE_CODES.has(errorCode2(error) ?? "")) throw error;
|
|
18439
|
+
await sleep2(delay);
|
|
18440
|
+
}
|
|
18441
|
+
}
|
|
18442
|
+
}
|
|
18443
|
+
async function sweepStaleTemporaries(directory, options = {}) {
|
|
18444
|
+
const maxAgeMs = options.maxAgeMs ?? STALE_TEMPORARY_AGE_MS;
|
|
18445
|
+
const removed = [];
|
|
18446
|
+
let entries;
|
|
18447
|
+
try {
|
|
18448
|
+
entries = await readdir(directory);
|
|
18449
|
+
} catch {
|
|
18450
|
+
return removed;
|
|
18451
|
+
}
|
|
18452
|
+
for (const entry of entries) {
|
|
18453
|
+
if (!TEMPORARY_NAME.test(entry)) continue;
|
|
18454
|
+
const path = join2(directory, entry);
|
|
18455
|
+
try {
|
|
18456
|
+
const info = await stat(path);
|
|
18457
|
+
if (!info.isFile() || Date.now() - info.mtimeMs < maxAgeMs) continue;
|
|
18458
|
+
await rm(path, { force: true });
|
|
18459
|
+
removed.push(path);
|
|
18460
|
+
} catch {
|
|
18461
|
+
}
|
|
18462
|
+
}
|
|
18463
|
+
return removed;
|
|
18464
|
+
}
|
|
18465
|
+
function errorCode2(error) {
|
|
18466
|
+
if (typeof error !== "object" || error === null || !("code" in error)) return void 0;
|
|
18467
|
+
const code = error.code;
|
|
18468
|
+
return typeof code === "string" ? code : void 0;
|
|
18469
|
+
}
|
|
18470
|
+
function sleep2(ms) {
|
|
18471
|
+
return new Promise((resolve4) => {
|
|
18472
|
+
setTimeout(resolve4, ms);
|
|
18473
|
+
});
|
|
18474
|
+
}
|
|
18475
|
+
|
|
18476
|
+
// src/server-credentials.ts
|
|
18359
18477
|
var credentialSchema = external_exports.object({
|
|
18360
18478
|
schemaVersion: external_exports.literal(1),
|
|
18361
18479
|
serverUrl: external_exports.string().url(),
|
|
@@ -18370,7 +18488,7 @@ var credentialSchema = external_exports.object({
|
|
|
18370
18488
|
var ServerCredentialStore = class {
|
|
18371
18489
|
path;
|
|
18372
18490
|
constructor(directory) {
|
|
18373
|
-
this.path =
|
|
18491
|
+
this.path = join3(directory, "server-credentials.json");
|
|
18374
18492
|
}
|
|
18375
18493
|
/** Serialize the complete read/refresh/write transaction across processes.
|
|
18376
18494
|
* Never steal an old lock: a suspended owner may still consume a one-use token.
|
|
@@ -18393,7 +18511,7 @@ var ServerCredentialStore = class {
|
|
|
18393
18511
|
try {
|
|
18394
18512
|
return await operation();
|
|
18395
18513
|
} finally {
|
|
18396
|
-
await
|
|
18514
|
+
await rm2(lock, { recursive: true });
|
|
18397
18515
|
}
|
|
18398
18516
|
}
|
|
18399
18517
|
async load(serverUrl, deviceId) {
|
|
@@ -18414,7 +18532,7 @@ var ServerCredentialStore = class {
|
|
|
18414
18532
|
return record7;
|
|
18415
18533
|
}
|
|
18416
18534
|
async clear() {
|
|
18417
|
-
await
|
|
18535
|
+
await rm2(this.path, { force: true });
|
|
18418
18536
|
}
|
|
18419
18537
|
};
|
|
18420
18538
|
var ServerCredentialsInvalidError = class extends Error {
|
|
@@ -18427,21 +18545,23 @@ var ServerCredentialsBusyError = class extends Error {
|
|
|
18427
18545
|
}
|
|
18428
18546
|
};
|
|
18429
18547
|
async function atomicWrite(path, contents) {
|
|
18430
|
-
|
|
18548
|
+
const directory = dirname2(path);
|
|
18549
|
+
await mkdir(directory, { recursive: true, mode: 448 });
|
|
18550
|
+
await sweepStaleTemporaries(directory);
|
|
18431
18551
|
const temporary = `${path}.${process.pid}.${uuidV7()}.tmp`;
|
|
18432
18552
|
await writeFile(temporary, contents, { encoding: "utf8", mode: 384, flag: "wx" });
|
|
18433
18553
|
await chmod(temporary, 384);
|
|
18434
|
-
await
|
|
18554
|
+
await replaceFile(temporary, path);
|
|
18435
18555
|
await chmod(path, 384);
|
|
18436
18556
|
}
|
|
18437
18557
|
async function assertPrivateMode(path) {
|
|
18438
18558
|
if (process.platform === "win32") return;
|
|
18439
|
-
const mode = (await
|
|
18559
|
+
const mode = (await stat2(path)).mode & 511;
|
|
18440
18560
|
if ((mode & 63) !== 0) throw new ServerCredentialsInvalidError("server credentials permissions must be 0600");
|
|
18441
18561
|
}
|
|
18442
18562
|
async function exists(path) {
|
|
18443
18563
|
try {
|
|
18444
|
-
await
|
|
18564
|
+
await stat2(path);
|
|
18445
18565
|
return true;
|
|
18446
18566
|
} catch (error) {
|
|
18447
18567
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") return false;
|
|
@@ -18572,7 +18692,7 @@ function normalizeServerUrl(value) {
|
|
|
18572
18692
|
}
|
|
18573
18693
|
|
|
18574
18694
|
// src/version.ts
|
|
18575
|
-
var PLUGIN_VERSION = "0.4.
|
|
18695
|
+
var PLUGIN_VERSION = "0.4.30";
|
|
18576
18696
|
|
|
18577
18697
|
// src/server-api.ts
|
|
18578
18698
|
var ENABLED_QR_PROVIDERS = ["github"];
|
|
@@ -19047,6 +19167,17 @@ function requireRecord(value, name2) {
|
|
|
19047
19167
|
// src/typert-gateway-switch.ts
|
|
19048
19168
|
var REMOTE_COMMAND_METHODS = ["execute", "list"];
|
|
19049
19169
|
var LOCAL_ONLY_NAMESPACES = /* @__PURE__ */ new Set(["dynamicCordisRunner"]);
|
|
19170
|
+
var LOCAL_FALLBACK_NAMESPACES = /* @__PURE__ */ new Set(["$events", "settings", "credentials", "dynamicCordisRunner"]);
|
|
19171
|
+
var PEER_GONE_CODES = /* @__PURE__ */ new Set([
|
|
19172
|
+
"TRANSPORT_CLOSED",
|
|
19173
|
+
"CLIENT_CLOSED",
|
|
19174
|
+
"RPC_TIMEOUT",
|
|
19175
|
+
"CONNECTION_FAILED",
|
|
19176
|
+
"CONNECTION_REPLACED",
|
|
19177
|
+
"NOT_CONNECTED",
|
|
19178
|
+
"UNAVAILABLE",
|
|
19179
|
+
"internal"
|
|
19180
|
+
]);
|
|
19050
19181
|
var ALL_REMOTE_COMMANDS = { execute: true, list: true };
|
|
19051
19182
|
var TypertGatewaySwitch = class {
|
|
19052
19183
|
runtime;
|
|
@@ -19198,7 +19329,7 @@ var TypertGatewaySwitch = class {
|
|
|
19198
19329
|
if (isLocalOnlyEndpoint(endpointOf(request))) return this.localInvoke(request);
|
|
19199
19330
|
if (this.remoteTarget !== void 0 && this.remoteAvailability()) {
|
|
19200
19331
|
return this.remoteTarget.invoke(request).catch((error) => {
|
|
19201
|
-
if (!
|
|
19332
|
+
if (!localFallbackAllowed(endpointOf(request), error)) throw error;
|
|
19202
19333
|
console.warn(`[dsh-remote] serving ${endpointOf(request)} locally: the peer did not answer it`, error);
|
|
19203
19334
|
return this.localInvoke(request);
|
|
19204
19335
|
});
|
|
@@ -19225,7 +19356,7 @@ var TypertGatewaySwitch = class {
|
|
|
19225
19356
|
*/
|
|
19226
19357
|
withLocalFallback(endpoint, remote, local) {
|
|
19227
19358
|
return remote().catch((error) => {
|
|
19228
|
-
if (!
|
|
19359
|
+
if (!localFallbackAllowed(endpoint, error)) throw error;
|
|
19229
19360
|
console.warn(`[dsh-remote] serving ${endpoint} locally: the peer did not answer it`, error);
|
|
19230
19361
|
return local();
|
|
19231
19362
|
});
|
|
@@ -19235,7 +19366,7 @@ var TypertGatewaySwitch = class {
|
|
|
19235
19366
|
if (normalized !== void 0) return normalized;
|
|
19236
19367
|
const source = error instanceof Error ? error : new Error("The Harness Gateway rejected the request.");
|
|
19237
19368
|
const code = "code" in source && typeof source.code === "string" ? source.code : "internal";
|
|
19238
|
-
const details = "details" in source &&
|
|
19369
|
+
const details = "details" in source && isRecord8(source.details) ? source.details : {};
|
|
19239
19370
|
return { code, message: source.message, details };
|
|
19240
19371
|
}
|
|
19241
19372
|
};
|
|
@@ -19275,7 +19406,7 @@ function requestFromCarrier(endpoint, payload, signal) {
|
|
|
19275
19406
|
if (segments.length !== 2 || segments.some((segment) => segment.length === 0)) {
|
|
19276
19407
|
throw new Error("The Harness Gateway endpoint is invalid.");
|
|
19277
19408
|
}
|
|
19278
|
-
if (!
|
|
19409
|
+
if (!isRecord8(payload) || !isRecord8(payload.args)) {
|
|
19279
19410
|
throw new Error("The Harness Gateway payload is invalid.");
|
|
19280
19411
|
}
|
|
19281
19412
|
return { namespace: segments[0], method: segments[1], args: payload.args, signal };
|
|
@@ -19283,15 +19414,27 @@ function requestFromCarrier(endpoint, payload, signal) {
|
|
|
19283
19414
|
function endpointOf(request) {
|
|
19284
19415
|
return `${request.namespace}/${request.method}`;
|
|
19285
19416
|
}
|
|
19286
|
-
function
|
|
19417
|
+
function namespaceOf(endpoint) {
|
|
19287
19418
|
const separator = endpoint.indexOf("/");
|
|
19288
|
-
return separator > 0
|
|
19419
|
+
return separator > 0 ? endpoint.slice(0, separator) : void 0;
|
|
19420
|
+
}
|
|
19421
|
+
function isLocalOnlyEndpoint(endpoint) {
|
|
19422
|
+
const namespace = namespaceOf(endpoint);
|
|
19423
|
+
return namespace !== void 0 && LOCAL_ONLY_NAMESPACES.has(namespace);
|
|
19424
|
+
}
|
|
19425
|
+
function localFallbackAllowed(endpoint, error) {
|
|
19426
|
+
if (!isUnansweredByPeer(error)) return false;
|
|
19427
|
+
if (!isRecord8(error)) return true;
|
|
19428
|
+
const code = typeof error.code === "string" ? error.code : "";
|
|
19429
|
+
if (!PEER_GONE_CODES.has(code)) return true;
|
|
19430
|
+
const namespace = namespaceOf(endpoint);
|
|
19431
|
+
return namespace !== void 0 && LOCAL_FALLBACK_NAMESPACES.has(namespace);
|
|
19289
19432
|
}
|
|
19290
19433
|
function isRemoteCommandMethod(method) {
|
|
19291
19434
|
return REMOTE_COMMAND_METHODS.includes(method);
|
|
19292
19435
|
}
|
|
19293
19436
|
function isUnansweredByPeer(error) {
|
|
19294
|
-
if (!
|
|
19437
|
+
if (!isRecord8(error)) return error instanceof Error;
|
|
19295
19438
|
const code = typeof error.code === "string" ? error.code : "";
|
|
19296
19439
|
return code === "" || UNANSWERED_BY_PEER_CODES.has(code);
|
|
19297
19440
|
}
|
|
@@ -19304,9 +19447,15 @@ var UNANSWERED_BY_PEER_CODES = /* @__PURE__ */ new Set([
|
|
|
19304
19447
|
"CONNECTION_REPLACED",
|
|
19305
19448
|
"NOT_CONNECTED",
|
|
19306
19449
|
"UNAVAILABLE",
|
|
19450
|
+
// The client's own transport codes: the peer cannot answer a call it never received, so these are
|
|
19451
|
+
// "unanswered" rather than a refusal - and PEER_GONE_CODES above is what decides whether the local
|
|
19452
|
+
// shell may answer for it.
|
|
19453
|
+
"TRANSPORT_CLOSED",
|
|
19454
|
+
"CLIENT_CLOSED",
|
|
19455
|
+
"RPC_TIMEOUT",
|
|
19307
19456
|
"internal"
|
|
19308
19457
|
]);
|
|
19309
|
-
function
|
|
19458
|
+
function isRecord8(value) {
|
|
19310
19459
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
19311
19460
|
}
|
|
19312
19461
|
|
|
@@ -19317,7 +19466,7 @@ import { networkInterfaces } from "node:os";
|
|
|
19317
19466
|
// src/native-rtc-helper.ts
|
|
19318
19467
|
import { spawn, spawnSync } from "node:child_process";
|
|
19319
19468
|
import { existsSync, readdirSync, realpathSync, statSync } from "node:fs";
|
|
19320
|
-
import { delimiter, dirname as dirname3, join as
|
|
19469
|
+
import { delimiter, dirname as dirname3, join as join4 } from "node:path";
|
|
19321
19470
|
import { fileURLToPath } from "node:url";
|
|
19322
19471
|
var cachedExternalFactory;
|
|
19323
19472
|
var cachedExternalFactoryResolved = false;
|
|
@@ -19368,21 +19517,21 @@ function nodeBinaryCandidates() {
|
|
|
19368
19517
|
add3(process.env.DSH_REMOTE_NODE);
|
|
19369
19518
|
add3(process.env.NODE);
|
|
19370
19519
|
if (process.versions.electron === void 0) add3(process.execPath);
|
|
19371
|
-
for (const part of (process.env.PATH ?? "").split(delimiter)) add3(
|
|
19520
|
+
for (const part of (process.env.PATH ?? "").split(delimiter)) add3(join4(part, process.platform === "win32" ? "node.exe" : "node"));
|
|
19372
19521
|
add3("/opt/homebrew/bin/node");
|
|
19373
19522
|
add3("/usr/local/bin/node");
|
|
19374
19523
|
add3("/usr/bin/node");
|
|
19375
|
-
add3(
|
|
19376
|
-
add3(
|
|
19377
|
-
add3(
|
|
19524
|
+
add3(join4(process.env.HOME ?? "", ".volta", "bin", process.platform === "win32" ? "node.exe" : "node"));
|
|
19525
|
+
add3(join4(process.env.HOME ?? "", ".asdf", "shims", process.platform === "win32" ? "node.exe" : "node"));
|
|
19526
|
+
add3(join4(process.env.HOME ?? "", ".local", "bin", process.platform === "win32" ? "node.exe" : "node"));
|
|
19378
19527
|
for (const nvmNode of nvmNodeCandidates()) add3(nvmNode);
|
|
19379
19528
|
return candidates;
|
|
19380
19529
|
}
|
|
19381
19530
|
function nvmNodeCandidates() {
|
|
19382
|
-
const root =
|
|
19531
|
+
const root = join4(process.env.HOME ?? "", ".nvm", "versions", "node");
|
|
19383
19532
|
if (root === "" || !existsSync(root)) return [];
|
|
19384
19533
|
try {
|
|
19385
|
-
return readdirSync(root).map((version) =>
|
|
19534
|
+
return readdirSync(root).map((version) => join4(root, version, "bin", process.platform === "win32" ? "node.exe" : "node")).sort((left, right) => right.localeCompare(left, "en", { numeric: true }));
|
|
19386
19535
|
} catch {
|
|
19387
19536
|
return [];
|
|
19388
19537
|
}
|
|
@@ -19413,10 +19562,10 @@ function isUsableExternalNode(candidate, requireFrom) {
|
|
|
19413
19562
|
}
|
|
19414
19563
|
function isExecutableFile(candidate) {
|
|
19415
19564
|
try {
|
|
19416
|
-
const
|
|
19417
|
-
if (!
|
|
19565
|
+
const stat8 = statSync(candidate);
|
|
19566
|
+
if (!stat8.isFile()) return false;
|
|
19418
19567
|
if (process.platform === "win32") return true;
|
|
19419
|
-
return (
|
|
19568
|
+
return (stat8.mode & 73) !== 0;
|
|
19420
19569
|
} catch {
|
|
19421
19570
|
return false;
|
|
19422
19571
|
}
|
|
@@ -20385,14 +20534,34 @@ var HOST_AUTHORIZATION_ERRORS = /* @__PURE__ */ new Set([
|
|
|
20385
20534
|
"DEVICE_REVOKED",
|
|
20386
20535
|
"TOKEN_EXPIRED"
|
|
20387
20536
|
]);
|
|
20537
|
+
var CREDENTIAL_FAILURE_CODES = /* @__PURE__ */ new Set([
|
|
20538
|
+
"AUTH_REQUIRED",
|
|
20539
|
+
"ACCOUNT_AUTH_REQUIRED",
|
|
20540
|
+
"AUTH_INVALID",
|
|
20541
|
+
"TOKEN_EXPIRED",
|
|
20542
|
+
"DEVICE_NOT_FOUND",
|
|
20543
|
+
"DEVICE_REVOKED",
|
|
20544
|
+
"MEMBERSHIP_REQUIRED"
|
|
20545
|
+
]);
|
|
20546
|
+
var LIVENESS_INTERVAL_MS = 3e4;
|
|
20547
|
+
var FAST_RECONNECT_INTERVAL_MS = 5e3;
|
|
20548
|
+
var LIVENESS_TIMEOUT_MS = 4e3;
|
|
20549
|
+
var LIVENESS_TOLERATED_FAILURES = 2;
|
|
20550
|
+
var NO_ANSWER_CODES = /* @__PURE__ */ new Set(["RPC_TIMEOUT", "CLIENT_CLOSED", "TRANSPORT_CLOSED", "RPC_ABORTED"]);
|
|
20551
|
+
function livenessProbeLost(error) {
|
|
20552
|
+
const code = typeof error === "object" && error !== null && "code" in error ? error.code : void 0;
|
|
20553
|
+
if (typeof code !== "string") return true;
|
|
20554
|
+
return NO_ANSWER_CODES.has(code);
|
|
20555
|
+
}
|
|
20388
20556
|
var ClientModeRuntime = class {
|
|
20389
|
-
constructor(config, identities, server, apiProxy, typertGateway, logger, host, rtcFactoryProvider = loadNodeRtcFactory) {
|
|
20557
|
+
constructor(config, identities, server, apiProxy, typertGateway, logger, host, rtcFactoryProvider = loadNodeRtcFactory, targetStore) {
|
|
20390
20558
|
this.config = config;
|
|
20391
20559
|
this.identities = identities;
|
|
20392
20560
|
this.server = server;
|
|
20393
20561
|
this.logger = logger;
|
|
20394
20562
|
this.host = host;
|
|
20395
20563
|
this.rtcFactoryProvider = rtcFactoryProvider;
|
|
20564
|
+
this.targetStore = targetStore;
|
|
20396
20565
|
this.proxySwitch = apiProxy === void 0 ? void 0 : new ApiProxySwitch(apiProxy);
|
|
20397
20566
|
this.gatewaySwitch = new TypertGatewaySwitch(typertGateway);
|
|
20398
20567
|
this.gatewaySwitch.setRemoteAvailability(() => this.connected !== void 0);
|
|
@@ -20417,12 +20586,56 @@ var ClientModeRuntime = class {
|
|
|
20417
20586
|
*/
|
|
20418
20587
|
remoteReconnectRun = 0;
|
|
20419
20588
|
pendingWorkspaceSelection;
|
|
20589
|
+
/**
|
|
20590
|
+
* Transport a running fast reconnect is replacing.
|
|
20591
|
+
*
|
|
20592
|
+
* The rebuild opens its own control connection and the Server closes the older one for the same
|
|
20593
|
+
* device, so that close belongs to our own replacement. Reading it as a lost peer is what made the
|
|
20594
|
+
* fast level escalate itself into the fallback before it could ever succeed.
|
|
20595
|
+
*/
|
|
20596
|
+
supersededClient;
|
|
20597
|
+
/**
|
|
20598
|
+
* True while a fast reconnect is rebuilding the link.
|
|
20599
|
+
*
|
|
20600
|
+
* The probe must not judge the link in the middle of its own replacement: the Server closes the
|
|
20601
|
+
* older connection for the device, so a check that ran then would read our replacement's side effect
|
|
20602
|
+
* as a second miss and escalate the level that is busy recovering. The rebuild's own outcome decides
|
|
20603
|
+
* first; only once it has settled does the cadence resume judging.
|
|
20604
|
+
*/
|
|
20605
|
+
fastRebuildInFlight = false;
|
|
20606
|
+
/**
|
|
20607
|
+
* The last workspace the user opened for a Host.
|
|
20608
|
+
*
|
|
20609
|
+
* Kept so a reconnect can republish it: re-selecting the workspace is what makes the native UI
|
|
20610
|
+
* re-read its session list, and without it a list poisoned by the outage stays wrong even after
|
|
20611
|
+
* the link is back.
|
|
20612
|
+
*/
|
|
20613
|
+
lastWorkspaceSelection;
|
|
20420
20614
|
codexVirtual;
|
|
20421
20615
|
proxySwitch;
|
|
20422
20616
|
gatewaySwitch;
|
|
20423
20617
|
codexStreams = /* @__PURE__ */ new Map();
|
|
20424
20618
|
connectionProgress;
|
|
20425
20619
|
connectionProgressRun = 0;
|
|
20620
|
+
/**
|
|
20621
|
+
* Host a boot is trying to restore, reported to the UI while the retry loop runs.
|
|
20622
|
+
*
|
|
20623
|
+
* Nothing is connected yet, so without this the window would show the local shell while the
|
|
20624
|
+
* user is still looking at the remote workspace they left.
|
|
20625
|
+
*/
|
|
20626
|
+
/**
|
|
20627
|
+
* Recovery in progress, and which kind.
|
|
20628
|
+
*
|
|
20629
|
+
* `restore` is a start that is reconnecting to the recorded target, `fast` keeps the session on
|
|
20630
|
+
* screen while its link is rebuilt, and `fallback` is the last resort that returns the user to
|
|
20631
|
+
* the local shell. The UI shows all three, so a reconnect is never invisible, and the retry loop
|
|
20632
|
+
* stops as soon as the user asks for local.
|
|
20633
|
+
*/
|
|
20634
|
+
reconnecting;
|
|
20635
|
+
/** Periodic proof of life for the live remote session; absent while nothing is connected. */
|
|
20636
|
+
livenessTimer;
|
|
20637
|
+
livenessInFlight = false;
|
|
20638
|
+
livenessFailures = 0;
|
|
20426
20639
|
closed = false;
|
|
20427
20640
|
async start() {
|
|
20428
20641
|
if (this.closed) throw new Error("client remote-mode runtime is closed");
|
|
@@ -20483,6 +20696,7 @@ var ClientModeRuntime = class {
|
|
|
20483
20696
|
...targetStatus,
|
|
20484
20697
|
connected: this.connected !== void 0,
|
|
20485
20698
|
fellBackToLocal: this.fellBackToLocal,
|
|
20699
|
+
...this.reconnecting === void 0 ? {} : { reconnecting: { ...this.reconnecting } },
|
|
20486
20700
|
transport: this.connected?.client.getStats().mode ?? "Disconnected",
|
|
20487
20701
|
connectedTargetDeviceId: this.connected?.target.deviceId,
|
|
20488
20702
|
preferredTransports: this.config.forceRelay ? ["relay"] : ["lan", "p2p", "turn", "relay"],
|
|
@@ -20629,10 +20843,14 @@ var ClientModeRuntime = class {
|
|
|
20629
20843
|
this.connected = void 0;
|
|
20630
20844
|
this.connectionProgress = void 0;
|
|
20631
20845
|
this.pendingWorkspaceSelection = void 0;
|
|
20846
|
+
this.lastWorkspaceSelection = void 0;
|
|
20632
20847
|
await this.closeCodexStreams(previous2?.client);
|
|
20633
20848
|
await previous2?.client.close().catch(() => void 0);
|
|
20634
20849
|
this.fellBackToLocal = false;
|
|
20850
|
+
this.reconnecting = void 0;
|
|
20851
|
+
this.stopLivenessWatch();
|
|
20635
20852
|
this.remoteReconnectRun += 1;
|
|
20853
|
+
await this.rememberTarget({ mode: "local" });
|
|
20636
20854
|
this.logger.info("Harness target switched", { mode: "local" });
|
|
20637
20855
|
return this.status();
|
|
20638
20856
|
}
|
|
@@ -20655,11 +20873,191 @@ var ClientModeRuntime = class {
|
|
|
20655
20873
|
await this.closeCodexVirtual();
|
|
20656
20874
|
this.selectRemoteTarget(next);
|
|
20657
20875
|
this.fellBackToLocal = false;
|
|
20876
|
+
this.reconnecting = void 0;
|
|
20877
|
+
await this.rememberTarget({ mode: "remote", hostDeviceId: next.target.deviceId, hostName: next.target.name });
|
|
20658
20878
|
await this.closeCodexStreams(previous?.client);
|
|
20659
20879
|
await previous?.client.close().catch(() => void 0);
|
|
20660
20880
|
this.logger.info("Harness target switched", { mode: "remote", targetDeviceId: shortId(next.target.deviceId) });
|
|
20661
20881
|
return this.status();
|
|
20662
20882
|
}
|
|
20883
|
+
/**
|
|
20884
|
+
* Prove the live remote link still answers, and act on the result.
|
|
20885
|
+
*
|
|
20886
|
+
* Only a missing answer counts as loss: the peer's own error (a refusal, an unknown method) came
|
|
20887
|
+
* back over the same link and therefore proves it is there.
|
|
20888
|
+
*
|
|
20889
|
+
* The two recovery levels differ in what the user keeps. The first miss rebuilds the link in
|
|
20890
|
+
* place, so the session, its workspace selection and the remote carriers all stay; the second
|
|
20891
|
+
* gives up and returns to the local shell.
|
|
20892
|
+
*/
|
|
20893
|
+
async verifyRemoteLiveness() {
|
|
20894
|
+
const connected = this.connected;
|
|
20895
|
+
if (connected === void 0 || this.livenessInFlight || this.fastRebuildInFlight) return;
|
|
20896
|
+
this.livenessInFlight = true;
|
|
20897
|
+
try {
|
|
20898
|
+
await connected.client.rpc("harness.transport.describe", {}, void 0, { timeoutMs: LIVENESS_TIMEOUT_MS });
|
|
20899
|
+
this.livenessFailures = 0;
|
|
20900
|
+
if (this.reconnecting?.phase === "fast") this.finishReconnect("the link answered again");
|
|
20901
|
+
} catch (error) {
|
|
20902
|
+
if (!livenessProbeLost(error)) {
|
|
20903
|
+
this.livenessFailures = 0;
|
|
20904
|
+
return;
|
|
20905
|
+
}
|
|
20906
|
+
this.livenessFailures += 1;
|
|
20907
|
+
this.logger.warn("remote Harness liveness check found no answer", {
|
|
20908
|
+
targetDeviceId: shortId(connected.target.deviceId),
|
|
20909
|
+
attempt: this.livenessFailures
|
|
20910
|
+
});
|
|
20911
|
+
if (this.livenessFailures === 1) {
|
|
20912
|
+
await this.enterFastReconnect(connected.target.deviceId, connected.target.name);
|
|
20913
|
+
return;
|
|
20914
|
+
}
|
|
20915
|
+
if (this.livenessFailures >= LIVENESS_TOLERATED_FAILURES) {
|
|
20916
|
+
this.handleRemoteTransportLost(connected.client, connected.target.deviceId, "unanswered-twice");
|
|
20917
|
+
}
|
|
20918
|
+
} finally {
|
|
20919
|
+
this.livenessInFlight = false;
|
|
20920
|
+
}
|
|
20921
|
+
}
|
|
20922
|
+
/**
|
|
20923
|
+
* Rebuild the link while the session stays on screen.
|
|
20924
|
+
*
|
|
20925
|
+
* This is what separates the two recovery levels: nothing is handed back to the local shell, so a
|
|
20926
|
+
* link that recovers does not cost the user the view they were working in.
|
|
20927
|
+
* @param targetDeviceId - the Host to rebuild the link to.
|
|
20928
|
+
*/
|
|
20929
|
+
async enterFastReconnect(targetDeviceId, targetName) {
|
|
20930
|
+
if (this.reconnecting !== void 0) return;
|
|
20931
|
+
this.reconnecting = { targetDeviceId, ...targetName === void 0 ? {} : { targetName }, phase: "fast" };
|
|
20932
|
+
this.logger.warn("remote Harness link stopped answering; reconnecting in place", {
|
|
20933
|
+
targetDeviceId: shortId(targetDeviceId)
|
|
20934
|
+
});
|
|
20935
|
+
this.armLivenessWatch(FAST_RECONNECT_INTERVAL_MS);
|
|
20936
|
+
await this.reestablish(targetDeviceId);
|
|
20937
|
+
}
|
|
20938
|
+
/**
|
|
20939
|
+
* Build a new transport for the session already on screen.
|
|
20940
|
+
*
|
|
20941
|
+
* The carriers hold the previous client, so they are rebound to the new one before the old client
|
|
20942
|
+
* is closed; leaving it open would keep pointing remote calls at a dead transport.
|
|
20943
|
+
* @param targetDeviceId - the Host to reconnect to.
|
|
20944
|
+
* @returns true when a new session is in place.
|
|
20945
|
+
*/
|
|
20946
|
+
async reestablish(targetDeviceId) {
|
|
20947
|
+
const previous = this.connected;
|
|
20948
|
+
this.supersededClient = previous?.client;
|
|
20949
|
+
this.fastRebuildInFlight = true;
|
|
20950
|
+
try {
|
|
20951
|
+
const next = await this.connect(targetDeviceId);
|
|
20952
|
+
if (previous === void 0) {
|
|
20953
|
+
this.supersededClient = void 0;
|
|
20954
|
+
await next.client.close().catch(() => void 0);
|
|
20955
|
+
return false;
|
|
20956
|
+
}
|
|
20957
|
+
await this.closePreview();
|
|
20958
|
+
this.connected = next;
|
|
20959
|
+
this.clearConnectionProgress(next.progressRunId);
|
|
20960
|
+
this.selectRemoteTarget(next);
|
|
20961
|
+
await this.closeCodexStreams(previous.client);
|
|
20962
|
+
await previous.client.close().catch(() => void 0);
|
|
20963
|
+
this.supersededClient = void 0;
|
|
20964
|
+
this.finishReconnect("link re-established");
|
|
20965
|
+
return true;
|
|
20966
|
+
} catch (error) {
|
|
20967
|
+
this.supersededClient = void 0;
|
|
20968
|
+
this.logger.warn("fast reconnect attempt failed", {
|
|
20969
|
+
targetDeviceId: shortId(targetDeviceId),
|
|
20970
|
+
code: safeErrorCode(error)
|
|
20971
|
+
});
|
|
20972
|
+
return false;
|
|
20973
|
+
} finally {
|
|
20974
|
+
this.fastRebuildInFlight = false;
|
|
20975
|
+
}
|
|
20976
|
+
}
|
|
20977
|
+
rememberWorkspaceSelection(selection) {
|
|
20978
|
+
this.pendingWorkspaceSelection = selection;
|
|
20979
|
+
this.lastWorkspaceSelection = { ...selection };
|
|
20980
|
+
}
|
|
20981
|
+
/**
|
|
20982
|
+
* Republish the workspace selection so the native UI re-reads its remote session list.
|
|
20983
|
+
*
|
|
20984
|
+
* The client half consumes status.workspaceSelection and reconnects that workspace; that refresh is
|
|
20985
|
+
* what replaces a list the outage had filled with local answers.
|
|
20986
|
+
* @param targetDeviceId - the Host the reconnect finished against.
|
|
20987
|
+
*/
|
|
20988
|
+
restoreWorkspaceSelection(targetDeviceId) {
|
|
20989
|
+
const selection = this.lastWorkspaceSelection;
|
|
20990
|
+
if (selection === void 0 || selection.targetDeviceId !== targetDeviceId) return;
|
|
20991
|
+
this.pendingWorkspaceSelection = { ...selection };
|
|
20992
|
+
this.logger.info("republishing the workspace selection to re-read the remote session list", {
|
|
20993
|
+
targetDeviceId: shortId(targetDeviceId),
|
|
20994
|
+
workspaceId: selection.workspaceId
|
|
20995
|
+
});
|
|
20996
|
+
}
|
|
20997
|
+
finishReconnect(reason) {
|
|
20998
|
+
const target2 = this.reconnecting?.targetDeviceId;
|
|
20999
|
+
if (target2 === void 0) return;
|
|
21000
|
+
this.reconnecting = void 0;
|
|
21001
|
+
this.livenessFailures = 0;
|
|
21002
|
+
this.armLivenessWatch(LIVENESS_INTERVAL_MS);
|
|
21003
|
+
this.restoreWorkspaceSelection(target2);
|
|
21004
|
+
this.logger.info("remote Harness reconnect finished", { targetDeviceId: shortId(target2), reason });
|
|
21005
|
+
}
|
|
21006
|
+
/**
|
|
21007
|
+
* Check the live session now, outside the cadence.
|
|
21008
|
+
*
|
|
21009
|
+
* Used when the page becomes visible again, which is when a suspended client is most likely to be
|
|
21010
|
+
* holding a link that already ended.
|
|
21011
|
+
* @returns the status after the check.
|
|
21012
|
+
*/
|
|
21013
|
+
async verifyRemoteConnection() {
|
|
21014
|
+
await this.verifyRemoteLiveness();
|
|
21015
|
+
return this.status();
|
|
21016
|
+
}
|
|
21017
|
+
armLivenessWatch(intervalMs) {
|
|
21018
|
+
if (this.livenessTimer !== void 0) clearInterval(this.livenessTimer);
|
|
21019
|
+
const timer = setInterval(() => {
|
|
21020
|
+
void this.verifyRemoteLiveness();
|
|
21021
|
+
}, intervalMs);
|
|
21022
|
+
timer.unref?.();
|
|
21023
|
+
this.livenessTimer = timer;
|
|
21024
|
+
}
|
|
21025
|
+
stopLivenessWatch() {
|
|
21026
|
+
if (this.livenessTimer !== void 0) clearInterval(this.livenessTimer);
|
|
21027
|
+
this.livenessTimer = void 0;
|
|
21028
|
+
this.livenessFailures = 0;
|
|
21029
|
+
this.livenessInFlight = false;
|
|
21030
|
+
}
|
|
21031
|
+
/**
|
|
21032
|
+
* Shared cleanup for a session whose transport is gone.
|
|
21033
|
+
*
|
|
21034
|
+
* A close event and an exhausted liveness check must leave exactly the same state behind, so both
|
|
21035
|
+
* paths run this. The session is gone for good here, which is why the phase becomes 'fallback' and
|
|
21036
|
+
* the retry loop keeps the UI saying that it is reconnecting.
|
|
21037
|
+
* @param client - the client that was connected.
|
|
21038
|
+
* @param targetDeviceId - the Host it was bound to.
|
|
21039
|
+
*/
|
|
21040
|
+
handleRemoteTransportLost(client, targetDeviceId, reason) {
|
|
21041
|
+
if (this.connected?.client !== client) return;
|
|
21042
|
+
this.supersededClient = void 0;
|
|
21043
|
+
const targetName = this.connected.target.name;
|
|
21044
|
+
this.stopLivenessWatch();
|
|
21045
|
+
void this.closePreview();
|
|
21046
|
+
this.connected = void 0;
|
|
21047
|
+
this.connectionProgress = void 0;
|
|
21048
|
+
this.pendingWorkspaceSelection = void 0;
|
|
21049
|
+
void this.closeCodexVirtual();
|
|
21050
|
+
this.proxySwitch?.selectLocal();
|
|
21051
|
+
this.gatewaySwitch.selectLocal();
|
|
21052
|
+
this.fellBackToLocal = true;
|
|
21053
|
+
this.reconnecting = { targetDeviceId, ...targetName === void 0 ? {} : { targetName }, phase: "fallback" };
|
|
21054
|
+
void client.close().catch(() => void 0);
|
|
21055
|
+
this.logger.warn("remote Harness transport lost; reconnecting", {
|
|
21056
|
+
targetDeviceId: shortId(targetDeviceId),
|
|
21057
|
+
reason
|
|
21058
|
+
});
|
|
21059
|
+
void this.reconnectRemoteSession(targetDeviceId);
|
|
21060
|
+
}
|
|
20663
21061
|
/**
|
|
20664
21062
|
* Re-establish a remote session whose transport closed.
|
|
20665
21063
|
*
|
|
@@ -20683,20 +21081,79 @@ var ClientModeRuntime = class {
|
|
|
20683
21081
|
if (this.connected !== void 0) return;
|
|
20684
21082
|
try {
|
|
20685
21083
|
await this.setMode("remote", targetDeviceId);
|
|
21084
|
+
this.restoreWorkspaceSelection(targetDeviceId);
|
|
20686
21085
|
this.logger.info("remote Harness session reconnected", { targetDeviceId: shortId(targetDeviceId) });
|
|
20687
21086
|
return;
|
|
20688
21087
|
} catch (error) {
|
|
21088
|
+
const code = safeErrorCode(error);
|
|
21089
|
+
if (code !== void 0 && CREDENTIAL_FAILURE_CODES.has(code)) {
|
|
21090
|
+
this.logger.warn("remote Harness reconnect stopped: authorization is required", {
|
|
21091
|
+
targetDeviceId: shortId(targetDeviceId),
|
|
21092
|
+
code
|
|
21093
|
+
});
|
|
21094
|
+
this.reconnecting = void 0;
|
|
21095
|
+
return;
|
|
21096
|
+
}
|
|
20689
21097
|
if (attempt < 3 || attempt % 10 === 0) {
|
|
20690
21098
|
this.logger.warn("remote Harness reconnect attempt failed", {
|
|
20691
21099
|
targetDeviceId: shortId(targetDeviceId),
|
|
20692
21100
|
attempt,
|
|
20693
|
-
code
|
|
21101
|
+
code
|
|
20694
21102
|
});
|
|
20695
21103
|
}
|
|
20696
21104
|
}
|
|
20697
21105
|
attempt += 1;
|
|
20698
21106
|
}
|
|
20699
21107
|
}
|
|
21108
|
+
/**
|
|
21109
|
+
* Persist the target a later boot may have to restore.
|
|
21110
|
+
*
|
|
21111
|
+
* A failure here must not fail a connect: the only cost is that a killed process comes back to
|
|
21112
|
+
* the local shell, which is where it would have been without this record.
|
|
21113
|
+
* @param target - the target to record.
|
|
21114
|
+
*/
|
|
21115
|
+
async rememberTarget(target2) {
|
|
21116
|
+
if (this.targetStore === void 0) return;
|
|
21117
|
+
try {
|
|
21118
|
+
await this.targetStore.save({
|
|
21119
|
+
...target2,
|
|
21120
|
+
...this.config.serverUrl === void 0 ? {} : { serverUrl: this.config.serverUrl }
|
|
21121
|
+
});
|
|
21122
|
+
} catch (error) {
|
|
21123
|
+
this.logger.warn("could not record the remote target", { code: safeErrorCode(error) });
|
|
21124
|
+
}
|
|
21125
|
+
}
|
|
21126
|
+
/**
|
|
21127
|
+
* Reconnect to the target this device was last using, with the usual backoff.
|
|
21128
|
+
*
|
|
21129
|
+
* A resumed app can miss the transport close entirely - Android may reclaim the process - so
|
|
21130
|
+
* nothing would start the retry loop and the user would face a local shell behind a remote
|
|
21131
|
+
* workspace view. Restoring the recorded target gives that case the same loop a dropped
|
|
21132
|
+
* transport gets. A target recorded for another Server is left alone: it is not reachable
|
|
21133
|
+
* through the configured one.
|
|
21134
|
+
* @returns true when a retry loop was started.
|
|
21135
|
+
*/
|
|
21136
|
+
async restoreLastTarget() {
|
|
21137
|
+
if (this.closed || this.connected !== void 0) return false;
|
|
21138
|
+
const record7 = await this.targetStore?.load();
|
|
21139
|
+
if (record7 === void 0 || record7.mode !== "remote" || record7.hostDeviceId === void 0) return false;
|
|
21140
|
+
if (record7.serverUrl !== void 0 && record7.serverUrl !== this.config.serverUrl) {
|
|
21141
|
+
this.logger.info("recorded remote target belongs to another Server; not restoring", {
|
|
21142
|
+
targetDeviceId: shortId(record7.hostDeviceId)
|
|
21143
|
+
});
|
|
21144
|
+
return false;
|
|
21145
|
+
}
|
|
21146
|
+
this.reconnecting = {
|
|
21147
|
+
targetDeviceId: record7.hostDeviceId,
|
|
21148
|
+
...record7.hostName === void 0 ? {} : { targetName: record7.hostName },
|
|
21149
|
+
phase: "restore"
|
|
21150
|
+
};
|
|
21151
|
+
this.logger.info("restoring the remote target of the previous run", {
|
|
21152
|
+
targetDeviceId: shortId(record7.hostDeviceId)
|
|
21153
|
+
});
|
|
21154
|
+
void this.reconnectRemoteSession(record7.hostDeviceId);
|
|
21155
|
+
return true;
|
|
21156
|
+
}
|
|
20700
21157
|
async listRemoteDirectory(targetDeviceId, path, signal) {
|
|
20701
21158
|
const remote = await this.ensureConnected(targetDeviceId, signal);
|
|
20702
21159
|
if (remote.features.remoteGateway) {
|
|
@@ -20751,7 +21208,7 @@ var ClientModeRuntime = class {
|
|
|
20751
21208
|
await this.closeCodexVirtual();
|
|
20752
21209
|
this.selectRemoteTarget(remote, transport);
|
|
20753
21210
|
const workspaceId = workspaceRecordId(workspace.workspace);
|
|
20754
|
-
this.
|
|
21211
|
+
this.rememberWorkspaceSelection({ targetDeviceId: remote.target.deviceId, workspaceId });
|
|
20755
21212
|
this.logger.info("Remote workspace opened", { targetDeviceId: shortId(remote.target.deviceId) });
|
|
20756
21213
|
return { ...this.status(), workspace };
|
|
20757
21214
|
}
|
|
@@ -20785,12 +21242,12 @@ var ClientModeRuntime = class {
|
|
|
20785
21242
|
this.codexVirtual = virtual;
|
|
20786
21243
|
this.selectCodexTarget(virtual, remote);
|
|
20787
21244
|
const preferredSessionId = await virtual.preferredSessionId(signal);
|
|
20788
|
-
this.
|
|
21245
|
+
this.rememberWorkspaceSelection({
|
|
20789
21246
|
targetDeviceId: remote.target.deviceId,
|
|
20790
21247
|
workspaceId,
|
|
20791
21248
|
backend: "codex",
|
|
20792
21249
|
...preferredSessionId === void 0 ? {} : { sessionId: preferredSessionId }
|
|
20793
|
-
};
|
|
21250
|
+
});
|
|
20794
21251
|
this.logger.info("CodeX virtual workspace opened", { targetDeviceId: shortId(remote.target.deviceId) });
|
|
20795
21252
|
return { ...this.status(), workspace };
|
|
20796
21253
|
}
|
|
@@ -20815,6 +21272,7 @@ var ClientModeRuntime = class {
|
|
|
20815
21272
|
return this.openCodexWorkspace(targetDeviceId, codexProjectWorkspaceId(project.id), signal);
|
|
20816
21273
|
}
|
|
20817
21274
|
consumeWorkspaceSelection(selection) {
|
|
21275
|
+
this.lastWorkspaceSelection = { ...selection };
|
|
20818
21276
|
const pending = this.pendingWorkspaceSelection;
|
|
20819
21277
|
if (pending?.targetDeviceId === selection.targetDeviceId && pending.workspaceId === selection.workspaceId && (pending.backend ?? "harness") === (selection.backend ?? "harness")) {
|
|
20820
21278
|
this.pendingWorkspaceSelection = void 0;
|
|
@@ -20825,6 +21283,7 @@ var ClientModeRuntime = class {
|
|
|
20825
21283
|
await this.closePreview();
|
|
20826
21284
|
if (this.closed) return;
|
|
20827
21285
|
this.closed = true;
|
|
21286
|
+
this.stopLivenessWatch();
|
|
20828
21287
|
this.proxySwitch?.selectLocal();
|
|
20829
21288
|
await this.closePreview();
|
|
20830
21289
|
this.gatewaySwitch.selectLocal();
|
|
@@ -20892,10 +21351,10 @@ var ClientModeRuntime = class {
|
|
|
20892
21351
|
}
|
|
20893
21352
|
}
|
|
20894
21353
|
stream.unsubscribe = remote.client.onEvent((event) => {
|
|
20895
|
-
if (event.event === "codex.app.frame" &&
|
|
21354
|
+
if (event.event === "codex.app.frame" && isRecord9(event.data) && event.data.streamId === value.streamId) {
|
|
20896
21355
|
this.appendCodexFrame(stream, event.data);
|
|
20897
21356
|
}
|
|
20898
|
-
if (event.event === "codex.app.stream.closed" &&
|
|
21357
|
+
if (event.event === "codex.app.stream.closed" && isRecord9(event.data) && event.data.streamId === value.streamId) {
|
|
20899
21358
|
stream.closed = typeof event.data.reason === "string" ? event.data.reason : "closed";
|
|
20900
21359
|
stream.wake();
|
|
20901
21360
|
}
|
|
@@ -20922,7 +21381,7 @@ var ClientModeRuntime = class {
|
|
|
20922
21381
|
stream.wake();
|
|
20923
21382
|
};
|
|
20924
21383
|
appendCodexFrame(stream, data2) {
|
|
20925
|
-
if (!
|
|
21384
|
+
if (!isRecord9(data2) || !isRecord9(data2.frame) || typeof data2.frame.method !== "string") return;
|
|
20926
21385
|
if (stream.frames.length >= 256) {
|
|
20927
21386
|
stream.closed = "overflow";
|
|
20928
21387
|
} else {
|
|
@@ -21128,21 +21587,15 @@ var ClientModeRuntime = class {
|
|
|
21128
21587
|
connectedPreference === void 0 ? void 0 : [connectedPreference]
|
|
21129
21588
|
);
|
|
21130
21589
|
connectedClient.onClose(() => {
|
|
21131
|
-
if (this.
|
|
21132
|
-
|
|
21133
|
-
|
|
21134
|
-
|
|
21135
|
-
|
|
21136
|
-
|
|
21137
|
-
this.
|
|
21138
|
-
this.gatewaySwitch.selectLocal();
|
|
21139
|
-
this.fellBackToLocal = true;
|
|
21140
|
-
void connectedClient.close().catch(() => void 0);
|
|
21141
|
-
this.logger.warn("remote Harness transport closed; reconnecting", {
|
|
21142
|
-
targetDeviceId: shortId(target2.deviceId)
|
|
21143
|
-
});
|
|
21144
|
-
void this.reconnectRemoteSession(target2.deviceId);
|
|
21590
|
+
if (this.supersededClient === connectedClient) {
|
|
21591
|
+
this.logger.info("ignoring the close of the transport a reconnect is replacing", {
|
|
21592
|
+
targetDeviceId: shortId(target2.deviceId)
|
|
21593
|
+
});
|
|
21594
|
+
return;
|
|
21595
|
+
}
|
|
21596
|
+
this.handleRemoteTransportLost(connectedClient, target2.deviceId, "transport-closed");
|
|
21145
21597
|
});
|
|
21598
|
+
this.armLivenessWatch(LIVENESS_INTERVAL_MS);
|
|
21146
21599
|
const connectionDetails = await connectedTransport.connectionDetails().catch(() => void 0);
|
|
21147
21600
|
this.logger.info("remote Harness transport ready", {
|
|
21148
21601
|
targetDeviceId: shortId(target2.deviceId),
|
|
@@ -21213,6 +21666,7 @@ var ClientModeRuntime = class {
|
|
|
21213
21666
|
async handleControl(endpoint, payload, signal) {
|
|
21214
21667
|
try {
|
|
21215
21668
|
if (endpoint === "status") return ok2(await this.detailedStatus());
|
|
21669
|
+
if (endpoint === "client.connection.verify") return ok2(await this.verifyRemoteConnection());
|
|
21216
21670
|
if (endpoint === "devices") return ok2(await this.devices());
|
|
21217
21671
|
if (endpoint === "client.account.login") {
|
|
21218
21672
|
const value = record4(payload);
|
|
@@ -21426,7 +21880,7 @@ function record4(value) {
|
|
|
21426
21880
|
}
|
|
21427
21881
|
return value;
|
|
21428
21882
|
}
|
|
21429
|
-
function
|
|
21883
|
+
function isRecord9(value) {
|
|
21430
21884
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
21431
21885
|
}
|
|
21432
21886
|
function ok2(value) {
|
|
@@ -21451,11 +21905,11 @@ async function readRemoteWorkspaceBaseline(gateway, signal) {
|
|
|
21451
21905
|
const iterator = source[Symbol.asyncIterator]();
|
|
21452
21906
|
try {
|
|
21453
21907
|
const first = await iterator.next();
|
|
21454
|
-
if (first.done || !
|
|
21908
|
+
if (first.done || !isRecord9(first.value) || first.value.type !== "baseline" || !isRecord9(first.value.value) || !Array.isArray(first.value.value.items)) {
|
|
21455
21909
|
throw new ClientModeError("INVALID_MESSAGE", "The remote Host returned an invalid Workspace baseline.");
|
|
21456
21910
|
}
|
|
21457
21911
|
return first.value.value.items.map((item) => {
|
|
21458
|
-
if (!
|
|
21912
|
+
if (!isRecord9(item) || typeof item.workspaceId !== "string" || typeof item.path !== "string" || typeof item.title !== "string") {
|
|
21459
21913
|
throw new ClientModeError("INVALID_MESSAGE", "The remote Host returned an invalid Workspace row.");
|
|
21460
21914
|
}
|
|
21461
21915
|
return { workspaceId: item.workspaceId, path: item.path, title: item.title };
|
|
@@ -21521,7 +21975,7 @@ async function probeRemoteHostFeatures(client, clientVersion) {
|
|
|
21521
21975
|
if (error instanceof Error && "code" in error && error.code === "METHOD_NOT_FOUND") return fallback;
|
|
21522
21976
|
throw error;
|
|
21523
21977
|
}
|
|
21524
|
-
if (!
|
|
21978
|
+
if (!isRecord9(value) || !Array.isArray(value.capabilities) || value.capabilities.some((capability) => typeof capability !== "string")) {
|
|
21525
21979
|
throw new ClientModeError("INVALID_MESSAGE", "The remote Host returned invalid transport capabilities.");
|
|
21526
21980
|
}
|
|
21527
21981
|
const capabilities = new Set(value.capabilities);
|
|
@@ -21606,6 +22060,67 @@ function iceServersForAttempt(attempt, iceServers) {
|
|
|
21606
22060
|
return attempt === "direct" ? stunOnlyIceServers(iceServers) : iceServers;
|
|
21607
22061
|
}
|
|
21608
22062
|
|
|
22063
|
+
// src/client-target-store.ts
|
|
22064
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
22065
|
+
import { chmod as chmod2, mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2 } from "node:fs/promises";
|
|
22066
|
+
import { join as join5 } from "node:path";
|
|
22067
|
+
var FILE_NAME = "client-target.json";
|
|
22068
|
+
var ClientTargetStore = class {
|
|
22069
|
+
constructor(directory) {
|
|
22070
|
+
this.directory = directory;
|
|
22071
|
+
}
|
|
22072
|
+
get file() {
|
|
22073
|
+
return join5(this.directory, FILE_NAME);
|
|
22074
|
+
}
|
|
22075
|
+
/** Read the record. A missing, unreadable or malformed file reads as "no record". */
|
|
22076
|
+
async load() {
|
|
22077
|
+
let raw;
|
|
22078
|
+
try {
|
|
22079
|
+
raw = await readFile3(this.file, "utf8");
|
|
22080
|
+
} catch {
|
|
22081
|
+
return void 0;
|
|
22082
|
+
}
|
|
22083
|
+
try {
|
|
22084
|
+
const value = JSON.parse(raw);
|
|
22085
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
22086
|
+
const record7 = value;
|
|
22087
|
+
if (record7.schemaVersion !== 1) return void 0;
|
|
22088
|
+
if (record7.mode !== "local" && record7.mode !== "remote") return void 0;
|
|
22089
|
+
if (record7.mode === "remote" && (typeof record7.hostDeviceId !== "string" || record7.hostDeviceId.length === 0)) {
|
|
22090
|
+
return void 0;
|
|
22091
|
+
}
|
|
22092
|
+
return {
|
|
22093
|
+
schemaVersion: 1,
|
|
22094
|
+
mode: record7.mode,
|
|
22095
|
+
...typeof record7.serverUrl === "string" && record7.serverUrl.length > 0 ? { serverUrl: record7.serverUrl } : {},
|
|
22096
|
+
...typeof record7.hostDeviceId === "string" && record7.hostDeviceId.length > 0 ? { hostDeviceId: record7.hostDeviceId } : {},
|
|
22097
|
+
...typeof record7.hostName === "string" && record7.hostName.length > 0 ? { hostName: record7.hostName } : {},
|
|
22098
|
+
savedAt: typeof record7.savedAt === "number" ? record7.savedAt : 0
|
|
22099
|
+
};
|
|
22100
|
+
} catch {
|
|
22101
|
+
return void 0;
|
|
22102
|
+
}
|
|
22103
|
+
}
|
|
22104
|
+
/** Record the current target. Failures reach the caller so a boot can log them. */
|
|
22105
|
+
async save(target2) {
|
|
22106
|
+
const value = {
|
|
22107
|
+
schemaVersion: 1,
|
|
22108
|
+
mode: target2.mode,
|
|
22109
|
+
...target2.serverUrl === void 0 ? {} : { serverUrl: target2.serverUrl },
|
|
22110
|
+
...target2.hostDeviceId === void 0 ? {} : { hostDeviceId: target2.hostDeviceId },
|
|
22111
|
+
...target2.hostName === void 0 ? {} : { hostName: target2.hostName },
|
|
22112
|
+
savedAt: Date.now()
|
|
22113
|
+
};
|
|
22114
|
+
await mkdir2(this.directory, { recursive: true, mode: 448 });
|
|
22115
|
+
await sweepStaleTemporaries(this.directory);
|
|
22116
|
+
const temporary = this.file + "." + String(process.pid) + "." + randomUUID2() + ".tmp";
|
|
22117
|
+
await writeFile2(temporary, JSON.stringify(value, null, 2) + "\n", { encoding: "utf8", mode: 384, flag: "wx" });
|
|
22118
|
+
await chmod2(temporary, 384);
|
|
22119
|
+
await replaceFile(temporary, this.file);
|
|
22120
|
+
await chmod2(this.file, 384);
|
|
22121
|
+
}
|
|
22122
|
+
};
|
|
22123
|
+
|
|
21609
22124
|
// src/control-runtime.ts
|
|
21610
22125
|
import { hostname as hostname2 } from "node:os";
|
|
21611
22126
|
import { existsSync as existsSync3 } from "node:fs";
|
|
@@ -21613,9 +22128,9 @@ import { execFileSync } from "node:child_process";
|
|
|
21613
22128
|
|
|
21614
22129
|
// src/identity-store.ts
|
|
21615
22130
|
import { createHash } from "node:crypto";
|
|
21616
|
-
import { chmod as
|
|
22131
|
+
import { chmod as chmod3, mkdir as mkdir3, readFile as readFile4, rm as rm3, stat as stat3, writeFile as writeFile3 } from "node:fs/promises";
|
|
21617
22132
|
import { homedir } from "node:os";
|
|
21618
|
-
import { dirname as dirname4, join as
|
|
22133
|
+
import { dirname as dirname4, join as join6 } from "node:path";
|
|
21619
22134
|
var identitySchema = external_exports.object({
|
|
21620
22135
|
schemaVersion: external_exports.literal(1),
|
|
21621
22136
|
deviceId: external_exports.string().uuid(),
|
|
@@ -21644,14 +22159,14 @@ var IdentityStore = class {
|
|
|
21644
22159
|
peers = /* @__PURE__ */ new Map();
|
|
21645
22160
|
constructor(options = {}) {
|
|
21646
22161
|
const env = options.env ?? process.env;
|
|
21647
|
-
const dshHome = env.DSH_HOME ||
|
|
21648
|
-
this.directory = options.directory ??
|
|
22162
|
+
const dshHome = env.DSH_HOME || join6(options.homeDirectory ?? homedir(), ".dsh");
|
|
22163
|
+
this.directory = options.directory ?? join6(dshHome, "remote");
|
|
21649
22164
|
}
|
|
21650
22165
|
async loadOrCreate(deviceName) {
|
|
21651
|
-
await
|
|
21652
|
-
await
|
|
21653
|
-
const devicePath =
|
|
21654
|
-
const keyPath =
|
|
22166
|
+
await mkdir3(this.directory, { recursive: true, mode: 448 });
|
|
22167
|
+
await chmod3(this.directory, 448);
|
|
22168
|
+
const devicePath = join6(this.directory, "device.json");
|
|
22169
|
+
const keyPath = join6(this.directory, "device.key");
|
|
21655
22170
|
const [hasDevice, hasKey] = await Promise.all([exists2(devicePath), exists2(keyPath)]);
|
|
21656
22171
|
if (hasDevice !== hasKey) {
|
|
21657
22172
|
throw new IdentityInvalidError("device identity is incomplete; repair it explicitly before reconnecting");
|
|
@@ -21665,8 +22180,8 @@ var IdentityStore = class {
|
|
|
21665
22180
|
}
|
|
21666
22181
|
await assertPrivateMode2(keyPath);
|
|
21667
22182
|
try {
|
|
21668
|
-
let record7 = identitySchema.parse(JSON.parse(await
|
|
21669
|
-
const privateKey = (await
|
|
22183
|
+
let record7 = identitySchema.parse(JSON.parse(await readFile4(devicePath, "utf8")));
|
|
22184
|
+
const privateKey = (await readFile4(keyPath, "utf8")).trim();
|
|
21670
22185
|
const regenerated = generateKeyPair(fromBase64Url2(privateKey));
|
|
21671
22186
|
if (regenerated.publicKey !== record7.publicKey) {
|
|
21672
22187
|
throw new IdentityInvalidError("device public and private keys do not match");
|
|
@@ -21688,7 +22203,7 @@ var IdentityStore = class {
|
|
|
21688
22203
|
return this.identity;
|
|
21689
22204
|
}
|
|
21690
22205
|
async reset(deviceName) {
|
|
21691
|
-
await
|
|
22206
|
+
await rm3(this.directory, { recursive: true, force: true });
|
|
21692
22207
|
this.identity = void 0;
|
|
21693
22208
|
this.peers.clear();
|
|
21694
22209
|
return this.loadOrCreate(deviceName);
|
|
@@ -21720,11 +22235,11 @@ var IdentityStore = class {
|
|
|
21720
22235
|
return removed;
|
|
21721
22236
|
}
|
|
21722
22237
|
async loadPeers() {
|
|
21723
|
-
const path =
|
|
22238
|
+
const path = join6(this.directory, "trusted-peers.json");
|
|
21724
22239
|
if (!await exists2(path)) {
|
|
21725
22240
|
await atomicJsonWrite(path, { schemaVersion: 1, peers: [] }, 384);
|
|
21726
22241
|
}
|
|
21727
|
-
const parsed = trustedPeersSchema.parse(JSON.parse(await
|
|
22242
|
+
const parsed = trustedPeersSchema.parse(JSON.parse(await readFile4(path, "utf8")));
|
|
21728
22243
|
const peers = /* @__PURE__ */ new Map();
|
|
21729
22244
|
for (const peer of parsed.peers) {
|
|
21730
22245
|
if (peer.fingerprint !== fingerprint(peer.publicKey)) {
|
|
@@ -21736,7 +22251,7 @@ var IdentityStore = class {
|
|
|
21736
22251
|
this.peers = peers;
|
|
21737
22252
|
}
|
|
21738
22253
|
async savePeers() {
|
|
21739
|
-
await atomicJsonWrite(
|
|
22254
|
+
await atomicJsonWrite(join6(this.directory, "trusted-peers.json"), {
|
|
21740
22255
|
schemaVersion: 1,
|
|
21741
22256
|
peers: [...this.peers.values()]
|
|
21742
22257
|
}, 384);
|
|
@@ -21745,7 +22260,7 @@ var IdentityStore = class {
|
|
|
21745
22260
|
function serverStorageDirectory(root, serverUrl, role) {
|
|
21746
22261
|
const origin = new URL(serverUrl).origin;
|
|
21747
22262
|
const scope = createHash("sha256").update(origin).digest("hex").slice(0, 24);
|
|
21748
|
-
return
|
|
22263
|
+
return join6(root, "servers", scope, role);
|
|
21749
22264
|
}
|
|
21750
22265
|
function fingerprint(publicKey) {
|
|
21751
22266
|
const compact = createHash("sha256").update(fromBase64Url2(publicKey)).digest("hex").slice(0, 12).toUpperCase();
|
|
@@ -21753,7 +22268,7 @@ function fingerprint(publicKey) {
|
|
|
21753
22268
|
}
|
|
21754
22269
|
async function assertPrivateMode2(path) {
|
|
21755
22270
|
if (process.platform === "win32") return;
|
|
21756
|
-
const mode = (await
|
|
22271
|
+
const mode = (await stat3(path)).mode & 511;
|
|
21757
22272
|
if ((mode & 63) !== 0) {
|
|
21758
22273
|
throw new IdentityInvalidError(`private key permissions must be 0600, got ${mode.toString(8).padStart(3, "0")}`);
|
|
21759
22274
|
}
|
|
@@ -21763,16 +22278,18 @@ async function atomicJsonWrite(path, value, mode) {
|
|
|
21763
22278
|
`, mode);
|
|
21764
22279
|
}
|
|
21765
22280
|
async function atomicTextWrite(path, value, mode) {
|
|
21766
|
-
|
|
22281
|
+
const directory = dirname4(path);
|
|
22282
|
+
await mkdir3(directory, { recursive: true, mode: 448 });
|
|
22283
|
+
await sweepStaleTemporaries(directory);
|
|
21767
22284
|
const temporary = `${path}.${process.pid}.${uuidV7()}.tmp`;
|
|
21768
|
-
await
|
|
21769
|
-
await
|
|
21770
|
-
await
|
|
21771
|
-
await
|
|
22285
|
+
await writeFile3(temporary, value, { encoding: "utf8", mode, flag: "wx" });
|
|
22286
|
+
await chmod3(temporary, mode);
|
|
22287
|
+
await replaceFile(temporary, path);
|
|
22288
|
+
await chmod3(path, mode);
|
|
21772
22289
|
}
|
|
21773
22290
|
async function exists2(path) {
|
|
21774
22291
|
try {
|
|
21775
|
-
await
|
|
22292
|
+
await stat3(path);
|
|
21776
22293
|
return true;
|
|
21777
22294
|
} catch (error) {
|
|
21778
22295
|
if (isNodeError(error) && error.code === "ENOENT") return false;
|
|
@@ -21930,11 +22447,11 @@ var ControlStatusStream = class {
|
|
|
21930
22447
|
};
|
|
21931
22448
|
|
|
21932
22449
|
// src/codex/domain.ts
|
|
21933
|
-
import { randomUUID as
|
|
22450
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
21934
22451
|
import { accessSync, constants, existsSync as existsSync2, readFileSync, readdirSync as readdirSync2, statSync as statSync2 } from "node:fs";
|
|
21935
|
-
import { readdir, realpath, stat as
|
|
22452
|
+
import { readdir as readdir2, realpath, stat as stat4 } from "node:fs/promises";
|
|
21936
22453
|
import { homedir as homedir2 } from "node:os";
|
|
21937
|
-
import { basename as basename2, isAbsolute as isAbsolute2, join as
|
|
22454
|
+
import { basename as basename2, isAbsolute as isAbsolute2, join as join7, posix, relative, resolve } from "node:path";
|
|
21938
22455
|
|
|
21939
22456
|
// src/codex/app-server.ts
|
|
21940
22457
|
import { spawn as spawn2 } from "node:child_process";
|
|
@@ -22120,7 +22637,7 @@ var CodexAppServerClient = class {
|
|
|
22120
22637
|
this.handleProcessFailure("CODEX_INVALID_RESPONSE", new Error("Codex App Server emitted invalid JSON."));
|
|
22121
22638
|
return;
|
|
22122
22639
|
}
|
|
22123
|
-
if (!
|
|
22640
|
+
if (!isRecord10(value)) {
|
|
22124
22641
|
this.handleProcessFailure("CODEX_INVALID_RESPONSE", new Error("Codex App Server emitted an invalid message."));
|
|
22125
22642
|
return;
|
|
22126
22643
|
}
|
|
@@ -22165,12 +22682,12 @@ var CodexAppServerClient = class {
|
|
|
22165
22682
|
}
|
|
22166
22683
|
};
|
|
22167
22684
|
function safeUpstreamError(value) {
|
|
22168
|
-
if (!
|
|
22685
|
+
if (!isRecord10(value) || typeof value.message !== "string") return "Codex App Server rejected the request.";
|
|
22169
22686
|
const message = value.message.toLowerCase();
|
|
22170
22687
|
if (message.includes("active writer")) return "Codex thread already has an active writer.";
|
|
22171
22688
|
return message.includes("not initialized") ? "Codex App Server is not initialized." : "Codex App Server rejected the request.";
|
|
22172
22689
|
}
|
|
22173
|
-
function
|
|
22690
|
+
function isRecord10(value) {
|
|
22174
22691
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
22175
22692
|
}
|
|
22176
22693
|
|
|
@@ -22430,15 +22947,15 @@ function decodeCanonicalBase64(value) {
|
|
|
22430
22947
|
}
|
|
22431
22948
|
return decoded;
|
|
22432
22949
|
}
|
|
22433
|
-
function
|
|
22950
|
+
function isRecord11(value) {
|
|
22434
22951
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
22435
22952
|
}
|
|
22436
22953
|
function safeErrorCode2(error) {
|
|
22437
|
-
if (
|
|
22954
|
+
if (isRecord11(error) && typeof error.code === "string") return error.code;
|
|
22438
22955
|
return "UNKNOWN";
|
|
22439
22956
|
}
|
|
22440
22957
|
function safeMethod(input2) {
|
|
22441
|
-
return
|
|
22958
|
+
return isRecord11(input2) && typeof input2.method === "string" ? input2.method : "invalid";
|
|
22442
22959
|
}
|
|
22443
22960
|
function concatChunks(chunks, totalBytes) {
|
|
22444
22961
|
const output = new Uint8Array(totalBytes);
|
|
@@ -22453,6 +22970,7 @@ function concatChunks(chunks, totalBytes) {
|
|
|
22453
22970
|
// src/codex/domain.ts
|
|
22454
22971
|
var APPROVAL_TTL_MS = 5 * 6e4;
|
|
22455
22972
|
var DEFAULT_RESTART_DELAYS_MS = [1e3, 2e3, 4e3, 8e3, 15e3];
|
|
22973
|
+
var CODEX_START_BUDGET_MS = 5e3;
|
|
22456
22974
|
var CODEX_PAGE_LIMIT2 = 100;
|
|
22457
22975
|
var MAX_CODEX_PAGES2 = 32;
|
|
22458
22976
|
var CODEX_HISTORY_PAGE_LIMIT = 25;
|
|
@@ -22485,11 +23003,11 @@ var CodexRemoteDomain = class {
|
|
|
22485
23003
|
if (!this.config.enabled) return;
|
|
22486
23004
|
try {
|
|
22487
23005
|
this.state = "starting";
|
|
22488
|
-
await this.
|
|
23006
|
+
await this.launchWithinBudget();
|
|
22489
23007
|
} catch (error) {
|
|
22490
23008
|
this.available = false;
|
|
22491
23009
|
this.state = "unavailable";
|
|
22492
|
-
this.unavailableCode =
|
|
23010
|
+
this.unavailableCode = errorCode3(error);
|
|
22493
23011
|
await this.disposeAppServer(this.appServer);
|
|
22494
23012
|
this.logger.warn("Codex Remote domain unavailable", { code: this.unavailableCode });
|
|
22495
23013
|
}
|
|
@@ -22716,6 +23234,32 @@ var CodexRemoteDomain = class {
|
|
|
22716
23234
|
await this.appServer?.close();
|
|
22717
23235
|
this.appServer = void 0;
|
|
22718
23236
|
}
|
|
23237
|
+
/**
|
|
23238
|
+
* Launch the App Server within {@link CODEX_START_BUDGET_MS}.
|
|
23239
|
+
*
|
|
23240
|
+
* The losing side of the race keeps running until the disposal in start()'s catch stops it, so
|
|
23241
|
+
* it needs its own handler: an unhandled rejection here would surface as a process-level error
|
|
23242
|
+
* for what is only an optional feature being unavailable.
|
|
23243
|
+
* @returns nothing once a candidate is ready.
|
|
23244
|
+
*/
|
|
23245
|
+
async launchWithinBudget() {
|
|
23246
|
+
let timer;
|
|
23247
|
+
const attempt = this.launchAppServer();
|
|
23248
|
+
attempt.catch(() => void 0);
|
|
23249
|
+
try {
|
|
23250
|
+
await Promise.race([
|
|
23251
|
+
attempt,
|
|
23252
|
+
new Promise((_resolve, reject) => {
|
|
23253
|
+
timer = setTimeout(() => {
|
|
23254
|
+
reject(new RpcError("CODEX_START_TIMEOUT", `Codex App Server did not start within ${CODEX_START_BUDGET_MS}ms.`));
|
|
23255
|
+
}, CODEX_START_BUDGET_MS);
|
|
23256
|
+
timer.unref?.();
|
|
23257
|
+
})
|
|
23258
|
+
]);
|
|
23259
|
+
} finally {
|
|
23260
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
23261
|
+
}
|
|
23262
|
+
}
|
|
22719
23263
|
async launchAppServer() {
|
|
22720
23264
|
let lastError;
|
|
22721
23265
|
for (const binary of codexBinaryCandidates(this.config.binary)) {
|
|
@@ -22734,7 +23278,7 @@ var CodexRemoteDomain = class {
|
|
|
22734
23278
|
this.appServer = appServer;
|
|
22735
23279
|
this.unsubscribeInbound = appServer.onInbound((message) => {
|
|
22736
23280
|
void this.handleInbound(message).catch((error) => {
|
|
22737
|
-
this.logger.warn("Codex inbound handling failed", { code:
|
|
23281
|
+
this.logger.warn("Codex inbound handling failed", { code: errorCode3(error) });
|
|
22738
23282
|
});
|
|
22739
23283
|
});
|
|
22740
23284
|
this.unsubscribeUnavailable = appServer.onUnavailable((code) => {
|
|
@@ -22800,7 +23344,7 @@ var CodexRemoteDomain = class {
|
|
|
22800
23344
|
} catch (error) {
|
|
22801
23345
|
this.available = false;
|
|
22802
23346
|
this.state = "restarting";
|
|
22803
|
-
this.unavailableCode =
|
|
23347
|
+
this.unavailableCode = errorCode3(error);
|
|
22804
23348
|
this.logger.warn("Codex App Server restart failed", {
|
|
22805
23349
|
attempt: this.restartAttempt,
|
|
22806
23350
|
code: this.unavailableCode
|
|
@@ -22868,14 +23412,14 @@ var CodexRemoteDomain = class {
|
|
|
22868
23412
|
await this.assertResultThreadAllowed(result);
|
|
22869
23413
|
if (codexPermissionPresetFromResponse(result) !== params.permissionPreset) {
|
|
22870
23414
|
await this.callUpstream("thread/settings/update", { threadId, ...settings });
|
|
22871
|
-
result = { ...
|
|
23415
|
+
result = { ...isRecord12(result) ? result : {}, ...settings, sandbox: settings.sandboxPolicy };
|
|
22872
23416
|
}
|
|
22873
23417
|
}
|
|
22874
23418
|
await this.publishPermission(threadId, result);
|
|
22875
23419
|
return result;
|
|
22876
23420
|
}
|
|
22877
23421
|
async publishPermission(threadId, value) {
|
|
22878
|
-
const source =
|
|
23422
|
+
const source = isRecord12(value) ? value : {};
|
|
22879
23423
|
const threadSettings = { approvalPolicy: source.approvalPolicy, sandboxPolicy: source.sandboxPolicy ?? source.sandbox };
|
|
22880
23424
|
await this.handleInbound({ kind: "notification", method: "thread/settings/updated", params: { threadId, threadSettings } });
|
|
22881
23425
|
}
|
|
@@ -22893,7 +23437,7 @@ var CodexRemoteDomain = class {
|
|
|
22893
23437
|
await appServer.respond(message.id, { decision: "decline" });
|
|
22894
23438
|
return;
|
|
22895
23439
|
}
|
|
22896
|
-
const requestHandle =
|
|
23440
|
+
const requestHandle = randomUUID3();
|
|
22897
23441
|
this.approvals.set(requestHandle, {
|
|
22898
23442
|
upstreamId: message.id,
|
|
22899
23443
|
connectionId: owner.connectionId,
|
|
@@ -22971,9 +23515,9 @@ var CodexRemoteDomain = class {
|
|
|
22971
23515
|
itemsView,
|
|
22972
23516
|
...cursor2 === void 0 ? {} : { cursor: cursor2 }
|
|
22973
23517
|
});
|
|
22974
|
-
const pageResult =
|
|
23518
|
+
const pageResult = isRecord12(result) ? result : {};
|
|
22975
23519
|
for (const rawTurn of array2(pageResult.data)) {
|
|
22976
|
-
if (!
|
|
23520
|
+
if (!isRecord12(rawTurn)) continue;
|
|
22977
23521
|
const turnId = typeof rawTurn.id === "string" ? rawTurn.id : void 0;
|
|
22978
23522
|
const items = rawTurn.itemsView === "full" || turnId === void 0 ? array2(rawTurn.items) : await this.readThreadItems(connectionId, threadId, turnId, array2(rawTurn.items));
|
|
22979
23523
|
turns.push({ ...rawTurn, items });
|
|
@@ -22995,9 +23539,9 @@ var CodexRemoteDomain = class {
|
|
|
22995
23539
|
sortDirection: "asc",
|
|
22996
23540
|
...cursor2 === void 0 ? {} : { cursor: cursor2 }
|
|
22997
23541
|
});
|
|
22998
|
-
const pageResult =
|
|
23542
|
+
const pageResult = isRecord12(result) ? result : {};
|
|
22999
23543
|
for (const entry of array2(pageResult.data)) {
|
|
23000
|
-
if (
|
|
23544
|
+
if (isRecord12(entry) && entry.item !== void 0) items.push(entry.item);
|
|
23001
23545
|
}
|
|
23002
23546
|
cursor2 = typeof pageResult.nextCursor === "string" && pageResult.nextCursor.length > 0 ? pageResult.nextCursor : void 0;
|
|
23003
23547
|
if (cursor2 === void 0) break;
|
|
@@ -23013,7 +23557,7 @@ var CodexRemoteDomain = class {
|
|
|
23013
23557
|
this.logger.warn("Codex history read fallback", {
|
|
23014
23558
|
connectionId: maskId(connectionId),
|
|
23015
23559
|
stage,
|
|
23016
|
-
code:
|
|
23560
|
+
code: errorCode3(error)
|
|
23017
23561
|
});
|
|
23018
23562
|
}
|
|
23019
23563
|
async assertResultThreadAllowed(result) {
|
|
@@ -23061,7 +23605,7 @@ var CodexRemoteDomain = class {
|
|
|
23061
23605
|
return [...this.peers.values()].some((peer) => peer.hasThreadSubscription(threadId));
|
|
23062
23606
|
}
|
|
23063
23607
|
resolveUpstreamApproval(params) {
|
|
23064
|
-
if (!
|
|
23608
|
+
if (!isRecord12(params) || typeof params.requestId !== "string" && typeof params.requestId !== "number") return;
|
|
23065
23609
|
for (const [handle, approval] of this.approvals) {
|
|
23066
23610
|
if (approval.upstreamId === params.requestId) this.approvals.delete(handle);
|
|
23067
23611
|
}
|
|
@@ -23084,7 +23628,7 @@ var CodexRemoteDomain = class {
|
|
|
23084
23628
|
this.approvalExpiryTimer = setTimeout(() => {
|
|
23085
23629
|
this.approvalExpiryTimer = void 0;
|
|
23086
23630
|
void this.expireApprovals().catch((error) => {
|
|
23087
|
-
this.logger.warn("Codex approval expiry failed", { code:
|
|
23631
|
+
this.logger.warn("Codex approval expiry failed", { code: errorCode3(error) });
|
|
23088
23632
|
});
|
|
23089
23633
|
}, Math.max(0, nextExpiry - Date.now()));
|
|
23090
23634
|
this.approvalExpiryTimer.unref?.();
|
|
@@ -23174,7 +23718,7 @@ var CodexRemoteDomain = class {
|
|
|
23174
23718
|
let candidate;
|
|
23175
23719
|
try {
|
|
23176
23720
|
candidate = await realpath(path);
|
|
23177
|
-
if (!(await
|
|
23721
|
+
if (!(await stat4(candidate)).isDirectory()) throw new Error("not a directory");
|
|
23178
23722
|
} catch {
|
|
23179
23723
|
throw new RpcError("CODEX_PATH_NOT_ALLOWED", "The CodeX working directory is not available as a Workspace.");
|
|
23180
23724
|
}
|
|
@@ -23194,7 +23738,7 @@ var CodexRemoteDomain = class {
|
|
|
23194
23738
|
}
|
|
23195
23739
|
try {
|
|
23196
23740
|
const canonical = await realpath(resolve(path));
|
|
23197
|
-
if (!(await
|
|
23741
|
+
if (!(await stat4(canonical)).isDirectory()) throw new Error("not a directory");
|
|
23198
23742
|
return canonical;
|
|
23199
23743
|
} catch {
|
|
23200
23744
|
throw new RpcError("CODEX_PATH_NOT_ALLOWED", "The CodeX project directory must be an existing absolute directory.");
|
|
@@ -23202,7 +23746,7 @@ var CodexRemoteDomain = class {
|
|
|
23202
23746
|
}
|
|
23203
23747
|
async listCodexDirectory(path) {
|
|
23204
23748
|
const target2 = await this.resolveCodexDirectory(path);
|
|
23205
|
-
const rows = await
|
|
23749
|
+
const rows = await readdir2(target2.path, { withFileTypes: true }).catch(() => void 0);
|
|
23206
23750
|
if (rows === void 0) {
|
|
23207
23751
|
throw new RpcError("CODEX_PATH_NOT_ALLOWED", "The CodeX working directory is not available as a Workspace.");
|
|
23208
23752
|
}
|
|
@@ -23211,12 +23755,12 @@ var CodexRemoteDomain = class {
|
|
|
23211
23755
|
const child = resolve(target2.path, row.name);
|
|
23212
23756
|
let directory = row.isDirectory();
|
|
23213
23757
|
if (!directory && row.isSymbolicLink()) {
|
|
23214
|
-
directory = await
|
|
23758
|
+
directory = await stat4(child).then((value) => value.isDirectory()).catch(() => false);
|
|
23215
23759
|
}
|
|
23216
23760
|
if (!directory) continue;
|
|
23217
23761
|
try {
|
|
23218
23762
|
const canonicalChild = await realpath(child);
|
|
23219
|
-
if (!(await
|
|
23763
|
+
if (!(await stat4(canonicalChild)).isDirectory()) continue;
|
|
23220
23764
|
if (!containsCodexPath(target2.canonicalRoot, canonicalChild)) continue;
|
|
23221
23765
|
} catch {
|
|
23222
23766
|
continue;
|
|
@@ -23245,7 +23789,7 @@ var CodexRemoteDomain = class {
|
|
|
23245
23789
|
let canonicalCandidate;
|
|
23246
23790
|
try {
|
|
23247
23791
|
canonicalCandidate = await realpath(path);
|
|
23248
|
-
if (!(await
|
|
23792
|
+
if (!(await stat4(canonicalCandidate)).isDirectory()) throw new Error("not a directory");
|
|
23249
23793
|
} catch {
|
|
23250
23794
|
throw new RpcError("CODEX_PATH_NOT_ALLOWED", "The CodeX working directory is not available as a Workspace.");
|
|
23251
23795
|
}
|
|
@@ -23275,15 +23819,15 @@ function codexBinaryCandidates(configured, hostPlatform = process.platform, user
|
|
|
23275
23819
|
if (hostPlatform !== "darwin") return [configured];
|
|
23276
23820
|
const bundledCandidates = [
|
|
23277
23821
|
"/Applications/ChatGPT.app",
|
|
23278
|
-
|
|
23822
|
+
posix.join(userHome, "Applications", "ChatGPT.app")
|
|
23279
23823
|
].flatMap((chatGptApp) => {
|
|
23280
|
-
const codexCli =
|
|
23824
|
+
const codexCli = posix.join(chatGptApp, "Contents", "Resources", "codex-cli");
|
|
23281
23825
|
try {
|
|
23282
|
-
const manifest = JSON.parse(readFileSync(
|
|
23283
|
-
if (!
|
|
23826
|
+
const manifest = JSON.parse(readFileSync(posix.join(codexCli, "codex-package.json"), "utf8"));
|
|
23827
|
+
if (!isRecord12(manifest) || typeof manifest.entrypoint !== "string" || manifest.entrypoint.length === 0) {
|
|
23284
23828
|
return [];
|
|
23285
23829
|
}
|
|
23286
|
-
const candidate =
|
|
23830
|
+
const candidate = posix.join(codexCli, manifest.entrypoint);
|
|
23287
23831
|
if (!existsSync2(candidate)) return [];
|
|
23288
23832
|
accessSync(candidate, constants.X_OK);
|
|
23289
23833
|
return [candidate];
|
|
@@ -23294,14 +23838,14 @@ function codexBinaryCandidates(configured, hostPlatform = process.platform, user
|
|
|
23294
23838
|
return [.../* @__PURE__ */ new Set([
|
|
23295
23839
|
...bundledCandidates,
|
|
23296
23840
|
"/Applications/ChatGPT.app/Contents/Resources/codex",
|
|
23297
|
-
|
|
23841
|
+
posix.join(userHome, "Applications", "ChatGPT.app", "Contents", "Resources", "codex"),
|
|
23298
23842
|
configured
|
|
23299
23843
|
])];
|
|
23300
23844
|
}
|
|
23301
23845
|
function bundledWindowsCodex(userHome) {
|
|
23302
|
-
const bin =
|
|
23846
|
+
const bin = join7(userHome, "AppData", "Local", "OpenAI", "Codex", "bin");
|
|
23303
23847
|
try {
|
|
23304
|
-
const newest = readdirSync2(bin, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) =>
|
|
23848
|
+
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);
|
|
23305
23849
|
const candidate = newest[0]?.candidate;
|
|
23306
23850
|
return candidate === void 0 ? [] : [candidate];
|
|
23307
23851
|
} catch {
|
|
@@ -23309,24 +23853,24 @@ function bundledWindowsCodex(userHome) {
|
|
|
23309
23853
|
}
|
|
23310
23854
|
}
|
|
23311
23855
|
function parseCallEnvelope(input2) {
|
|
23312
|
-
if (!
|
|
23856
|
+
if (!isRecord12(input2) || typeof input2.method !== "string" || !("params" in input2) || Object.keys(input2).some((key) => key !== "method" && key !== "params")) {
|
|
23313
23857
|
throw new RpcError("INVALID_MESSAGE", "The Codex call envelope is invalid.");
|
|
23314
23858
|
}
|
|
23315
23859
|
return { method: input2.method, params: input2.params };
|
|
23316
23860
|
}
|
|
23317
23861
|
function parseRespond(input2) {
|
|
23318
|
-
if (!
|
|
23862
|
+
if (!isRecord12(input2) || typeof input2.requestHandle !== "string" || !["accept", "decline", "cancel"].includes(String(input2.decision)) || Object.keys(input2).some((key) => key !== "requestHandle" && key !== "decision")) {
|
|
23319
23863
|
throw new RpcError("INVALID_MESSAGE", "The Codex approval response is invalid.");
|
|
23320
23864
|
}
|
|
23321
23865
|
return input2;
|
|
23322
23866
|
}
|
|
23323
23867
|
function accountCanRun(result) {
|
|
23324
|
-
if (!
|
|
23325
|
-
return result.requiresOpenaiAuth === false ||
|
|
23868
|
+
if (!isRecord12(result) || typeof result.requiresOpenaiAuth !== "boolean") return false;
|
|
23869
|
+
return result.requiresOpenaiAuth === false || isRecord12(result.account);
|
|
23326
23870
|
}
|
|
23327
23871
|
function sanitizeAccount(result) {
|
|
23328
|
-
if (!
|
|
23329
|
-
const account =
|
|
23872
|
+
if (!isRecord12(result)) throw new RpcError("CODEX_INVALID_RESPONSE", "Codex App Server returned invalid account state.");
|
|
23873
|
+
const account = isRecord12(result.account) ? result.account : void 0;
|
|
23330
23874
|
return {
|
|
23331
23875
|
authenticated: account !== void 0 || result.requiresOpenaiAuth === false,
|
|
23332
23876
|
requiresOpenaiAuth: result.requiresOpenaiAuth === true,
|
|
@@ -23339,11 +23883,11 @@ function sanitizeAccount(result) {
|
|
|
23339
23883
|
};
|
|
23340
23884
|
}
|
|
23341
23885
|
function sanitizeThreadList(result) {
|
|
23342
|
-
if (!
|
|
23886
|
+
if (!isRecord12(result) || !Array.isArray(result.data)) {
|
|
23343
23887
|
throw new RpcError("CODEX_INVALID_RESPONSE", "Codex App Server returned an invalid thread list.");
|
|
23344
23888
|
}
|
|
23345
23889
|
const data2 = result.data.flatMap((value) => {
|
|
23346
|
-
if (!
|
|
23890
|
+
if (!isRecord12(value) || typeof value.id !== "string") return [];
|
|
23347
23891
|
return [{
|
|
23348
23892
|
id: value.id,
|
|
23349
23893
|
...typeof value.sessionId === "string" ? { sessionId: value.sessionId } : {},
|
|
@@ -23355,7 +23899,7 @@ function sanitizeThreadList(result) {
|
|
|
23355
23899
|
...typeof value.updatedAt === "number" && Number.isFinite(value.updatedAt) ? { updatedAt: value.updatedAt } : {},
|
|
23356
23900
|
...typeof value.archived === "boolean" ? { archived: value.archived } : {},
|
|
23357
23901
|
...typeof value.isPinned === "boolean" ? { isPinned: value.isPinned } : {},
|
|
23358
|
-
...
|
|
23902
|
+
...isRecord12(value.status) ? { status: value.status } : {}
|
|
23359
23903
|
}];
|
|
23360
23904
|
});
|
|
23361
23905
|
return {
|
|
@@ -23365,18 +23909,18 @@ function sanitizeThreadList(result) {
|
|
|
23365
23909
|
};
|
|
23366
23910
|
}
|
|
23367
23911
|
function filterThreadListByWorkspaceAuthority(result, authority) {
|
|
23368
|
-
if (!
|
|
23912
|
+
if (!isRecord12(result) || !Array.isArray(result.data)) {
|
|
23369
23913
|
throw new RpcError("CODEX_INVALID_RESPONSE", "Codex App Server returned an invalid thread list.");
|
|
23370
23914
|
}
|
|
23371
23915
|
return {
|
|
23372
23916
|
...result,
|
|
23373
|
-
data: result.data.map((record7) =>
|
|
23917
|
+
data: result.data.map((record7) => isRecord12(record7) ? record7 : void 0).filter((thread) => thread !== void 0 && isThreadAllowedByWorkspaceAuthority(thread, authority))
|
|
23374
23918
|
};
|
|
23375
23919
|
}
|
|
23376
23920
|
function sanitizeProject(value) {
|
|
23377
|
-
if (!
|
|
23921
|
+
if (!isRecord12(value) || typeof value.id !== "string" || typeof value.name !== "string") return void 0;
|
|
23378
23922
|
const roots = Array.isArray(value.roots) ? value.roots.flatMap((root) => {
|
|
23379
|
-
const path =
|
|
23923
|
+
const path = isRecord12(root) && typeof root.path === "string" && root.path.length > 0 ? root.path : void 0;
|
|
23380
23924
|
return path === void 0 || !isAbsolute2(path) ? [] : [{ path }];
|
|
23381
23925
|
}) : [];
|
|
23382
23926
|
if (roots.length === 0) return void 0;
|
|
@@ -23390,7 +23934,7 @@ function sanitizeProject(value) {
|
|
|
23390
23934
|
};
|
|
23391
23935
|
}
|
|
23392
23936
|
function sanitizeProjectList(result) {
|
|
23393
|
-
if (!
|
|
23937
|
+
if (!isRecord12(result) || !Array.isArray(result.data)) {
|
|
23394
23938
|
throw new RpcError("CODEX_INVALID_RESPONSE", "Codex App Server returned an invalid project list.");
|
|
23395
23939
|
}
|
|
23396
23940
|
const data2 = result.data.flatMap((value) => sanitizeProject(value) ?? []);
|
|
@@ -23400,7 +23944,7 @@ function sanitizeProjectList(result) {
|
|
|
23400
23944
|
};
|
|
23401
23945
|
}
|
|
23402
23946
|
function sanitizeProjectCreate(result) {
|
|
23403
|
-
const project =
|
|
23947
|
+
const project = isRecord12(result) ? sanitizeProject(result.project) : void 0;
|
|
23404
23948
|
if (project === void 0) {
|
|
23405
23949
|
throw new RpcError("CODEX_INVALID_RESPONSE", "Codex App Server returned an invalid created project.");
|
|
23406
23950
|
}
|
|
@@ -23433,19 +23977,19 @@ function codexDirectoryCrumbs(root, path) {
|
|
|
23433
23977
|
return crumbs2;
|
|
23434
23978
|
}
|
|
23435
23979
|
function extractThread(result) {
|
|
23436
|
-
return
|
|
23980
|
+
return isRecord12(result) && isRecord12(result.thread) ? result.thread : void 0;
|
|
23437
23981
|
}
|
|
23438
23982
|
function extractTurnId(result) {
|
|
23439
|
-
if (!
|
|
23983
|
+
if (!isRecord12(result)) return void 0;
|
|
23440
23984
|
if (typeof result.turnId === "string" && result.turnId.length > 0) return result.turnId;
|
|
23441
|
-
if (
|
|
23985
|
+
if (isRecord12(result.turn) && typeof result.turn.id === "string" && result.turn.id.length > 0) return result.turn.id;
|
|
23442
23986
|
return void 0;
|
|
23443
23987
|
}
|
|
23444
23988
|
function extractThreadId(params) {
|
|
23445
|
-
if (!
|
|
23989
|
+
if (!isRecord12(params)) return void 0;
|
|
23446
23990
|
if (typeof params.threadId === "string") return params.threadId;
|
|
23447
|
-
if (
|
|
23448
|
-
if (
|
|
23991
|
+
if (isRecord12(params.thread) && typeof params.thread.id === "string") return params.thread.id;
|
|
23992
|
+
if (isRecord12(params.turn) && typeof params.turn.threadId === "string") return params.turn.threadId;
|
|
23449
23993
|
return void 0;
|
|
23450
23994
|
}
|
|
23451
23995
|
function optionalInteger2(value) {
|
|
@@ -23487,13 +24031,13 @@ function mapCodexImageInputs(params) {
|
|
|
23487
24031
|
return {
|
|
23488
24032
|
...params,
|
|
23489
24033
|
input: params.input.map((value) => {
|
|
23490
|
-
if (!
|
|
24034
|
+
if (!isRecord12(value) || value.type !== "image" || typeof value.mediaType !== "string" || typeof value.data !== "string") return value;
|
|
23491
24035
|
return { type: "image", url: `data:${value.mediaType};base64,${value.data}` };
|
|
23492
24036
|
})
|
|
23493
24037
|
};
|
|
23494
24038
|
}
|
|
23495
24039
|
function sanitizeApprovalParams(params, requestHandle) {
|
|
23496
|
-
if (!
|
|
24040
|
+
if (!isRecord12(params)) return { requestHandle };
|
|
23497
24041
|
const safe = { ...params };
|
|
23498
24042
|
delete safe.proposedExecpolicyAmendment;
|
|
23499
24043
|
delete safe.additionalPermissions;
|
|
@@ -23511,7 +24055,7 @@ function mapAppServerError(error) {
|
|
|
23511
24055
|
}
|
|
23512
24056
|
return new RpcError("CODEX_UPSTREAM_ERROR", "Codex App Server could not complete the request.");
|
|
23513
24057
|
}
|
|
23514
|
-
function
|
|
24058
|
+
function errorCode3(error) {
|
|
23515
24059
|
if (error instanceof RpcError || error instanceof CodexAppServerError) return error.code;
|
|
23516
24060
|
return "CODEX_START_FAILED";
|
|
23517
24061
|
}
|
|
@@ -23524,7 +24068,7 @@ function isProjectListFallbackError(error) {
|
|
|
23524
24068
|
function canTryNextBinary(error) {
|
|
23525
24069
|
return !(error instanceof RpcError) || !["CODEX_AUTH_REQUIRED", "CODEX_CLOSED"].includes(error.code);
|
|
23526
24070
|
}
|
|
23527
|
-
function
|
|
24071
|
+
function isRecord12(value) {
|
|
23528
24072
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
23529
24073
|
}
|
|
23530
24074
|
function array2(value) {
|
|
@@ -23960,11 +24504,11 @@ function editableConfig(config) {
|
|
|
23960
24504
|
...config.acp === void 0 ? {} : { acp: { enabled: config.acp.enabled, backends: config.acp.backends } }
|
|
23961
24505
|
};
|
|
23962
24506
|
}
|
|
23963
|
-
function
|
|
24507
|
+
function isRecord13(value) {
|
|
23964
24508
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
23965
24509
|
}
|
|
23966
24510
|
function record5(value) {
|
|
23967
|
-
if (!
|
|
24511
|
+
if (!isRecord13(value)) throw new ClientModeError("INVALID_MESSAGE", "The control request payload is invalid.");
|
|
23968
24512
|
return value;
|
|
23969
24513
|
}
|
|
23970
24514
|
function ok3(value) {
|
|
@@ -24089,7 +24633,7 @@ var TerminalPolicy = class {
|
|
|
24089
24633
|
};
|
|
24090
24634
|
|
|
24091
24635
|
// src/service.ts
|
|
24092
|
-
import { randomUUID as
|
|
24636
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
24093
24637
|
|
|
24094
24638
|
// src/connection-controller.ts
|
|
24095
24639
|
var ConnectionController = class {
|
|
@@ -24714,7 +25258,7 @@ var HostServerConnection = class {
|
|
|
24714
25258
|
await this.connectOnce();
|
|
24715
25259
|
delayMs = this.config.reconnect.initialDelayMs;
|
|
24716
25260
|
} catch (error) {
|
|
24717
|
-
const code =
|
|
25261
|
+
const code = errorCode4(error);
|
|
24718
25262
|
if (code === "CREDENTIALS_REFRESHED") continue;
|
|
24719
25263
|
this.terminalError = code;
|
|
24720
25264
|
this.logger.warn("server control connection failed", {
|
|
@@ -24729,7 +25273,7 @@ var HostServerConnection = class {
|
|
|
24729
25273
|
try {
|
|
24730
25274
|
await this.api.clearAuthorization();
|
|
24731
25275
|
} catch (clearError) {
|
|
24732
|
-
this.logger.error("failed to clear revoked Host authorization", { code:
|
|
25276
|
+
this.logger.error("failed to clear revoked Host authorization", { code: errorCode4(clearError) });
|
|
24733
25277
|
}
|
|
24734
25278
|
}
|
|
24735
25279
|
if (TERMINAL_AUTH_ERRORS.has(code) || !this.config.reconnect.enabled) return;
|
|
@@ -24809,7 +25353,7 @@ var HostServerConnection = class {
|
|
|
24809
25353
|
if (!acknowledged) throw new ControlConnectionError("INVALID_MESSAGE", "Server sent a frame before hello.ack.");
|
|
24810
25354
|
await this.handleFrame(frame);
|
|
24811
25355
|
}).catch((error) => {
|
|
24812
|
-
const code =
|
|
25356
|
+
const code = errorCode4(error);
|
|
24813
25357
|
this.terminalError = code;
|
|
24814
25358
|
this.logger.error("server control frame failed", {
|
|
24815
25359
|
code,
|
|
@@ -24921,7 +25465,7 @@ var HostServerConnection = class {
|
|
|
24921
25465
|
this.sendControl("connect.rejected", { connectionId: payload.connectionId });
|
|
24922
25466
|
this.logger.warn("connection rejected by account authorization", {
|
|
24923
25467
|
clientDeviceId: shortId3(payload.clientDeviceId),
|
|
24924
|
-
code:
|
|
25468
|
+
code: errorCode4(error)
|
|
24925
25469
|
});
|
|
24926
25470
|
return;
|
|
24927
25471
|
}
|
|
@@ -25070,7 +25614,7 @@ var HostServerConnection = class {
|
|
|
25070
25614
|
} catch (error) {
|
|
25071
25615
|
this.logger.warn("TURN credentials unavailable; trying direct candidates", {
|
|
25072
25616
|
connectionId: shortId3(tunnel.connectionId),
|
|
25073
|
-
code:
|
|
25617
|
+
code: errorCode4(error)
|
|
25074
25618
|
});
|
|
25075
25619
|
}
|
|
25076
25620
|
if (!tunnel.preferredTransports.includes("turn")) iceServers = stunOnlyIceServers(iceServers);
|
|
@@ -25507,11 +26051,11 @@ function rtcDiagnostics(rtc) {
|
|
|
25507
26051
|
return void 0;
|
|
25508
26052
|
}
|
|
25509
26053
|
}
|
|
25510
|
-
function
|
|
26054
|
+
function errorCode4(error) {
|
|
25511
26055
|
return error instanceof ServerApiError || error instanceof ControlConnectionError ? error.code : "CONNECTION_FAILED";
|
|
25512
26056
|
}
|
|
25513
26057
|
function isRetryable(error) {
|
|
25514
|
-
return !TERMINAL_AUTH_ERRORS.has(
|
|
26058
|
+
return !TERMINAL_AUTH_ERRORS.has(errorCode4(error)) && (!(error instanceof ServerApiError) || error.retryable);
|
|
25515
26059
|
}
|
|
25516
26060
|
function closeCode(code) {
|
|
25517
26061
|
if (code === 4002) return "AUTH_INVALID";
|
|
@@ -25523,7 +26067,7 @@ function closeCode(code) {
|
|
|
25523
26067
|
}
|
|
25524
26068
|
|
|
25525
26069
|
// src/remote-directory-browser.ts
|
|
25526
|
-
import { readdir as
|
|
26070
|
+
import { readdir as readdir3, stat as stat5 } from "node:fs/promises";
|
|
25527
26071
|
import { homedir as homedir3, platform as platform2 } from "node:os";
|
|
25528
26072
|
import { basename as basename3, dirname as dirname5, isAbsolute as isAbsolute3, parse, resolve as resolve2 } from "node:path";
|
|
25529
26073
|
var MAX_ENTRIES = 500;
|
|
@@ -25532,13 +26076,13 @@ async function listRemoteDirectory(path, signal) {
|
|
|
25532
26076
|
const home = resolve2(homedir3());
|
|
25533
26077
|
const target2 = path === void 0 || path.trim() === "" ? home : resolve2(path);
|
|
25534
26078
|
if (!isAbsolute3(target2)) throw new Error("The remote directory path must be absolute.");
|
|
25535
|
-
const rows = await
|
|
26079
|
+
const rows = await readdir3(target2, { withFileTypes: true });
|
|
25536
26080
|
const directories = [];
|
|
25537
26081
|
for (const row of rows) {
|
|
25538
26082
|
signal?.throwIfAborted();
|
|
25539
26083
|
const child = resolve2(target2, row.name);
|
|
25540
26084
|
let directory = row.isDirectory();
|
|
25541
|
-
if (!directory && row.isSymbolicLink()) directory = await
|
|
26085
|
+
if (!directory && row.isSymbolicLink()) directory = await stat5(child).then((value) => value.isDirectory()).catch(() => false);
|
|
25542
26086
|
if (!directory) continue;
|
|
25543
26087
|
directories.push({ name: row.name, path: child, hidden: platform2() !== "win32" && row.name.startsWith(".") });
|
|
25544
26088
|
}
|
|
@@ -26765,11 +27309,11 @@ var HarnessRemoteBridge = class {
|
|
|
26765
27309
|
};
|
|
26766
27310
|
function normalizeWorkspaceChangesPayload(endpoint, payload, harnessVersion) {
|
|
26767
27311
|
if (endpoint !== "workspaceFiles/changes" || !requiresWorkspaceChangePath(harnessVersion)) return payload;
|
|
26768
|
-
if (!
|
|
27312
|
+
if (!isRecord14(payload) || !isRecord14(payload.args) || Object.hasOwn(payload.args, "path")) return payload;
|
|
26769
27313
|
return { ...payload, args: { ...payload.args, path: "." } };
|
|
26770
27314
|
}
|
|
26771
27315
|
function normalizeWorkspaceRequestPayload(endpoint, payload, harnessVersion) {
|
|
26772
|
-
if (!
|
|
27316
|
+
if (!isRecord14(payload) || !isRecord14(payload.args)) return payload;
|
|
26773
27317
|
const args = payload.args;
|
|
26774
27318
|
if (endpoint === "workspaceFiles/readBytes" && requiresWorkspaceChangePath(harnessVersion)) {
|
|
26775
27319
|
if (!Object.hasOwn(args, "range") || Object.hasOwn(args, "options")) return payload;
|
|
@@ -26837,13 +27381,13 @@ function needsDirectoryFallback(result) {
|
|
|
26837
27381
|
}
|
|
26838
27382
|
function requestArgs(payload) {
|
|
26839
27383
|
const root = record6(payload);
|
|
26840
|
-
const args =
|
|
27384
|
+
const args = isRecord14(root.args) ? root.args : root;
|
|
26841
27385
|
return record6(args.request ?? args._request ?? args);
|
|
26842
27386
|
}
|
|
26843
27387
|
function record6(value) {
|
|
26844
|
-
return
|
|
27388
|
+
return isRecord14(value) ? value : {};
|
|
26845
27389
|
}
|
|
26846
|
-
function
|
|
27390
|
+
function isRecord14(value) {
|
|
26847
27391
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
26848
27392
|
}
|
|
26849
27393
|
|
|
@@ -26963,8 +27507,8 @@ import { execFileSync as execFileSync2 } from "node:child_process";
|
|
|
26963
27507
|
|
|
26964
27508
|
// src/codex-workspace-bridge.ts
|
|
26965
27509
|
import { spawn as spawn4 } from "node:child_process";
|
|
26966
|
-
import { realpath as realpath2, lstat, readdir as
|
|
26967
|
-
import { isAbsolute as isAbsolute4, join as
|
|
27510
|
+
import { realpath as realpath2, lstat, readdir as readdir4, readFile as readFile5, stat as stat6, watch } from "node:fs/promises";
|
|
27511
|
+
import { isAbsolute as isAbsolute4, join as join8, relative as relative2, resolve as resolve3 } from "node:path";
|
|
26968
27512
|
var MAX_READ_BYTES = 4 * 1024 * 1024;
|
|
26969
27513
|
var MAX_INPUT_BYTES = 64 * 1024;
|
|
26970
27514
|
var MAX_COLS = 240;
|
|
@@ -27051,17 +27595,17 @@ var CodexWorkspaceBridge = class {
|
|
|
27051
27595
|
const target2 = await this.safePath(root, path, endpoint === "workspaceFiles/list");
|
|
27052
27596
|
try {
|
|
27053
27597
|
if (endpoint === "workspaceFiles/list") {
|
|
27054
|
-
const entries = await
|
|
27598
|
+
const entries = await readdir4(target2, { withFileTypes: true });
|
|
27055
27599
|
const result = [];
|
|
27056
27600
|
for (const entry of entries.slice(0, 500)) {
|
|
27057
|
-
const item =
|
|
27601
|
+
const item = join8(target2, entry.name);
|
|
27058
27602
|
const info2 = await lstat(item);
|
|
27059
27603
|
if (info2.isSymbolicLink()) continue;
|
|
27060
27604
|
result.push({ name: entry.name, type: info2.isDirectory() ? "directory" : info2.isFile() ? "file" : "other", ...info2.isFile() ? { size: info2.size } : {} });
|
|
27061
27605
|
}
|
|
27062
27606
|
return { ok: true, value: { path, entries: result, truncated: entries.length > 500 } };
|
|
27063
27607
|
}
|
|
27064
|
-
const info = await
|
|
27608
|
+
const info = await stat6(target2);
|
|
27065
27609
|
const absolutePath = target2;
|
|
27066
27610
|
const version = `${info.mtimeMs}:${info.size}`;
|
|
27067
27611
|
if (endpoint === "workspaceFiles/stat") {
|
|
@@ -27070,7 +27614,7 @@ var CodexWorkspaceBridge = class {
|
|
|
27070
27614
|
if (!info.isFile()) throw new RpcError("CODEX_WORKSPACE_INVALID_PATH", "The requested workspace path is not a file.");
|
|
27071
27615
|
const offset = readOffset(args);
|
|
27072
27616
|
const limit = readLimit(args);
|
|
27073
|
-
const bytes = await
|
|
27617
|
+
const bytes = await readFile5(target2);
|
|
27074
27618
|
if (bytes.byteLength > MAX_READ_BYTES) throw new RpcError("CODEX_WORKSPACE_TOO_LARGE", "The requested workspace file is too large.");
|
|
27075
27619
|
if (endpoint === "workspaceFiles/readBytes") {
|
|
27076
27620
|
const slice2 = bytes.subarray(offset, Math.min(offset + limit, bytes.length));
|
|
@@ -27194,7 +27738,7 @@ var CodexWorkspaceBridge = class {
|
|
|
27194
27738
|
throw new RpcError("METHOD_NOT_FOUND", "The requested terminal method does not exist.");
|
|
27195
27739
|
}
|
|
27196
27740
|
async createTerminal(sessionId, cwd, args) {
|
|
27197
|
-
const request =
|
|
27741
|
+
const request = isRecord15(args.request) ? args.request : {};
|
|
27198
27742
|
const id4 = stringId(request.id);
|
|
27199
27743
|
if ([...this.terminals.values()].some((item) => item.sessionId === sessionId && item.id === id4)) throw new RpcError("REQUEST_CONFLICT", "The terminal id is already active.");
|
|
27200
27744
|
if (this.terminals.size >= MAX_TERMINALS) throw new RpcError("RATE_LIMITED", "Too many remote terminals are active.", void 0, true);
|
|
@@ -27268,7 +27812,7 @@ var CodexWorkspaceBridge = class {
|
|
|
27268
27812
|
if (!cwd) throw new RpcError("CODEX_WORKSPACE_UNAVAILABLE", "The CodeX thread has no available workspace.");
|
|
27269
27813
|
try {
|
|
27270
27814
|
const root = await realpath2(cwd);
|
|
27271
|
-
const info = await
|
|
27815
|
+
const info = await stat6(root);
|
|
27272
27816
|
if (!info.isDirectory()) throw new Error();
|
|
27273
27817
|
return root;
|
|
27274
27818
|
} catch {
|
|
@@ -27395,10 +27939,10 @@ function screenOf(terminal) {
|
|
|
27395
27939
|
return (terminal.truncated ? "\x1Bc" : "") + terminal.screen.join("");
|
|
27396
27940
|
}
|
|
27397
27941
|
function argsOf(payload) {
|
|
27398
|
-
const value =
|
|
27399
|
-
return
|
|
27942
|
+
const value = isRecord15(payload) ? payload : {};
|
|
27943
|
+
return isRecord15(value.args) ? value.args : value;
|
|
27400
27944
|
}
|
|
27401
|
-
function
|
|
27945
|
+
function isRecord15(value) {
|
|
27402
27946
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
27403
27947
|
}
|
|
27404
27948
|
function stringId(value) {
|
|
@@ -27409,9 +27953,9 @@ function bounded(value, fallback, max) {
|
|
|
27409
27953
|
return typeof value === "number" && Number.isInteger(value) && value > 0 ? Math.min(value, max) : fallback;
|
|
27410
27954
|
}
|
|
27411
27955
|
function byteOrLineRange(args) {
|
|
27412
|
-
const options =
|
|
27413
|
-
if (options !== void 0 &&
|
|
27414
|
-
return
|
|
27956
|
+
const options = isRecord15(args.options) ? args.options : void 0;
|
|
27957
|
+
if (options !== void 0 && isRecord15(options.range)) return options.range;
|
|
27958
|
+
return isRecord15(args.range) ? args.range : args;
|
|
27415
27959
|
}
|
|
27416
27960
|
function readOffset(args) {
|
|
27417
27961
|
const range = byteOrLineRange(args);
|
|
@@ -27502,6 +28046,14 @@ var HostPluginRuntime = class {
|
|
|
27502
28046
|
* device identity, unlike clearing the authorization.
|
|
27503
28047
|
*/
|
|
27504
28048
|
paused;
|
|
28049
|
+
/**
|
|
28050
|
+
* Whether start() has finished wiring the Server connection.
|
|
28051
|
+
*
|
|
28052
|
+
* The Codex domain is optional business that waits on an external binary, so it runs in the
|
|
28053
|
+
* background; without this flag a Host that is merely still starting would report itself as
|
|
28054
|
+
* offline and look broken to the user and to other clients.
|
|
28055
|
+
*/
|
|
28056
|
+
starting = true;
|
|
27505
28057
|
harnessVersion;
|
|
27506
28058
|
closed = false;
|
|
27507
28059
|
codex;
|
|
@@ -27529,7 +28081,6 @@ var HostPluginRuntime = class {
|
|
|
27529
28081
|
fingerprint: this.identity.fingerprint,
|
|
27530
28082
|
server: this.config.serverUrl ?? "not configured"
|
|
27531
28083
|
});
|
|
27532
|
-
await this.codex.start();
|
|
27533
28084
|
if (this.serverApi !== void 0) {
|
|
27534
28085
|
this.harnessVersion = await this.readHarnessVersion();
|
|
27535
28086
|
this.serverApi.setHarnessVersion(this.harnessVersion);
|
|
@@ -27537,6 +28088,9 @@ var HostPluginRuntime = class {
|
|
|
27537
28088
|
this.serverConnection = this.createServerConnection(this.identity);
|
|
27538
28089
|
if (!this.paused) this.serverConnection.start();
|
|
27539
28090
|
}
|
|
28091
|
+
this.starting = false;
|
|
28092
|
+
void this.codex.start().catch(() => {
|
|
28093
|
+
});
|
|
27540
28094
|
}
|
|
27541
28095
|
currentIdentity() {
|
|
27542
28096
|
if (this.identity === void 0) throw new Error("remote runtime has not started");
|
|
@@ -27554,6 +28108,7 @@ var HostPluginRuntime = class {
|
|
|
27554
28108
|
configured: this.serverApi !== void 0,
|
|
27555
28109
|
online: this.serverConnection?.isOnline() ?? false,
|
|
27556
28110
|
reconnecting: this.serverConnection?.isReconnecting() ?? false,
|
|
28111
|
+
starting: this.starting,
|
|
27557
28112
|
...this.serverConnection?.lastActivity() === void 0 ? {} : { lastActiveAt: this.serverConnection.lastActivity() },
|
|
27558
28113
|
...error === void 0 ? {} : { error },
|
|
27559
28114
|
...authorization?.account === void 0 ? {} : { account: authorization.account },
|
|
@@ -27757,12 +28312,12 @@ var HostPluginRuntime = class {
|
|
|
27757
28312
|
}
|
|
27758
28313
|
async readHarnessVersion() {
|
|
27759
28314
|
let reportedVersion;
|
|
27760
|
-
let
|
|
28315
|
+
let errorCode6;
|
|
27761
28316
|
try {
|
|
27762
|
-
const response = await this.apiProxy?.host.describe({ rpcId:
|
|
28317
|
+
const response = await this.apiProxy?.host.describe({ rpcId: randomUUID4(), payload: {} });
|
|
27763
28318
|
if (response === void 0) throw new Error("ApiProxy is unavailable");
|
|
27764
28319
|
if (!response.result.ok) {
|
|
27765
|
-
|
|
28320
|
+
errorCode6 = response.result.error.code;
|
|
27766
28321
|
} else {
|
|
27767
28322
|
reportedVersion = normalizeHarnessVersion(response.result.value.version);
|
|
27768
28323
|
}
|
|
@@ -27771,7 +28326,7 @@ var HostPluginRuntime = class {
|
|
|
27771
28326
|
const distributionVersion = reportedVersion === void 0 || reportedVersion === "0.0.1" ? await readHarnessDistributionVersion() : void 0;
|
|
27772
28327
|
const version = selectHarnessVersion(reportedVersion, distributionVersion);
|
|
27773
28328
|
if (version !== void 0) return version;
|
|
27774
|
-
this.logger.warn("Harness version is unavailable",
|
|
28329
|
+
this.logger.warn("Harness version is unavailable", errorCode6 === void 0 ? void 0 : { code: errorCode6 });
|
|
27775
28330
|
return void 0;
|
|
27776
28331
|
}
|
|
27777
28332
|
hostCapabilities() {
|
|
@@ -27827,9 +28382,9 @@ function isPlainRecord(value) {
|
|
|
27827
28382
|
}
|
|
27828
28383
|
|
|
27829
28384
|
// src/cli.ts
|
|
27830
|
-
import { stat as
|
|
28385
|
+
import { stat as stat7 } from "node:fs/promises";
|
|
27831
28386
|
import { hostname as hostname3 } from "node:os";
|
|
27832
|
-
import { join as
|
|
28387
|
+
import { join as join9 } from "node:path";
|
|
27833
28388
|
var QR_POLL_INTERVAL_MS = 2e3;
|
|
27834
28389
|
var TERMINAL_QR_MARGIN = 4;
|
|
27835
28390
|
async function runCli(args = process.argv.slice(2), dependencies = {}) {
|
|
@@ -27917,7 +28472,7 @@ async function status(args, runtime) {
|
|
|
27917
28472
|
`Server: ${serverUrl}`,
|
|
27918
28473
|
"Host control: enabled (dsh-TUI default)"
|
|
27919
28474
|
];
|
|
27920
|
-
if (!await exists3(
|
|
28475
|
+
if (!await exists3(join9(directory, "device.json"))) {
|
|
27921
28476
|
lines.push("Device: not initialized", "Authorization: logged out", "Credential: unavailable");
|
|
27922
28477
|
write(runtime.stdout, `${lines.join("\n")}
|
|
27923
28478
|
|
|
@@ -27961,7 +28516,7 @@ async function logout(args, runtime) {
|
|
|
27961
28516
|
const serverUrl = selectedServer();
|
|
27962
28517
|
const root = new IdentityStore({ env: runtime.env }).directory;
|
|
27963
28518
|
const directory = serverStorageDirectory(root, serverUrl, "host");
|
|
27964
|
-
if (!await exists3(
|
|
28519
|
+
if (!await exists3(join9(directory, "device.json"))) {
|
|
27965
28520
|
await new ServerCredentialStore(directory).clear();
|
|
27966
28521
|
write(runtime.stdout, "This Host is already logged out.\n");
|
|
27967
28522
|
return 0;
|
|
@@ -28081,7 +28636,7 @@ function terminalLink(url, target2) {
|
|
|
28081
28636
|
}
|
|
28082
28637
|
async function exists3(path) {
|
|
28083
28638
|
try {
|
|
28084
|
-
await
|
|
28639
|
+
await stat7(path);
|
|
28085
28640
|
return true;
|
|
28086
28641
|
} catch (error) {
|
|
28087
28642
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") return false;
|
|
@@ -28148,7 +28703,7 @@ function installTuiRemoteCommand(ctx, resolveTarget) {
|
|
|
28148
28703
|
} catch (error) {
|
|
28149
28704
|
return {
|
|
28150
28705
|
kind: "error",
|
|
28151
|
-
text: `Local Remote Host credentials were cleared, but Server revocation failed (${
|
|
28706
|
+
text: `Local Remote Host credentials were cleared, but Server revocation failed (${errorCode5(error)}).`
|
|
28152
28707
|
};
|
|
28153
28708
|
}
|
|
28154
28709
|
}
|
|
@@ -28204,7 +28759,7 @@ function registerOptional(ctx, feature, register2) {
|
|
|
28204
28759
|
return register2() ?? (() => {
|
|
28205
28760
|
});
|
|
28206
28761
|
} catch (error) {
|
|
28207
|
-
ctx.logger.warn(`dsh-TUI Remote ${feature} is unavailable`, { code:
|
|
28762
|
+
ctx.logger.warn(`dsh-TUI Remote ${feature} is unavailable`, { code: errorCode5(error) });
|
|
28208
28763
|
return () => {
|
|
28209
28764
|
};
|
|
28210
28765
|
}
|
|
@@ -28247,7 +28802,7 @@ function remoteStatusLines(target2) {
|
|
|
28247
28802
|
const diagnostics = runtime.diagnostics();
|
|
28248
28803
|
const codex = runtime.codexStatus();
|
|
28249
28804
|
const capabilities = new Set(diagnostics.capabilities);
|
|
28250
|
-
const connection = status2.online ? "online" : status2.reconnecting ? "reconnecting" : status2.accountRequired ? "authorization required" : "offline";
|
|
28805
|
+
const connection = status2.online ? "online" : status2.starting ? "starting" : status2.reconnecting ? "reconnecting" : status2.accountRequired ? "authorization required" : "offline";
|
|
28251
28806
|
return [
|
|
28252
28807
|
`Server: ${config.serverUrl ?? "not configured"}`,
|
|
28253
28808
|
"Host control: enabled",
|
|
@@ -28317,7 +28872,7 @@ var RemoteLoginController = class {
|
|
|
28317
28872
|
await this.poll(attempt, provider, session, target2.runtime);
|
|
28318
28873
|
} catch (error) {
|
|
28319
28874
|
if (attempt !== this.attempt) return;
|
|
28320
|
-
this.update({ phase: "error", provider, error:
|
|
28875
|
+
this.update({ phase: "error", provider, error: errorCode5(error) });
|
|
28321
28876
|
}
|
|
28322
28877
|
}
|
|
28323
28878
|
async poll(attempt, provider, session, runtime) {
|
|
@@ -28435,7 +28990,7 @@ function terminalLink2(url) {
|
|
|
28435
28990
|
function printableWidth(value) {
|
|
28436
28991
|
return value.replace(/\u001B\[[0-9;]*m/gu, "").length;
|
|
28437
28992
|
}
|
|
28438
|
-
function
|
|
28993
|
+
function errorCode5(error) {
|
|
28439
28994
|
if (error instanceof ServerApiError) return error.code;
|
|
28440
28995
|
if (error instanceof Error && "code" in error && typeof error.code === "string") return error.code;
|
|
28441
28996
|
return "CONNECTION_FAILED";
|
|
@@ -28478,7 +29033,7 @@ function createRemoteFileContentProvider(call, options = {}) {
|
|
|
28478
29033
|
const offset = request.offset + received;
|
|
28479
29034
|
const range = await call("fileviewer.readRange", { path: locator, offset, length }, request.signal);
|
|
28480
29035
|
if (range.offset !== offset) throw new Error("The Remote Host returned a mismatched file range.");
|
|
28481
|
-
const bytes =
|
|
29036
|
+
const bytes = decodeBase642(range.data);
|
|
28482
29037
|
if (bytes.byteLength > length) throw new Error("The Remote Host returned more file bytes than requested.");
|
|
28483
29038
|
chunks.push(bytes);
|
|
28484
29039
|
received += bytes.byteLength;
|
|
@@ -28510,7 +29065,7 @@ function currentSaveAsAllowed(value) {
|
|
|
28510
29065
|
function currentSaveAsMaxBytes(value) {
|
|
28511
29066
|
return typeof value === "function" ? value() : value ?? REMOTE_FILE_SAVE_AS_MAX_BYTES;
|
|
28512
29067
|
}
|
|
28513
|
-
function
|
|
29068
|
+
function decodeBase642(value) {
|
|
28514
29069
|
const binary = atob(value);
|
|
28515
29070
|
const bytes = new Uint8Array(binary.length);
|
|
28516
29071
|
for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);
|
|
@@ -28663,7 +29218,10 @@ async function activate(ctx, readConfig, entryId, tuiBinding) {
|
|
|
28663
29218
|
apiProxy,
|
|
28664
29219
|
nativeTypertGateway,
|
|
28665
29220
|
logger,
|
|
28666
|
-
hostControl
|
|
29221
|
+
hostControl,
|
|
29222
|
+
// The RTC factory keeps its default; the target store lives beside the plugin state.
|
|
29223
|
+
void 0,
|
|
29224
|
+
new ClientTargetStore(defaultIdentityDirectory)
|
|
28667
29225
|
);
|
|
28668
29226
|
}
|
|
28669
29227
|
const controlRuntime = connection === void 0 ? void 0 : new PluginControlRuntime(config, defaultIdentityDirectory, settingsBinding, clientRuntime, hostControl, deepseekSession);
|
|
@@ -28678,6 +29236,7 @@ async function activate(ctx, readConfig, entryId, tuiBinding) {
|
|
|
28678
29236
|
await runtime.start();
|
|
28679
29237
|
if (clientRuntime !== void 0) {
|
|
28680
29238
|
await clientRuntime.start();
|
|
29239
|
+
await clientRuntime.restoreLastTarget();
|
|
28681
29240
|
} else {
|
|
28682
29241
|
logger.warn("client remote mode is unavailable", {
|
|
28683
29242
|
serverConfigured: config.serverUrl !== void 0,
|