@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.
Files changed (232) hide show
  1. package/README.md +14 -3
  2. package/cordis.patch.yml +30 -30
  3. package/dist/client.github.js +1 -1
  4. package/dist/client.js +1 -1
  5. package/dist/index.js +1216 -396
  6. package/dist/index.js.map +4 -4
  7. package/dsh-plugin.json +41 -41
  8. package/package.json +165 -165
  9. package/dist/acp.d.ts +0 -75
  10. package/dist/acp.d.ts.map +0 -1
  11. package/dist/acp.js +0 -61
  12. package/dist/acp.js.map +0 -1
  13. package/dist/api-proxy-switch.d.ts +0 -33
  14. package/dist/api-proxy-switch.d.ts.map +0 -1
  15. package/dist/api-proxy-switch.js +0 -109
  16. package/dist/api-proxy-switch.js.map +0 -1
  17. package/dist/atomic-file.d.ts +0 -16
  18. package/dist/atomic-file.d.ts.map +0 -1
  19. package/dist/atomic-file.js +0 -81
  20. package/dist/atomic-file.js.map +0 -1
  21. package/dist/bin/doctor.d.ts +0 -3
  22. package/dist/bin/doctor.d.ts.map +0 -1
  23. package/dist/bin/doctor.js +0 -13
  24. package/dist/bin/doctor.js.map +0 -1
  25. package/dist/cli.d.ts +0 -32
  26. package/dist/cli.d.ts.map +0 -1
  27. package/dist/cli.js +0 -289
  28. package/dist/cli.js.map +0 -1
  29. package/dist/client-runtime.d.ts +0 -375
  30. package/dist/client-runtime.d.ts.map +0 -1
  31. package/dist/client-runtime.js +0 -1768
  32. package/dist/client-runtime.js.map +0 -1
  33. package/dist/client-secure-transport.d.ts +0 -23
  34. package/dist/client-secure-transport.d.ts.map +0 -1
  35. package/dist/client-secure-transport.js +0 -144
  36. package/dist/client-secure-transport.js.map +0 -1
  37. package/dist/client-target-store.d.ts +0 -40
  38. package/dist/client-target-store.d.ts.map +0 -1
  39. package/dist/client-target-store.js +0 -79
  40. package/dist/client-target-store.js.map +0 -1
  41. package/dist/client.d.ts +0 -13
  42. package/dist/client.d.ts.map +0 -1
  43. package/dist/codex/app-server.d.ts +0 -71
  44. package/dist/codex/app-server.d.ts.map +0 -1
  45. package/dist/codex/app-server.js +0 -268
  46. package/dist/codex/app-server.js.map +0 -1
  47. package/dist/codex/codex-image-codec.d.ts +0 -7
  48. package/dist/codex/codex-image-codec.d.ts.map +0 -1
  49. package/dist/codex/codex-image-codec.js +0 -288
  50. package/dist/codex/codex-image-codec.js.map +0 -1
  51. package/dist/codex/domain.d.ts +0 -102
  52. package/dist/codex/domain.d.ts.map +0 -1
  53. package/dist/codex/domain.js +0 -1323
  54. package/dist/codex/domain.js.map +0 -1
  55. package/dist/codex/method-policy.d.ts +0 -338
  56. package/dist/codex/method-policy.d.ts.map +0 -1
  57. package/dist/codex/method-policy.js +0 -141
  58. package/dist/codex/method-policy.js.map +0 -1
  59. package/dist/codex/peer-bridge.d.ts +0 -57
  60. package/dist/codex/peer-bridge.d.ts.map +0 -1
  61. package/dist/codex/peer-bridge.js +0 -299
  62. package/dist/codex/peer-bridge.js.map +0 -1
  63. package/dist/codex/permissions.d.ts +0 -4
  64. package/dist/codex/permissions.d.ts.map +0 -1
  65. package/dist/codex/permissions.js +0 -21
  66. package/dist/codex/permissions.js.map +0 -1
  67. package/dist/codex/session-id.d.ts +0 -10
  68. package/dist/codex/session-id.d.ts.map +0 -1
  69. package/dist/codex/session-id.js +0 -15
  70. package/dist/codex/session-id.js.map +0 -1
  71. package/dist/codex/virtual-harness.d.ts +0 -209
  72. package/dist/codex/virtual-harness.d.ts.map +0 -1
  73. package/dist/codex/virtual-harness.js +0 -2650
  74. package/dist/codex/virtual-harness.js.map +0 -1
  75. package/dist/codex-workspace-bridge.d.ts +0 -108
  76. package/dist/codex-workspace-bridge.d.ts.map +0 -1
  77. package/dist/codex-workspace-bridge.js +0 -502
  78. package/dist/codex-workspace-bridge.js.map +0 -1
  79. package/dist/config.d.ts +0 -99
  80. package/dist/config.d.ts.map +0 -1
  81. package/dist/config.js +0 -127
  82. package/dist/config.js.map +0 -1
  83. package/dist/connection-controller.d.ts +0 -44
  84. package/dist/connection-controller.d.ts.map +0 -1
  85. package/dist/connection-controller.js +0 -185
  86. package/dist/connection-controller.js.map +0 -1
  87. package/dist/control-route.d.ts +0 -34
  88. package/dist/control-route.d.ts.map +0 -1
  89. package/dist/control-route.js +0 -136
  90. package/dist/control-route.js.map +0 -1
  91. package/dist/control-runtime.d.ts +0 -138
  92. package/dist/control-runtime.d.ts.map +0 -1
  93. package/dist/control-runtime.js +0 -543
  94. package/dist/control-runtime.js.map +0 -1
  95. package/dist/control-stream.d.ts +0 -67
  96. package/dist/control-stream.d.ts.map +0 -1
  97. package/dist/control-stream.js +0 -179
  98. package/dist/control-stream.js.map +0 -1
  99. package/dist/defaults.d.ts +0 -9
  100. package/dist/defaults.d.ts.map +0 -1
  101. package/dist/defaults.js +0 -9
  102. package/dist/defaults.js.map +0 -1
  103. package/dist/file-viewer-bridge.d.ts +0 -30
  104. package/dist/file-viewer-bridge.d.ts.map +0 -1
  105. package/dist/file-viewer-bridge.js +0 -128
  106. package/dist/file-viewer-bridge.js.map +0 -1
  107. package/dist/file-viewer-contract.d.ts +0 -5
  108. package/dist/file-viewer-contract.d.ts.map +0 -1
  109. package/dist/file-viewer-contract.js +0 -4
  110. package/dist/file-viewer-contract.js.map +0 -1
  111. package/dist/harness-api-bridge.d.ts +0 -81
  112. package/dist/harness-api-bridge.d.ts.map +0 -1
  113. package/dist/harness-api-bridge.js +0 -862
  114. package/dist/harness-api-bridge.js.map +0 -1
  115. package/dist/harness-api-history.d.ts +0 -3
  116. package/dist/harness-api-history.d.ts.map +0 -1
  117. package/dist/harness-api-history.js +0 -59
  118. package/dist/harness-api-history.js.map +0 -1
  119. package/dist/harness-remote-bridge.d.ts +0 -60
  120. package/dist/harness-remote-bridge.d.ts.map +0 -1
  121. package/dist/harness-remote-bridge.js +0 -540
  122. package/dist/harness-remote-bridge.js.map +0 -1
  123. package/dist/harness-version.d.ts +0 -23
  124. package/dist/harness-version.d.ts.map +0 -1
  125. package/dist/harness-version.js +0 -95
  126. package/dist/harness-version.js.map +0 -1
  127. package/dist/identity-store.d.ts +0 -45
  128. package/dist/identity-store.d.ts.map +0 -1
  129. package/dist/identity-store.js +0 -186
  130. package/dist/identity-store.js.map +0 -1
  131. package/dist/ids.d.ts +0 -2
  132. package/dist/ids.d.ts.map +0 -1
  133. package/dist/ids.js +0 -17
  134. package/dist/ids.js.map +0 -1
  135. package/dist/index.d.ts +0 -78
  136. package/dist/index.d.ts.map +0 -1
  137. package/dist/logging.d.ts +0 -18
  138. package/dist/logging.d.ts.map +0 -1
  139. package/dist/logging.js +0 -30
  140. package/dist/logging.js.map +0 -1
  141. package/dist/loopback-host.d.ts +0 -20
  142. package/dist/loopback-host.d.ts.map +0 -1
  143. package/dist/loopback-host.js +0 -245
  144. package/dist/loopback-host.js.map +0 -1
  145. package/dist/loopback-preview.d.ts +0 -19
  146. package/dist/loopback-preview.d.ts.map +0 -1
  147. package/dist/loopback-preview.js +0 -232
  148. package/dist/loopback-preview.js.map +0 -1
  149. package/dist/native-rtc-helper.d.ts +0 -14
  150. package/dist/native-rtc-helper.d.ts.map +0 -1
  151. package/dist/native-rtc-helper.js +0 -575
  152. package/dist/native-rtc-helper.js.map +0 -1
  153. package/dist/remote-api-proxy.d.ts +0 -17
  154. package/dist/remote-api-proxy.d.ts.map +0 -1
  155. package/dist/remote-api-proxy.js +0 -451
  156. package/dist/remote-api-proxy.js.map +0 -1
  157. package/dist/remote-directory-browser.d.ts +0 -16
  158. package/dist/remote-directory-browser.d.ts.map +0 -1
  159. package/dist/remote-directory-browser.js +0 -48
  160. package/dist/remote-directory-browser.js.map +0 -1
  161. package/dist/remote-file-content-provider.d.ts +0 -48
  162. package/dist/remote-file-content-provider.d.ts.map +0 -1
  163. package/dist/remote-file-content-provider.js +0 -93
  164. package/dist/remote-file-content-provider.js.map +0 -1
  165. package/dist/remote-typert-gateway.d.ts +0 -20
  166. package/dist/remote-typert-gateway.d.ts.map +0 -1
  167. package/dist/remote-typert-gateway.js +0 -334
  168. package/dist/remote-typert-gateway.js.map +0 -1
  169. package/dist/rpc-binary-attachments.d.ts +0 -33
  170. package/dist/rpc-binary-attachments.d.ts.map +0 -1
  171. package/dist/rpc-binary-attachments.js +0 -142
  172. package/dist/rpc-binary-attachments.js.map +0 -1
  173. package/dist/rpc-router.d.ts +0 -31
  174. package/dist/rpc-router.d.ts.map +0 -1
  175. package/dist/rpc-router.js +0 -201
  176. package/dist/rpc-router.js.map +0 -1
  177. package/dist/safe-error.d.ts +0 -8
  178. package/dist/safe-error.d.ts.map +0 -1
  179. package/dist/safe-error.js +0 -20
  180. package/dist/safe-error.js.map +0 -1
  181. package/dist/server-api.d.ts +0 -109
  182. package/dist/server-api.d.ts.map +0 -1
  183. package/dist/server-api.js +0 -520
  184. package/dist/server-api.js.map +0 -1
  185. package/dist/server-connection.d.ts +0 -79
  186. package/dist/server-connection.d.ts.map +0 -1
  187. package/dist/server-connection.js +0 -1023
  188. package/dist/server-connection.js.map +0 -1
  189. package/dist/server-credentials.d.ts +0 -55
  190. package/dist/server-credentials.d.ts.map +0 -1
  191. package/dist/server-credentials.js +0 -108
  192. package/dist/server-credentials.js.map +0 -1
  193. package/dist/service.d.ts +0 -151
  194. package/dist/service.d.ts.map +0 -1
  195. package/dist/service.js +0 -448
  196. package/dist/service.js.map +0 -1
  197. package/dist/session-format-compat.d.ts +0 -3
  198. package/dist/session-format-compat.d.ts.map +0 -1
  199. package/dist/session-format-compat.js +0 -183
  200. package/dist/session-format-compat.js.map +0 -1
  201. package/dist/status-stream.d.ts +0 -48
  202. package/dist/status-stream.d.ts.map +0 -1
  203. package/dist/status-stream.js +0 -133
  204. package/dist/status-stream.js.map +0 -1
  205. package/dist/terminal-policy.d.ts +0 -21
  206. package/dist/terminal-policy.d.ts.map +0 -1
  207. package/dist/terminal-policy.js +0 -68
  208. package/dist/terminal-policy.js.map +0 -1
  209. package/dist/tui-command.d.ts +0 -13
  210. package/dist/tui-command.d.ts.map +0 -1
  211. package/dist/tui-command.js +0 -392
  212. package/dist/tui-command.js.map +0 -1
  213. package/dist/typert-gateway-contract.d.ts +0 -59
  214. package/dist/typert-gateway-contract.d.ts.map +0 -1
  215. package/dist/typert-gateway-contract.js +0 -2
  216. package/dist/typert-gateway-contract.js.map +0 -1
  217. package/dist/typert-gateway-switch.d.ts +0 -85
  218. package/dist/typert-gateway-switch.d.ts.map +0 -1
  219. package/dist/typert-gateway-switch.js +0 -448
  220. package/dist/typert-gateway-switch.js.map +0 -1
  221. package/dist/types.d.ts +0 -20
  222. package/dist/types.d.ts.map +0 -1
  223. package/dist/types.js +0 -2
  224. package/dist/types.js.map +0 -1
  225. package/dist/version.d.ts +0 -12
  226. package/dist/version.d.ts.map +0 -1
  227. package/dist/version.js +0 -12
  228. package/dist/version.js.map +0 -1
  229. package/dist/werift-rtc.d.ts +0 -121
  230. package/dist/werift-rtc.d.ts.map +0 -1
  231. package/dist/werift-rtc.js +0 -490
  232. 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
