@blue-soda/dsh-remote 0.5.6 → 0.5.8
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 +14 -3
- package/cordis.patch.yml +30 -30
- package/dist/client.github.js +1 -1
- package/dist/client.js +1 -1
- package/dist/index.js +1216 -396
- package/dist/index.js.map +4 -4
- package/dsh-plugin.json +41 -41
- package/package.json +165 -165
- package/dist/acp.d.ts +0 -75
- package/dist/acp.d.ts.map +0 -1
- package/dist/acp.js +0 -61
- package/dist/acp.js.map +0 -1
- package/dist/api-proxy-switch.d.ts +0 -33
- package/dist/api-proxy-switch.d.ts.map +0 -1
- package/dist/api-proxy-switch.js +0 -109
- package/dist/api-proxy-switch.js.map +0 -1
- package/dist/atomic-file.d.ts +0 -16
- package/dist/atomic-file.d.ts.map +0 -1
- package/dist/atomic-file.js +0 -81
- package/dist/atomic-file.js.map +0 -1
- package/dist/bin/doctor.d.ts +0 -3
- package/dist/bin/doctor.d.ts.map +0 -1
- package/dist/bin/doctor.js +0 -13
- package/dist/bin/doctor.js.map +0 -1
- package/dist/cli.d.ts +0 -32
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js +0 -289
- package/dist/cli.js.map +0 -1
- package/dist/client-runtime.d.ts +0 -375
- package/dist/client-runtime.d.ts.map +0 -1
- package/dist/client-runtime.js +0 -1768
- package/dist/client-runtime.js.map +0 -1
- package/dist/client-secure-transport.d.ts +0 -23
- package/dist/client-secure-transport.d.ts.map +0 -1
- package/dist/client-secure-transport.js +0 -144
- package/dist/client-secure-transport.js.map +0 -1
- package/dist/client-target-store.d.ts +0 -40
- package/dist/client-target-store.d.ts.map +0 -1
- package/dist/client-target-store.js +0 -79
- package/dist/client-target-store.js.map +0 -1
- package/dist/client.d.ts +0 -13
- package/dist/client.d.ts.map +0 -1
- package/dist/codex/app-server.d.ts +0 -71
- package/dist/codex/app-server.d.ts.map +0 -1
- package/dist/codex/app-server.js +0 -268
- package/dist/codex/app-server.js.map +0 -1
- package/dist/codex/codex-image-codec.d.ts +0 -7
- package/dist/codex/codex-image-codec.d.ts.map +0 -1
- package/dist/codex/codex-image-codec.js +0 -288
- package/dist/codex/codex-image-codec.js.map +0 -1
- package/dist/codex/domain.d.ts +0 -102
- package/dist/codex/domain.d.ts.map +0 -1
- package/dist/codex/domain.js +0 -1323
- package/dist/codex/domain.js.map +0 -1
- package/dist/codex/method-policy.d.ts +0 -338
- package/dist/codex/method-policy.d.ts.map +0 -1
- package/dist/codex/method-policy.js +0 -141
- package/dist/codex/method-policy.js.map +0 -1
- package/dist/codex/peer-bridge.d.ts +0 -57
- package/dist/codex/peer-bridge.d.ts.map +0 -1
- package/dist/codex/peer-bridge.js +0 -299
- package/dist/codex/peer-bridge.js.map +0 -1
- package/dist/codex/permissions.d.ts +0 -4
- package/dist/codex/permissions.d.ts.map +0 -1
- package/dist/codex/permissions.js +0 -21
- package/dist/codex/permissions.js.map +0 -1
- package/dist/codex/session-id.d.ts +0 -10
- package/dist/codex/session-id.d.ts.map +0 -1
- package/dist/codex/session-id.js +0 -15
- package/dist/codex/session-id.js.map +0 -1
- package/dist/codex/virtual-harness.d.ts +0 -209
- package/dist/codex/virtual-harness.d.ts.map +0 -1
- package/dist/codex/virtual-harness.js +0 -2650
- package/dist/codex/virtual-harness.js.map +0 -1
- package/dist/codex-workspace-bridge.d.ts +0 -108
- package/dist/codex-workspace-bridge.d.ts.map +0 -1
- package/dist/codex-workspace-bridge.js +0 -502
- package/dist/codex-workspace-bridge.js.map +0 -1
- package/dist/config.d.ts +0 -99
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js +0 -127
- package/dist/config.js.map +0 -1
- package/dist/connection-controller.d.ts +0 -44
- package/dist/connection-controller.d.ts.map +0 -1
- package/dist/connection-controller.js +0 -185
- package/dist/connection-controller.js.map +0 -1
- package/dist/control-route.d.ts +0 -34
- package/dist/control-route.d.ts.map +0 -1
- package/dist/control-route.js +0 -136
- package/dist/control-route.js.map +0 -1
- package/dist/control-runtime.d.ts +0 -138
- package/dist/control-runtime.d.ts.map +0 -1
- package/dist/control-runtime.js +0 -543
- package/dist/control-runtime.js.map +0 -1
- package/dist/control-stream.d.ts +0 -67
- package/dist/control-stream.d.ts.map +0 -1
- package/dist/control-stream.js +0 -179
- package/dist/control-stream.js.map +0 -1
- package/dist/defaults.d.ts +0 -9
- package/dist/defaults.d.ts.map +0 -1
- package/dist/defaults.js +0 -9
- package/dist/defaults.js.map +0 -1
- package/dist/file-viewer-bridge.d.ts +0 -30
- package/dist/file-viewer-bridge.d.ts.map +0 -1
- package/dist/file-viewer-bridge.js +0 -128
- package/dist/file-viewer-bridge.js.map +0 -1
- package/dist/file-viewer-contract.d.ts +0 -5
- package/dist/file-viewer-contract.d.ts.map +0 -1
- package/dist/file-viewer-contract.js +0 -4
- package/dist/file-viewer-contract.js.map +0 -1
- package/dist/harness-api-bridge.d.ts +0 -81
- package/dist/harness-api-bridge.d.ts.map +0 -1
- package/dist/harness-api-bridge.js +0 -862
- package/dist/harness-api-bridge.js.map +0 -1
- package/dist/harness-api-history.d.ts +0 -3
- package/dist/harness-api-history.d.ts.map +0 -1
- package/dist/harness-api-history.js +0 -59
- package/dist/harness-api-history.js.map +0 -1
- package/dist/harness-remote-bridge.d.ts +0 -60
- package/dist/harness-remote-bridge.d.ts.map +0 -1
- package/dist/harness-remote-bridge.js +0 -540
- package/dist/harness-remote-bridge.js.map +0 -1
- package/dist/harness-version.d.ts +0 -23
- package/dist/harness-version.d.ts.map +0 -1
- package/dist/harness-version.js +0 -95
- package/dist/harness-version.js.map +0 -1
- package/dist/identity-store.d.ts +0 -45
- package/dist/identity-store.d.ts.map +0 -1
- package/dist/identity-store.js +0 -186
- package/dist/identity-store.js.map +0 -1
- package/dist/ids.d.ts +0 -2
- package/dist/ids.d.ts.map +0 -1
- package/dist/ids.js +0 -17
- package/dist/ids.js.map +0 -1
- package/dist/index.d.ts +0 -78
- package/dist/index.d.ts.map +0 -1
- package/dist/logging.d.ts +0 -18
- package/dist/logging.d.ts.map +0 -1
- package/dist/logging.js +0 -30
- package/dist/logging.js.map +0 -1
- package/dist/loopback-host.d.ts +0 -20
- package/dist/loopback-host.d.ts.map +0 -1
- package/dist/loopback-host.js +0 -245
- package/dist/loopback-host.js.map +0 -1
- package/dist/loopback-preview.d.ts +0 -19
- package/dist/loopback-preview.d.ts.map +0 -1
- package/dist/loopback-preview.js +0 -232
- package/dist/loopback-preview.js.map +0 -1
- package/dist/native-rtc-helper.d.ts +0 -14
- package/dist/native-rtc-helper.d.ts.map +0 -1
- package/dist/native-rtc-helper.js +0 -575
- package/dist/native-rtc-helper.js.map +0 -1
- package/dist/remote-api-proxy.d.ts +0 -17
- package/dist/remote-api-proxy.d.ts.map +0 -1
- package/dist/remote-api-proxy.js +0 -451
- package/dist/remote-api-proxy.js.map +0 -1
- package/dist/remote-directory-browser.d.ts +0 -16
- package/dist/remote-directory-browser.d.ts.map +0 -1
- package/dist/remote-directory-browser.js +0 -48
- package/dist/remote-directory-browser.js.map +0 -1
- package/dist/remote-file-content-provider.d.ts +0 -48
- package/dist/remote-file-content-provider.d.ts.map +0 -1
- package/dist/remote-file-content-provider.js +0 -93
- package/dist/remote-file-content-provider.js.map +0 -1
- package/dist/remote-typert-gateway.d.ts +0 -20
- package/dist/remote-typert-gateway.d.ts.map +0 -1
- package/dist/remote-typert-gateway.js +0 -334
- package/dist/remote-typert-gateway.js.map +0 -1
- package/dist/rpc-binary-attachments.d.ts +0 -33
- package/dist/rpc-binary-attachments.d.ts.map +0 -1
- package/dist/rpc-binary-attachments.js +0 -142
- package/dist/rpc-binary-attachments.js.map +0 -1
- package/dist/rpc-router.d.ts +0 -31
- package/dist/rpc-router.d.ts.map +0 -1
- package/dist/rpc-router.js +0 -201
- package/dist/rpc-router.js.map +0 -1
- package/dist/safe-error.d.ts +0 -8
- package/dist/safe-error.d.ts.map +0 -1
- package/dist/safe-error.js +0 -20
- package/dist/safe-error.js.map +0 -1
- package/dist/server-api.d.ts +0 -109
- package/dist/server-api.d.ts.map +0 -1
- package/dist/server-api.js +0 -520
- package/dist/server-api.js.map +0 -1
- package/dist/server-connection.d.ts +0 -79
- package/dist/server-connection.d.ts.map +0 -1
- package/dist/server-connection.js +0 -1023
- package/dist/server-connection.js.map +0 -1
- package/dist/server-credentials.d.ts +0 -55
- package/dist/server-credentials.d.ts.map +0 -1
- package/dist/server-credentials.js +0 -108
- package/dist/server-credentials.js.map +0 -1
- package/dist/service.d.ts +0 -151
- package/dist/service.d.ts.map +0 -1
- package/dist/service.js +0 -448
- package/dist/service.js.map +0 -1
- package/dist/session-format-compat.d.ts +0 -3
- package/dist/session-format-compat.d.ts.map +0 -1
- package/dist/session-format-compat.js +0 -183
- package/dist/session-format-compat.js.map +0 -1
- package/dist/status-stream.d.ts +0 -48
- package/dist/status-stream.d.ts.map +0 -1
- package/dist/status-stream.js +0 -133
- package/dist/status-stream.js.map +0 -1
- package/dist/terminal-policy.d.ts +0 -21
- package/dist/terminal-policy.d.ts.map +0 -1
- package/dist/terminal-policy.js +0 -68
- package/dist/terminal-policy.js.map +0 -1
- package/dist/tui-command.d.ts +0 -13
- package/dist/tui-command.d.ts.map +0 -1
- package/dist/tui-command.js +0 -392
- package/dist/tui-command.js.map +0 -1
- package/dist/typert-gateway-contract.d.ts +0 -59
- package/dist/typert-gateway-contract.d.ts.map +0 -1
- package/dist/typert-gateway-contract.js +0 -2
- package/dist/typert-gateway-contract.js.map +0 -1
- package/dist/typert-gateway-switch.d.ts +0 -85
- package/dist/typert-gateway-switch.d.ts.map +0 -1
- package/dist/typert-gateway-switch.js +0 -448
- package/dist/typert-gateway-switch.js.map +0 -1
- package/dist/types.d.ts +0 -20
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -2
- package/dist/types.js.map +0 -1
- package/dist/version.d.ts +0 -12
- package/dist/version.d.ts.map +0 -1
- package/dist/version.js +0 -12
- package/dist/version.js.map +0 -1
- package/dist/werift-rtc.d.ts +0 -121
- package/dist/werift-rtc.d.ts.map +0 -1
- package/dist/werift-rtc.js +0 -490
- package/dist/werift-rtc.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -4,17 +4,10 @@ var __export = (target2, all) => {
|
|
|
4
4
|
__defProp(target2, name2, { get: all[name2], enumerable: true });
|
|
5
5
|
};
|
|
6
6
|
|
|
7
|
-
//
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
if (socket.readyState !== 1 || Date.now() - started > 1e4)
|
|
12
|
-
throw new Error("Relay consumer is too slow or disconnected");
|
|
13
|
-
await new Promise((resolve4) => setTimeout(resolve4, 10));
|
|
14
|
-
}
|
|
15
|
-
if (socket.readyState !== 1)
|
|
16
|
-
throw new Error("Relay transport closed");
|
|
17
|
-
}
|
|
7
|
+
// src/client-runtime.ts
|
|
8
|
+
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
9
|
+
import { homedir as homedir2 } from "node:os";
|
|
10
|
+
import { join as join6 } from "node:path";
|
|
18
11
|
|
|
19
12
|
// ../../node_modules/zod/v3/external.js
|
|
20
13
|
var external_exports = {};
|
|
@@ -177,8 +170,8 @@ var util;
|
|
|
177
170
|
return void 0;
|
|
178
171
|
};
|
|
179
172
|
util2.isInteger = typeof Number.isInteger === "function" ? (val) => Number.isInteger(val) : (val) => typeof val === "number" && Number.isFinite(val) && Math.floor(val) === val;
|
|
180
|
-
function joinValues(
|
|
181
|
-
return
|
|
173
|
+
function joinValues(array2, separator = " | ") {
|
|
174
|
+
return array2.map((val) => typeof val === "string" ? `'${val}'` : val).join(separator);
|
|
182
175
|
}
|
|
183
176
|
util2.joinValues = joinValues;
|
|
184
177
|
util2.jsonStringifyReplacer = (_, value) => {
|
|
@@ -4594,6 +4587,32 @@ function cryptoRandomId() {
|
|
|
4594
4587
|
return `msg_${Date.now().toString(36)}_${Math.random().toString(36).slice(2)}`;
|
|
4595
4588
|
}
|
|
4596
4589
|
|
|
4590
|
+
// src/history-policy.ts
|
|
4591
|
+
var HISTORY_PAGE_SIZES = [50, 30, 20, 12, 6, 3, 1];
|
|
4592
|
+
var RESPONSE_ENVELOPE_BYTES = 8 * 1024;
|
|
4593
|
+
var HISTORY_BUDGET_BYTES = MAX_SECURE_MESSAGE_BYTES - RESPONSE_ENVELOPE_BYTES;
|
|
4594
|
+
function historyPageSizes(start = HISTORY_PAGE_SIZES[0]) {
|
|
4595
|
+
const sizes = [start];
|
|
4596
|
+
for (const size of HISTORY_PAGE_SIZES) if (size < start) sizes.push(size);
|
|
4597
|
+
return sizes;
|
|
4598
|
+
}
|
|
4599
|
+
function codexHistoryStatsFileEnabled(env = process.env) {
|
|
4600
|
+
const value = env.DSH_CODEX_HISTORY_STATS?.trim().toLowerCase();
|
|
4601
|
+
return value === "1" || value === "true" || value === "on" || value === "yes";
|
|
4602
|
+
}
|
|
4603
|
+
|
|
4604
|
+
// ../webrtc/dist/websocket-backpressure.js
|
|
4605
|
+
async function waitForRelayCapacity(socket) {
|
|
4606
|
+
const started = Date.now();
|
|
4607
|
+
while ((socket.bufferedAmount ?? 0) > 512 * 1024) {
|
|
4608
|
+
if (socket.readyState !== 1 || Date.now() - started > 1e4)
|
|
4609
|
+
throw new Error("Relay consumer is too slow or disconnected");
|
|
4610
|
+
await new Promise((resolve4) => setTimeout(resolve4, 10));
|
|
4611
|
+
}
|
|
4612
|
+
if (socket.readyState !== 1)
|
|
4613
|
+
throw new Error("Relay transport closed");
|
|
4614
|
+
}
|
|
4615
|
+
|
|
4597
4616
|
// ../webrtc/dist/transport.js
|
|
4598
4617
|
var BaseTransport = class {
|
|
4599
4618
|
handlers = /* @__PURE__ */ new Set();
|
|
@@ -6242,7 +6261,7 @@ function hexToBytes(hex) {
|
|
|
6242
6261
|
const al = hl / 2;
|
|
6243
6262
|
if (hl % 2)
|
|
6244
6263
|
throw new Error("hex string expected, got unpadded hex of length " + hl);
|
|
6245
|
-
const
|
|
6264
|
+
const array2 = new Uint8Array(al);
|
|
6246
6265
|
for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) {
|
|
6247
6266
|
const n1 = asciiToBase16(hex.charCodeAt(hi));
|
|
6248
6267
|
const n2 = asciiToBase16(hex.charCodeAt(hi + 1));
|
|
@@ -6250,9 +6269,9 @@ function hexToBytes(hex) {
|
|
|
6250
6269
|
const char = hex[hi] + hex[hi + 1];
|
|
6251
6270
|
throw new Error('hex string expected, got non-hex character "' + char + '" at index ' + hi);
|
|
6252
6271
|
}
|
|
6253
|
-
|
|
6272
|
+
array2[ai] = n1 * 16 + n2;
|
|
6254
6273
|
}
|
|
6255
|
-
return
|
|
6274
|
+
return array2;
|
|
6256
6275
|
}
|
|
6257
6276
|
function utf8ToBytes(str) {
|
|
6258
6277
|
if (typeof str !== "string")
|
|
@@ -8959,7 +8978,7 @@ function hexToBytes2(hex) {
|
|
|
8959
8978
|
const al = hl / 2;
|
|
8960
8979
|
if (hl % 2)
|
|
8961
8980
|
throw new RangeError("hex string expected, got unpadded hex of length " + hl);
|
|
8962
|
-
const
|
|
8981
|
+
const array2 = new Uint8Array(al);
|
|
8963
8982
|
for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) {
|
|
8964
8983
|
const n1 = asciiToBase162(hex.charCodeAt(hi));
|
|
8965
8984
|
const n2 = asciiToBase162(hex.charCodeAt(hi + 1));
|
|
@@ -8967,9 +8986,9 @@ function hexToBytes2(hex) {
|
|
|
8967
8986
|
const char = hex[hi] + hex[hi + 1];
|
|
8968
8987
|
throw new RangeError('hex string expected, got non-hex character "' + char + '" at index ' + hi);
|
|
8969
8988
|
}
|
|
8970
|
-
|
|
8989
|
+
array2[ai] = n1 * 16 + n2;
|
|
8971
8990
|
}
|
|
8972
|
-
return
|
|
8991
|
+
return array2;
|
|
8973
8992
|
}
|
|
8974
8993
|
function concatBytes2(...arrays) {
|
|
8975
8994
|
let sum = 0;
|
|
@@ -13418,7 +13437,8 @@ var entryConfigSchema = s.object({
|
|
|
13418
13437
|
]),
|
|
13419
13438
|
codex: s.object({
|
|
13420
13439
|
enabled: s.boolean(),
|
|
13421
|
-
binary: s.string()
|
|
13440
|
+
binary: s.string(),
|
|
13441
|
+
showUnprojectedSessions: s.boolean()
|
|
13422
13442
|
}),
|
|
13423
13443
|
acp: s.object({ enabled: s.boolean(), backends: s.array(s.object({ id: s.string(), enabled: s.boolean(), command: s.string(), args: s.array(s.string()), cwd: s.string() })) })
|
|
13424
13444
|
});
|
|
@@ -13451,7 +13471,8 @@ var configSchema = external_exports.object({
|
|
|
13451
13471
|
reconnect: reconnectSchema.optional(),
|
|
13452
13472
|
codex: external_exports.object({
|
|
13453
13473
|
enabled: external_exports.boolean().optional(),
|
|
13454
|
-
binary: external_exports.string().trim().min(1).max(4096).optional()
|
|
13474
|
+
binary: external_exports.string().trim().min(1).max(4096).optional(),
|
|
13475
|
+
showUnprojectedSessions: external_exports.boolean().optional()
|
|
13455
13476
|
}).strict().optional(),
|
|
13456
13477
|
acp: external_exports.object({ enabled: external_exports.boolean().optional(), backends: external_exports.array(external_exports.object({ id: external_exports.string().trim().regex(/^[a-z0-9][a-z0-9._-]{0,31}$/i), enabled: external_exports.boolean().optional(), command: external_exports.string().trim().min(1).max(4096).optional(), args: external_exports.array(external_exports.string().max(4096)).max(32).optional(), cwd: external_exports.string().max(4096).optional() }).strict()).max(12).optional(), backend: external_exports.string().trim().regex(/^[a-z0-9][a-z0-9._-]{0,31}$/i).optional(), command: external_exports.string().trim().min(1).max(4096).optional(), args: external_exports.array(external_exports.string().max(4096)).max(32).optional(), cwd: external_exports.string().max(4096).optional() }).strict().optional()
|
|
13457
13478
|
}).strict();
|
|
@@ -13493,7 +13514,8 @@ function resolveConfig(input2 = {}, env = process.env) {
|
|
|
13493
13514
|
// an account and, while unavailable, retries every minute. The bundled profile patch says the same thing, so
|
|
13494
13515
|
// an install that never touches the switch has it off.
|
|
13495
13516
|
enabled: parsed.codex?.enabled ?? false,
|
|
13496
|
-
binary: parsed.codex?.binary ?? "codex"
|
|
13517
|
+
binary: parsed.codex?.binary ?? "codex",
|
|
13518
|
+
showUnprojectedSessions: parsed.codex?.showUnprojectedSessions ?? true
|
|
13497
13519
|
},
|
|
13498
13520
|
acp: { enabled: parsed.acp?.enabled ?? true, backends: [.../* @__PURE__ */ new Set(["codex", "cursor", "kimi", ...parsed.acp?.backends?.map((item) => item.id) ?? []])].map((id4) => {
|
|
13499
13521
|
const d = parsed.acp?.backends?.find((x) => x.id === id4);
|
|
@@ -13521,7 +13543,7 @@ function normalizeServerUrl(value) {
|
|
|
13521
13543
|
}
|
|
13522
13544
|
|
|
13523
13545
|
// src/version.ts
|
|
13524
|
-
var PLUGIN_VERSION = "0.5.
|
|
13546
|
+
var PLUGIN_VERSION = "0.5.8";
|
|
13525
13547
|
|
|
13526
13548
|
// src/server-api.ts
|
|
13527
13549
|
var ENABLED_QR_PROVIDERS = ["github"];
|
|
@@ -13998,6 +14020,7 @@ function requireRecord(value, name2) {
|
|
|
13998
14020
|
|
|
13999
14021
|
// src/server-connection.ts
|
|
14000
14022
|
var DEFAULT_WEBRTC_NEGOTIATE_TIMEOUT_MS = 12e3;
|
|
14023
|
+
var CLOSE_DRAIN_TIMEOUT_MS = 5e3;
|
|
14001
14024
|
var HostServerConnection = class {
|
|
14002
14025
|
constructor(config, identity, identities, api, connections, logger, createWebSocket = (url) => new WebSocket(url), rtcFactoryProvider, hostCapabilities = () => ["harness.api.v1"], harnessVersion) {
|
|
14003
14026
|
this.config = config;
|
|
@@ -14041,7 +14064,7 @@ var HostServerConnection = class {
|
|
|
14041
14064
|
}
|
|
14042
14065
|
if (this.resumeQueued) return;
|
|
14043
14066
|
this.resumeQueued = true;
|
|
14044
|
-
void this.running.finally(() => {
|
|
14067
|
+
void this.running.catch(() => void 0).finally(() => {
|
|
14045
14068
|
this.resumeQueued = false;
|
|
14046
14069
|
if (!this.stopped) this.start();
|
|
14047
14070
|
});
|
|
@@ -14077,7 +14100,7 @@ var HostServerConnection = class {
|
|
|
14077
14100
|
this.reconnectRequested = true;
|
|
14078
14101
|
this.retryWake?.();
|
|
14079
14102
|
this.socket?.close(4e3, "manual reconnect");
|
|
14080
|
-
void this.running.finally(() => {
|
|
14103
|
+
void this.running.catch(() => void 0).finally(() => {
|
|
14081
14104
|
if (!this.reconnectRequested || this.stopped) return;
|
|
14082
14105
|
this.reconnectRequested = false;
|
|
14083
14106
|
this.start();
|
|
@@ -14107,7 +14130,7 @@ var HostServerConnection = class {
|
|
|
14107
14130
|
...connectionFailureDetail(error)
|
|
14108
14131
|
});
|
|
14109
14132
|
if (TERMINAL_AUTH_ERRORS.has(code)) {
|
|
14110
|
-
this.logger.warn(code === "CONNECTION_REPLACED" ? "Another instance is using this Host identity. Standing by: retrying in five minutes, and taking over as soon as it leaves." : code === "SERVER_CREDENTIALS_BUSY" ? "Credential refresh is locked. Stop other instances; after a crash, stop all instances before removing server-credentials.json.refresh-lock and authorizing again." : "Host authorization failed. Run /remote login or authorize this Host again in Remote settings.");
|
|
14133
|
+
this.logger.warn(code === "CONNECTION_REPLACED" ? "Another instance is using this Host identity. Standing by: retrying in five minutes, and taking over as soon as it leaves." : code === "SERVER_CREDENTIALS_BUSY" ? "Credential refresh is locked. Stop other instances; after a crash, stop all instances before removing server-credentials.json.refresh-lock and authorizing again." : code === "CONTROL_DISABLED" ? 'This device is not accepting control. Turn on "Allow control of this device" to reconnect.' : "Host authorization failed. Run /remote login or authorize this Host again in Remote settings.");
|
|
14111
14134
|
}
|
|
14112
14135
|
if (code === "DEVICE_REVOKED" || code === "AUTH_INVALID") {
|
|
14113
14136
|
try {
|
|
@@ -14166,9 +14189,25 @@ var HostServerConnection = class {
|
|
|
14166
14189
|
});
|
|
14167
14190
|
};
|
|
14168
14191
|
socket.onmessage = (event) => {
|
|
14192
|
+
let frame;
|
|
14193
|
+
try {
|
|
14194
|
+
frame = decodeControl(event.data, this.controlFrameLimits);
|
|
14195
|
+
} catch (error) {
|
|
14196
|
+
this.controlFrameFailed(socket, error);
|
|
14197
|
+
return;
|
|
14198
|
+
}
|
|
14199
|
+
this.lastActiveAt = Date.now();
|
|
14200
|
+
if (acknowledged && (frame.type === "ping" || frame.type === "pong")) {
|
|
14201
|
+
if (frame.type === "pong") return;
|
|
14202
|
+
const nonce = objectValue(frame.payload, "nonce");
|
|
14203
|
+
if (typeof nonce !== "string") {
|
|
14204
|
+
this.controlFrameFailed(socket, new ControlConnectionError("INVALID_MESSAGE", "Control ping has no nonce."));
|
|
14205
|
+
return;
|
|
14206
|
+
}
|
|
14207
|
+
this.sendControl("pong", { nonce });
|
|
14208
|
+
return;
|
|
14209
|
+
}
|
|
14169
14210
|
messageQueue = messageQueue.then(async () => {
|
|
14170
|
-
const frame = decodeControl(event.data, this.controlFrameLimits);
|
|
14171
|
-
this.lastActiveAt = Date.now();
|
|
14172
14211
|
if (frame.type === "hello.ack") {
|
|
14173
14212
|
const payload = requireHelloAck(frame.payload);
|
|
14174
14213
|
this.authRecoveryAttempted = false;
|
|
@@ -14193,13 +14232,7 @@ var HostServerConnection = class {
|
|
|
14193
14232
|
if (!acknowledged) throw new ControlConnectionError("INVALID_MESSAGE", "Server sent a frame before hello.ack.");
|
|
14194
14233
|
await this.handleFrame(frame);
|
|
14195
14234
|
}).catch((error) => {
|
|
14196
|
-
|
|
14197
|
-
this.terminalError = code;
|
|
14198
|
-
this.logger.error("server control frame failed", {
|
|
14199
|
-
code,
|
|
14200
|
-
reason: diagnosticReason(error)
|
|
14201
|
-
});
|
|
14202
|
-
socket.close(4008, "invalid control frame");
|
|
14235
|
+
this.controlFrameFailed(socket, error);
|
|
14203
14236
|
});
|
|
14204
14237
|
};
|
|
14205
14238
|
socket.onerror = (event) => {
|
|
@@ -14209,7 +14242,7 @@ var HostServerConnection = class {
|
|
|
14209
14242
|
};
|
|
14210
14243
|
socket.onclose = (event) => {
|
|
14211
14244
|
const close = async () => {
|
|
14212
|
-
await messageQueue
|
|
14245
|
+
await drainWithin(messageQueue, CLOSE_DRAIN_TIMEOUT_MS);
|
|
14213
14246
|
if (this.stopped) {
|
|
14214
14247
|
finish();
|
|
14215
14248
|
return;
|
|
@@ -14237,13 +14270,23 @@ var HostServerConnection = class {
|
|
|
14237
14270
|
finish(new ControlConnectionError("DEVICE_REVOKED", "The Server revoked this Host device."));
|
|
14238
14271
|
return;
|
|
14239
14272
|
}
|
|
14240
|
-
if (acknowledged) this.terminalError = closeCode(event.code);
|
|
14241
|
-
finish(acknowledged ? void 0 : new ControlConnectionError(closeCode(event.code), event.reason || "Server control connection closed."));
|
|
14273
|
+
if (acknowledged) this.terminalError = closeCode(event.code, event.reason);
|
|
14274
|
+
finish(acknowledged ? void 0 : new ControlConnectionError(closeCode(event.code, event.reason), event.reason || "Server control connection closed."));
|
|
14242
14275
|
};
|
|
14243
14276
|
void close();
|
|
14244
14277
|
};
|
|
14245
14278
|
});
|
|
14246
14279
|
}
|
|
14280
|
+
/** Record a frame failure and close the socket, so the run retries from a clean state. */
|
|
14281
|
+
controlFrameFailed(socket, error) {
|
|
14282
|
+
const code = errorCode2(error);
|
|
14283
|
+
this.terminalError = code;
|
|
14284
|
+
this.logger.error("server control frame failed", {
|
|
14285
|
+
code,
|
|
14286
|
+
reason: diagnosticReason(error)
|
|
14287
|
+
});
|
|
14288
|
+
socket.close(4008, "invalid control frame");
|
|
14289
|
+
}
|
|
14247
14290
|
async handleFrame(frame) {
|
|
14248
14291
|
if (frame.type === "ping") {
|
|
14249
14292
|
const nonce = objectValue(frame.payload, "nonce");
|
|
@@ -14705,10 +14748,25 @@ var HostServerConnection = class {
|
|
|
14705
14748
|
}
|
|
14706
14749
|
};
|
|
14707
14750
|
var REPLACED_FIRST_RETRY_MS = 65e3;
|
|
14751
|
+
async function drainWithin(promise, ms) {
|
|
14752
|
+
let timer;
|
|
14753
|
+
try {
|
|
14754
|
+
await Promise.race([
|
|
14755
|
+
promise.catch(() => void 0),
|
|
14756
|
+
new Promise((resolve4) => {
|
|
14757
|
+
timer = setTimeout(resolve4, ms);
|
|
14758
|
+
timer.unref?.();
|
|
14759
|
+
})
|
|
14760
|
+
]);
|
|
14761
|
+
} finally {
|
|
14762
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
14763
|
+
}
|
|
14764
|
+
}
|
|
14708
14765
|
var TERMINAL_AUTH_ERRORS = /* @__PURE__ */ new Set([
|
|
14709
14766
|
"SERVER_CREDENTIALS_BUSY",
|
|
14710
14767
|
"ACCOUNT_AUTH_REQUIRED",
|
|
14711
14768
|
"AUTH_INVALID",
|
|
14769
|
+
"CONTROL_DISABLED",
|
|
14712
14770
|
"DEVICE_OWNERSHIP_REQUIRED",
|
|
14713
14771
|
"DEVICE_REVOKED",
|
|
14714
14772
|
"TOKEN_EXPIRED"
|
|
@@ -14926,14 +14984,25 @@ function errorCode2(error) {
|
|
|
14926
14984
|
function isRetryable(error) {
|
|
14927
14985
|
return !TERMINAL_AUTH_ERRORS.has(errorCode2(error)) && (!(error instanceof ServerApiError) || error.retryable);
|
|
14928
14986
|
}
|
|
14929
|
-
function closeCode(code) {
|
|
14987
|
+
function closeCode(code, reason) {
|
|
14930
14988
|
if (code === 4002) return "AUTH_INVALID";
|
|
14931
14989
|
if (code === 4003) return "CONNECTION_REPLACED";
|
|
14932
14990
|
if (code === 4004) return "DEVICE_REVOKED";
|
|
14933
14991
|
if (code === 4007) return "RATE_LIMITED";
|
|
14934
14992
|
if (code === 4011) return "UNSUPPORTED_VERSION";
|
|
14993
|
+
if (code === 4001 && reason !== void 0 && REJECTED_HELLO_CODES.has(reason)) return reason;
|
|
14935
14994
|
return "CONNECTION_FAILED";
|
|
14936
14995
|
}
|
|
14996
|
+
var REJECTED_HELLO_CODES = /* @__PURE__ */ new Set([
|
|
14997
|
+
"AUTH_REQUIRED",
|
|
14998
|
+
"AUTH_INVALID",
|
|
14999
|
+
"TOKEN_EXPIRED",
|
|
15000
|
+
"DEVICE_REVOKED",
|
|
15001
|
+
"CONTROL_DISABLED",
|
|
15002
|
+
"MEMBERSHIP_REQUIRED",
|
|
15003
|
+
"UNSUPPORTED_VERSION",
|
|
15004
|
+
"RATE_LIMITED"
|
|
15005
|
+
]);
|
|
14937
15006
|
|
|
14938
15007
|
// src/loopback-preview.ts
|
|
14939
15008
|
import { randomBytes as randomBytes7, randomUUID } from "node:crypto";
|
|
@@ -16893,7 +16962,7 @@ var RemoteHarnessApiProxy = class {
|
|
|
16893
16962
|
if (committed.transferId !== transferId || committed.totalBytes <= 0 || committed.totalBytes > MAX_HARNESS_API_TRANSFER_BYTES || committed.totalChunks !== Math.ceil(committed.totalBytes / HARNESS_API_TRANSFER_CHUNK_BYTES)) {
|
|
16894
16963
|
throw new Error("The remote Host returned an invalid Harness API transfer descriptor.");
|
|
16895
16964
|
}
|
|
16896
|
-
const
|
|
16965
|
+
const responseBytes2 = new Uint8Array(committed.totalBytes);
|
|
16897
16966
|
let offset = 0;
|
|
16898
16967
|
for (let index = 0; index < committed.totalChunks; index += 1) {
|
|
16899
16968
|
const result = await this.client.rpc(
|
|
@@ -16909,10 +16978,10 @@ var RemoteHarnessApiProxy = class {
|
|
|
16909
16978
|
if (chunk.byteLength !== expectedBytes) {
|
|
16910
16979
|
throw new Error("The remote Host returned an invalid Harness API transfer chunk.");
|
|
16911
16980
|
}
|
|
16912
|
-
|
|
16981
|
+
responseBytes2.set(chunk, offset);
|
|
16913
16982
|
offset += chunk.byteLength;
|
|
16914
16983
|
}
|
|
16915
|
-
return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(
|
|
16984
|
+
return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(responseBytes2));
|
|
16916
16985
|
} finally {
|
|
16917
16986
|
if (opened) {
|
|
16918
16987
|
await this.client.rpc("harness.api.transfer.close", { transferId }).catch(() => void 0);
|
|
@@ -17387,7 +17456,7 @@ var RemoteTypertGateway2 = class {
|
|
|
17387
17456
|
if (committed.transferId !== transferId || committed.totalBytes <= 0 || committed.totalBytes > MAX_HARNESS_API_TRANSFER_BYTES || committed.totalChunks !== Math.ceil(committed.totalBytes / HARNESS_API_TRANSFER_CHUNK_BYTES)) {
|
|
17388
17457
|
throw new Error("The remote Host returned an invalid Harness Remote transfer descriptor.");
|
|
17389
17458
|
}
|
|
17390
|
-
const
|
|
17459
|
+
const responseBytes2 = new Uint8Array(committed.totalBytes);
|
|
17391
17460
|
let offset = 0;
|
|
17392
17461
|
for (let index = 0; index < committed.totalChunks; index += 1) {
|
|
17393
17462
|
const result = await this.client.rpc(
|
|
@@ -17403,10 +17472,10 @@ var RemoteTypertGateway2 = class {
|
|
|
17403
17472
|
if (chunk.byteLength !== expectedBytes) {
|
|
17404
17473
|
throw new Error("The remote Host returned an invalid Harness Remote transfer chunk.");
|
|
17405
17474
|
}
|
|
17406
|
-
|
|
17475
|
+
responseBytes2.set(chunk, offset);
|
|
17407
17476
|
offset += chunk.byteLength;
|
|
17408
17477
|
}
|
|
17409
|
-
return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(
|
|
17478
|
+
return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(responseBytes2));
|
|
17410
17479
|
} finally {
|
|
17411
17480
|
if (opened) await this.client.rpc("harness.remote.transfer.close", { transferId }).catch(() => void 0);
|
|
17412
17481
|
}
|
|
@@ -17548,6 +17617,11 @@ function patchWelcomeDescribe2(value) {
|
|
|
17548
17617
|
return { ...value, namespaces };
|
|
17549
17618
|
}
|
|
17550
17619
|
|
|
17620
|
+
// src/codex/virtual-harness.ts
|
|
17621
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
17622
|
+
import { homedir } from "node:os";
|
|
17623
|
+
import { join as join4 } from "node:path";
|
|
17624
|
+
|
|
17551
17625
|
// src/codex/permissions.ts
|
|
17552
17626
|
function codexPermissionPresetFromResponse(value) {
|
|
17553
17627
|
const root = record(value);
|
|
@@ -17578,135 +17652,6 @@ function resolveCodexThreadKey(key) {
|
|
|
17578
17652
|
return codexThreadIdFromKey(key) ?? key;
|
|
17579
17653
|
}
|
|
17580
17654
|
|
|
17581
|
-
// src/codex/method-policy.ts
|
|
17582
|
-
var id2 = external_exports.string().min(1).max(256);
|
|
17583
|
-
var cursor = external_exports.string().min(1).max(4096).nullable().optional();
|
|
17584
|
-
var textInput = external_exports.object({
|
|
17585
|
-
type: external_exports.literal("text"),
|
|
17586
|
-
text: external_exports.string().min(1).max(256 * 1024)
|
|
17587
|
-
}).strict();
|
|
17588
|
-
var imageMediaType = external_exports.enum(["image/png", "image/jpeg", "image/webp", "image/gif"]);
|
|
17589
|
-
var canonicalBase64 = external_exports.string().min(4).max(288 * 1024 * 1024).refine((value) => value.length % 4 === 0 && /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/u.test(value), {
|
|
17590
|
-
message: "Image data must use canonical base64."
|
|
17591
|
-
});
|
|
17592
|
-
var imageInput = external_exports.object({
|
|
17593
|
-
type: external_exports.literal("image"),
|
|
17594
|
-
mediaType: imageMediaType,
|
|
17595
|
-
data: canonicalBase64
|
|
17596
|
-
}).strict();
|
|
17597
|
-
var input = external_exports.array(external_exports.union([textInput, imageInput])).min(1).max(16);
|
|
17598
|
-
var permissionPreset = external_exports.enum(["workspace-write", "danger-full-access"]);
|
|
17599
|
-
var projectRoot = external_exports.object({
|
|
17600
|
-
path: external_exports.string().min(1).max(4096)
|
|
17601
|
-
}).strict();
|
|
17602
|
-
var CODEX_HISTORY_MAX_MESSAGES = 200;
|
|
17603
|
-
var schemas = {
|
|
17604
|
-
"account/read": external_exports.object({ refreshToken: external_exports.literal(false).optional() }).strict(),
|
|
17605
|
-
"model/list": external_exports.object({
|
|
17606
|
-
cursor,
|
|
17607
|
-
limit: external_exports.number().int().min(1).max(100).optional(),
|
|
17608
|
-
includeHidden: external_exports.boolean().optional()
|
|
17609
|
-
}).strict(),
|
|
17610
|
-
"project/list": external_exports.object({
|
|
17611
|
-
cursor,
|
|
17612
|
-
limit: external_exports.number().int().min(1).max(100).optional()
|
|
17613
|
-
}).strict(),
|
|
17614
|
-
"project/create": external_exports.object({
|
|
17615
|
-
name: external_exports.string().trim().min(1).max(256),
|
|
17616
|
-
roots: external_exports.array(projectRoot).length(1),
|
|
17617
|
-
idempotencyKey: external_exports.string().min(16).max(256)
|
|
17618
|
-
}).strict(),
|
|
17619
|
-
"thread/list": external_exports.object({
|
|
17620
|
-
cursor,
|
|
17621
|
-
limit: external_exports.number().int().min(1).max(100).optional(),
|
|
17622
|
-
sortKey: external_exports.enum(["created_at", "updated_at", "recency_at"]).optional(),
|
|
17623
|
-
sortDirection: external_exports.enum(["asc", "desc"]).optional(),
|
|
17624
|
-
modelProviders: external_exports.array(external_exports.string().min(1).max(128)).max(32).nullable().optional(),
|
|
17625
|
-
sourceKinds: external_exports.array(external_exports.enum(["cli", "vscode", "exec", "appServer", "unknown"])).max(8).optional(),
|
|
17626
|
-
archived: external_exports.boolean().optional(),
|
|
17627
|
-
isPinned: external_exports.boolean().optional(),
|
|
17628
|
-
cwd: external_exports.union([external_exports.string().min(1).max(4096), external_exports.array(external_exports.string().min(1).max(4096)).min(1).max(32)]).optional(),
|
|
17629
|
-
useStateDbOnly: external_exports.boolean().optional(),
|
|
17630
|
-
searchTerm: external_exports.string().max(1024).optional(),
|
|
17631
|
-
// Upstream `ThreadListParams` declares these two as well. Refusing them turned a
|
|
17632
|
-
// legitimate client query into "The CodeX call parameters are invalid.".
|
|
17633
|
-
originators: external_exports.array(external_exports.string().min(1).max(256)).max(32).nullable().optional(),
|
|
17634
|
-
sectionId: external_exports.string().max(256).nullable().optional()
|
|
17635
|
-
}).strict(),
|
|
17636
|
-
"thread/read": external_exports.object({ threadId: id2, includeTurns: external_exports.boolean().optional() }).strict(),
|
|
17637
|
-
"dsh/sessionHistory": external_exports.object({
|
|
17638
|
-
threadId: id2,
|
|
17639
|
-
beforeSeq: external_exports.number().int().nonnegative().optional(),
|
|
17640
|
-
throughSeq: external_exports.number().int().min(-1).optional(),
|
|
17641
|
-
maxMessages: external_exports.number().int().min(1).max(CODEX_HISTORY_MAX_MESSAGES).optional()
|
|
17642
|
-
}).strict(),
|
|
17643
|
-
"dsh/directoryList": external_exports.object({
|
|
17644
|
-
path: external_exports.string().min(1).max(4096)
|
|
17645
|
-
}).strict(),
|
|
17646
|
-
"thread/start": external_exports.object({
|
|
17647
|
-
cwd: external_exports.string().min(1).max(4096),
|
|
17648
|
-
model: external_exports.string().min(1).max(128).optional(),
|
|
17649
|
-
personality: external_exports.string().min(1).max(64).optional(),
|
|
17650
|
-
permissionPreset: permissionPreset.optional()
|
|
17651
|
-
}).strict(),
|
|
17652
|
-
"thread/resume": external_exports.object({
|
|
17653
|
-
threadId: id2,
|
|
17654
|
-
model: external_exports.string().min(1).max(128).optional(),
|
|
17655
|
-
permissionPreset: permissionPreset.optional()
|
|
17656
|
-
}).strict(),
|
|
17657
|
-
"thread/fork": external_exports.object({
|
|
17658
|
-
threadId: id2,
|
|
17659
|
-
lastTurnId: id2.optional(),
|
|
17660
|
-
permissionPreset: permissionPreset.optional()
|
|
17661
|
-
}).strict(),
|
|
17662
|
-
// Upstream declares a plain string and uses the empty value to clear a name, so the
|
|
17663
|
-
// trim-based minimum rejected a legitimate rename.
|
|
17664
|
-
"thread/name/set": external_exports.object({ threadId: id2, name: external_exports.string().max(256) }).strict(),
|
|
17665
|
-
"thread/archive": external_exports.object({ threadId: id2 }).strict(),
|
|
17666
|
-
"thread/unarchive": external_exports.object({ threadId: id2 }).strict(),
|
|
17667
|
-
"thread/unsubscribe": external_exports.object({ threadId: id2 }).strict(),
|
|
17668
|
-
"turn/start": external_exports.object({
|
|
17669
|
-
threadId: id2,
|
|
17670
|
-
input,
|
|
17671
|
-
model: external_exports.string().min(1).max(128).optional(),
|
|
17672
|
-
effort: external_exports.enum(["none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra"]).optional(),
|
|
17673
|
-
summary: external_exports.enum(["auto", "concise", "detailed", "none"]).optional(),
|
|
17674
|
-
personality: external_exports.string().min(1).max(64).optional(),
|
|
17675
|
-
permissionPreset: permissionPreset.optional()
|
|
17676
|
-
}).strict(),
|
|
17677
|
-
"turn/steer": external_exports.object({
|
|
17678
|
-
threadId: id2,
|
|
17679
|
-
input,
|
|
17680
|
-
expectedTurnId: id2,
|
|
17681
|
-
// Declared by upstream `TurnSteerParams`; see the thread/list note above.
|
|
17682
|
-
clientUserMessageId: external_exports.string().max(256).nullable().optional()
|
|
17683
|
-
}).strict(),
|
|
17684
|
-
"turn/interrupt": external_exports.object({ threadId: id2, turnId: id2 }).strict()
|
|
17685
|
-
};
|
|
17686
|
-
var CODEX_APP_ALLOWLIST = Object.freeze(Object.keys(schemas));
|
|
17687
|
-
function parseCodexCall(method, params) {
|
|
17688
|
-
if (!Object.prototype.hasOwnProperty.call(schemas, method)) {
|
|
17689
|
-
throw new RpcError("METHOD_NOT_ALLOWED", "The requested Codex method is not available over Remote.");
|
|
17690
|
-
}
|
|
17691
|
-
const schema = schemas[method];
|
|
17692
|
-
const parsed = schema.safeParse(params);
|
|
17693
|
-
if (!parsed.success) {
|
|
17694
|
-
const detail = parsed.error.issues.slice(0, 4).map((issue) => {
|
|
17695
|
-
const path = issue.path.length === 0 ? "(root)" : issue.path.join(".");
|
|
17696
|
-
const keys = "keys" in issue && Array.isArray(issue.keys) ? ` [${issue.keys.join(", ")}]` : "";
|
|
17697
|
-
return `${path}${keys}: ${issue.code}`;
|
|
17698
|
-
}).join(", ");
|
|
17699
|
-
throw new RpcError("INVALID_MESSAGE", `The CodeX call parameters are invalid. (${method} \u2192 ${detail})`);
|
|
17700
|
-
}
|
|
17701
|
-
return { method, params: parsed.data };
|
|
17702
|
-
}
|
|
17703
|
-
function isThreadMutation(method) {
|
|
17704
|
-
return method === "turn/start" || method === "turn/steer" || method === "turn/interrupt";
|
|
17705
|
-
}
|
|
17706
|
-
function threadIdFromParams(params) {
|
|
17707
|
-
return typeof params.threadId === "string" ? params.threadId : void 0;
|
|
17708
|
-
}
|
|
17709
|
-
|
|
17710
17655
|
// src/codex/codex-image-codec.ts
|
|
17711
17656
|
var CODEX_IMAGE_MEDIA_TYPES2 = /* @__PURE__ */ new Set(["image/png", "image/jpeg", "image/webp", "image/gif"]);
|
|
17712
17657
|
var CODEX_IMAGE_ATTACHMENT_PREFIX = "codex-image:";
|
|
@@ -17941,9 +17886,10 @@ var CODEX_MODEL = "codex";
|
|
|
17941
17886
|
var CODEX_PAGE_LIMIT = 100;
|
|
17942
17887
|
var MAX_CODEX_PAGES = 32;
|
|
17943
17888
|
var MAX_LIVE_TOOL_OUTPUT = 128 * 1024;
|
|
17944
|
-
var DEFAULT_HISTORY_MESSAGES = 50;
|
|
17945
17889
|
var SESSION_SEARCH_RESULT_LIMIT = 20;
|
|
17946
17890
|
var SESSION_SEARCH_SNIPPET_CODE_POINTS = 240;
|
|
17891
|
+
var HISTORY_PAGE_CACHE_TTL_MS = 6e4;
|
|
17892
|
+
var HISTORY_PAGE_CACHE_LIMIT = 24;
|
|
17947
17893
|
var CODEX_DEFAULT_PERMISSION = "workspace-write";
|
|
17948
17894
|
var MAX_CODEX_PROMPT_PARTS = 16;
|
|
17949
17895
|
var MAX_CODEX_PROMPT_TEXT = 256 * 1024;
|
|
@@ -17990,6 +17936,7 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
|
|
|
17990
17936
|
this.sessionGeneration = sessionGeneration;
|
|
17991
17937
|
this.hostCarrier = hostCarrier;
|
|
17992
17938
|
this.api = this.createApiProxy();
|
|
17939
|
+
this.installHistoryCacheInspector();
|
|
17993
17940
|
this.stopCapabilityWatch = this.client.watchCapability?.((available) => {
|
|
17994
17941
|
if (!available) this.onSessionLost?.("domain-notice");
|
|
17995
17942
|
});
|
|
@@ -18009,6 +17956,10 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
|
|
|
18009
17956
|
selectedModels = /* @__PURE__ */ new Map();
|
|
18010
17957
|
selectedPermissions = /* @__PURE__ */ new Map();
|
|
18011
17958
|
imageAttachments = /* @__PURE__ */ new Map();
|
|
17959
|
+
historyPageCache = /* @__PURE__ */ new Map();
|
|
17960
|
+
historyPageHits = 0;
|
|
17961
|
+
historyPageMisses = 0;
|
|
17962
|
+
historyCacheInspector;
|
|
18012
17963
|
modelDirectory;
|
|
18013
17964
|
modelDirectoryPromise;
|
|
18014
17965
|
lastProjectionSeq = 0;
|
|
@@ -18124,6 +18075,10 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
|
|
|
18124
18075
|
return business(await this.archiveSession(requestArg(args), signal));
|
|
18125
18076
|
case "session/list":
|
|
18126
18077
|
return business(success({ items: await this.sessionSummaries(signal) }));
|
|
18078
|
+
case "thread/list": {
|
|
18079
|
+
const catalog = await this.refreshCatalog(signal);
|
|
18080
|
+
return business(success({ data: catalog.threads, nextCursor: null }));
|
|
18081
|
+
}
|
|
18127
18082
|
case "session/search":
|
|
18128
18083
|
return business(await this.searchSessions(requestArg(args), signal));
|
|
18129
18084
|
case "session/create":
|
|
@@ -18237,6 +18192,17 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
|
|
|
18237
18192
|
this.selectedModels.clear();
|
|
18238
18193
|
this.selectedPermissions.clear();
|
|
18239
18194
|
this.imageAttachments.clear();
|
|
18195
|
+
this.historyPageCache.clear();
|
|
18196
|
+
const globalScope = globalThis;
|
|
18197
|
+
if (this.historyCacheInspector !== void 0 && globalScope.__dshCodexHistoryCacheStats === this.historyCacheInspector) {
|
|
18198
|
+
delete globalScope.__dshCodexHistoryCacheStats;
|
|
18199
|
+
}
|
|
18200
|
+
const scope = globalThis;
|
|
18201
|
+
if (scope.window !== void 0 && this.historyCacheInspector !== void 0 && scope.window.__dshCodexHistoryCacheStats === this.historyCacheInspector) {
|
|
18202
|
+
delete scope.window.__dshCodexHistoryCacheStats;
|
|
18203
|
+
}
|
|
18204
|
+
scope.document?.documentElement?.removeAttribute?.("data-dsh-codex-history-cache-stats");
|
|
18205
|
+
this.historyCacheInspector = void 0;
|
|
18240
18206
|
}
|
|
18241
18207
|
async refreshCatalog(signal) {
|
|
18242
18208
|
const catalog = await loadCatalog(this.client, signal, this.pendingThreads);
|
|
@@ -18510,6 +18476,7 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
|
|
|
18510
18476
|
});
|
|
18511
18477
|
}
|
|
18512
18478
|
acceptCodexFrame(follow, frame) {
|
|
18479
|
+
this.invalidateHistoryPages(follow.threadId);
|
|
18513
18480
|
const params = record3(frame.params);
|
|
18514
18481
|
if (frame.method === "thread/name/updated") {
|
|
18515
18482
|
const name2 = string2(params.threadName);
|
|
@@ -19162,29 +19129,27 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
|
|
|
19162
19129
|
return thread;
|
|
19163
19130
|
}
|
|
19164
19131
|
async readHistoryPage(threadId, page, signal) {
|
|
19165
|
-
const maxMessages =
|
|
19166
|
-
|
|
19167
|
-
|
|
19168
|
-
|
|
19169
|
-
|
|
19170
|
-
|
|
19171
|
-
|
|
19172
|
-
|
|
19173
|
-
|
|
19174
|
-
|
|
19175
|
-
|
|
19176
|
-
|
|
19177
|
-
|
|
19178
|
-
|
|
19179
|
-
|
|
19180
|
-
|
|
19181
|
-
|
|
19182
|
-
|
|
19183
|
-
|
|
19184
|
-
|
|
19185
|
-
|
|
19186
|
-
);
|
|
19187
|
-
}
|
|
19132
|
+
const maxMessages = Math.min(
|
|
19133
|
+
HISTORY_PAGE_SIZES[0],
|
|
19134
|
+
Math.max(1, page.maxMessages ?? HISTORY_PAGE_SIZES[0])
|
|
19135
|
+
);
|
|
19136
|
+
const cacheKey = `${this.host.deviceId}|${threadId}|${page.beforeSeq ?? ""}|${page.throughSeq ?? ""}|${maxMessages}`;
|
|
19137
|
+
const cached = this.historyPageCache.get(cacheKey);
|
|
19138
|
+
if (cached !== void 0 && Date.now() - cached.at < HISTORY_PAGE_CACHE_TTL_MS) {
|
|
19139
|
+
const ageMs = Date.now() - cached.at;
|
|
19140
|
+
this.historyPageHits += 1;
|
|
19141
|
+
this.cacheHistoryImages(cached.page);
|
|
19142
|
+
this.publishHistoryCacheStats();
|
|
19143
|
+
this.logHistoryCache("hit", threadId, { ...page, maxMessages }, cached.page.records.length, ageMs);
|
|
19144
|
+
return cached.page;
|
|
19145
|
+
}
|
|
19146
|
+
this.historyPageMisses += 1;
|
|
19147
|
+
let value = record3(await this.client.request("dsh/sessionHistory", {
|
|
19148
|
+
threadId,
|
|
19149
|
+
...page.beforeSeq === void 0 ? {} : { beforeSeq: page.beforeSeq },
|
|
19150
|
+
...page.throughSeq === void 0 ? {} : { throughSeq: page.throughSeq },
|
|
19151
|
+
maxMessages
|
|
19152
|
+
}, signal));
|
|
19188
19153
|
value = adaptCodexHistoryPage(value, this.sessionGeneration);
|
|
19189
19154
|
if (!isCodexNativeHistoryPage(value, `codex:${threadId}`)) {
|
|
19190
19155
|
throw new Error("CodeX Remote returned an invalid paginated History.");
|
|
@@ -19194,8 +19159,115 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
|
|
|
19194
19159
|
this.setPermissionSelection(`codex:${threadId}`, preset === "workspace-write" || preset === "danger-full-access" ? preset : void 0);
|
|
19195
19160
|
}
|
|
19196
19161
|
this.cacheHistoryImages(value);
|
|
19162
|
+
this.historyPageCache.set(cacheKey, { page: value, at: Date.now() });
|
|
19163
|
+
if (this.historyPageCache.size > HISTORY_PAGE_CACHE_LIMIT) {
|
|
19164
|
+
const oldest = this.historyPageCache.keys().next().value;
|
|
19165
|
+
if (oldest !== void 0) this.historyPageCache.delete(oldest);
|
|
19166
|
+
}
|
|
19167
|
+
this.publishHistoryCacheStats();
|
|
19168
|
+
this.logHistoryCache("miss", threadId, { ...page, maxMessages }, value.records.length);
|
|
19197
19169
|
return value;
|
|
19198
19170
|
}
|
|
19171
|
+
/** A live change or a new turn makes the cached page stale. */
|
|
19172
|
+
invalidateHistoryPages(threadId) {
|
|
19173
|
+
const marker = `|${threadId}|`;
|
|
19174
|
+
let removed = false;
|
|
19175
|
+
for (const key of [...this.historyPageCache.keys()]) {
|
|
19176
|
+
if (key.includes(marker)) {
|
|
19177
|
+
this.historyPageCache.delete(key);
|
|
19178
|
+
removed = true;
|
|
19179
|
+
}
|
|
19180
|
+
}
|
|
19181
|
+
if (removed) this.publishHistoryCacheStats();
|
|
19182
|
+
}
|
|
19183
|
+
/**
|
|
19184
|
+
* Expose cache metadata to the current page's DevTools as
|
|
19185
|
+
* `__dshCodexHistoryCacheStats()`. The function is metadata only: it never
|
|
19186
|
+
* returns message text or event data, so the Session content stays private.
|
|
19187
|
+
* It is installed on both the realm global and `window`, and mirrored into a
|
|
19188
|
+
* DOM data attribute so a console in another realm can read it too.
|
|
19189
|
+
*/
|
|
19190
|
+
installHistoryCacheInspector() {
|
|
19191
|
+
const globalScope = globalThis;
|
|
19192
|
+
const scope = globalThis;
|
|
19193
|
+
const inspector = () => this.historyCacheStats();
|
|
19194
|
+
globalScope.__dshCodexHistoryCacheStats = inspector;
|
|
19195
|
+
if (scope.window !== void 0) scope.window.__dshCodexHistoryCacheStats = inspector;
|
|
19196
|
+
this.historyCacheInspector = inspector;
|
|
19197
|
+
this.publishHistoryCacheStats();
|
|
19198
|
+
scope.console?.info?.("[dsh-remote] codex history cache inspector ready; call __dshCodexHistoryCacheStats()");
|
|
19199
|
+
}
|
|
19200
|
+
publishHistoryCacheStats() {
|
|
19201
|
+
const stats = this.historyCacheStats();
|
|
19202
|
+
this.writeHistoryCacheStatsFile(stats);
|
|
19203
|
+
const scope = globalThis;
|
|
19204
|
+
const root = scope.document?.documentElement;
|
|
19205
|
+
if (root?.setAttribute === void 0) return;
|
|
19206
|
+
try {
|
|
19207
|
+
root.setAttribute("data-dsh-codex-history-cache-stats", JSON.stringify(stats));
|
|
19208
|
+
} catch {
|
|
19209
|
+
}
|
|
19210
|
+
}
|
|
19211
|
+
/**
|
|
19212
|
+
* The Codex virtual harness runs in the DSH client's Node process, not in the
|
|
19213
|
+
* browser page, so DevTools cannot see its globals. Mirror the same metadata to
|
|
19214
|
+
* the client home for inspection without any message content.
|
|
19215
|
+
*/
|
|
19216
|
+
writeHistoryCacheStatsFile(stats) {
|
|
19217
|
+
if (!codexHistoryStatsFileEnabled() || process.env.VITEST !== void 0) return;
|
|
19218
|
+
try {
|
|
19219
|
+
const home = process.env.DSH_HOME ?? join4(homedir(), ".dsh");
|
|
19220
|
+
mkdirSync(home, { recursive: true });
|
|
19221
|
+
writeFileSync(
|
|
19222
|
+
join4(home, "codex-history-cache-stats.json"),
|
|
19223
|
+
JSON.stringify({
|
|
19224
|
+
...stats,
|
|
19225
|
+
pid: process.pid,
|
|
19226
|
+
source: "codex-harness",
|
|
19227
|
+
writtenAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
19228
|
+
}, null, 2)
|
|
19229
|
+
);
|
|
19230
|
+
} catch {
|
|
19231
|
+
}
|
|
19232
|
+
}
|
|
19233
|
+
logHistoryCache(event, threadId, page, records, ageMs) {
|
|
19234
|
+
const scope = globalThis;
|
|
19235
|
+
scope.console?.info?.("[dsh-remote] codex history page cache " + event, {
|
|
19236
|
+
threadId,
|
|
19237
|
+
beforeSeq: page.beforeSeq ?? null,
|
|
19238
|
+
...page.maxMessages === void 0 ? {} : { maxMessages: page.maxMessages },
|
|
19239
|
+
...records === void 0 ? {} : { records },
|
|
19240
|
+
...ageMs === void 0 ? {} : { ageMs },
|
|
19241
|
+
hits: this.historyPageHits,
|
|
19242
|
+
misses: this.historyPageMisses
|
|
19243
|
+
});
|
|
19244
|
+
}
|
|
19245
|
+
historyCacheStats() {
|
|
19246
|
+
const now = Date.now();
|
|
19247
|
+
return {
|
|
19248
|
+
ttlMs: HISTORY_PAGE_CACHE_TTL_MS,
|
|
19249
|
+
hits: this.historyPageHits,
|
|
19250
|
+
misses: this.historyPageMisses,
|
|
19251
|
+
active: true,
|
|
19252
|
+
entries: [...this.historyPageCache.entries()].map(([key, { page, at }]) => {
|
|
19253
|
+
const records = page.records;
|
|
19254
|
+
const firstSeq = records[0]?.event.seq;
|
|
19255
|
+
const lastSeq = records.at(-1)?.event.seq;
|
|
19256
|
+
return {
|
|
19257
|
+
key,
|
|
19258
|
+
ageMs: now - at,
|
|
19259
|
+
expired: now - at >= HISTORY_PAGE_CACHE_TTL_MS,
|
|
19260
|
+
sessionId: page.header.id,
|
|
19261
|
+
records: records.length,
|
|
19262
|
+
cursor: page.cursor,
|
|
19263
|
+
nextTurn: page.nextTurn,
|
|
19264
|
+
hasMore: page.hasMore,
|
|
19265
|
+
...firstSeq === void 0 ? {} : { firstSeq },
|
|
19266
|
+
...lastSeq === void 0 ? {} : { lastSeq }
|
|
19267
|
+
};
|
|
19268
|
+
})
|
|
19269
|
+
};
|
|
19270
|
+
}
|
|
19199
19271
|
cacheHistoryImages(history) {
|
|
19200
19272
|
const sessionId = history.header.id;
|
|
19201
19273
|
const entries = "entries" in history ? history.entries : history.records;
|
|
@@ -19457,12 +19529,26 @@ async function loadCatalog(client, signal, pendingThreads) {
|
|
|
19457
19529
|
const workspaceByProjectId = new Map(projects.map((project, index) => [project.id, projectWorkspaces[index]]));
|
|
19458
19530
|
const visibleSessionIds = /* @__PURE__ */ new Set();
|
|
19459
19531
|
const visibleThreads = [];
|
|
19532
|
+
const unmatched = [];
|
|
19460
19533
|
for (const { thread, session } of projected) {
|
|
19461
19534
|
const projectWorkspaceForSession = findProjectWorkspace(thread, session, projects, workspaceByProjectId);
|
|
19462
19535
|
if (projectWorkspaceForSession !== void 0) {
|
|
19536
|
+
rebaseSessionCwdOnProjectRoot(session, projectWorkspaceForSession.path);
|
|
19537
|
+
rebaseThreadCwdOnProjectRoot(thread, projectWorkspaceForSession.path);
|
|
19463
19538
|
addSessionToWorkspace(projectWorkspaceForSession, session);
|
|
19464
19539
|
visibleSessionIds.add(session.id);
|
|
19465
19540
|
visibleThreads.push(thread);
|
|
19541
|
+
} else {
|
|
19542
|
+
unmatched.push({ thread, session });
|
|
19543
|
+
}
|
|
19544
|
+
}
|
|
19545
|
+
if (unmatched.length > 0) {
|
|
19546
|
+
const workspace = defaultCodexWorkspace(unmatched.map((value) => value.session));
|
|
19547
|
+
projectWorkspaces.push(workspace);
|
|
19548
|
+
for (const { thread, session } of unmatched) {
|
|
19549
|
+
addSessionToWorkspace(workspace, session);
|
|
19550
|
+
visibleSessionIds.add(session.id);
|
|
19551
|
+
visibleThreads.push(thread);
|
|
19466
19552
|
}
|
|
19467
19553
|
}
|
|
19468
19554
|
const sessions = projected.map((value) => value.session).filter((session) => visibleSessionIds.has(session.id));
|
|
@@ -19553,13 +19639,17 @@ async function loadModelDirectory(client, signal) {
|
|
|
19553
19639
|
models
|
|
19554
19640
|
};
|
|
19555
19641
|
}
|
|
19556
|
-
function projectCodexNativeHistory(thread, sessionId, sessionGeneration = "legacy") {
|
|
19642
|
+
function projectCodexNativeHistory(thread, sessionId, sessionGeneration = "legacy", start = {}) {
|
|
19557
19643
|
const entries = [];
|
|
19558
|
-
let seq = 0;
|
|
19559
|
-
let turnNumber = 0;
|
|
19644
|
+
let seq = start.seq ?? 0;
|
|
19645
|
+
let turnNumber = start.turn ?? 0;
|
|
19646
|
+
let turnSeqBase = 0;
|
|
19647
|
+
let turnEventIndex = 0;
|
|
19648
|
+
let lastSeq = start.stable === true ? -1 : (start.seq ?? 0) - 1;
|
|
19560
19649
|
const active = activeTurnId(thread.turns);
|
|
19561
19650
|
const append = (type, data2, time = Date.now(), view, sourceEventSeqs) => {
|
|
19562
|
-
const eventSeq = seq++;
|
|
19651
|
+
const eventSeq = start.stable === true ? turnSeqBase + turnEventIndex++ : seq++;
|
|
19652
|
+
lastSeq = eventSeq;
|
|
19563
19653
|
entries.push({
|
|
19564
19654
|
type: "event",
|
|
19565
19655
|
event: {
|
|
@@ -19576,13 +19666,16 @@ function projectCodexNativeHistory(thread, sessionId, sessionGeneration = "legac
|
|
|
19576
19666
|
};
|
|
19577
19667
|
for (const rawTurn of array(thread.turns)) {
|
|
19578
19668
|
const turn = record3(rawTurn);
|
|
19669
|
+
const turnId = string2(turn.id);
|
|
19579
19670
|
const turnItems = array(turn.items).map(record3);
|
|
19580
19671
|
const toolImages = turnItems.flatMap(toolOutputImageBlocks);
|
|
19581
19672
|
let imageOwner = -1;
|
|
19582
19673
|
for (let index = 0; index < turnItems.length; index += 1) {
|
|
19583
19674
|
if (turnItems[index].type === "agentMessage") imageOwner = index;
|
|
19584
19675
|
}
|
|
19585
|
-
turnNumber
|
|
19676
|
+
turnNumber = (turnId === void 0 ? void 0 : start.turnNumbers?.get(turnId)) ?? turnNumber + 1;
|
|
19677
|
+
turnSeqBase = turnNumber * 4096;
|
|
19678
|
+
turnEventIndex = 0;
|
|
19586
19679
|
const turnActive = isActiveCodexTurn(turn);
|
|
19587
19680
|
const time = normalizeTime(turn.createdAt) || normalizeTime(turn.startedAt) || normalizeTime(thread.createdAt) || Date.now();
|
|
19588
19681
|
append("turn/start", { turn: turnNumber }, time);
|
|
@@ -19626,7 +19719,7 @@ function projectCodexNativeHistory(thread, sessionId, sessionGeneration = "legac
|
|
|
19626
19719
|
...sessionGeneration === "v3" ? { isSeeded: false } : {}
|
|
19627
19720
|
},
|
|
19628
19721
|
entries,
|
|
19629
|
-
lastSeq
|
|
19722
|
+
lastSeq,
|
|
19630
19723
|
nextTurn: turnNumber,
|
|
19631
19724
|
...active === void 0 ? {} : { activeTurnId: active }
|
|
19632
19725
|
};
|
|
@@ -19635,7 +19728,7 @@ function paginateCodexNativeHistory(history, request) {
|
|
|
19635
19728
|
const throughSeq = Math.min(request.throughSeq ?? history.lastSeq, history.lastSeq);
|
|
19636
19729
|
const endSeq = Math.min(throughSeq, request.beforeSeq === void 0 ? throughSeq : request.beforeSeq - 1);
|
|
19637
19730
|
const window = endSeq < 0 ? [] : history.entries.filter((entry) => entry.event.seq <= endSeq);
|
|
19638
|
-
const maxMessages = request.maxMessages ??
|
|
19731
|
+
const maxMessages = request.maxMessages ?? HISTORY_PAGE_SIZES[0];
|
|
19639
19732
|
let messages = 0;
|
|
19640
19733
|
let cut = 0;
|
|
19641
19734
|
for (let index = window.length - 1; index >= 0; index -= 1) {
|
|
@@ -19910,6 +20003,20 @@ function nativeWorkspace(view) {
|
|
|
19910
20003
|
function nativeVisibleWorkspaces(catalog, selectedWorkspaceId) {
|
|
19911
20004
|
return catalog.workspaces.filter((workspace) => workspace.sessionIds.length > 0 || workspace.workspaceId === selectedWorkspaceId);
|
|
19912
20005
|
}
|
|
20006
|
+
var DEFAULT_CODEX_WORKSPACE_ID = "codex-workspace:default";
|
|
20007
|
+
var DEFAULT_CODEX_WORKSPACE_TITLE = "\u9ED8\u8BA4\u5DE5\u4F5C\u533A";
|
|
20008
|
+
function defaultCodexWorkspace(sessions) {
|
|
20009
|
+
const first = sessions[0];
|
|
20010
|
+
return {
|
|
20011
|
+
workspaceId: DEFAULT_CODEX_WORKSPACE_ID,
|
|
20012
|
+
path: sessions.find((session) => session.cwd !== void 0)?.cwd ?? "/",
|
|
20013
|
+
title: DEFAULT_CODEX_WORKSPACE_TITLE,
|
|
20014
|
+
sessionIds: [],
|
|
20015
|
+
sessionCount: 0,
|
|
20016
|
+
createdAt: isoTime(first.createdAt),
|
|
20017
|
+
updatedAt: isoTime(first.updatedAt || first.createdAt)
|
|
20018
|
+
};
|
|
20019
|
+
}
|
|
19913
20020
|
function projectWorkspace(project) {
|
|
19914
20021
|
const root = project.roots[0];
|
|
19915
20022
|
return {
|
|
@@ -19951,6 +20058,28 @@ function findProjectWorkspace(thread, session, projects, workspaceByProjectId) {
|
|
|
19951
20058
|
})[0];
|
|
19952
20059
|
return match === void 0 ? void 0 : workspaceByProjectId.get(match.project.id);
|
|
19953
20060
|
}
|
|
20061
|
+
function rebasedCwdOnProjectRoot(cwd, projectRoot2) {
|
|
20062
|
+
if (process.platform !== "win32") return void 0;
|
|
20063
|
+
const root = projectRoot2.replace(/[\\/]+$/u, "");
|
|
20064
|
+
if (root.length === 0) return void 0;
|
|
20065
|
+
const normalizedRoot = root.toLowerCase();
|
|
20066
|
+
const normalizedCwd = cwd.toLowerCase();
|
|
20067
|
+
if (normalizedCwd === normalizedRoot) return root;
|
|
20068
|
+
if (normalizedCwd.startsWith(`${normalizedRoot}\\`) || normalizedCwd.startsWith(`${normalizedRoot}/`)) {
|
|
20069
|
+
return `${root}${cwd.slice(root.length)}`;
|
|
20070
|
+
}
|
|
20071
|
+
return void 0;
|
|
20072
|
+
}
|
|
20073
|
+
function rebaseSessionCwdOnProjectRoot(session, projectRoot2) {
|
|
20074
|
+
if (session.cwd === void 0) return;
|
|
20075
|
+
const next = rebasedCwdOnProjectRoot(session.cwd, projectRoot2);
|
|
20076
|
+
if (next !== void 0) session.cwd = next;
|
|
20077
|
+
}
|
|
20078
|
+
function rebaseThreadCwdOnProjectRoot(thread, projectRoot2) {
|
|
20079
|
+
if (typeof thread.cwd !== "string") return;
|
|
20080
|
+
const next = rebasedCwdOnProjectRoot(thread.cwd, projectRoot2);
|
|
20081
|
+
if (next !== void 0) thread.cwd = next;
|
|
20082
|
+
}
|
|
19954
20083
|
function addSessionToWorkspace(workspace, session) {
|
|
19955
20084
|
if (!workspace.sessionIds.includes(session.id)) {
|
|
19956
20085
|
workspace.sessionIds.push(session.id);
|
|
@@ -19967,7 +20096,8 @@ function containsPath(root, candidate) {
|
|
|
19967
20096
|
return normalizedCandidate === normalizedRoot || normalizedCandidate.startsWith(`${normalizedRoot}/`) || normalizedCandidate.startsWith(`${normalizedRoot}\\`);
|
|
19968
20097
|
}
|
|
19969
20098
|
function normalizePathForCompare(path) {
|
|
19970
|
-
|
|
20099
|
+
const trimmed = path.replace(/[\\/]+$/u, "") || path;
|
|
20100
|
+
return process.platform === "win32" ? trimmed.toLowerCase() : trimmed;
|
|
19971
20101
|
}
|
|
19972
20102
|
function modelSelection() {
|
|
19973
20103
|
return { provider: CODEX_PROVIDER, model: CODEX_MODEL };
|
|
@@ -20217,10 +20347,6 @@ function failFrom(error) {
|
|
|
20217
20347
|
const source = error instanceof Error ? error : new Error(String(error));
|
|
20218
20348
|
return fail(errorCode3(source) ?? "internal", source.message, errorDetails(source));
|
|
20219
20349
|
}
|
|
20220
|
-
function isLegacySessionHistoryUnsupported(error) {
|
|
20221
|
-
if (!(error instanceof Error)) return false;
|
|
20222
|
-
return errorCode3(error) === "METHOD_NOT_ALLOWED" || errorCode3(error) === "METHOD_NOT_FOUND" || error.message.includes("The requested Codex method is not available over Remote.");
|
|
20223
|
-
}
|
|
20224
20350
|
function errorCode3(error) {
|
|
20225
20351
|
return "code" in error && typeof error.code === "string" ? error.code : void 0;
|
|
20226
20352
|
}
|
|
@@ -20720,7 +20846,7 @@ import { networkInterfaces } from "node:os";
|
|
|
20720
20846
|
// src/native-rtc-helper.ts
|
|
20721
20847
|
import { spawn, spawnSync } from "node:child_process";
|
|
20722
20848
|
import { existsSync, readdirSync, realpathSync, statSync } from "node:fs";
|
|
20723
|
-
import { delimiter, dirname as dirname3, join as
|
|
20849
|
+
import { delimiter, dirname as dirname3, join as join5 } from "node:path";
|
|
20724
20850
|
import { fileURLToPath } from "node:url";
|
|
20725
20851
|
var cachedExternalFactory;
|
|
20726
20852
|
var cachedExternalFactoryResolved = false;
|
|
@@ -20771,21 +20897,21 @@ function nodeBinaryCandidates() {
|
|
|
20771
20897
|
add3(process.env.DSH_REMOTE_NODE);
|
|
20772
20898
|
add3(process.env.NODE);
|
|
20773
20899
|
if (process.versions.electron === void 0) add3(process.execPath);
|
|
20774
|
-
for (const part of (process.env.PATH ?? "").split(delimiter)) add3(
|
|
20900
|
+
for (const part of (process.env.PATH ?? "").split(delimiter)) add3(join5(part, process.platform === "win32" ? "node.exe" : "node"));
|
|
20775
20901
|
add3("/opt/homebrew/bin/node");
|
|
20776
20902
|
add3("/usr/local/bin/node");
|
|
20777
20903
|
add3("/usr/bin/node");
|
|
20778
|
-
add3(
|
|
20779
|
-
add3(
|
|
20780
|
-
add3(
|
|
20904
|
+
add3(join5(process.env.HOME ?? "", ".volta", "bin", process.platform === "win32" ? "node.exe" : "node"));
|
|
20905
|
+
add3(join5(process.env.HOME ?? "", ".asdf", "shims", process.platform === "win32" ? "node.exe" : "node"));
|
|
20906
|
+
add3(join5(process.env.HOME ?? "", ".local", "bin", process.platform === "win32" ? "node.exe" : "node"));
|
|
20781
20907
|
for (const nvmNode of nvmNodeCandidates()) add3(nvmNode);
|
|
20782
20908
|
return candidates;
|
|
20783
20909
|
}
|
|
20784
20910
|
function nvmNodeCandidates() {
|
|
20785
|
-
const root =
|
|
20911
|
+
const root = join5(process.env.HOME ?? "", ".nvm", "versions", "node");
|
|
20786
20912
|
if (root === "" || !existsSync(root)) return [];
|
|
20787
20913
|
try {
|
|
20788
|
-
return readdirSync(root).map((version) =>
|
|
20914
|
+
return readdirSync(root).map((version) => join5(root, version, "bin", process.platform === "win32" ? "node.exe" : "node")).sort((left, right) => right.localeCompare(left, "en", { numeric: true }));
|
|
20789
20915
|
} catch {
|
|
20790
20916
|
return [];
|
|
20791
20917
|
}
|
|
@@ -21815,6 +21941,7 @@ var ClientModeRuntime = class {
|
|
|
21815
21941
|
this.proxySwitch = apiProxy === void 0 ? void 0 : new ApiProxySwitch(apiProxy);
|
|
21816
21942
|
this.gatewaySwitch = new TypertGatewaySwitch(typertGateway, { peer: this.peer });
|
|
21817
21943
|
this.gatewaySwitch.setRemoteAvailability(() => this.connected !== void 0);
|
|
21944
|
+
this.installCodexHistoryCacheInspector();
|
|
21818
21945
|
}
|
|
21819
21946
|
preview;
|
|
21820
21947
|
identity;
|
|
@@ -23269,11 +23396,49 @@ var ClientModeRuntime = class {
|
|
|
23269
23396
|
const virtual = this.codexVirtual;
|
|
23270
23397
|
this.codexVirtual = void 0;
|
|
23271
23398
|
await virtual?.close();
|
|
23399
|
+
this.installCodexHistoryCacheInspector();
|
|
23272
23400
|
}
|
|
23273
|
-
|
|
23274
|
-
|
|
23401
|
+
/**
|
|
23402
|
+
* Keep `__dshCodexHistoryCacheStats()` defined for the whole client lifetime.
|
|
23403
|
+
* The Codex virtual harness has its own inspector while a workspace is open;
|
|
23404
|
+
* this runtime-level one fills the gap before the first open and after close.
|
|
23405
|
+
*/
|
|
23406
|
+
installCodexHistoryCacheInspector() {
|
|
23407
|
+
const globalScope = globalThis;
|
|
23408
|
+
const scope = globalThis;
|
|
23409
|
+
const inspect = () => this.codexVirtual?.historyCacheStats() ?? {
|
|
23410
|
+
ttlMs: 6e4,
|
|
23411
|
+
hits: 0,
|
|
23412
|
+
misses: 0,
|
|
23413
|
+
active: false,
|
|
23414
|
+
entries: []
|
|
23415
|
+
};
|
|
23416
|
+
globalScope.__dshCodexHistoryCacheStats = inspect;
|
|
23417
|
+
if (scope.window !== void 0) scope.window.__dshCodexHistoryCacheStats = inspect;
|
|
23418
|
+
this.writeCodexHistoryCacheStats(inspect());
|
|
23419
|
+
scope.console?.info?.("[dsh-remote] codex history cache inspector ready (client runtime)");
|
|
23275
23420
|
}
|
|
23276
|
-
|
|
23421
|
+
writeCodexHistoryCacheStats(stats) {
|
|
23422
|
+
if (!codexHistoryStatsFileEnabled() || process.env.VITEST !== void 0) return;
|
|
23423
|
+
try {
|
|
23424
|
+
const home = process.env.DSH_HOME ?? join6(homedir2(), ".dsh");
|
|
23425
|
+
mkdirSync2(home, { recursive: true });
|
|
23426
|
+
writeFileSync2(
|
|
23427
|
+
join6(home, "codex-history-cache-stats.json"),
|
|
23428
|
+
JSON.stringify({
|
|
23429
|
+
...stats,
|
|
23430
|
+
pid: process.pid,
|
|
23431
|
+
source: "client-runtime",
|
|
23432
|
+
writtenAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
23433
|
+
}, null, 2)
|
|
23434
|
+
);
|
|
23435
|
+
} catch {
|
|
23436
|
+
}
|
|
23437
|
+
}
|
|
23438
|
+
assertRemoteCompatible(remote) {
|
|
23439
|
+
this.selectHarnessRemoteTransport(remote);
|
|
23440
|
+
}
|
|
23441
|
+
selectHarnessRemoteTransport(remote) {
|
|
23277
23442
|
const localRemoteGateway = this.gatewaySwitch.supportsCarrier();
|
|
23278
23443
|
const localSessionGeneration = harnessSessionGeneration(this.host?.localHarnessVersion?.());
|
|
23279
23444
|
if (localRemoteGateway && remote.features.remoteGateway) {
|
|
@@ -23976,14 +24141,14 @@ function iceServersForAttempt(attempt, iceServers) {
|
|
|
23976
24141
|
// src/client-target-store.ts
|
|
23977
24142
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
23978
24143
|
import { chmod as chmod2, mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2 } from "node:fs/promises";
|
|
23979
|
-
import { join as
|
|
24144
|
+
import { join as join7 } from "node:path";
|
|
23980
24145
|
var FILE_NAME = "client-target.json";
|
|
23981
24146
|
var ClientTargetStore = class {
|
|
23982
24147
|
constructor(directory) {
|
|
23983
24148
|
this.directory = directory;
|
|
23984
24149
|
}
|
|
23985
24150
|
get file() {
|
|
23986
|
-
return
|
|
24151
|
+
return join7(this.directory, FILE_NAME);
|
|
23987
24152
|
}
|
|
23988
24153
|
/** Read the record. A missing, unreadable or malformed file reads as "no record". */
|
|
23989
24154
|
async load() {
|
|
@@ -24059,8 +24224,8 @@ import { execFileSync } from "node:child_process";
|
|
|
24059
24224
|
import { existsSync as existsSync2 } from "node:fs";
|
|
24060
24225
|
import { createHash } from "node:crypto";
|
|
24061
24226
|
import { chmod as chmod3, cp, mkdir as mkdir3, readFile as readFile4, rm as rm3, stat as stat3, writeFile as writeFile3 } from "node:fs/promises";
|
|
24062
|
-
import { homedir } from "node:os";
|
|
24063
|
-
import { dirname as dirname4, join as
|
|
24227
|
+
import { homedir as homedir3 } from "node:os";
|
|
24228
|
+
import { dirname as dirname4, join as join8 } from "node:path";
|
|
24064
24229
|
var identitySchema = external_exports.object({
|
|
24065
24230
|
schemaVersion: external_exports.literal(1),
|
|
24066
24231
|
deviceId: external_exports.string().uuid(),
|
|
@@ -24089,14 +24254,14 @@ var IdentityStore = class {
|
|
|
24089
24254
|
peers = /* @__PURE__ */ new Map();
|
|
24090
24255
|
constructor(options = {}) {
|
|
24091
24256
|
const env = options.env ?? process.env;
|
|
24092
|
-
const dshHome = env.DSH_HOME ||
|
|
24093
|
-
this.directory = options.directory ??
|
|
24257
|
+
const dshHome = env.DSH_HOME || join8(options.homeDirectory ?? homedir3(), ".dsh");
|
|
24258
|
+
this.directory = options.directory ?? join8(dshHome, "remote");
|
|
24094
24259
|
}
|
|
24095
24260
|
async loadOrCreate(deviceName) {
|
|
24096
24261
|
await mkdir3(this.directory, { recursive: true, mode: 448 });
|
|
24097
24262
|
await chmod3(this.directory, 448);
|
|
24098
|
-
const devicePath =
|
|
24099
|
-
const keyPath =
|
|
24263
|
+
const devicePath = join8(this.directory, "device.json");
|
|
24264
|
+
const keyPath = join8(this.directory, "device.key");
|
|
24100
24265
|
const [hasDevice, hasKey] = await Promise.all([exists2(devicePath), exists2(keyPath)]);
|
|
24101
24266
|
if (hasDevice !== hasKey) {
|
|
24102
24267
|
throw new IdentityInvalidError("device identity is incomplete; repair it explicitly before reconnecting");
|
|
@@ -24165,7 +24330,7 @@ var IdentityStore = class {
|
|
|
24165
24330
|
return removed;
|
|
24166
24331
|
}
|
|
24167
24332
|
async loadPeers() {
|
|
24168
|
-
const path =
|
|
24333
|
+
const path = join8(this.directory, "trusted-peers.json");
|
|
24169
24334
|
if (!await exists2(path)) {
|
|
24170
24335
|
await atomicJsonWrite(path, { schemaVersion: 1, peers: [] }, 384);
|
|
24171
24336
|
}
|
|
@@ -24181,7 +24346,7 @@ var IdentityStore = class {
|
|
|
24181
24346
|
this.peers = peers;
|
|
24182
24347
|
}
|
|
24183
24348
|
async savePeers() {
|
|
24184
|
-
await atomicJsonWrite(
|
|
24349
|
+
await atomicJsonWrite(join8(this.directory, "trusted-peers.json"), {
|
|
24185
24350
|
schemaVersion: 1,
|
|
24186
24351
|
peers: [...this.peers.values()]
|
|
24187
24352
|
}, 384);
|
|
@@ -24190,7 +24355,7 @@ var IdentityStore = class {
|
|
|
24190
24355
|
function serverStorageDirectory(root, serverUrl, role) {
|
|
24191
24356
|
const origin = new URL(serverUrl).origin;
|
|
24192
24357
|
const scope = createHash("sha256").update(origin).digest("hex").slice(0, 24);
|
|
24193
|
-
return
|
|
24358
|
+
return join8(root, "servers", scope, role);
|
|
24194
24359
|
}
|
|
24195
24360
|
async function ensureDeviceDirectory(root, serverUrl) {
|
|
24196
24361
|
const directory = serverStorageDirectory(root, serverUrl, "device");
|
|
@@ -24391,10 +24556,11 @@ var ControlStatusStream = class {
|
|
|
24391
24556
|
|
|
24392
24557
|
// src/codex/domain.ts
|
|
24393
24558
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
24559
|
+
import { Buffer as Buffer4 } from "node:buffer";
|
|
24394
24560
|
import { accessSync, constants, existsSync as existsSync3, readFileSync, readdirSync as readdirSync2, statSync as statSync2 } from "node:fs";
|
|
24395
24561
|
import { readdir as readdir2, realpath, stat as stat4 } from "node:fs/promises";
|
|
24396
|
-
import { homedir as
|
|
24397
|
-
import { basename as basename2, isAbsolute as isAbsolute2, join as
|
|
24562
|
+
import { homedir as homedir4 } from "node:os";
|
|
24563
|
+
import { basename as basename2, isAbsolute as isAbsolute2, join as join9, posix, relative, resolve } from "node:path";
|
|
24398
24564
|
|
|
24399
24565
|
// src/codex/app-server.ts
|
|
24400
24566
|
import { spawn as spawn2 } from "node:child_process";
|
|
@@ -24651,6 +24817,135 @@ function isRecord10(value) {
|
|
|
24651
24817
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
24652
24818
|
}
|
|
24653
24819
|
|
|
24820
|
+
// src/codex/method-policy.ts
|
|
24821
|
+
var id2 = external_exports.string().min(1).max(256);
|
|
24822
|
+
var cursor = external_exports.string().min(1).max(4096).nullable().optional();
|
|
24823
|
+
var textInput = external_exports.object({
|
|
24824
|
+
type: external_exports.literal("text"),
|
|
24825
|
+
text: external_exports.string().min(1).max(256 * 1024)
|
|
24826
|
+
}).strict();
|
|
24827
|
+
var imageMediaType = external_exports.enum(["image/png", "image/jpeg", "image/webp", "image/gif"]);
|
|
24828
|
+
var canonicalBase64 = external_exports.string().min(4).max(288 * 1024 * 1024).refine((value) => value.length % 4 === 0 && /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/u.test(value), {
|
|
24829
|
+
message: "Image data must use canonical base64."
|
|
24830
|
+
});
|
|
24831
|
+
var imageInput = external_exports.object({
|
|
24832
|
+
type: external_exports.literal("image"),
|
|
24833
|
+
mediaType: imageMediaType,
|
|
24834
|
+
data: canonicalBase64
|
|
24835
|
+
}).strict();
|
|
24836
|
+
var input = external_exports.array(external_exports.union([textInput, imageInput])).min(1).max(16);
|
|
24837
|
+
var permissionPreset = external_exports.enum(["workspace-write", "danger-full-access"]);
|
|
24838
|
+
var projectRoot = external_exports.object({
|
|
24839
|
+
path: external_exports.string().min(1).max(4096)
|
|
24840
|
+
}).strict();
|
|
24841
|
+
var CODEX_HISTORY_MAX_MESSAGES = 200;
|
|
24842
|
+
var schemas = {
|
|
24843
|
+
"account/read": external_exports.object({ refreshToken: external_exports.literal(false).optional() }).strict(),
|
|
24844
|
+
"model/list": external_exports.object({
|
|
24845
|
+
cursor,
|
|
24846
|
+
limit: external_exports.number().int().min(1).max(100).optional(),
|
|
24847
|
+
includeHidden: external_exports.boolean().optional()
|
|
24848
|
+
}).strict(),
|
|
24849
|
+
"project/list": external_exports.object({
|
|
24850
|
+
cursor,
|
|
24851
|
+
limit: external_exports.number().int().min(1).max(100).optional()
|
|
24852
|
+
}).strict(),
|
|
24853
|
+
"project/create": external_exports.object({
|
|
24854
|
+
name: external_exports.string().trim().min(1).max(256),
|
|
24855
|
+
roots: external_exports.array(projectRoot).length(1),
|
|
24856
|
+
idempotencyKey: external_exports.string().min(16).max(256)
|
|
24857
|
+
}).strict(),
|
|
24858
|
+
"thread/list": external_exports.object({
|
|
24859
|
+
cursor,
|
|
24860
|
+
limit: external_exports.number().int().min(1).max(100).optional(),
|
|
24861
|
+
sortKey: external_exports.enum(["created_at", "updated_at", "recency_at"]).optional(),
|
|
24862
|
+
sortDirection: external_exports.enum(["asc", "desc"]).optional(),
|
|
24863
|
+
modelProviders: external_exports.array(external_exports.string().min(1).max(128)).max(32).nullable().optional(),
|
|
24864
|
+
sourceKinds: external_exports.array(external_exports.enum(["cli", "vscode", "exec", "appServer", "unknown"])).max(8).optional(),
|
|
24865
|
+
archived: external_exports.boolean().optional(),
|
|
24866
|
+
isPinned: external_exports.boolean().optional(),
|
|
24867
|
+
cwd: external_exports.union([external_exports.string().min(1).max(4096), external_exports.array(external_exports.string().min(1).max(4096)).min(1).max(32)]).optional(),
|
|
24868
|
+
useStateDbOnly: external_exports.boolean().optional(),
|
|
24869
|
+
searchTerm: external_exports.string().max(1024).optional(),
|
|
24870
|
+
// Upstream `ThreadListParams` declares these two as well. Refusing them turned a
|
|
24871
|
+
// legitimate client query into "The CodeX call parameters are invalid.".
|
|
24872
|
+
originators: external_exports.array(external_exports.string().min(1).max(256)).max(32).nullable().optional(),
|
|
24873
|
+
sectionId: external_exports.string().max(256).nullable().optional()
|
|
24874
|
+
}).strict(),
|
|
24875
|
+
"thread/read": external_exports.object({ threadId: id2, includeTurns: external_exports.boolean().optional() }).strict(),
|
|
24876
|
+
"dsh/sessionHistory": external_exports.object({
|
|
24877
|
+
threadId: id2,
|
|
24878
|
+
beforeSeq: external_exports.number().int().nonnegative().optional(),
|
|
24879
|
+
throughSeq: external_exports.number().int().min(-1).optional(),
|
|
24880
|
+
maxMessages: external_exports.number().int().min(1).max(CODEX_HISTORY_MAX_MESSAGES).optional()
|
|
24881
|
+
}).strict(),
|
|
24882
|
+
"dsh/directoryList": external_exports.object({
|
|
24883
|
+
path: external_exports.string().min(1).max(4096)
|
|
24884
|
+
}).strict(),
|
|
24885
|
+
"thread/start": external_exports.object({
|
|
24886
|
+
cwd: external_exports.string().min(1).max(4096),
|
|
24887
|
+
model: external_exports.string().min(1).max(128).optional(),
|
|
24888
|
+
personality: external_exports.string().min(1).max(64).optional(),
|
|
24889
|
+
permissionPreset: permissionPreset.optional()
|
|
24890
|
+
}).strict(),
|
|
24891
|
+
"thread/resume": external_exports.object({
|
|
24892
|
+
threadId: id2,
|
|
24893
|
+
model: external_exports.string().min(1).max(128).optional(),
|
|
24894
|
+
permissionPreset: permissionPreset.optional()
|
|
24895
|
+
}).strict(),
|
|
24896
|
+
"thread/fork": external_exports.object({
|
|
24897
|
+
threadId: id2,
|
|
24898
|
+
lastTurnId: id2.optional(),
|
|
24899
|
+
permissionPreset: permissionPreset.optional()
|
|
24900
|
+
}).strict(),
|
|
24901
|
+
// Upstream declares a plain string and uses the empty value to clear a name, so the
|
|
24902
|
+
// trim-based minimum rejected a legitimate rename.
|
|
24903
|
+
"thread/name/set": external_exports.object({ threadId: id2, name: external_exports.string().max(256) }).strict(),
|
|
24904
|
+
"thread/archive": external_exports.object({ threadId: id2 }).strict(),
|
|
24905
|
+
"thread/unarchive": external_exports.object({ threadId: id2 }).strict(),
|
|
24906
|
+
"thread/unsubscribe": external_exports.object({ threadId: id2 }).strict(),
|
|
24907
|
+
"turn/start": external_exports.object({
|
|
24908
|
+
threadId: id2,
|
|
24909
|
+
input,
|
|
24910
|
+
model: external_exports.string().min(1).max(128).optional(),
|
|
24911
|
+
effort: external_exports.enum(["none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra"]).optional(),
|
|
24912
|
+
summary: external_exports.enum(["auto", "concise", "detailed", "none"]).optional(),
|
|
24913
|
+
personality: external_exports.string().min(1).max(64).optional(),
|
|
24914
|
+
permissionPreset: permissionPreset.optional()
|
|
24915
|
+
}).strict(),
|
|
24916
|
+
"turn/steer": external_exports.object({
|
|
24917
|
+
threadId: id2,
|
|
24918
|
+
input,
|
|
24919
|
+
expectedTurnId: id2,
|
|
24920
|
+
// Declared by upstream `TurnSteerParams`; see the thread/list note above.
|
|
24921
|
+
clientUserMessageId: external_exports.string().max(256).nullable().optional()
|
|
24922
|
+
}).strict(),
|
|
24923
|
+
"turn/interrupt": external_exports.object({ threadId: id2, turnId: id2 }).strict()
|
|
24924
|
+
};
|
|
24925
|
+
var CODEX_APP_ALLOWLIST = Object.freeze(Object.keys(schemas));
|
|
24926
|
+
function parseCodexCall(method, params) {
|
|
24927
|
+
if (!Object.prototype.hasOwnProperty.call(schemas, method)) {
|
|
24928
|
+
throw new RpcError("METHOD_NOT_ALLOWED", "The requested Codex method is not available over Remote.");
|
|
24929
|
+
}
|
|
24930
|
+
const schema = schemas[method];
|
|
24931
|
+
const parsed = schema.safeParse(params);
|
|
24932
|
+
if (!parsed.success) {
|
|
24933
|
+
const detail = parsed.error.issues.slice(0, 4).map((issue) => {
|
|
24934
|
+
const path = issue.path.length === 0 ? "(root)" : issue.path.join(".");
|
|
24935
|
+
const keys = "keys" in issue && Array.isArray(issue.keys) ? ` [${issue.keys.join(", ")}]` : "";
|
|
24936
|
+
return `${path}${keys}: ${issue.code}`;
|
|
24937
|
+
}).join(", ");
|
|
24938
|
+
throw new RpcError("INVALID_MESSAGE", `The CodeX call parameters are invalid. (${method} \u2192 ${detail})`);
|
|
24939
|
+
}
|
|
24940
|
+
return { method, params: parsed.data };
|
|
24941
|
+
}
|
|
24942
|
+
function isThreadMutation(method) {
|
|
24943
|
+
return method === "turn/start" || method === "turn/steer" || method === "turn/interrupt";
|
|
24944
|
+
}
|
|
24945
|
+
function threadIdFromParams(params) {
|
|
24946
|
+
return typeof params.threadId === "string" ? params.threadId : void 0;
|
|
24947
|
+
}
|
|
24948
|
+
|
|
24654
24949
|
// src/codex/peer-bridge.ts
|
|
24655
24950
|
import { Buffer as Buffer3 } from "node:buffer";
|
|
24656
24951
|
var streamOpenSchema = external_exports.object({
|
|
@@ -24797,22 +25092,22 @@ var CodexPeerBridge = class {
|
|
|
24797
25092
|
});
|
|
24798
25093
|
throw error;
|
|
24799
25094
|
}
|
|
24800
|
-
const
|
|
24801
|
-
if (
|
|
24802
|
-
if (
|
|
25095
|
+
const responseBytes2 = new TextEncoder().encode(JSON.stringify(response));
|
|
25096
|
+
if (responseBytes2.byteLength <= INLINE_TRANSFER_RESPONSE_BYTES) return { kind: "inline", response };
|
|
25097
|
+
if (responseBytes2.byteLength > MAX_CODEX_APP_TRANSFER_BYTES) {
|
|
24803
25098
|
throw new RpcError("RESPONSE_TOO_LARGE", "The Codex response exceeds the bounded transfer limit.");
|
|
24804
25099
|
}
|
|
24805
25100
|
if (this.outgoingTransfers.size >= MAX_ACTIVE_TRANSFERS) {
|
|
24806
25101
|
throw new RpcError("RATE_LIMITED", "Too many Codex response transfers are active.", void 0, true);
|
|
24807
25102
|
}
|
|
24808
|
-
const totalChunks = Math.ceil(
|
|
25103
|
+
const totalChunks = Math.ceil(responseBytes2.byteLength / CODEX_APP_TRANSFER_CHUNK_BYTES);
|
|
24809
25104
|
this.outgoingTransfers.set(params.transferId, {
|
|
24810
|
-
bytes:
|
|
25105
|
+
bytes: responseBytes2,
|
|
24811
25106
|
totalChunks,
|
|
24812
25107
|
nextIndex: 0,
|
|
24813
25108
|
touchedAt: Date.now()
|
|
24814
25109
|
});
|
|
24815
|
-
return { kind: "chunked", transferId: params.transferId, totalBytes:
|
|
25110
|
+
return { kind: "chunked", transferId: params.transferId, totalBytes: responseBytes2.byteLength, totalChunks };
|
|
24816
25111
|
}
|
|
24817
25112
|
readTransfer(input2) {
|
|
24818
25113
|
this.requireOpen();
|
|
@@ -24941,6 +25236,219 @@ function concatChunks(chunks, totalBytes) {
|
|
|
24941
25236
|
return output;
|
|
24942
25237
|
}
|
|
24943
25238
|
|
|
25239
|
+
// src/codex/history-window.ts
|
|
25240
|
+
var PAGE_LIMIT = 25;
|
|
25241
|
+
var MAX_PAGES = 64;
|
|
25242
|
+
var CACHE_TTL_MS = 5 * 6e4;
|
|
25243
|
+
var CACHE_LIMIT = 16;
|
|
25244
|
+
var CodexHistoryWindows = class {
|
|
25245
|
+
constructor(call, logger) {
|
|
25246
|
+
this.call = call;
|
|
25247
|
+
this.logger = logger;
|
|
25248
|
+
}
|
|
25249
|
+
windows = /* @__PURE__ */ new Map();
|
|
25250
|
+
async window(threadId, metadata, beforeSeq, maxMessages) {
|
|
25251
|
+
const updatedAt = typeof metadata.updatedAt === "number" ? metadata.updatedAt : void 0;
|
|
25252
|
+
const cached = this.windows.get(threadId);
|
|
25253
|
+
const fresh = cached !== void 0 && (updatedAt === void 0 || cached.updatedAt === void 0 || cached.updatedAt === updatedAt) && Date.now() - cached.at < CACHE_TTL_MS;
|
|
25254
|
+
if (fresh && cached !== void 0) {
|
|
25255
|
+
cached.at = Date.now();
|
|
25256
|
+
await this.ensureOlder(threadId, cached, beforeSeq, maxMessages);
|
|
25257
|
+
this.logger?.debug("codex history cache hit", {
|
|
25258
|
+
threadId,
|
|
25259
|
+
turns: cached.loaded.size,
|
|
25260
|
+
records: cached.history.entries.length
|
|
25261
|
+
});
|
|
25262
|
+
return cached;
|
|
25263
|
+
}
|
|
25264
|
+
if (cached !== void 0) {
|
|
25265
|
+
const appended = await this.appendNewTurns(threadId, cached, updatedAt);
|
|
25266
|
+
if (appended !== void 0) {
|
|
25267
|
+
await this.ensureOlder(threadId, appended, beforeSeq, maxMessages);
|
|
25268
|
+
this.remember(threadId, appended);
|
|
25269
|
+
this.logger?.debug("codex history cache appended", {
|
|
25270
|
+
threadId,
|
|
25271
|
+
turns: appended.loaded.size,
|
|
25272
|
+
records: appended.history.entries.length
|
|
25273
|
+
});
|
|
25274
|
+
return appended;
|
|
25275
|
+
}
|
|
25276
|
+
}
|
|
25277
|
+
const built = await this.rebuild(threadId, metadata, cached?.acceptedPageSize);
|
|
25278
|
+
await this.ensureOlder(threadId, built, beforeSeq, maxMessages);
|
|
25279
|
+
this.remember(threadId, built);
|
|
25280
|
+
return built;
|
|
25281
|
+
}
|
|
25282
|
+
async rebuild(threadId, metadata, acceptedPageSize) {
|
|
25283
|
+
const startedAt = performance.now();
|
|
25284
|
+
const index = await this.readTurnIndex(threadId);
|
|
25285
|
+
const window = {
|
|
25286
|
+
metadata,
|
|
25287
|
+
turnNumbers: index.numbers,
|
|
25288
|
+
turnStartSeq: index.turnStartSeq,
|
|
25289
|
+
totalTurns: index.total,
|
|
25290
|
+
totalEvents: index.totalEvents,
|
|
25291
|
+
loaded: /* @__PURE__ */ new Map(),
|
|
25292
|
+
history: projectCodexNativeHistory({ ...metadata, turns: [] }, `codex:${threadId}`, "legacy", {
|
|
25293
|
+
turnNumbers: index.numbers
|
|
25294
|
+
}),
|
|
25295
|
+
...typeof metadata.updatedAt === "number" ? { updatedAt: metadata.updatedAt } : {},
|
|
25296
|
+
...acceptedPageSize === void 0 ? {} : { acceptedPageSize },
|
|
25297
|
+
at: Date.now()
|
|
25298
|
+
};
|
|
25299
|
+
this.logger?.debug("codex history index", {
|
|
25300
|
+
threadId,
|
|
25301
|
+
turns: index.total,
|
|
25302
|
+
pages: index.pages,
|
|
25303
|
+
durationMs: Math.round(performance.now() - startedAt)
|
|
25304
|
+
});
|
|
25305
|
+
await this.loadTail(threadId, window);
|
|
25306
|
+
return window;
|
|
25307
|
+
}
|
|
25308
|
+
async readTurnIndex(threadId) {
|
|
25309
|
+
const numbers = /* @__PURE__ */ new Map();
|
|
25310
|
+
const turnStartSeq = /* @__PURE__ */ new Map();
|
|
25311
|
+
let total = 0;
|
|
25312
|
+
let totalEvents = 0;
|
|
25313
|
+
let pages = 0;
|
|
25314
|
+
let cursor2;
|
|
25315
|
+
for (let page = 0; page < MAX_PAGES; page += 1) {
|
|
25316
|
+
const result = await this.turnPage(threadId, cursor2, "asc", "summary");
|
|
25317
|
+
pages += 1;
|
|
25318
|
+
for (const rawTurn of result.turns) {
|
|
25319
|
+
const turnId = typeof rawTurn.id === "string" ? rawTurn.id : void 0;
|
|
25320
|
+
if (turnId === void 0 || numbers.has(turnId)) continue;
|
|
25321
|
+
total += 1;
|
|
25322
|
+
numbers.set(turnId, total);
|
|
25323
|
+
turnStartSeq.set(turnId, totalEvents);
|
|
25324
|
+
totalEvents += projectCodexNativeHistory({ turns: [rawTurn] }, `codex:${threadId}`).entries.length;
|
|
25325
|
+
}
|
|
25326
|
+
cursor2 = result.nextCursor;
|
|
25327
|
+
if (cursor2 === void 0) break;
|
|
25328
|
+
}
|
|
25329
|
+
return { numbers, turnStartSeq, total, totalEvents, pages };
|
|
25330
|
+
}
|
|
25331
|
+
async loadTail(threadId, window) {
|
|
25332
|
+
let cursor2;
|
|
25333
|
+
let fetched = 0;
|
|
25334
|
+
for (let page = 0; page < MAX_PAGES; page += 1) {
|
|
25335
|
+
const result = await this.turnPage(threadId, cursor2, "desc", "full");
|
|
25336
|
+
fetched += result.turns.length;
|
|
25337
|
+
this.addTurns(window, result.turns);
|
|
25338
|
+
cursor2 = result.nextCursor;
|
|
25339
|
+
this.project(threadId, window);
|
|
25340
|
+
if (surfaceRecords(window.history) >= HISTORY_PAGE_SIZES[0] || cursor2 === void 0) break;
|
|
25341
|
+
}
|
|
25342
|
+
window.olderCursor = cursor2;
|
|
25343
|
+
this.logger?.debug("codex history tail", {
|
|
25344
|
+
threadId,
|
|
25345
|
+
turnsFetched: fetched,
|
|
25346
|
+
loadedTurns: window.loaded.size,
|
|
25347
|
+
records: window.history.entries.length,
|
|
25348
|
+
older: window.olderCursor !== void 0
|
|
25349
|
+
});
|
|
25350
|
+
}
|
|
25351
|
+
async ensureOlder(threadId, window, beforeSeq, maxMessages) {
|
|
25352
|
+
if (beforeSeq === void 0) return;
|
|
25353
|
+
const needed = Math.min(maxMessages ?? HISTORY_PAGE_SIZES[0], HISTORY_PAGE_SIZES[0]);
|
|
25354
|
+
const available = () => window.history.entries.filter((entry) => entry.event.seq < beforeSeq && isSurfaceEvent(entry.event.type) && entry.event.surfaceOp === "append").length;
|
|
25355
|
+
let fetched = 0;
|
|
25356
|
+
for (let page = 0; page < MAX_PAGES; page += 1) {
|
|
25357
|
+
if (available() >= needed) break;
|
|
25358
|
+
if (window.olderCursor === void 0) break;
|
|
25359
|
+
const result = await this.turnPage(threadId, window.olderCursor, "desc", "full");
|
|
25360
|
+
fetched += result.turns.length;
|
|
25361
|
+
this.addTurns(window, result.turns);
|
|
25362
|
+
window.olderCursor = result.nextCursor;
|
|
25363
|
+
this.project(threadId, window);
|
|
25364
|
+
}
|
|
25365
|
+
if (fetched > 0) {
|
|
25366
|
+
this.logger?.debug("codex history older", {
|
|
25367
|
+
threadId,
|
|
25368
|
+
beforeSeq,
|
|
25369
|
+
needed,
|
|
25370
|
+
turnsFetched: fetched,
|
|
25371
|
+
loadedTurns: window.loaded.size
|
|
25372
|
+
});
|
|
25373
|
+
}
|
|
25374
|
+
}
|
|
25375
|
+
async appendNewTurns(threadId, cached, updatedAt) {
|
|
25376
|
+
if (cached.newestTurnId === void 0) return void 0;
|
|
25377
|
+
const newer = [];
|
|
25378
|
+
let cursor2;
|
|
25379
|
+
for (let page = 0; page < MAX_PAGES; page += 1) {
|
|
25380
|
+
const result = await this.turnPage(threadId, cursor2, "desc", "full");
|
|
25381
|
+
for (const rawTurn of result.turns) {
|
|
25382
|
+
if (typeof rawTurn.id === "string" && rawTurn.id === cached.newestTurnId) {
|
|
25383
|
+
if (newer.length === 0) {
|
|
25384
|
+
cached.updatedAt = updatedAt;
|
|
25385
|
+
cached.at = Date.now();
|
|
25386
|
+
return cached;
|
|
25387
|
+
}
|
|
25388
|
+
this.addTurns(cached, [...newer].reverse());
|
|
25389
|
+
this.project(threadId, cached);
|
|
25390
|
+
cached.updatedAt = updatedAt;
|
|
25391
|
+
cached.at = Date.now();
|
|
25392
|
+
return cached;
|
|
25393
|
+
}
|
|
25394
|
+
newer.push(rawTurn);
|
|
25395
|
+
}
|
|
25396
|
+
cursor2 = result.nextCursor;
|
|
25397
|
+
if (cursor2 === void 0) return void 0;
|
|
25398
|
+
}
|
|
25399
|
+
return void 0;
|
|
25400
|
+
}
|
|
25401
|
+
addTurns(window, turns) {
|
|
25402
|
+
for (const turn of turns) {
|
|
25403
|
+
const turnId = typeof turn.id === "string" ? turn.id : void 0;
|
|
25404
|
+
if (turnId === void 0) continue;
|
|
25405
|
+
if (!window.turnNumbers.has(turnId)) {
|
|
25406
|
+
window.totalTurns += 1;
|
|
25407
|
+
window.turnNumbers.set(turnId, window.totalTurns);
|
|
25408
|
+
window.turnStartSeq.set(turnId, window.totalEvents);
|
|
25409
|
+
window.totalEvents += projectCodexNativeHistory({ turns: [turn] }, window.history.header.id).entries.length;
|
|
25410
|
+
}
|
|
25411
|
+
window.loaded.set(turnId, turn);
|
|
25412
|
+
}
|
|
25413
|
+
}
|
|
25414
|
+
project(threadId, window) {
|
|
25415
|
+
const turns = [...window.loaded.entries()].sort((left, right) => (window.turnNumbers.get(left[0]) ?? 0) - (window.turnNumbers.get(right[0]) ?? 0)).map(([, turn]) => turn);
|
|
25416
|
+
const first = [...window.loaded.keys()].sort((left, right) => (window.turnNumbers.get(left) ?? 0) - (window.turnNumbers.get(right) ?? 0))[0];
|
|
25417
|
+
const startSeq = first === void 0 ? 0 : window.turnStartSeq.get(first) ?? 0;
|
|
25418
|
+
const history = projectCodexNativeHistory({ ...window.metadata, turns }, `codex:${threadId}`, "legacy", {
|
|
25419
|
+
seq: startSeq,
|
|
25420
|
+
turnNumbers: window.turnNumbers
|
|
25421
|
+
});
|
|
25422
|
+
history.nextTurn = window.totalTurns + 1;
|
|
25423
|
+
window.history = history;
|
|
25424
|
+
const newest = [...window.turnNumbers.entries()].sort((left, right) => right[1] - left[1])[0]?.[0];
|
|
25425
|
+
if (newest !== void 0) window.newestTurnId = newest;
|
|
25426
|
+
}
|
|
25427
|
+
async turnPage(threadId, cursor2, direction, itemsView) {
|
|
25428
|
+
const result = await this.call("thread/turns/list", {
|
|
25429
|
+
threadId,
|
|
25430
|
+
limit: PAGE_LIMIT,
|
|
25431
|
+
sortDirection: direction,
|
|
25432
|
+
itemsView,
|
|
25433
|
+
...cursor2 === void 0 ? {} : { cursor: cursor2 }
|
|
25434
|
+
});
|
|
25435
|
+
const page = typeof result === "object" && result !== null ? result : {};
|
|
25436
|
+
const turns = Array.isArray(page.data) ? page.data.filter((turn) => typeof turn === "object" && turn !== null && !Array.isArray(turn)) : [];
|
|
25437
|
+
const nextCursor = typeof page.nextCursor === "string" && page.nextCursor.length > 0 ? page.nextCursor : void 0;
|
|
25438
|
+
return { turns, nextCursor };
|
|
25439
|
+
}
|
|
25440
|
+
remember(threadId, window) {
|
|
25441
|
+
if (this.windows.size >= CACHE_LIMIT && !this.windows.has(threadId)) {
|
|
25442
|
+
const oldest = this.windows.keys().next().value;
|
|
25443
|
+
if (oldest !== void 0) this.windows.delete(oldest);
|
|
25444
|
+
}
|
|
25445
|
+
this.windows.set(threadId, window);
|
|
25446
|
+
}
|
|
25447
|
+
};
|
|
25448
|
+
function surfaceRecords(history) {
|
|
25449
|
+
return history.entries.filter((entry) => isSurfaceEvent(entry.event.type) && entry.event.surfaceOp === "append").length;
|
|
25450
|
+
}
|
|
25451
|
+
|
|
24944
25452
|
// src/codex/domain.ts
|
|
24945
25453
|
var APPROVAL_TTL_MS = 5 * 6e4;
|
|
24946
25454
|
var DEFAULT_RESTART_DELAYS_MS = [1e3, 2e3, 4e3, 8e3, 15e3];
|
|
@@ -24949,8 +25457,6 @@ var ACCOUNT_PROBE_TIMEOUT_MS = 6e4;
|
|
|
24949
25457
|
var CODEX_START_BUDGET_MS = 9e4;
|
|
24950
25458
|
var CODEX_PAGE_LIMIT2 = 100;
|
|
24951
25459
|
var MAX_CODEX_PAGES2 = 32;
|
|
24952
|
-
var CODEX_HISTORY_PAGE_LIMIT = 25;
|
|
24953
|
-
var MAX_CODEX_HISTORY_PAGES = 64;
|
|
24954
25460
|
var CODEX_DIRECTORY_ENTRY_LIMIT = 500;
|
|
24955
25461
|
var CodexRemoteDomain = class {
|
|
24956
25462
|
constructor(config, logger, createAppServer = (binary, targetLogger) => new CodexAppServerClient(binary, targetLogger), restartDelaysMs = DEFAULT_RESTART_DELAYS_MS) {
|
|
@@ -24958,6 +25464,7 @@ var CodexRemoteDomain = class {
|
|
|
24958
25464
|
this.createAppServer = createAppServer;
|
|
24959
25465
|
this.restartDelaysMs = restartDelaysMs;
|
|
24960
25466
|
this.config = config;
|
|
25467
|
+
this.historyWindows = new CodexHistoryWindows((method, params) => this.callUpstream(method, params), logger);
|
|
24961
25468
|
}
|
|
24962
25469
|
appServer;
|
|
24963
25470
|
unsubscribeInbound;
|
|
@@ -24967,6 +25474,8 @@ var CodexRemoteDomain = class {
|
|
|
24967
25474
|
turnOwners = /* @__PURE__ */ new Map();
|
|
24968
25475
|
approvals = /* @__PURE__ */ new Map();
|
|
24969
25476
|
permissionPresets = /* @__PURE__ */ new Map();
|
|
25477
|
+
/** Per-Thread turn index plus the tail-first full-item window. */
|
|
25478
|
+
historyWindows;
|
|
24970
25479
|
approvalExpiryTimer;
|
|
24971
25480
|
restartTimer;
|
|
24972
25481
|
restartAttempt = 0;
|
|
@@ -25068,14 +25577,16 @@ var CodexRemoteDomain = class {
|
|
|
25068
25577
|
}
|
|
25069
25578
|
if (call.method === "thread/list") {
|
|
25070
25579
|
const result = sanitizeThreadList(await this.callUpstream(call.method, call.params));
|
|
25580
|
+
const authority = await this.readWorkspaceAuthority(result.data);
|
|
25071
25581
|
const authorized = filterThreadListByWorkspaceAuthority(
|
|
25072
25582
|
result,
|
|
25073
|
-
|
|
25583
|
+
authority,
|
|
25584
|
+
this.config.showUnprojectedSessions !== false
|
|
25074
25585
|
);
|
|
25075
|
-
return {
|
|
25586
|
+
return rebaseThreadListCwds({
|
|
25076
25587
|
...authorized,
|
|
25077
25588
|
data: uniqueCodexThreadRows(authorized.data)
|
|
25078
|
-
};
|
|
25589
|
+
}, authority.roots);
|
|
25079
25590
|
}
|
|
25080
25591
|
if (call.method === "thread/start") {
|
|
25081
25592
|
const cwd = await this.requireCodexWorkspacePath(call.params.cwd);
|
|
@@ -25094,21 +25605,15 @@ var CodexRemoteDomain = class {
|
|
|
25094
25605
|
}
|
|
25095
25606
|
const threadId = threadIdFromParams(call.params);
|
|
25096
25607
|
if (call.method === "dsh/sessionHistory") {
|
|
25097
|
-
const thread = await this.
|
|
25098
|
-
const
|
|
25099
|
-
|
|
25100
|
-
|
|
25101
|
-
|
|
25102
|
-
|
|
25103
|
-
|
|
25104
|
-
|
|
25105
|
-
);
|
|
25106
|
-
const activeTurnId2 = typeof page.activeTurnId === "string" ? page.activeTurnId : this.turnOwners.get(threadId)?.turnId;
|
|
25107
|
-
return {
|
|
25108
|
-
...page,
|
|
25109
|
-
...activeTurnId2 === void 0 ? {} : { activeTurnId: activeTurnId2 },
|
|
25110
|
-
permissionPreset: this.permissionPresets.get(threadId) ?? null
|
|
25111
|
-
};
|
|
25608
|
+
const { thread: metadata } = await this.readKnownThread(threadId);
|
|
25609
|
+
const beforeSeq = optionalInteger2(call.params.beforeSeq);
|
|
25610
|
+
const maxMessages = optionalInteger2(call.params.maxMessages);
|
|
25611
|
+
const window = await this.historyWindows.window(threadId, metadata, beforeSeq, maxMessages);
|
|
25612
|
+
return this.historyPage(window, threadId, {
|
|
25613
|
+
beforeSeq,
|
|
25614
|
+
throughSeq: optionalInteger2(call.params.throughSeq),
|
|
25615
|
+
maxMessages
|
|
25616
|
+
});
|
|
25112
25617
|
}
|
|
25113
25618
|
if (call.method === "dsh/directoryList") {
|
|
25114
25619
|
return this.listCodexDirectory(call.params.path);
|
|
@@ -25611,103 +26116,60 @@ var CodexRemoteDomain = class {
|
|
|
25611
26116
|
return { thread: listed, ...typeof listed.cwd === "string" && listed.cwd.length > 0 ? { cwd: listed.cwd } : {} };
|
|
25612
26117
|
}
|
|
25613
26118
|
const thread = extractThread(result);
|
|
25614
|
-
if (thread === void 0 || thread.id !== threadId || !
|
|
26119
|
+
if (thread === void 0 || thread.id !== threadId || !this.threadAllowed(thread, authority)) {
|
|
25615
26120
|
throw new RpcError("CODEX_THREAD_NOT_ALLOWED", "The Codex thread is not available through this Remote Host.");
|
|
25616
26121
|
}
|
|
25617
26122
|
return { thread, ...typeof thread.cwd === "string" && thread.cwd.length > 0 ? { cwd: thread.cwd } : {} };
|
|
25618
26123
|
}
|
|
25619
|
-
|
|
25620
|
-
const
|
|
25621
|
-
|
|
25622
|
-
|
|
25623
|
-
|
|
25624
|
-
|
|
25625
|
-
|
|
25626
|
-
|
|
25627
|
-
|
|
25628
|
-
|
|
25629
|
-
|
|
25630
|
-
|
|
25631
|
-
|
|
25632
|
-
|
|
25633
|
-
|
|
25634
|
-
|
|
25635
|
-
|
|
25636
|
-
|
|
25637
|
-
|
|
25638
|
-
|
|
25639
|
-
|
|
25640
|
-
} catch (legacyError) {
|
|
25641
|
-
if (!isHistoryReadRecoverable(legacyError)) throw legacyError;
|
|
25642
|
-
this.logHistoryFallback(connectionId, "thread-read-full", legacyError);
|
|
25643
|
-
return { ...metadata, turns: [] };
|
|
25644
|
-
}
|
|
25645
|
-
}
|
|
25646
|
-
async readThreadTurns(connectionId, threadId, itemsView) {
|
|
25647
|
-
const turns = [];
|
|
25648
|
-
let cursor2;
|
|
25649
|
-
for (let page = 0; page < MAX_CODEX_HISTORY_PAGES; page += 1) {
|
|
25650
|
-
const result = await this.callUpstream("thread/turns/list", {
|
|
26124
|
+
historyPage(window, threadId, request) {
|
|
26125
|
+
const requested = request.maxMessages ?? HISTORY_PAGE_SIZES[0];
|
|
26126
|
+
const start = Math.max(1, Math.min(requested, HISTORY_PAGE_SIZES[0], window.acceptedPageSize ?? requested));
|
|
26127
|
+
const activeTurnId2 = this.turnOwners.get(threadId)?.turnId;
|
|
26128
|
+
for (const size of historyPageSizes(start)) {
|
|
26129
|
+
const page = paginateCodexNativeHistory(window.history, {
|
|
26130
|
+
beforeSeq: request.beforeSeq,
|
|
26131
|
+
throughSeq: request.throughSeq,
|
|
26132
|
+
maxMessages: size
|
|
26133
|
+
});
|
|
26134
|
+
const resolvedActiveTurnId = page.activeTurnId ?? activeTurnId2;
|
|
26135
|
+
const response = {
|
|
26136
|
+
...page,
|
|
26137
|
+
cursor: window.history.lastSeq,
|
|
26138
|
+
nextTurn: window.totalTurns + 1,
|
|
26139
|
+
hasMore: page.hasMore || window.olderCursor !== void 0,
|
|
26140
|
+
...resolvedActiveTurnId === void 0 ? {} : { activeTurnId: resolvedActiveTurnId },
|
|
26141
|
+
permissionPreset: this.permissionPresets.get(threadId) ?? null
|
|
26142
|
+
};
|
|
26143
|
+
const bytes = Buffer4.byteLength(JSON.stringify(response), "utf8");
|
|
26144
|
+
this.logger.debug("codex history page", {
|
|
25651
26145
|
threadId,
|
|
25652
|
-
|
|
25653
|
-
|
|
25654
|
-
|
|
25655
|
-
|
|
26146
|
+
requested,
|
|
26147
|
+
size,
|
|
26148
|
+
bytes,
|
|
26149
|
+
budget: HISTORY_BUDGET_BYTES,
|
|
26150
|
+
loadedTurns: window.loaded.size,
|
|
26151
|
+
totalTurns: window.totalTurns
|
|
25656
26152
|
});
|
|
25657
|
-
|
|
25658
|
-
|
|
25659
|
-
|
|
25660
|
-
const turnId = typeof rawTurn.id === "string" ? rawTurn.id : void 0;
|
|
25661
|
-
const items = rawTurn.itemsView === "full" || turnId === void 0 ? array2(rawTurn.items) : await this.readThreadItems(connectionId, threadId, turnId, array2(rawTurn.items));
|
|
25662
|
-
turns.push({ ...rawTurn, items });
|
|
26153
|
+
if (bytes <= HISTORY_BUDGET_BYTES) {
|
|
26154
|
+
window.acceptedPageSize = size;
|
|
26155
|
+
return response;
|
|
25663
26156
|
}
|
|
25664
|
-
cursor2 = typeof pageResult.nextCursor === "string" && pageResult.nextCursor.length > 0 ? pageResult.nextCursor : void 0;
|
|
25665
|
-
if (cursor2 === void 0) break;
|
|
25666
26157
|
}
|
|
25667
|
-
|
|
25668
|
-
}
|
|
25669
|
-
async readThreadItems(connectionId, threadId, turnId, fallbackItems) {
|
|
25670
|
-
const items = [];
|
|
25671
|
-
let cursor2;
|
|
25672
|
-
try {
|
|
25673
|
-
for (let page = 0; page < MAX_CODEX_HISTORY_PAGES; page += 1) {
|
|
25674
|
-
const result = await this.callUpstream("thread/items/list", {
|
|
25675
|
-
threadId,
|
|
25676
|
-
turnId,
|
|
25677
|
-
limit: CODEX_HISTORY_PAGE_LIMIT,
|
|
25678
|
-
sortDirection: "asc",
|
|
25679
|
-
...cursor2 === void 0 ? {} : { cursor: cursor2 }
|
|
25680
|
-
});
|
|
25681
|
-
const pageResult = isRecord12(result) ? result : {};
|
|
25682
|
-
for (const entry of array2(pageResult.data)) {
|
|
25683
|
-
if (isRecord12(entry) && entry.item !== void 0) items.push(entry.item);
|
|
25684
|
-
}
|
|
25685
|
-
cursor2 = typeof pageResult.nextCursor === "string" && pageResult.nextCursor.length > 0 ? pageResult.nextCursor : void 0;
|
|
25686
|
-
if (cursor2 === void 0) break;
|
|
25687
|
-
}
|
|
25688
|
-
} catch (error) {
|
|
25689
|
-
if (!isHistoryReadRecoverable(error)) throw error;
|
|
25690
|
-
this.logHistoryFallback(connectionId, "items", error);
|
|
25691
|
-
return fallbackItems;
|
|
25692
|
-
}
|
|
25693
|
-
return items;
|
|
25694
|
-
}
|
|
25695
|
-
logHistoryFallback(connectionId, stage, error) {
|
|
25696
|
-
this.logger.warn("Codex history read fallback", {
|
|
25697
|
-
connectionId: maskId(connectionId),
|
|
25698
|
-
stage,
|
|
25699
|
-
code: errorCode4(error)
|
|
25700
|
-
});
|
|
26158
|
+
throw new RpcError("RESPONSE_TOO_LARGE", "The Codex history page is too large for the secure Remote channel.");
|
|
25701
26159
|
}
|
|
25702
26160
|
async assertResultThreadAllowed(result) {
|
|
25703
26161
|
const thread = extractThread(result);
|
|
25704
26162
|
if (thread === void 0 || typeof thread.id !== "string") {
|
|
25705
26163
|
throw new RpcError("CODEX_INVALID_RESPONSE", "Codex App Server returned an invalid thread.");
|
|
25706
26164
|
}
|
|
25707
|
-
if (!
|
|
26165
|
+
if (!this.threadAllowed(thread, await this.readWorkspaceAuthority())) {
|
|
25708
26166
|
throw new RpcError("CODEX_THREAD_NOT_ALLOWED", "The Codex thread is not available through this Remote Host.");
|
|
25709
26167
|
}
|
|
25710
26168
|
}
|
|
26169
|
+
/** The authority check, widened when unprojected Sessions are exposed. */
|
|
26170
|
+
threadAllowed(thread, authority) {
|
|
26171
|
+
return this.config.showUnprojectedSessions !== false || isThreadAllowedByWorkspaceAuthority(thread, authority);
|
|
26172
|
+
}
|
|
25711
26173
|
claimTurn(threadId, connectionId, method, turnId) {
|
|
25712
26174
|
const owner = this.turnOwners.get(threadId);
|
|
25713
26175
|
const peerDeviceId = this.peerDeviceIds.get(connectionId) ?? connectionId;
|
|
@@ -25848,7 +26310,7 @@ var CodexRemoteDomain = class {
|
|
|
25848
26310
|
}));
|
|
25849
26311
|
const thread = result.data.find((item) => item.id === threadId);
|
|
25850
26312
|
if (thread !== void 0) {
|
|
25851
|
-
return
|
|
26313
|
+
return this.threadAllowed(thread, authority) ? thread : void 0;
|
|
25852
26314
|
}
|
|
25853
26315
|
cursor2 = typeof result.nextCursor === "string" && result.nextCursor.length > 0 ? result.nextCursor : void 0;
|
|
25854
26316
|
if (cursor2 === void 0) break;
|
|
@@ -25859,6 +26321,14 @@ var CodexRemoteDomain = class {
|
|
|
25859
26321
|
if (!isAbsolute2(path)) {
|
|
25860
26322
|
throw new RpcError("CODEX_PATH_NOT_ALLOWED", "The CodeX working directory is not available as a Workspace.");
|
|
25861
26323
|
}
|
|
26324
|
+
if (this.config.showUnprojectedSessions !== false) {
|
|
26325
|
+
try {
|
|
26326
|
+
const candidate2 = await realpath(path);
|
|
26327
|
+
if ((await stat4(candidate2)).isDirectory()) return candidate2;
|
|
26328
|
+
} catch {
|
|
26329
|
+
}
|
|
26330
|
+
throw new RpcError("CODEX_PATH_NOT_ALLOWED", "The CodeX working directory is not available as a Workspace.");
|
|
26331
|
+
}
|
|
25862
26332
|
const paths = await this.listCodexWorkspacePaths();
|
|
25863
26333
|
const lexicalCandidate = resolve(path);
|
|
25864
26334
|
let candidate;
|
|
@@ -25959,7 +26429,7 @@ var CodexRemoteDomain = class {
|
|
|
25959
26429
|
return paths;
|
|
25960
26430
|
}
|
|
25961
26431
|
};
|
|
25962
|
-
function codexBinaryCandidates(configured, hostPlatform = process.platform, userHome =
|
|
26432
|
+
function codexBinaryCandidates(configured, hostPlatform = process.platform, userHome = homedir4(), localAppData = process.env.LOCALAPPDATA) {
|
|
25963
26433
|
if (configured !== "codex") return [configured];
|
|
25964
26434
|
if (hostPlatform === "win32") {
|
|
25965
26435
|
return [.../* @__PURE__ */ new Set([
|
|
@@ -25995,12 +26465,12 @@ function codexBinaryCandidates(configured, hostPlatform = process.platform, user
|
|
|
25995
26465
|
])];
|
|
25996
26466
|
}
|
|
25997
26467
|
function bundledWindowsCodex(userHome) {
|
|
25998
|
-
return bundledWindowsCodexAt(
|
|
26468
|
+
return bundledWindowsCodexAt(join9(userHome, "AppData", "Local"));
|
|
25999
26469
|
}
|
|
26000
26470
|
function bundledWindowsCodexAt(localAppData) {
|
|
26001
|
-
const bin =
|
|
26471
|
+
const bin = join9(localAppData, "OpenAI", "Codex", "bin");
|
|
26002
26472
|
try {
|
|
26003
|
-
const newest = readdirSync2(bin, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) =>
|
|
26473
|
+
const newest = readdirSync2(bin, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => join9(bin, entry.name, "codex.exe")).filter((candidate2) => existsSync3(candidate2)).map((candidate2) => ({ candidate: candidate2, modified: statSync2(candidate2).mtimeMs })).sort((left, right) => right.modified - left.modified);
|
|
26004
26474
|
const candidate = newest[0]?.candidate;
|
|
26005
26475
|
return candidate === void 0 ? [] : [candidate];
|
|
26006
26476
|
} catch {
|
|
@@ -26073,13 +26543,30 @@ function sanitizeThreadList(result) {
|
|
|
26073
26543
|
...typeof result.backwardsCursor === "string" && result.backwardsCursor.length > 0 ? { backwardsCursor: result.backwardsCursor } : {}
|
|
26074
26544
|
};
|
|
26075
26545
|
}
|
|
26076
|
-
function filterThreadListByWorkspaceAuthority(result, authority) {
|
|
26546
|
+
function filterThreadListByWorkspaceAuthority(result, authority, includeUnprojected = false) {
|
|
26077
26547
|
if (!isRecord12(result) || !Array.isArray(result.data)) {
|
|
26078
26548
|
throw new RpcError("CODEX_INVALID_RESPONSE", "Codex App Server returned an invalid thread list.");
|
|
26079
26549
|
}
|
|
26080
26550
|
return {
|
|
26081
26551
|
...result,
|
|
26082
|
-
data: result.data.map((record7) => isRecord12(record7) ? record7 : void 0).filter((thread) => thread !== void 0 && isThreadAllowedByWorkspaceAuthority(thread, authority))
|
|
26552
|
+
data: result.data.map((record7) => isRecord12(record7) ? record7 : void 0).filter((thread) => thread !== void 0 && (includeUnprojected || isThreadAllowedByWorkspaceAuthority(thread, authority)))
|
|
26553
|
+
};
|
|
26554
|
+
}
|
|
26555
|
+
function rebaseThreadListCwds(result, roots) {
|
|
26556
|
+
if (process.platform !== "win32" || roots.length === 0) return result;
|
|
26557
|
+
const ordered = [...roots].sort((left, right) => right.length - left.length);
|
|
26558
|
+
return {
|
|
26559
|
+
...result,
|
|
26560
|
+
data: result.data.map((thread) => {
|
|
26561
|
+
const cwd = typeof thread.cwd === "string" ? thread.cwd : void 0;
|
|
26562
|
+
if (cwd === void 0) return thread;
|
|
26563
|
+
const root = ordered.find((candidate) => containsCodexPath(candidate, cwd));
|
|
26564
|
+
if (root === void 0) return thread;
|
|
26565
|
+
const normalizedRoot = normalizeCodexPathForCompare(root);
|
|
26566
|
+
const normalizedCwd = normalizeCodexPathForCompare(cwd);
|
|
26567
|
+
const rebased = normalizedCwd === normalizedRoot ? root : normalizedCwd.startsWith(`${normalizedRoot}/`) || normalizedCwd.startsWith(`${normalizedRoot}\\`) ? `${root}${cwd.slice(root.length)}` : cwd;
|
|
26568
|
+
return rebased === cwd ? thread : { ...thread, cwd: rebased };
|
|
26569
|
+
})
|
|
26083
26570
|
};
|
|
26084
26571
|
}
|
|
26085
26572
|
function sanitizeProject(value) {
|
|
@@ -26236,12 +26723,6 @@ function canTryNextBinary(error) {
|
|
|
26236
26723
|
function isRecord12(value) {
|
|
26237
26724
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
26238
26725
|
}
|
|
26239
|
-
function array2(value) {
|
|
26240
|
-
return Array.isArray(value) ? value : [];
|
|
26241
|
-
}
|
|
26242
|
-
function maskId(value) {
|
|
26243
|
-
return value.length <= 12 ? value : `${value.slice(0, 8)}\u2026${value.slice(-4)}`;
|
|
26244
|
-
}
|
|
26245
26726
|
function isActiveWriterMessage(message) {
|
|
26246
26727
|
return message.toLowerCase().includes("active writer");
|
|
26247
26728
|
}
|
|
@@ -26579,12 +27060,14 @@ var PluginControlRuntime = class {
|
|
|
26579
27060
|
/** Consecutive reconciliations without a DSH session: DSH refreshes its grant, so one empty read is not a sign-out. */
|
|
26580
27061
|
/** Guards against re-entry through configure(), which reads the settings view back. */
|
|
26581
27062
|
reconcilingWithDeepSeek = false;
|
|
27063
|
+
/** The authorization exchange in flight; a settings poll must not start a second one. */
|
|
27064
|
+
deepseekAuthorization;
|
|
26582
27065
|
/**
|
|
26583
27066
|
* Keep this device's Server credentials in step with DSH's DeepSeek session - that session is the credential, and
|
|
26584
27067
|
* the Server verifies it before issuing this device its own tokens.
|
|
26585
27068
|
*
|
|
26586
27069
|
* Three cases: DSH signed in and this device not authorized -> authorize; DSH signed out and this device authorized
|
|
26587
|
-
* -> sign out; otherwise nothing. At most
|
|
27070
|
+
* -> sign out; otherwise nothing. At most one exchange at a time, from the settings poll the panel already makes.
|
|
26588
27071
|
*/
|
|
26589
27072
|
async reconcileWithDeepSeek() {
|
|
26590
27073
|
if (this.deepseekSession === void 0 || this.reconcilingWithDeepSeek) return;
|
|
@@ -26593,19 +27076,20 @@ var PluginControlRuntime = class {
|
|
|
26593
27076
|
const session = await this.deepseekSession.read();
|
|
26594
27077
|
if (this.client !== void 0) this.client.hasDeepSeekSession = session !== void 0;
|
|
26595
27078
|
const authorized = this.client === void 0 ? false : await this.client.hasServerAuthorization();
|
|
26596
|
-
console.error(`[dsh-remote] reconcile ${JSON.stringify({ hasSession: session !== void 0, authorized })}`);
|
|
26597
27079
|
if (session === void 0 && authorized) {
|
|
26598
27080
|
await this.logout({ releaseDeepSeek: false });
|
|
26599
27081
|
return;
|
|
26600
27082
|
}
|
|
26601
27083
|
if (session === void 0 || authorized) return;
|
|
27084
|
+
if (this.deepseekAuthorization !== void 0) return;
|
|
26602
27085
|
if (this.client !== void 0) this.client.authorizingWithDeepSeek = true;
|
|
26603
|
-
|
|
27086
|
+
this.deepseekAuthorization = (async () => {
|
|
26604
27087
|
try {
|
|
26605
27088
|
await this.configure({ provider: "deepseek", serverUrl: this.config.serverUrl });
|
|
26606
27089
|
} catch (error) {
|
|
26607
27090
|
console.error(`[dsh-remote] reconcile failed ${JSON.stringify({ code: String(error?.code ?? "") })}`);
|
|
26608
27091
|
} finally {
|
|
27092
|
+
this.deepseekAuthorization = void 0;
|
|
26609
27093
|
if (this.client !== void 0) this.client.authorizingWithDeepSeek = false;
|
|
26610
27094
|
}
|
|
26611
27095
|
})();
|
|
@@ -27352,12 +27836,12 @@ function isOversizedListing(value) {
|
|
|
27352
27836
|
|
|
27353
27837
|
// src/remote-directory-browser.ts
|
|
27354
27838
|
import { readdir as readdir3, stat as stat5 } from "node:fs/promises";
|
|
27355
|
-
import { homedir as
|
|
27839
|
+
import { homedir as homedir5, platform as platform2 } from "node:os";
|
|
27356
27840
|
import { basename as basename3, dirname as dirname5, isAbsolute as isAbsolute3, parse, resolve as resolve2 } from "node:path";
|
|
27357
27841
|
var MAX_ENTRIES = 500;
|
|
27358
27842
|
async function listRemoteDirectory(path, signal) {
|
|
27359
27843
|
signal?.throwIfAborted();
|
|
27360
|
-
const home = resolve2(
|
|
27844
|
+
const home = resolve2(homedir5());
|
|
27361
27845
|
const target2 = path === void 0 || path.trim() === "" ? home : resolve2(path);
|
|
27362
27846
|
if (!isAbsolute3(target2)) throw new Error("The remote directory path must be absolute.");
|
|
27363
27847
|
const rows = await readdir3(target2, { withFileTypes: true });
|
|
@@ -27418,8 +27902,8 @@ async function callHistoryWithRetry(callWithTimeout, payload, rpcId2, pageSizes)
|
|
|
27418
27902
|
}
|
|
27419
27903
|
throw new RpcError("INTERNAL_ERROR", "Failed to load session history with a fallback page size.");
|
|
27420
27904
|
}
|
|
27421
|
-
function sessionHistoryFallbackPageSizes(
|
|
27422
|
-
const requested = normalizeSessionHistoryPageSize(
|
|
27905
|
+
function sessionHistoryFallbackPageSizes(requestedMaxMessages2) {
|
|
27906
|
+
const requested = normalizeSessionHistoryPageSize(requestedMaxMessages2);
|
|
27423
27907
|
const sizes = [];
|
|
27424
27908
|
if (requested === void 0) {
|
|
27425
27909
|
sizes.push(...SESSION_HISTORY_PAGE_SIZES);
|
|
@@ -27782,25 +28266,25 @@ var HarnessApiBridge = class {
|
|
|
27782
28266
|
}
|
|
27783
28267
|
const nativeRequest = callSchema2.parse(request);
|
|
27784
28268
|
const response = await this.call(nativeRequest);
|
|
27785
|
-
const
|
|
27786
|
-
if (
|
|
28269
|
+
const responseBytes2 = new TextEncoder().encode(JSON.stringify(response));
|
|
28270
|
+
if (responseBytes2.byteLength <= INLINE_TRANSFER_RESPONSE_BYTES2) {
|
|
27787
28271
|
return { kind: "inline", response };
|
|
27788
28272
|
}
|
|
27789
|
-
if (
|
|
28273
|
+
if (responseBytes2.byteLength > MAX_HARNESS_API_TRANSFER_BYTES) {
|
|
27790
28274
|
throw new RpcError("RESPONSE_TOO_LARGE", "The Harness API response exceeds the bounded transfer limit.");
|
|
27791
28275
|
}
|
|
27792
28276
|
this.pruneTransfers();
|
|
27793
28277
|
if (this.outgoingTransfers.size >= MAX_ACTIVE_API_TRANSFERS) {
|
|
27794
28278
|
throw new RpcError("RATE_LIMITED", "Too many Harness API response transfers are active.", void 0, true);
|
|
27795
28279
|
}
|
|
27796
|
-
const totalChunks = Math.ceil(
|
|
28280
|
+
const totalChunks = Math.ceil(responseBytes2.byteLength / HARNESS_API_TRANSFER_CHUNK_BYTES);
|
|
27797
28281
|
this.outgoingTransfers.set(params.transferId, {
|
|
27798
|
-
bytes:
|
|
28282
|
+
bytes: responseBytes2,
|
|
27799
28283
|
totalChunks,
|
|
27800
28284
|
nextIndex: 0,
|
|
27801
28285
|
touchedAt: Date.now()
|
|
27802
28286
|
});
|
|
27803
|
-
return { kind: "chunked", transferId: params.transferId, totalBytes:
|
|
28287
|
+
return { kind: "chunked", transferId: params.transferId, totalBytes: responseBytes2.byteLength, totalChunks };
|
|
27804
28288
|
}
|
|
27805
28289
|
readTransfer(input2) {
|
|
27806
28290
|
this.pruneTransfers();
|
|
@@ -28221,6 +28705,8 @@ function concatChunks2(chunks, totalBytes) {
|
|
|
28221
28705
|
}
|
|
28222
28706
|
|
|
28223
28707
|
// src/harness-remote-bridge.ts
|
|
28708
|
+
import { Buffer as Buffer5 } from "node:buffer";
|
|
28709
|
+
import { isAbsolute as isAbsolute4, join as join10, relative as relative2, sep } from "node:path";
|
|
28224
28710
|
var endpointSchema = external_exports.string().min(1).max(128).regex(/^(?:\$events(?:\/result)?|[A-Za-z0-9_$.-]+\/[A-Za-z0-9_$.-]+)$/);
|
|
28225
28711
|
var callSchema3 = external_exports.object({ endpoint: endpointSchema, payload: external_exports.unknown() }).strict();
|
|
28226
28712
|
var streamOpenSchema3 = external_exports.object({
|
|
@@ -28268,6 +28754,12 @@ var directoryListSchema2 = external_exports.object({
|
|
|
28268
28754
|
var MAX_ACTIVE_STREAMS2 = MAX_ALPHA_STREAMS_PER_CONNECTION;
|
|
28269
28755
|
var MAX_ACTIVE_TRANSFERS2 = MAX_ACTIVE_TRANSFERS_PER_DIRECTION;
|
|
28270
28756
|
var INLINE_TRANSFER_RESPONSE_BYTES3 = 2 * 1024 * 1024;
|
|
28757
|
+
var STREAM_FRAME_ENVELOPE_BYTES = 4 * 1024;
|
|
28758
|
+
var RESPONSE_ENVELOPE_BYTES2 = 8 * 1024;
|
|
28759
|
+
var WORKSPACE_FILE_ENDPOINTS = /* @__PURE__ */ new Set(["workspaceFiles/stat", "workspaceFiles/read", "workspaceFiles/readBytes", "workspaceFiles/list"]);
|
|
28760
|
+
var REMOTE_HISTORY_CALL_ENDPOINTS = /* @__PURE__ */ new Set(["session/page", "session/history"]);
|
|
28761
|
+
var REMOTE_HISTORY_STREAM_ENDPOINTS = /* @__PURE__ */ new Set(["session/follow"]);
|
|
28762
|
+
var MAX_CACHED_HISTORY_PAGES = 64;
|
|
28271
28763
|
var HARNESS_REMOTE_ALLOWLIST = [
|
|
28272
28764
|
"$events",
|
|
28273
28765
|
"$events/result",
|
|
@@ -28345,17 +28837,22 @@ var HARNESS_REMOTE_ALLOWLIST = [
|
|
|
28345
28837
|
];
|
|
28346
28838
|
var allowedEndpoints = new Set(HARNESS_REMOTE_ALLOWLIST);
|
|
28347
28839
|
var HarnessRemoteBridge = class {
|
|
28348
|
-
constructor(gateway, publish, logger, harnessVersion, terminal = new TerminalPolicy(() => false, "", /* @__PURE__ */ new Map()), codexWorkspace) {
|
|
28840
|
+
constructor(gateway, publish, logger, harnessVersion, terminal = new TerminalPolicy(() => false, "", /* @__PURE__ */ new Map()), codexWorkspace, sessionHeaderCwd) {
|
|
28349
28841
|
this.gateway = gateway;
|
|
28350
28842
|
this.publish = publish;
|
|
28351
28843
|
this.logger = logger;
|
|
28352
28844
|
this.harnessVersion = harnessVersion;
|
|
28353
28845
|
this.terminal = terminal;
|
|
28354
28846
|
this.codexWorkspace = codexWorkspace;
|
|
28847
|
+
this.sessionHeaderCwd = sessionHeaderCwd;
|
|
28355
28848
|
}
|
|
28356
28849
|
streams = /* @__PURE__ */ new Map();
|
|
28357
28850
|
incomingTransfers = /* @__PURE__ */ new Map();
|
|
28358
28851
|
outgoingTransfers = /* @__PURE__ */ new Map();
|
|
28852
|
+
/** Last page size that fit one secure message, per session, so probing is paid once. */
|
|
28853
|
+
historyPageSizes = /* @__PURE__ */ new Map();
|
|
28854
|
+
/** Last working directory read per Session, so a file open does not re-project on every chunk. */
|
|
28855
|
+
sessionWorkingDirectories = /* @__PURE__ */ new Map();
|
|
28359
28856
|
/**
|
|
28360
28857
|
* Answer one remote Gateway call.
|
|
28361
28858
|
*
|
|
@@ -28396,7 +28893,7 @@ var HarnessRemoteBridge = class {
|
|
|
28396
28893
|
const signal = AbortSignal.timeout(6e4);
|
|
28397
28894
|
try {
|
|
28398
28895
|
const nativePayload = normalizeWorkspaceRequestPayload(params.endpoint, params.payload, this.harnessVersion);
|
|
28399
|
-
const nativeResult = params.endpoint === "commands/execute" ? await dispatchCommandForHost(this.gateway, params.payload, signal, this.harnessVersion) : await this.
|
|
28896
|
+
const nativeResult = REMOTE_HISTORY_CALL_ENDPOINTS.has(params.endpoint) ? await this.dispatchHistoryPage(params.endpoint, nativePayload, signal) : params.endpoint === "commands/execute" ? await dispatchCommandForHost(this.gateway, params.payload, signal, this.harnessVersion) : await this.dispatchWorkspaceFile(params.endpoint, nativePayload, signal);
|
|
28400
28897
|
const result = params.endpoint === "directoryPicker/list" && needsDirectoryFallback(nativeResult) ? await this.directoryList(params.payload, signal) : nativeResult;
|
|
28401
28898
|
this.logger?.debug("harness remote call ok", {
|
|
28402
28899
|
endpoint: params.endpoint,
|
|
@@ -28417,7 +28914,10 @@ var HarnessRemoteBridge = class {
|
|
|
28417
28914
|
this.logger?.warn("harness remote call failed", {
|
|
28418
28915
|
endpoint: params.endpoint,
|
|
28419
28916
|
durationMs: Math.round(performance.now() - startedAt),
|
|
28420
|
-
code: this.gateway.failure(error).code
|
|
28917
|
+
code: this.gateway.failure(error).code,
|
|
28918
|
+
// The code alone is what the client sees; the message is the only thing that says WHY the
|
|
28919
|
+
// Host threw (a strict-schema rejection, an oversized page, a session the gateway lost).
|
|
28920
|
+
message: error instanceof Error ? error.message : String(error)
|
|
28421
28921
|
});
|
|
28422
28922
|
throw error;
|
|
28423
28923
|
}
|
|
@@ -28487,22 +28987,22 @@ var HarnessRemoteBridge = class {
|
|
|
28487
28987
|
throw new RpcError("INVALID_MESSAGE", "The Harness Remote transfer does not contain a valid request.");
|
|
28488
28988
|
}
|
|
28489
28989
|
const response = await this.call(callSchema3.parse(request));
|
|
28490
|
-
const
|
|
28491
|
-
if (
|
|
28492
|
-
if (
|
|
28990
|
+
const responseBytes2 = new TextEncoder().encode(JSON.stringify(response));
|
|
28991
|
+
if (responseBytes2.byteLength <= INLINE_TRANSFER_RESPONSE_BYTES3) return { kind: "inline", response };
|
|
28992
|
+
if (responseBytes2.byteLength > MAX_HARNESS_API_TRANSFER_BYTES) {
|
|
28493
28993
|
throw new RpcError("RESPONSE_TOO_LARGE", "The Harness Remote response exceeds the bounded transfer limit.");
|
|
28494
28994
|
}
|
|
28495
28995
|
if (this.outgoingTransfers.size >= MAX_ACTIVE_TRANSFERS2) {
|
|
28496
28996
|
throw new RpcError("RATE_LIMITED", "Too many Harness Remote response transfers are active.", void 0, true);
|
|
28497
28997
|
}
|
|
28498
|
-
const totalChunks = Math.ceil(
|
|
28998
|
+
const totalChunks = Math.ceil(responseBytes2.byteLength / HARNESS_API_TRANSFER_CHUNK_BYTES);
|
|
28499
28999
|
this.outgoingTransfers.set(params.transferId, {
|
|
28500
|
-
bytes:
|
|
29000
|
+
bytes: responseBytes2,
|
|
28501
29001
|
totalChunks,
|
|
28502
29002
|
nextIndex: 0,
|
|
28503
29003
|
touchedAt: Date.now()
|
|
28504
29004
|
});
|
|
28505
|
-
return { kind: "chunked", transferId: params.transferId, totalBytes:
|
|
29005
|
+
return { kind: "chunked", transferId: params.transferId, totalBytes: responseBytes2.byteLength, totalChunks };
|
|
28506
29006
|
}
|
|
28507
29007
|
readTransfer(input2) {
|
|
28508
29008
|
this.pruneTransfers();
|
|
@@ -28520,7 +29020,7 @@ var HarnessRemoteBridge = class {
|
|
|
28520
29020
|
return {
|
|
28521
29021
|
transferId: params.transferId,
|
|
28522
29022
|
index: params.index,
|
|
28523
|
-
data:
|
|
29023
|
+
data: Buffer5.from(transfer.bytes.subarray(start, end)).toString("base64")
|
|
28524
29024
|
};
|
|
28525
29025
|
}
|
|
28526
29026
|
closeTransfer(input2) {
|
|
@@ -28540,17 +29040,197 @@ var HarnessRemoteBridge = class {
|
|
|
28540
29040
|
}
|
|
28541
29041
|
const controller = new AbortController();
|
|
28542
29042
|
this.streams.set(params.streamId, { controller });
|
|
28543
|
-
let source;
|
|
28544
29043
|
try {
|
|
28545
|
-
|
|
29044
|
+
const nativePayload = normalizeWorkspaceChangesPayload(params.endpoint, params.payload, this.harnessVersion);
|
|
29045
|
+
if (REMOTE_HISTORY_STREAM_ENDPOINTS.has(params.endpoint)) {
|
|
29046
|
+
const opened = await this.openHistoryStream(params, nativePayload, controller.signal);
|
|
29047
|
+
void this.pump(params.streamId, opened.source, controller.signal, opened.frame);
|
|
29048
|
+
return { opened: true, streamId: params.streamId };
|
|
29049
|
+
}
|
|
29050
|
+
const source = (this.codexWorkspace === void 0 ? void 0 : await this.codexWorkspace.open(params.endpoint, params.payload, controller.signal)) ?? await this.gateway.open(params.endpoint, nativePayload, controller.signal);
|
|
28546
29051
|
controller.signal.throwIfAborted();
|
|
29052
|
+
void this.pump(params.streamId, source, controller.signal);
|
|
29053
|
+
return { opened: true, streamId: params.streamId };
|
|
28547
29054
|
} catch (error) {
|
|
28548
29055
|
this.streams.delete(params.streamId);
|
|
28549
29056
|
controller.abort();
|
|
29057
|
+
this.logger?.warn("harness remote stream open failed", {
|
|
29058
|
+
endpoint: params.endpoint,
|
|
29059
|
+
message: error instanceof Error ? error.message : String(error)
|
|
29060
|
+
});
|
|
28550
29061
|
throw error;
|
|
28551
29062
|
}
|
|
28552
|
-
|
|
28553
|
-
|
|
29063
|
+
}
|
|
29064
|
+
/**
|
|
29065
|
+
* Open a history stream with its opening page chosen by byte budget.
|
|
29066
|
+
*
|
|
29067
|
+
* `session/follow` yields one snapshot page and then live events. The snapshot cannot be
|
|
29068
|
+
* retried after the stream is handed to the client, so each attempt is pulled here, measured,
|
|
29069
|
+
* and closed again when it would not fit one secure message. The accepted iterator continues
|
|
29070
|
+
* after that first frame, which the pump publishes before the live tail.
|
|
29071
|
+
* @param params - the validated stream-open request.
|
|
29072
|
+
* @param payload - the request payload after endpoint normalization.
|
|
29073
|
+
* @param signal - the stream's cancellation signal.
|
|
29074
|
+
* @returns the remaining source and the measured first frame.
|
|
29075
|
+
*/
|
|
29076
|
+
async openHistoryStream(params, payload, signal) {
|
|
29077
|
+
const { key, sizes } = this.historyPageSizesFor(payload);
|
|
29078
|
+
for (const size of sizes) {
|
|
29079
|
+
const source = await this.gateway.open(params.endpoint, withHistoryWindow(payload, size), signal);
|
|
29080
|
+
const iterator = source[Symbol.asyncIterator]();
|
|
29081
|
+
let first;
|
|
29082
|
+
try {
|
|
29083
|
+
first = await iterator.next();
|
|
29084
|
+
} catch (error) {
|
|
29085
|
+
await iterator.return?.();
|
|
29086
|
+
throw error;
|
|
29087
|
+
}
|
|
29088
|
+
if (first.done === true) {
|
|
29089
|
+
await iterator.return?.();
|
|
29090
|
+
return { source: EMPTY_ASYNC_ITERABLE };
|
|
29091
|
+
}
|
|
29092
|
+
const frame = {
|
|
29093
|
+
streamId: params.streamId,
|
|
29094
|
+
hasValue: true,
|
|
29095
|
+
...first.value === void 0 ? {} : { value: collectRpcAttachments(first.value) }
|
|
29096
|
+
};
|
|
29097
|
+
const bytes = streamFrameBytes(frame);
|
|
29098
|
+
this.logger?.debug("history stream attempt", { endpoint: params.endpoint, size, bytes });
|
|
29099
|
+
if (bytes <= MAX_SECURE_MESSAGE_BYTES - STREAM_FRAME_ENVELOPE_BYTES) {
|
|
29100
|
+
this.rememberHistoryPageSize(key, size);
|
|
29101
|
+
return { source: remainingIterable(iterator), frame };
|
|
29102
|
+
}
|
|
29103
|
+
await iterator.return?.();
|
|
29104
|
+
}
|
|
29105
|
+
throw new RpcError(
|
|
29106
|
+
"RESPONSE_TOO_LARGE",
|
|
29107
|
+
"The Harness history page is too large for the secure Remote channel. Request a smaller page.",
|
|
29108
|
+
{ maxBytes: MAX_SECURE_MESSAGE_BYTES },
|
|
29109
|
+
true
|
|
29110
|
+
);
|
|
29111
|
+
}
|
|
29112
|
+
/**
|
|
29113
|
+
* Answer one unary history page with the largest window that still fits.
|
|
29114
|
+
*
|
|
29115
|
+
* DSH's client asks for 500 messages; a session whose only Turn is long answers that with
|
|
29116
|
+
* its whole log, which the 4 MiB secure-message limit cannot carry. Each page is measured
|
|
29117
|
+
* after attachment tagging (the form the wire carries) and the request steps down through
|
|
29118
|
+
* {@link HISTORY_PAGE_SIZES} until it fits. Only a size overflow retries: a business failure
|
|
29119
|
+
* is returned immediately.
|
|
29120
|
+
* @param endpoint - the history endpoint being called.
|
|
29121
|
+
* @param payload - the request payload after endpoint normalization.
|
|
29122
|
+
* @param signal - the call's timeout signal.
|
|
29123
|
+
* @returns the page, or `RESPONSE_TOO_LARGE` when even one message cannot cross.
|
|
29124
|
+
*/
|
|
29125
|
+
async dispatchHistoryPage(endpoint, payload, signal) {
|
|
29126
|
+
const { key, sizes } = this.historyPageSizesFor(payload);
|
|
29127
|
+
for (const size of sizes) {
|
|
29128
|
+
const startedAt = performance.now();
|
|
29129
|
+
const result = await this.gateway.dispatch(endpoint, withHistoryWindow(payload, size), signal);
|
|
29130
|
+
if (!result.ok) return result;
|
|
29131
|
+
const value = result.value;
|
|
29132
|
+
const wire = collectRpcAttachments(result);
|
|
29133
|
+
const bytes = responseBytes(wire);
|
|
29134
|
+
this.logger?.debug("history page attempt", {
|
|
29135
|
+
endpoint,
|
|
29136
|
+
size,
|
|
29137
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
29138
|
+
ok: result.ok,
|
|
29139
|
+
records: Array.isArray(value?.records) ? value.records.length : void 0,
|
|
29140
|
+
hasMore: value?.hasMore,
|
|
29141
|
+
bytes
|
|
29142
|
+
});
|
|
29143
|
+
if (bytes <= MAX_SECURE_MESSAGE_BYTES - RESPONSE_ENVELOPE_BYTES2) {
|
|
29144
|
+
this.rememberHistoryPageSize(key, size);
|
|
29145
|
+
return wire;
|
|
29146
|
+
}
|
|
29147
|
+
}
|
|
29148
|
+
return {
|
|
29149
|
+
ok: false,
|
|
29150
|
+
error: {
|
|
29151
|
+
code: "RESPONSE_TOO_LARGE",
|
|
29152
|
+
message: "The Harness history page is too large for the secure Remote channel. Request a smaller page.",
|
|
29153
|
+
details: { maxBytes: MAX_SECURE_MESSAGE_BYTES }
|
|
29154
|
+
}
|
|
29155
|
+
};
|
|
29156
|
+
}
|
|
29157
|
+
/** The ladder for one history request, starting from what last fit for this session. */
|
|
29158
|
+
historyPageSizesFor(payload) {
|
|
29159
|
+
const key = historyPageKey(payload);
|
|
29160
|
+
const requested = requestedMaxMessages(payload) ?? HISTORY_PAGE_SIZES[0];
|
|
29161
|
+
const remembered = this.historyPageSizes.get(key);
|
|
29162
|
+
const start = Math.max(1, Math.min(requested, HISTORY_PAGE_SIZES[0], remembered ?? requested));
|
|
29163
|
+
this.logger?.debug("history window", { key: key.slice(0, 120), requested, remembered, start });
|
|
29164
|
+
return { key, sizes: historyPageSizes(start) };
|
|
29165
|
+
}
|
|
29166
|
+
rememberHistoryPageSize(key, size) {
|
|
29167
|
+
if (this.historyPageSizes.size >= MAX_CACHED_HISTORY_PAGES && !this.historyPageSizes.has(key)) {
|
|
29168
|
+
const oldest = this.historyPageSizes.keys().next().value;
|
|
29169
|
+
if (oldest !== void 0) this.historyPageSizes.delete(oldest);
|
|
29170
|
+
}
|
|
29171
|
+
this.historyPageSizes.set(key, size);
|
|
29172
|
+
}
|
|
29173
|
+
/**
|
|
29174
|
+
* Dispatch a workspace-file call, re-rooting a path written against the Session's current
|
|
29175
|
+
* directory onto the header root the Host resolves against.
|
|
29176
|
+
*
|
|
29177
|
+
* DSH names the workspace root from the Session header, so a Session that changed its working
|
|
29178
|
+
* directory mid-run resolves relative links (and deliverables built from the root) against the
|
|
29179
|
+
* stale header. The live `workingDirectory` projection is the correction; when the re-rooted
|
|
29180
|
+
* path misses, the original path is tried so a file created before the change still opens.
|
|
29181
|
+
*/
|
|
29182
|
+
async dispatchWorkspaceFile(endpoint, payload, signal) {
|
|
29183
|
+
if (!WORKSPACE_FILE_ENDPOINTS.has(endpoint) || this.sessionHeaderCwd === void 0) {
|
|
29184
|
+
return this.gateway.dispatch(endpoint, payload, signal);
|
|
29185
|
+
}
|
|
29186
|
+
const location = workspaceFileLocation(payload);
|
|
29187
|
+
if (location === void 0) return this.gateway.dispatch(endpoint, payload, signal);
|
|
29188
|
+
const headerCwd = this.sessionHeaderCwd(location.sessionId);
|
|
29189
|
+
if (headerCwd === void 0) return this.gateway.dispatch(endpoint, payload, signal);
|
|
29190
|
+
const liveCwd = await this.sessionWorkingDirectory(location.sessionId, signal);
|
|
29191
|
+
if (liveCwd === void 0) return this.gateway.dispatch(endpoint, payload, signal);
|
|
29192
|
+
const nextPath = rerootWorkspacePath(headerCwd, liveCwd, location.path);
|
|
29193
|
+
if (nextPath === void 0 || nextPath === location.path) return this.gateway.dispatch(endpoint, payload, signal);
|
|
29194
|
+
const rewritten = replaceWorkspaceFilePath(payload, nextPath);
|
|
29195
|
+
if (rewritten === payload) return this.gateway.dispatch(endpoint, payload, signal);
|
|
29196
|
+
const rerooted = await this.gateway.dispatch(endpoint, rewritten, signal);
|
|
29197
|
+
if (!workspaceFileMiss(endpoint, rerooted)) {
|
|
29198
|
+
this.logger?.debug("workspace file path re-rooted", {
|
|
29199
|
+
endpoint,
|
|
29200
|
+
headerCwd,
|
|
29201
|
+
liveCwd,
|
|
29202
|
+
path: location.path,
|
|
29203
|
+
nextPath
|
|
29204
|
+
});
|
|
29205
|
+
return rerooted;
|
|
29206
|
+
}
|
|
29207
|
+
this.logger?.debug("workspace file path re-root missed; using the original path", {
|
|
29208
|
+
endpoint,
|
|
29209
|
+
path: location.path,
|
|
29210
|
+
nextPath
|
|
29211
|
+
});
|
|
29212
|
+
return this.gateway.dispatch(endpoint, payload, signal);
|
|
29213
|
+
}
|
|
29214
|
+
/** Read the Session's live working directory from its projections, cached briefly. */
|
|
29215
|
+
async sessionWorkingDirectory(sessionId, signal) {
|
|
29216
|
+
const cached = this.sessionWorkingDirectories.get(sessionId);
|
|
29217
|
+
if (cached !== void 0 && Date.now() - cached.at < 5e3) return cached.cwd;
|
|
29218
|
+
let cwd;
|
|
29219
|
+
try {
|
|
29220
|
+
const result = await this.gateway.dispatch("session/projections", { args: { request: { sessionId } } }, signal);
|
|
29221
|
+
const value = result.ok ? result.value : void 0;
|
|
29222
|
+
const values = isRecord14(value) && isRecord14(value.values) ? value.values : void 0;
|
|
29223
|
+
const raw = values?.workingDirectory;
|
|
29224
|
+
if (typeof raw === "string" && raw.length > 0) cwd = raw;
|
|
29225
|
+
} catch {
|
|
29226
|
+
cwd = void 0;
|
|
29227
|
+
}
|
|
29228
|
+
if (this.sessionWorkingDirectories.size >= MAX_CACHED_HISTORY_PAGES && !this.sessionWorkingDirectories.has(sessionId)) {
|
|
29229
|
+
const oldest = this.sessionWorkingDirectories.keys().next().value;
|
|
29230
|
+
if (oldest !== void 0) this.sessionWorkingDirectories.delete(oldest);
|
|
29231
|
+
}
|
|
29232
|
+
this.sessionWorkingDirectories.set(sessionId, { cwd, at: Date.now() });
|
|
29233
|
+
return cwd;
|
|
28554
29234
|
}
|
|
28555
29235
|
closeStream(input2) {
|
|
28556
29236
|
const params = streamCloseSchema3.parse(input2);
|
|
@@ -28566,6 +29246,7 @@ var HarnessRemoteBridge = class {
|
|
|
28566
29246
|
this.streams.clear();
|
|
28567
29247
|
this.incomingTransfers.clear();
|
|
28568
29248
|
this.outgoingTransfers.clear();
|
|
29249
|
+
this.historyPageSizes.clear();
|
|
28569
29250
|
for (const [, stream] of streams) stream.controller.abort(reason);
|
|
28570
29251
|
await this.codexWorkspace?.closeAll();
|
|
28571
29252
|
}
|
|
@@ -28577,17 +29258,28 @@ var HarnessRemoteBridge = class {
|
|
|
28577
29258
|
throw new RpcError("FEATURE_NOT_SUPPORTED", "This Harness version does not support Remote event transport.");
|
|
28578
29259
|
}
|
|
28579
29260
|
}
|
|
28580
|
-
async pump(streamId, source, signal) {
|
|
29261
|
+
async pump(streamId, source, signal, initialFrame) {
|
|
28581
29262
|
let reason = "completed";
|
|
28582
29263
|
let failure2;
|
|
28583
29264
|
try {
|
|
29265
|
+
if (initialFrame !== void 0 && !signal.aborted) await this.publish("harness.remote.frame", initialFrame);
|
|
28584
29266
|
for await (const value of source) {
|
|
28585
29267
|
if (signal.aborted) break;
|
|
28586
|
-
|
|
29268
|
+
const frame = {
|
|
28587
29269
|
streamId,
|
|
28588
29270
|
hasValue: true,
|
|
28589
29271
|
...value === void 0 ? {} : { value: collectRpcAttachments(value) }
|
|
28590
|
-
}
|
|
29272
|
+
};
|
|
29273
|
+
if (streamFrameBytes(frame) > MAX_SECURE_MESSAGE_BYTES - STREAM_FRAME_ENVELOPE_BYTES) {
|
|
29274
|
+
failure2 = {
|
|
29275
|
+
code: "RESPONSE_TOO_LARGE",
|
|
29276
|
+
message: "The Harness stream frame is too large for the secure Remote channel.",
|
|
29277
|
+
details: { maxBytes: MAX_SECURE_MESSAGE_BYTES }
|
|
29278
|
+
};
|
|
29279
|
+
reason = "failed";
|
|
29280
|
+
break;
|
|
29281
|
+
}
|
|
29282
|
+
await this.publish("harness.remote.frame", frame);
|
|
28591
29283
|
}
|
|
28592
29284
|
if (signal.aborted) reason = "cancelled";
|
|
28593
29285
|
} catch (error) {
|
|
@@ -28608,6 +29300,130 @@ var HarnessRemoteBridge = class {
|
|
|
28608
29300
|
for (const [id4, transfer] of this.outgoingTransfers) if (transfer.touchedAt < cutoff) this.outgoingTransfers.delete(id4);
|
|
28609
29301
|
}
|
|
28610
29302
|
};
|
|
29303
|
+
function withHistoryWindow(payload, maxMessages) {
|
|
29304
|
+
if (!isRecord14(payload)) return payload;
|
|
29305
|
+
if (Array.isArray(payload.args)) {
|
|
29306
|
+
const [first, ...rest] = payload.args;
|
|
29307
|
+
return isRecord14(first) ? { ...payload, args: [windowedParams(first, maxMessages), ...rest] } : payload;
|
|
29308
|
+
}
|
|
29309
|
+
if (isRecord14(payload.args)) return { ...payload, args: windowedParams(payload.args, maxMessages) };
|
|
29310
|
+
return windowedParams(payload, maxMessages);
|
|
29311
|
+
}
|
|
29312
|
+
function windowedParams(params, maxMessages) {
|
|
29313
|
+
return isRecord14(params.request) ? { ...params, request: windowedRequest(params.request, maxMessages) } : windowedRequest(params, maxMessages);
|
|
29314
|
+
}
|
|
29315
|
+
function windowedRequest(request, maxMessages) {
|
|
29316
|
+
const turnWindow = isRecord14(request.turnWindow) ? request.turnWindow : void 0;
|
|
29317
|
+
const next = { ...request, maxMessages };
|
|
29318
|
+
if (turnWindow !== void 0) {
|
|
29319
|
+
next.turnWindow = typeof turnWindow.minMessages === "number" && Number.isFinite(turnWindow.minMessages) ? { ...turnWindow, minMessages: Math.min(turnWindow.minMessages, maxMessages) } : turnWindow;
|
|
29320
|
+
}
|
|
29321
|
+
return next;
|
|
29322
|
+
}
|
|
29323
|
+
function historyRequestOf(payload) {
|
|
29324
|
+
let node = payload;
|
|
29325
|
+
for (let depth = 0; depth < 4; depth += 1) {
|
|
29326
|
+
if (!isRecord14(node)) return void 0;
|
|
29327
|
+
if (Array.isArray(node.args)) {
|
|
29328
|
+
node = node.args[0];
|
|
29329
|
+
continue;
|
|
29330
|
+
}
|
|
29331
|
+
if (isRecord14(node.args)) {
|
|
29332
|
+
node = node.args;
|
|
29333
|
+
continue;
|
|
29334
|
+
}
|
|
29335
|
+
if (isRecord14(node.request)) {
|
|
29336
|
+
node = node.request;
|
|
29337
|
+
continue;
|
|
29338
|
+
}
|
|
29339
|
+
return node;
|
|
29340
|
+
}
|
|
29341
|
+
return void 0;
|
|
29342
|
+
}
|
|
29343
|
+
function requestedMaxMessages(payload) {
|
|
29344
|
+
const value = historyRequestOf(payload)?.maxMessages;
|
|
29345
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : void 0;
|
|
29346
|
+
}
|
|
29347
|
+
function workspaceFileLocation(payload) {
|
|
29348
|
+
if (!isRecord14(payload)) return void 0;
|
|
29349
|
+
if (Array.isArray(payload.args)) {
|
|
29350
|
+
if (payload.args.length >= 2 && typeof payload.args[0] === "string" && typeof payload.args[1] === "string") {
|
|
29351
|
+
return { sessionId: payload.args[0], path: payload.args[1] };
|
|
29352
|
+
}
|
|
29353
|
+
const first = payload.args[0];
|
|
29354
|
+
if (isRecord14(first)) return workspaceFileLocation(first);
|
|
29355
|
+
return void 0;
|
|
29356
|
+
}
|
|
29357
|
+
const candidate = isRecord14(payload.args) ? payload.args : isRecord14(payload.request) ? payload.request : payload;
|
|
29358
|
+
const sessionId = [candidate.workspaceFileScopeId, candidate.workspaceFileScope, candidate.sessionId].find((value) => typeof value === "string" && value.length > 0);
|
|
29359
|
+
const path = candidate.path;
|
|
29360
|
+
if (typeof sessionId !== "string" || typeof path !== "string" || path.length === 0) return void 0;
|
|
29361
|
+
return { sessionId, path };
|
|
29362
|
+
}
|
|
29363
|
+
function replaceWorkspaceFilePath(payload, nextPath) {
|
|
29364
|
+
if (!isRecord14(payload)) return payload;
|
|
29365
|
+
if (Array.isArray(payload.args)) {
|
|
29366
|
+
if (payload.args.length >= 2 && typeof payload.args[0] === "string" && typeof payload.args[1] === "string") {
|
|
29367
|
+
const args = [...payload.args];
|
|
29368
|
+
args[1] = nextPath;
|
|
29369
|
+
return { ...payload, args };
|
|
29370
|
+
}
|
|
29371
|
+
const first = payload.args[0];
|
|
29372
|
+
if (!isRecord14(first)) return payload;
|
|
29373
|
+
return { ...payload, args: [{ ...first, path: nextPath }, ...payload.args.slice(1)] };
|
|
29374
|
+
}
|
|
29375
|
+
if (isRecord14(payload.args)) return { ...payload, args: { ...payload.args, path: nextPath } };
|
|
29376
|
+
if (isRecord14(payload.request)) return { ...payload, request: { ...payload.request, path: nextPath } };
|
|
29377
|
+
return { ...payload, path: nextPath };
|
|
29378
|
+
}
|
|
29379
|
+
function workspaceFileMiss(endpoint, result) {
|
|
29380
|
+
if (!result.ok) return true;
|
|
29381
|
+
if (endpoint !== "workspaceFiles/stat") return false;
|
|
29382
|
+
const value = result.value;
|
|
29383
|
+
return isRecord14(value) && value.exists === false;
|
|
29384
|
+
}
|
|
29385
|
+
function rerootWorkspacePath(headerCwd, liveCwd, target2) {
|
|
29386
|
+
if (samePath(headerCwd, liveCwd)) return void 0;
|
|
29387
|
+
if (isAbsolute4(target2)) {
|
|
29388
|
+
const suffix = relative2(headerCwd, target2);
|
|
29389
|
+
if (suffix === "" || suffix === "." || isAbsolute4(suffix) || suffix.startsWith(`..${sep}`) || suffix === "..") return void 0;
|
|
29390
|
+
return join10(liveCwd, suffix);
|
|
29391
|
+
}
|
|
29392
|
+
const prefix = relative2(headerCwd, liveCwd);
|
|
29393
|
+
if (prefix !== "" && prefix !== "." && !isAbsolute4(prefix) && !prefix.startsWith(`..${sep}`) && prefix !== "..") {
|
|
29394
|
+
return join10(prefix, target2);
|
|
29395
|
+
}
|
|
29396
|
+
return join10(liveCwd, target2);
|
|
29397
|
+
}
|
|
29398
|
+
function samePath(left, right) {
|
|
29399
|
+
const normalize = (value) => {
|
|
29400
|
+
const trimmed = value.replace(/[\\/]+$/u, "");
|
|
29401
|
+
return process.platform === "win32" ? trimmed.toLowerCase() : trimmed;
|
|
29402
|
+
};
|
|
29403
|
+
return normalize(left) === normalize(right);
|
|
29404
|
+
}
|
|
29405
|
+
function historyPageKey(payload) {
|
|
29406
|
+
const request = historyRequestOf(payload);
|
|
29407
|
+
if (request === void 0) return "default";
|
|
29408
|
+
const address = request.address ?? request.request ?? request.threadId ?? request.sessionId;
|
|
29409
|
+
if (address === void 0) return "default";
|
|
29410
|
+
try {
|
|
29411
|
+
return JSON.stringify(address);
|
|
29412
|
+
} catch {
|
|
29413
|
+
return "default";
|
|
29414
|
+
}
|
|
29415
|
+
}
|
|
29416
|
+
function responseBytes(result) {
|
|
29417
|
+
return Buffer5.byteLength(JSON.stringify(result), "utf8");
|
|
29418
|
+
}
|
|
29419
|
+
function streamFrameBytes(frame) {
|
|
29420
|
+
return Buffer5.byteLength(JSON.stringify(frame), "utf8");
|
|
29421
|
+
}
|
|
29422
|
+
function remainingIterable(iterator) {
|
|
29423
|
+
return { [Symbol.asyncIterator]: () => iterator };
|
|
29424
|
+
}
|
|
29425
|
+
var EMPTY_ASYNC_ITERABLE = { async *[Symbol.asyncIterator]() {
|
|
29426
|
+
} };
|
|
28611
29427
|
function normalizeWorkspaceChangesPayload(endpoint, payload, harnessVersion) {
|
|
28612
29428
|
if (endpoint !== "workspaceFiles/changes" || !requiresWorkspaceChangePath(harnessVersion)) return payload;
|
|
28613
29429
|
if (!isRecord14(payload) || !isRecord14(payload.args) || Object.hasOwn(payload.args, "path")) return payload;
|
|
@@ -28661,7 +29477,7 @@ async function dispatchCommandForHost(gateway, payload, signal, harnessVersion)
|
|
|
28661
29477
|
);
|
|
28662
29478
|
}
|
|
28663
29479
|
function decodeCanonicalBase643(value) {
|
|
28664
|
-
const bytes =
|
|
29480
|
+
const bytes = Buffer5.from(value, "base64");
|
|
28665
29481
|
if (bytes.toString("base64") !== value) throw new RpcError("INVALID_MESSAGE", "The Harness Remote transfer chunk is invalid.");
|
|
28666
29482
|
return bytes;
|
|
28667
29483
|
}
|
|
@@ -28809,7 +29625,7 @@ import { execFileSync as execFileSync2 } from "node:child_process";
|
|
|
28809
29625
|
// src/codex-workspace-bridge.ts
|
|
28810
29626
|
import { spawn as spawn4 } from "node:child_process";
|
|
28811
29627
|
import { realpath as realpath2, lstat, readdir as readdir4, readFile as readFile5, stat as stat6, watch } from "node:fs/promises";
|
|
28812
|
-
import { isAbsolute as
|
|
29628
|
+
import { isAbsolute as isAbsolute5, join as join11, relative as relative3, resolve as resolve3 } from "node:path";
|
|
28813
29629
|
var MAX_READ_BYTES = 4 * 1024 * 1024;
|
|
28814
29630
|
var MAX_INPUT_BYTES = 64 * 1024;
|
|
28815
29631
|
var MAX_COLS = 240;
|
|
@@ -28896,7 +29712,7 @@ var CodexWorkspaceBridge = class {
|
|
|
28896
29712
|
const entries = await readdir4(target2, { withFileTypes: true });
|
|
28897
29713
|
const result = [];
|
|
28898
29714
|
for (const entry of entries.slice(0, 500)) {
|
|
28899
|
-
const item =
|
|
29715
|
+
const item = join11(target2, entry.name);
|
|
28900
29716
|
const info2 = await lstat(item);
|
|
28901
29717
|
if (info2.isSymbolicLink()) continue;
|
|
28902
29718
|
result.push({ name: entry.name, type: info2.isDirectory() ? "directory" : info2.isFile() ? "file" : "other", ...info2.isFile() ? { size: info2.size } : {} });
|
|
@@ -29118,15 +29934,15 @@ var CodexWorkspaceBridge = class {
|
|
|
29118
29934
|
}
|
|
29119
29935
|
}
|
|
29120
29936
|
async safePath(root, path, directory) {
|
|
29121
|
-
const candidate =
|
|
29122
|
-
const rel =
|
|
29123
|
-
if (rel.startsWith("..") ||
|
|
29937
|
+
const candidate = isAbsolute5(path) ? resolve3(path) : resolve3(root, path);
|
|
29938
|
+
const rel = relative3(root, candidate);
|
|
29939
|
+
if (rel.startsWith("..") || isAbsolute5(rel)) throw new RpcError("CODEX_WORKSPACE_PATH_DENIED", "The requested workspace path is outside the CodeX workspace.");
|
|
29124
29940
|
try {
|
|
29125
29941
|
const info = await lstat(candidate);
|
|
29126
29942
|
if (info.isSymbolicLink()) throw new Error();
|
|
29127
29943
|
const canonical = await realpath2(candidate);
|
|
29128
|
-
const canonicalRel =
|
|
29129
|
-
if (canonicalRel.startsWith("..") ||
|
|
29944
|
+
const canonicalRel = relative3(root, canonical);
|
|
29945
|
+
if (canonicalRel.startsWith("..") || isAbsolute5(canonicalRel)) throw new Error();
|
|
29130
29946
|
if (directory && !info.isDirectory()) throw new Error();
|
|
29131
29947
|
return canonical;
|
|
29132
29948
|
} catch {
|
|
@@ -29267,7 +30083,7 @@ function readLimit(args) {
|
|
|
29267
30083
|
|
|
29268
30084
|
// src/service.ts
|
|
29269
30085
|
var HostPluginRuntime = class {
|
|
29270
|
-
constructor(config, identities, apiProxy, logger, localGateway, fileViewerHost, terminalSpawner) {
|
|
30086
|
+
constructor(config, identities, apiProxy, logger, localGateway, fileViewerHost, terminalSpawner, sessionSource) {
|
|
29271
30087
|
this.config = config;
|
|
29272
30088
|
this.identities = identities;
|
|
29273
30089
|
this.apiProxy = apiProxy;
|
|
@@ -29275,6 +30091,7 @@ var HostPluginRuntime = class {
|
|
|
29275
30091
|
this.localGateway = localGateway;
|
|
29276
30092
|
this.fileViewerHost = fileViewerHost;
|
|
29277
30093
|
this.terminalSpawner = terminalSpawner;
|
|
30094
|
+
this.sessionSource = sessionSource;
|
|
29278
30095
|
this.terminalEnabled = config.terminal.enabled;
|
|
29279
30096
|
this.loopbackPorts = [...config.loopback.ports];
|
|
29280
30097
|
this.paused = config.hostControl?.paused === true;
|
|
@@ -29299,7 +30116,8 @@ var HostPluginRuntime = class {
|
|
|
29299
30116
|
() => this.terminalEnabled,
|
|
29300
30117
|
this.codexWorkspaceState,
|
|
29301
30118
|
this.terminalSpawner
|
|
29302
|
-
)
|
|
30119
|
+
),
|
|
30120
|
+
this.sessionSource === void 0 ? void 0 : (sessionId) => this.sessionSource?.()?.get?.(sessionId)?.header?.cwd
|
|
29303
30121
|
) : void 0;
|
|
29304
30122
|
const fileViewer = new RemoteFileViewerBridge(
|
|
29305
30123
|
() => this.fileViewerHost?.(),
|
|
@@ -29721,7 +30539,7 @@ function isPlainRecord(value) {
|
|
|
29721
30539
|
// src/cli.ts
|
|
29722
30540
|
import { stat as stat7 } from "node:fs/promises";
|
|
29723
30541
|
import { hostname as hostname3 } from "node:os";
|
|
29724
|
-
import { join as
|
|
30542
|
+
import { join as join12 } from "node:path";
|
|
29725
30543
|
var QR_POLL_INTERVAL_MS = 2e3;
|
|
29726
30544
|
var TERMINAL_QR_MARGIN = 4;
|
|
29727
30545
|
async function runCli(args = process.argv.slice(2), dependencies = {}) {
|
|
@@ -29809,7 +30627,7 @@ async function status(args, runtime) {
|
|
|
29809
30627
|
`Server: ${serverUrl}`,
|
|
29810
30628
|
"Host control: enabled (dsh-TUI default)"
|
|
29811
30629
|
];
|
|
29812
|
-
if (!await exists3(
|
|
30630
|
+
if (!await exists3(join12(directory, "device.json"))) {
|
|
29813
30631
|
lines.push("Device: not initialized", "Authorization: logged out", "Credential: unavailable");
|
|
29814
30632
|
write(runtime.stdout, `${lines.join("\n")}
|
|
29815
30633
|
|
|
@@ -29853,7 +30671,7 @@ async function logout(args, runtime) {
|
|
|
29853
30671
|
const serverUrl = selectedServer();
|
|
29854
30672
|
const root = new IdentityStore({ env: runtime.env }).directory;
|
|
29855
30673
|
const directory = serverStorageDirectory(root, serverUrl, "host");
|
|
29856
|
-
if (!await exists3(
|
|
30674
|
+
if (!await exists3(join12(directory, "device.json"))) {
|
|
29857
30675
|
await new ServerCredentialStore(directory).clear();
|
|
29858
30676
|
write(runtime.stdout, "This Host is already logged out.\n");
|
|
29859
30677
|
return 0;
|
|
@@ -30518,6 +31336,7 @@ async function activate(ctx, readConfig, entryId, tuiBinding) {
|
|
|
30518
31336
|
const nativeTypertGateway = ctx.get("typertGateway");
|
|
30519
31337
|
const localTypertGateway = new TypertGatewaySwitch(nativeTypertGateway, { peer: peerFor }).local();
|
|
30520
31338
|
const subprocess = ctx.get("subprocess", false);
|
|
31339
|
+
const sessionSource = () => ctx.get("sessions", false);
|
|
30521
31340
|
const deepseekAccount = ctx.get("deepseekAccount", false);
|
|
30522
31341
|
const deepseekSession = deepseekAccount === void 0 ? void 0 : {
|
|
30523
31342
|
read: async () => {
|
|
@@ -30549,7 +31368,8 @@ async function activate(ctx, readConfig, entryId, tuiBinding) {
|
|
|
30549
31368
|
logger,
|
|
30550
31369
|
localTypertGateway,
|
|
30551
31370
|
() => ctx.get("fileViewerHost"),
|
|
30552
|
-
subprocess === void 0 ? void 0 : subprocessTerminalSpawner(subprocess)
|
|
31371
|
+
subprocess === void 0 ? void 0 : subprocessTerminalSpawner(subprocess),
|
|
31372
|
+
sessionSource
|
|
30553
31373
|
);
|
|
30554
31374
|
let clientRuntime;
|
|
30555
31375
|
const hostControl = runtime;
|