- // ../webrtc/dist/websocket-backpressure.js
8
- async function waitForRelayCapacity(socket) {
9
- const started = Date.now();
10
- while ((socket.bufferedAmount ?? 0) > 512 * 1024) {
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(array3, separator = " | ") {
181
- return array3.map((val) => typeof val === "string" ? `'${val}'` : val).join(separator);
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 array3 = new Uint8Array(al);
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
- array3[ai] = n1 * 16 + n2;
6272
+ array2[ai] = n1 * 16 + n2;
6254
6273
  }
6255
- return array3;
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 array3 = new Uint8Array(al);
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
- array3[ai] = n1 * 16 + n2;
8989
+ array2[ai] = n1 * 16 + n2;
8971
8990
  }
8972
- return array3;
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.6";
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
- const code = errorCode2(error);
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.catch(() => void 0);
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 responseBytes = new Uint8Array(committed.totalBytes);
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
- responseBytes.set(chunk, offset);
16981
+ responseBytes2.set(chunk, offset);
16913
16982
  offset += chunk.byteLength;
16914
16983
  }
16915
- return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(responseBytes));
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 responseBytes = new Uint8Array(committed.totalBytes);
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
- responseBytes.set(chunk, offset);
17475
+ responseBytes2.set(chunk, offset);
17407
17476
  offset += chunk.byteLength;
17408
17477
  }
17409
- return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(responseBytes));
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 = page.maxMessages === void 0 ? void 0 : Math.min(CODEX_HISTORY_MAX_MESSAGES, Math.max(1, page.maxMessages));
19166
- let value;
19167
- try {
19168
- value = record3(await this.client.request("dsh/sessionHistory", {
19169
- threadId,
19170
- ...page.beforeSeq === void 0 ? {} : { beforeSeq: page.beforeSeq },
19171
- ...page.throughSeq === void 0 ? {} : { throughSeq: page.throughSeq },
19172
- ...maxMessages === void 0 ? {} : { maxMessages }
19173
- }, signal));
19174
- } catch (error) {
19175
- if (!isLegacySessionHistoryUnsupported(error)) throw error;
19176
- const result = record3(await this.client.request("thread/read", { threadId, includeTurns: true }, signal));
19177
- const thread = record3(result.thread);
19178
- if (string2(thread.id) !== threadId) throw new Error("CodeX returned an invalid Thread history.");
19179
- value = paginateCodexNativeHistory(
19180
- projectCodexNativeHistory(thread, `codex:${threadId}`, this.sessionGeneration),
19181
- {
19182
- beforeSeq: page.beforeSeq,
19183
- throughSeq: page.throughSeq,
19184
- maxMessages
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 += 1;
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: seq - 1,
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 ?? DEFAULT_HISTORY_MESSAGES;
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
- return path.replace(/[\\/]+$/u, "") || path;
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 join4 } from "node:path";
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(join4(part, process.platform === "win32" ? "node.exe" : "node"));
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(join4(process.env.HOME ?? "", ".volta", "bin", process.platform === "win32" ? "node.exe" : "node"));
20779
- add3(join4(process.env.HOME ?? "", ".asdf", "shims", process.platform === "win32" ? "node.exe" : "node"));
20780
- add3(join4(process.env.HOME ?? "", ".local", "bin", process.platform === "win32" ? "node.exe" : "node"));
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 = join4(process.env.HOME ?? "", ".nvm", "versions", "node");
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) => join4(root, version, "bin", process.platform === "win32" ? "node.exe" : "node")).sort((left, right) => right.localeCompare(left, "en", { numeric: true }));
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
- assertRemoteCompatible(remote) {
23274
- this.selectHarnessRemoteTransport(remote);
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
- selectHarnessRemoteTransport(remote) {
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 join5 } from "node:path";
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 join5(this.directory, FILE_NAME);
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 join6 } from "node:path";
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 || join6(options.homeDirectory ?? homedir(), ".dsh");
24093
- this.directory = options.directory ?? join6(dshHome, "remote");
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 = join6(this.directory, "device.json");
24099
- const keyPath = join6(this.directory, "device.key");
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 = join6(this.directory, "trusted-peers.json");
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(join6(this.directory, "trusted-peers.json"), {
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 join6(root, "servers", scope, role);
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 homedir2 } from "node:os";
24397
- import { basename as basename2, isAbsolute as isAbsolute2, join as join7, posix, relative, resolve } from "node:path";
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 responseBytes = new TextEncoder().encode(JSON.stringify(response));
24801
- if (responseBytes.byteLength <= INLINE_TRANSFER_RESPONSE_BYTES) return { kind: "inline", response };
24802
- if (responseBytes.byteLength > MAX_CODEX_APP_TRANSFER_BYTES) {
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(responseBytes.byteLength / CODEX_APP_TRANSFER_CHUNK_BYTES);
25103
+ const totalChunks = Math.ceil(responseBytes2.byteLength / CODEX_APP_TRANSFER_CHUNK_BYTES);
24809
25104
  this.outgoingTransfers.set(params.transferId, {
24810
- bytes: responseBytes,
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: responseBytes.byteLength, totalChunks };
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
- await this.readWorkspaceAuthority(result.data)
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.readThreadForHistory(connectionId, threadId);
25098
- const page = paginateCodexNativeHistory(
25099
- projectCodexNativeHistory(thread, `codex:${threadId}`),
25100
- {
25101
- beforeSeq: optionalInteger2(call.params.beforeSeq),
25102
- throughSeq: optionalInteger2(call.params.throughSeq),
25103
- maxMessages: optionalInteger2(call.params.maxMessages)
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 || !isThreadAllowedByWorkspaceAuthority(thread, authority)) {
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
- async readThreadForHistory(connectionId, threadId) {
25620
- const { thread: metadata } = await this.readKnownThread(threadId);
25621
- try {
25622
- return { ...metadata, turns: await this.readThreadTurns(connectionId, threadId, "full") };
25623
- } catch (fullError) {
25624
- if (!isHistoryReadRecoverable(fullError)) throw fullError;
25625
- this.logHistoryFallback(connectionId, "turns-full", fullError);
25626
- }
25627
- try {
25628
- return { ...metadata, turns: await this.readThreadTurns(connectionId, threadId, "summary") };
25629
- } catch (summaryError) {
25630
- if (!isHistoryReadRecoverable(summaryError)) throw summaryError;
25631
- this.logHistoryFallback(connectionId, "turns-summary", summaryError);
25632
- }
25633
- try {
25634
- const legacyResult = await this.callUpstream("thread/read", { threadId, includeTurns: true });
25635
- const legacyThread = extractThread(legacyResult);
25636
- if (legacyThread === void 0 || legacyThread.id !== threadId) {
25637
- throw new RpcError("CODEX_INVALID_RESPONSE", "Codex App Server returned an invalid Thread history.");
25638
- }
25639
- return legacyThread;
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
- limit: CODEX_HISTORY_PAGE_LIMIT,
25653
- sortDirection: "asc",
25654
- itemsView,
25655
- ...cursor2 === void 0 ? {} : { cursor: cursor2 }
26146
+ requested,
26147
+ size,
26148
+ bytes,
26149
+ budget: HISTORY_BUDGET_BYTES,
26150
+ loadedTurns: window.loaded.size,
26151
+ totalTurns: window.totalTurns
25656
26152
  });
25657
- const pageResult = isRecord12(result) ? result : {};
25658
- for (const rawTurn of array2(pageResult.data)) {
25659
- if (!isRecord12(rawTurn)) continue;
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
- return turns;
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 (!isThreadAllowedByWorkspaceAuthority(thread, await this.readWorkspaceAuthority())) {
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 isThreadAllowedByWorkspaceAuthority(thread, authority) ? thread : void 0;
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 = homedir2(), localAppData = process.env.LOCALAPPDATA) {
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(join7(userHome, "AppData", "Local"));
26468
+ return bundledWindowsCodexAt(join9(userHome, "AppData", "Local"));
25999
26469
  }
26000
26470
  function bundledWindowsCodexAt(localAppData) {
26001
- const bin = join7(localAppData, "OpenAI", "Codex", "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) => join7(bin, entry.name, "codex.exe")).filter((candidate2) => existsSync3(candidate2)).map((candidate2) => ({ candidate: candidate2, modified: statSync2(candidate2).mtimeMs })).sort((left, right) => right.modified - left.modified);
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 once per ten seconds, from the settings poll the panel already makes.
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
- void (async () => {
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 homedir3, platform as platform2 } from "node:os";
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(homedir3());
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(requestedMaxMessages) {
27422
- const requested = normalizeSessionHistoryPageSize(requestedMaxMessages);
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 responseBytes = new TextEncoder().encode(JSON.stringify(response));
27786
- if (responseBytes.byteLength <= INLINE_TRANSFER_RESPONSE_BYTES2) {
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 (responseBytes.byteLength > MAX_HARNESS_API_TRANSFER_BYTES) {
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(responseBytes.byteLength / HARNESS_API_TRANSFER_CHUNK_BYTES);
28280
+ const totalChunks = Math.ceil(responseBytes2.byteLength / HARNESS_API_TRANSFER_CHUNK_BYTES);
27797
28281
  this.outgoingTransfers.set(params.transferId, {
27798
- bytes: responseBytes,
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: responseBytes.byteLength, totalChunks };
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.gateway.dispatch(params.endpoint, nativePayload, signal);
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 responseBytes = new TextEncoder().encode(JSON.stringify(response));
28491
- if (responseBytes.byteLength <= INLINE_TRANSFER_RESPONSE_BYTES3) return { kind: "inline", response };
28492
- if (responseBytes.byteLength > MAX_HARNESS_API_TRANSFER_BYTES) {
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(responseBytes.byteLength / HARNESS_API_TRANSFER_CHUNK_BYTES);
28998
+ const totalChunks = Math.ceil(responseBytes2.byteLength / HARNESS_API_TRANSFER_CHUNK_BYTES);
28499
28999
  this.outgoingTransfers.set(params.transferId, {
28500
- bytes: responseBytes,
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: responseBytes.byteLength, totalChunks };
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: Buffer.from(transfer.bytes.subarray(start, end)).toString("base64")
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
- source = (this.codexWorkspace === void 0 ? void 0 : await this.codexWorkspace.open(params.endpoint, params.payload, controller.signal)) ?? await this.gateway.open(params.endpoint, normalizeWorkspaceChangesPayload(params.endpoint, params.payload, this.harnessVersion), controller.signal);
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
- void this.pump(params.streamId, source, controller.signal);
28553
- return { opened: true, streamId: params.streamId };
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
- await this.publish("harness.remote.frame", {
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 = Buffer.from(value, "base64");
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 isAbsolute4, join as join8, relative as relative2, resolve as resolve3 } from "node:path";
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 = join8(target2, entry.name);
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 = isAbsolute4(path) ? resolve3(path) : resolve3(root, path);
29122
- const rel = relative2(root, candidate);
29123
- if (rel.startsWith("..") || isAbsolute4(rel)) throw new RpcError("CODEX_WORKSPACE_PATH_DENIED", "The requested workspace path is outside the CodeX workspace.");
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 = relative2(root, canonical);
29129
- if (canonicalRel.startsWith("..") || isAbsolute4(canonicalRel)) throw new Error();
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 join9 } from "node:path";
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(join9(directory, "device.json"))) {
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(join9(directory, "device.json"))) {
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;