@blue-soda/dsh-remote 0.5.7 → 0.5.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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.7";
13546
+ var PLUGIN_VERSION = "0.5.8";
13525
13547
 
13526
13548
  // src/server-api.ts
13527
13549
  var ENABLED_QR_PROVIDERS = ["github"];
@@ -17595,6 +17617,11 @@ function patchWelcomeDescribe2(value) {
17595
17617
  return { ...value, namespaces };
17596
17618
  }
17597
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
+
17598
17625
  // src/codex/permissions.ts
17599
17626
  function codexPermissionPresetFromResponse(value) {
17600
17627
  const root = record(value);
@@ -17625,135 +17652,6 @@ function resolveCodexThreadKey(key) {
17625
17652
  return codexThreadIdFromKey(key) ?? key;
17626
17653
  }
17627
17654
 
17628
- // src/codex/method-policy.ts
17629
- var id2 = external_exports.string().min(1).max(256);
17630
- var cursor = external_exports.string().min(1).max(4096).nullable().optional();
17631
- var textInput = external_exports.object({
17632
- type: external_exports.literal("text"),
17633
- text: external_exports.string().min(1).max(256 * 1024)
17634
- }).strict();
17635
- var imageMediaType = external_exports.enum(["image/png", "image/jpeg", "image/webp", "image/gif"]);
17636
- 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), {
17637
- message: "Image data must use canonical base64."
17638
- });
17639
- var imageInput = external_exports.object({
17640
- type: external_exports.literal("image"),
17641
- mediaType: imageMediaType,
17642
- data: canonicalBase64
17643
- }).strict();
17644
- var input = external_exports.array(external_exports.union([textInput, imageInput])).min(1).max(16);
17645
- var permissionPreset = external_exports.enum(["workspace-write", "danger-full-access"]);
17646
- var projectRoot = external_exports.object({
17647
- path: external_exports.string().min(1).max(4096)
17648
- }).strict();
17649
- var CODEX_HISTORY_MAX_MESSAGES = 200;
17650
- var schemas = {
17651
- "account/read": external_exports.object({ refreshToken: external_exports.literal(false).optional() }).strict(),
17652
- "model/list": external_exports.object({
17653
- cursor,
17654
- limit: external_exports.number().int().min(1).max(100).optional(),
17655
- includeHidden: external_exports.boolean().optional()
17656
- }).strict(),
17657
- "project/list": external_exports.object({
17658
- cursor,
17659
- limit: external_exports.number().int().min(1).max(100).optional()
17660
- }).strict(),
17661
- "project/create": external_exports.object({
17662
- name: external_exports.string().trim().min(1).max(256),
17663
- roots: external_exports.array(projectRoot).length(1),
17664
- idempotencyKey: external_exports.string().min(16).max(256)
17665
- }).strict(),
17666
- "thread/list": external_exports.object({
17667
- cursor,
17668
- limit: external_exports.number().int().min(1).max(100).optional(),
17669
- sortKey: external_exports.enum(["created_at", "updated_at", "recency_at"]).optional(),
17670
- sortDirection: external_exports.enum(["asc", "desc"]).optional(),
17671
- modelProviders: external_exports.array(external_exports.string().min(1).max(128)).max(32).nullable().optional(),
17672
- sourceKinds: external_exports.array(external_exports.enum(["cli", "vscode", "exec", "appServer", "unknown"])).max(8).optional(),
17673
- archived: external_exports.boolean().optional(),
17674
- isPinned: external_exports.boolean().optional(),
17675
- 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(),
17676
- useStateDbOnly: external_exports.boolean().optional(),
17677
- searchTerm: external_exports.string().max(1024).optional(),
17678
- // Upstream `ThreadListParams` declares these two as well. Refusing them turned a
17679
- // legitimate client query into "The CodeX call parameters are invalid.".
17680
- originators: external_exports.array(external_exports.string().min(1).max(256)).max(32).nullable().optional(),
17681
- sectionId: external_exports.string().max(256).nullable().optional()
17682
- }).strict(),
17683
- "thread/read": external_exports.object({ threadId: id2, includeTurns: external_exports.boolean().optional() }).strict(),
17684
- "dsh/sessionHistory": external_exports.object({
17685
- threadId: id2,
17686
- beforeSeq: external_exports.number().int().nonnegative().optional(),
17687
- throughSeq: external_exports.number().int().min(-1).optional(),
17688
- maxMessages: external_exports.number().int().min(1).max(CODEX_HISTORY_MAX_MESSAGES).optional()
17689
- }).strict(),
17690
- "dsh/directoryList": external_exports.object({
17691
- path: external_exports.string().min(1).max(4096)
17692
- }).strict(),
17693
- "thread/start": external_exports.object({
17694
- cwd: external_exports.string().min(1).max(4096),
17695
- model: external_exports.string().min(1).max(128).optional(),
17696
- personality: external_exports.string().min(1).max(64).optional(),
17697
- permissionPreset: permissionPreset.optional()
17698
- }).strict(),
17699
- "thread/resume": external_exports.object({
17700
- threadId: id2,
17701
- model: external_exports.string().min(1).max(128).optional(),
17702
- permissionPreset: permissionPreset.optional()
17703
- }).strict(),
17704
- "thread/fork": external_exports.object({
17705
- threadId: id2,
17706
- lastTurnId: id2.optional(),
17707
- permissionPreset: permissionPreset.optional()
17708
- }).strict(),
17709
- // Upstream declares a plain string and uses the empty value to clear a name, so the
17710
- // trim-based minimum rejected a legitimate rename.
17711
- "thread/name/set": external_exports.object({ threadId: id2, name: external_exports.string().max(256) }).strict(),
17712
- "thread/archive": external_exports.object({ threadId: id2 }).strict(),
17713
- "thread/unarchive": external_exports.object({ threadId: id2 }).strict(),
17714
- "thread/unsubscribe": external_exports.object({ threadId: id2 }).strict(),
17715
- "turn/start": external_exports.object({
17716
- threadId: id2,
17717
- input,
17718
- model: external_exports.string().min(1).max(128).optional(),
17719
- effort: external_exports.enum(["none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra"]).optional(),
17720
- summary: external_exports.enum(["auto", "concise", "detailed", "none"]).optional(),
17721
- personality: external_exports.string().min(1).max(64).optional(),
17722
- permissionPreset: permissionPreset.optional()
17723
- }).strict(),
17724
- "turn/steer": external_exports.object({
17725
- threadId: id2,
17726
- input,
17727
- expectedTurnId: id2,
17728
- // Declared by upstream `TurnSteerParams`; see the thread/list note above.
17729
- clientUserMessageId: external_exports.string().max(256).nullable().optional()
17730
- }).strict(),
17731
- "turn/interrupt": external_exports.object({ threadId: id2, turnId: id2 }).strict()
17732
- };
17733
- var CODEX_APP_ALLOWLIST = Object.freeze(Object.keys(schemas));
17734
- function parseCodexCall(method, params) {
17735
- if (!Object.prototype.hasOwnProperty.call(schemas, method)) {
17736
- throw new RpcError("METHOD_NOT_ALLOWED", "The requested Codex method is not available over Remote.");
17737
- }
17738
- const schema = schemas[method];
17739
- const parsed = schema.safeParse(params);
17740
- if (!parsed.success) {
17741
- const detail = parsed.error.issues.slice(0, 4).map((issue) => {
17742
- const path = issue.path.length === 0 ? "(root)" : issue.path.join(".");
17743
- const keys = "keys" in issue && Array.isArray(issue.keys) ? ` [${issue.keys.join(", ")}]` : "";
17744
- return `${path}${keys}: ${issue.code}`;
17745
- }).join(", ");
17746
- throw new RpcError("INVALID_MESSAGE", `The CodeX call parameters are invalid. (${method} \u2192 ${detail})`);
17747
- }
17748
- return { method, params: parsed.data };
17749
- }
17750
- function isThreadMutation(method) {
17751
- return method === "turn/start" || method === "turn/steer" || method === "turn/interrupt";
17752
- }
17753
- function threadIdFromParams(params) {
17754
- return typeof params.threadId === "string" ? params.threadId : void 0;
17755
- }
17756
-
17757
17655
  // src/codex/codex-image-codec.ts
17758
17656
  var CODEX_IMAGE_MEDIA_TYPES2 = /* @__PURE__ */ new Set(["image/png", "image/jpeg", "image/webp", "image/gif"]);
17759
17657
  var CODEX_IMAGE_ATTACHMENT_PREFIX = "codex-image:";
@@ -17988,9 +17886,10 @@ var CODEX_MODEL = "codex";
17988
17886
  var CODEX_PAGE_LIMIT = 100;
17989
17887
  var MAX_CODEX_PAGES = 32;
17990
17888
  var MAX_LIVE_TOOL_OUTPUT = 128 * 1024;
17991
- var DEFAULT_HISTORY_MESSAGES = 50;
17992
17889
  var SESSION_SEARCH_RESULT_LIMIT = 20;
17993
17890
  var SESSION_SEARCH_SNIPPET_CODE_POINTS = 240;
17891
+ var HISTORY_PAGE_CACHE_TTL_MS = 6e4;
17892
+ var HISTORY_PAGE_CACHE_LIMIT = 24;
17994
17893
  var CODEX_DEFAULT_PERMISSION = "workspace-write";
17995
17894
  var MAX_CODEX_PROMPT_PARTS = 16;
17996
17895
  var MAX_CODEX_PROMPT_TEXT = 256 * 1024;
@@ -18037,6 +17936,7 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
18037
17936
  this.sessionGeneration = sessionGeneration;
18038
17937
  this.hostCarrier = hostCarrier;
18039
17938
  this.api = this.createApiProxy();
17939
+ this.installHistoryCacheInspector();
18040
17940
  this.stopCapabilityWatch = this.client.watchCapability?.((available) => {
18041
17941
  if (!available) this.onSessionLost?.("domain-notice");
18042
17942
  });
@@ -18056,6 +17956,10 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
18056
17956
  selectedModels = /* @__PURE__ */ new Map();
18057
17957
  selectedPermissions = /* @__PURE__ */ new Map();
18058
17958
  imageAttachments = /* @__PURE__ */ new Map();
17959
+ historyPageCache = /* @__PURE__ */ new Map();
17960
+ historyPageHits = 0;
17961
+ historyPageMisses = 0;
17962
+ historyCacheInspector;
18059
17963
  modelDirectory;
18060
17964
  modelDirectoryPromise;
18061
17965
  lastProjectionSeq = 0;
@@ -18171,6 +18075,10 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
18171
18075
  return business(await this.archiveSession(requestArg(args), signal));
18172
18076
  case "session/list":
18173
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
+ }
18174
18082
  case "session/search":
18175
18083
  return business(await this.searchSessions(requestArg(args), signal));
18176
18084
  case "session/create":
@@ -18284,6 +18192,17 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
18284
18192
  this.selectedModels.clear();
18285
18193
  this.selectedPermissions.clear();
18286
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;
18287
18206
  }
18288
18207
  async refreshCatalog(signal) {
18289
18208
  const catalog = await loadCatalog(this.client, signal, this.pendingThreads);
@@ -18557,6 +18476,7 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
18557
18476
  });
18558
18477
  }
18559
18478
  acceptCodexFrame(follow, frame) {
18479
+ this.invalidateHistoryPages(follow.threadId);
18560
18480
  const params = record3(frame.params);
18561
18481
  if (frame.method === "thread/name/updated") {
18562
18482
  const name2 = string2(params.threadName);
@@ -19209,29 +19129,27 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
19209
19129
  return thread;
19210
19130
  }
19211
19131
  async readHistoryPage(threadId, page, signal) {
19212
- const maxMessages = page.maxMessages === void 0 ? void 0 : Math.min(CODEX_HISTORY_MAX_MESSAGES, Math.max(1, page.maxMessages));
19213
- let value;
19214
- try {
19215
- value = record3(await this.client.request("dsh/sessionHistory", {
19216
- threadId,
19217
- ...page.beforeSeq === void 0 ? {} : { beforeSeq: page.beforeSeq },
19218
- ...page.throughSeq === void 0 ? {} : { throughSeq: page.throughSeq },
19219
- ...maxMessages === void 0 ? {} : { maxMessages }
19220
- }, signal));
19221
- } catch (error) {
19222
- if (!isLegacySessionHistoryUnsupported(error)) throw error;
19223
- const result = record3(await this.client.request("thread/read", { threadId, includeTurns: true }, signal));
19224
- const thread = record3(result.thread);
19225
- if (string2(thread.id) !== threadId) throw new Error("CodeX returned an invalid Thread history.");
19226
- value = paginateCodexNativeHistory(
19227
- projectCodexNativeHistory(thread, `codex:${threadId}`, this.sessionGeneration),
19228
- {
19229
- beforeSeq: page.beforeSeq,
19230
- throughSeq: page.throughSeq,
19231
- maxMessages
19232
- }
19233
- );
19234
- }
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));
19235
19153
  value = adaptCodexHistoryPage(value, this.sessionGeneration);
19236
19154
  if (!isCodexNativeHistoryPage(value, `codex:${threadId}`)) {
19237
19155
  throw new Error("CodeX Remote returned an invalid paginated History.");
@@ -19241,8 +19159,115 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
19241
19159
  this.setPermissionSelection(`codex:${threadId}`, preset === "workspace-write" || preset === "danger-full-access" ? preset : void 0);
19242
19160
  }
19243
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);
19244
19169
  return value;
19245
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
+ }
19246
19271
  cacheHistoryImages(history) {
19247
19272
  const sessionId = history.header.id;
19248
19273
  const entries = "entries" in history ? history.entries : history.records;
@@ -19504,12 +19529,26 @@ async function loadCatalog(client, signal, pendingThreads) {
19504
19529
  const workspaceByProjectId = new Map(projects.map((project, index) => [project.id, projectWorkspaces[index]]));
19505
19530
  const visibleSessionIds = /* @__PURE__ */ new Set();
19506
19531
  const visibleThreads = [];
19532
+ const unmatched = [];
19507
19533
  for (const { thread, session } of projected) {
19508
19534
  const projectWorkspaceForSession = findProjectWorkspace(thread, session, projects, workspaceByProjectId);
19509
19535
  if (projectWorkspaceForSession !== void 0) {
19536
+ rebaseSessionCwdOnProjectRoot(session, projectWorkspaceForSession.path);
19537
+ rebaseThreadCwdOnProjectRoot(thread, projectWorkspaceForSession.path);
19510
19538
  addSessionToWorkspace(projectWorkspaceForSession, session);
19511
19539
  visibleSessionIds.add(session.id);
19512
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);
19513
19552
  }
19514
19553
  }
19515
19554
  const sessions = projected.map((value) => value.session).filter((session) => visibleSessionIds.has(session.id));
@@ -19600,13 +19639,17 @@ async function loadModelDirectory(client, signal) {
19600
19639
  models
19601
19640
  };
19602
19641
  }
19603
- function projectCodexNativeHistory(thread, sessionId, sessionGeneration = "legacy") {
19642
+ function projectCodexNativeHistory(thread, sessionId, sessionGeneration = "legacy", start = {}) {
19604
19643
  const entries = [];
19605
- let seq = 0;
19606
- 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;
19607
19649
  const active = activeTurnId(thread.turns);
19608
19650
  const append = (type, data2, time = Date.now(), view, sourceEventSeqs) => {
19609
- const eventSeq = seq++;
19651
+ const eventSeq = start.stable === true ? turnSeqBase + turnEventIndex++ : seq++;
19652
+ lastSeq = eventSeq;
19610
19653
  entries.push({
19611
19654
  type: "event",
19612
19655
  event: {
@@ -19623,13 +19666,16 @@ function projectCodexNativeHistory(thread, sessionId, sessionGeneration = "legac
19623
19666
  };
19624
19667
  for (const rawTurn of array(thread.turns)) {
19625
19668
  const turn = record3(rawTurn);
19669
+ const turnId = string2(turn.id);
19626
19670
  const turnItems = array(turn.items).map(record3);
19627
19671
  const toolImages = turnItems.flatMap(toolOutputImageBlocks);
19628
19672
  let imageOwner = -1;
19629
19673
  for (let index = 0; index < turnItems.length; index += 1) {
19630
19674
  if (turnItems[index].type === "agentMessage") imageOwner = index;
19631
19675
  }
19632
- turnNumber += 1;
19676
+ turnNumber = (turnId === void 0 ? void 0 : start.turnNumbers?.get(turnId)) ?? turnNumber + 1;
19677
+ turnSeqBase = turnNumber * 4096;
19678
+ turnEventIndex = 0;
19633
19679
  const turnActive = isActiveCodexTurn(turn);
19634
19680
  const time = normalizeTime(turn.createdAt) || normalizeTime(turn.startedAt) || normalizeTime(thread.createdAt) || Date.now();
19635
19681
  append("turn/start", { turn: turnNumber }, time);
@@ -19673,7 +19719,7 @@ function projectCodexNativeHistory(thread, sessionId, sessionGeneration = "legac
19673
19719
  ...sessionGeneration === "v3" ? { isSeeded: false } : {}
19674
19720
  },
19675
19721
  entries,
19676
- lastSeq: seq - 1,
19722
+ lastSeq,
19677
19723
  nextTurn: turnNumber,
19678
19724
  ...active === void 0 ? {} : { activeTurnId: active }
19679
19725
  };
@@ -19682,7 +19728,7 @@ function paginateCodexNativeHistory(history, request) {
19682
19728
  const throughSeq = Math.min(request.throughSeq ?? history.lastSeq, history.lastSeq);
19683
19729
  const endSeq = Math.min(throughSeq, request.beforeSeq === void 0 ? throughSeq : request.beforeSeq - 1);
19684
19730
  const window = endSeq < 0 ? [] : history.entries.filter((entry) => entry.event.seq <= endSeq);
19685
- const maxMessages = request.maxMessages ?? DEFAULT_HISTORY_MESSAGES;
19731
+ const maxMessages = request.maxMessages ?? HISTORY_PAGE_SIZES[0];
19686
19732
  let messages = 0;
19687
19733
  let cut = 0;
19688
19734
  for (let index = window.length - 1; index >= 0; index -= 1) {
@@ -19957,6 +20003,20 @@ function nativeWorkspace(view) {
19957
20003
  function nativeVisibleWorkspaces(catalog, selectedWorkspaceId) {
19958
20004
  return catalog.workspaces.filter((workspace) => workspace.sessionIds.length > 0 || workspace.workspaceId === selectedWorkspaceId);
19959
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
+ }
19960
20020
  function projectWorkspace(project) {
19961
20021
  const root = project.roots[0];
19962
20022
  return {
@@ -19998,6 +20058,28 @@ function findProjectWorkspace(thread, session, projects, workspaceByProjectId) {
19998
20058
  })[0];
19999
20059
  return match === void 0 ? void 0 : workspaceByProjectId.get(match.project.id);
20000
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
+ }
20001
20083
  function addSessionToWorkspace(workspace, session) {
20002
20084
  if (!workspace.sessionIds.includes(session.id)) {
20003
20085
  workspace.sessionIds.push(session.id);
@@ -20014,7 +20096,8 @@ function containsPath(root, candidate) {
20014
20096
  return normalizedCandidate === normalizedRoot || normalizedCandidate.startsWith(`${normalizedRoot}/`) || normalizedCandidate.startsWith(`${normalizedRoot}\\`);
20015
20097
  }
20016
20098
  function normalizePathForCompare(path) {
20017
- return path.replace(/[\\/]+$/u, "") || path;
20099
+ const trimmed = path.replace(/[\\/]+$/u, "") || path;
20100
+ return process.platform === "win32" ? trimmed.toLowerCase() : trimmed;
20018
20101
  }
20019
20102
  function modelSelection() {
20020
20103
  return { provider: CODEX_PROVIDER, model: CODEX_MODEL };
@@ -20264,10 +20347,6 @@ function failFrom(error) {
20264
20347
  const source = error instanceof Error ? error : new Error(String(error));
20265
20348
  return fail(errorCode3(source) ?? "internal", source.message, errorDetails(source));
20266
20349
  }
20267
- function isLegacySessionHistoryUnsupported(error) {
20268
- if (!(error instanceof Error)) return false;
20269
- return errorCode3(error) === "METHOD_NOT_ALLOWED" || errorCode3(error) === "METHOD_NOT_FOUND" || error.message.includes("The requested Codex method is not available over Remote.");
20270
- }
20271
20350
  function errorCode3(error) {
20272
20351
  return "code" in error && typeof error.code === "string" ? error.code : void 0;
20273
20352
  }
@@ -20767,7 +20846,7 @@ import { networkInterfaces } from "node:os";
20767
20846
  // src/native-rtc-helper.ts
20768
20847
  import { spawn, spawnSync } from "node:child_process";
20769
20848
  import { existsSync, readdirSync, realpathSync, statSync } from "node:fs";
20770
- import { delimiter, dirname as dirname3, join as join4 } from "node:path";
20849
+ import { delimiter, dirname as dirname3, join as join5 } from "node:path";
20771
20850
  import { fileURLToPath } from "node:url";
20772
20851
  var cachedExternalFactory;
20773
20852
  var cachedExternalFactoryResolved = false;
@@ -20818,21 +20897,21 @@ function nodeBinaryCandidates() {
20818
20897
  add3(process.env.DSH_REMOTE_NODE);
20819
20898
  add3(process.env.NODE);
20820
20899
  if (process.versions.electron === void 0) add3(process.execPath);
20821
- 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"));
20822
20901
  add3("/opt/homebrew/bin/node");
20823
20902
  add3("/usr/local/bin/node");
20824
20903
  add3("/usr/bin/node");
20825
- add3(join4(process.env.HOME ?? "", ".volta", "bin", process.platform === "win32" ? "node.exe" : "node"));
20826
- add3(join4(process.env.HOME ?? "", ".asdf", "shims", process.platform === "win32" ? "node.exe" : "node"));
20827
- 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"));
20828
20907
  for (const nvmNode of nvmNodeCandidates()) add3(nvmNode);
20829
20908
  return candidates;
20830
20909
  }
20831
20910
  function nvmNodeCandidates() {
20832
- const root = join4(process.env.HOME ?? "", ".nvm", "versions", "node");
20911
+ const root = join5(process.env.HOME ?? "", ".nvm", "versions", "node");
20833
20912
  if (root === "" || !existsSync(root)) return [];
20834
20913
  try {
20835
- 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 }));
20836
20915
  } catch {
20837
20916
  return [];
20838
20917
  }
@@ -21862,6 +21941,7 @@ var ClientModeRuntime = class {
21862
21941
  this.proxySwitch = apiProxy === void 0 ? void 0 : new ApiProxySwitch(apiProxy);
21863
21942
  this.gatewaySwitch = new TypertGatewaySwitch(typertGateway, { peer: this.peer });
21864
21943
  this.gatewaySwitch.setRemoteAvailability(() => this.connected !== void 0);
21944
+ this.installCodexHistoryCacheInspector();
21865
21945
  }
21866
21946
  preview;
21867
21947
  identity;
@@ -23316,6 +23396,44 @@ var ClientModeRuntime = class {
23316
23396
  const virtual = this.codexVirtual;
23317
23397
  this.codexVirtual = void 0;
23318
23398
  await virtual?.close();
23399
+ this.installCodexHistoryCacheInspector();
23400
+ }
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)");
23420
+ }
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
+ }
23319
23437
  }
23320
23438
  assertRemoteCompatible(remote) {
23321
23439
  this.selectHarnessRemoteTransport(remote);
@@ -24023,14 +24141,14 @@ function iceServersForAttempt(attempt, iceServers) {
24023
24141
  // src/client-target-store.ts
24024
24142
  import { randomUUID as randomUUID2 } from "node:crypto";
24025
24143
  import { chmod as chmod2, mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2 } from "node:fs/promises";
24026
- import { join as join5 } from "node:path";
24144
+ import { join as join7 } from "node:path";
24027
24145
  var FILE_NAME = "client-target.json";
24028
24146
  var ClientTargetStore = class {
24029
24147
  constructor(directory) {
24030
24148
  this.directory = directory;
24031
24149
  }
24032
24150
  get file() {
24033
- return join5(this.directory, FILE_NAME);
24151
+ return join7(this.directory, FILE_NAME);
24034
24152
  }
24035
24153
  /** Read the record. A missing, unreadable or malformed file reads as "no record". */
24036
24154
  async load() {
@@ -24106,8 +24224,8 @@ import { execFileSync } from "node:child_process";
24106
24224
  import { existsSync as existsSync2 } from "node:fs";
24107
24225
  import { createHash } from "node:crypto";
24108
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";
24109
- import { homedir } from "node:os";
24110
- 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";
24111
24229
  var identitySchema = external_exports.object({
24112
24230
  schemaVersion: external_exports.literal(1),
24113
24231
  deviceId: external_exports.string().uuid(),
@@ -24136,14 +24254,14 @@ var IdentityStore = class {
24136
24254
  peers = /* @__PURE__ */ new Map();
24137
24255
  constructor(options = {}) {
24138
24256
  const env = options.env ?? process.env;
24139
- const dshHome = env.DSH_HOME || join6(options.homeDirectory ?? homedir(), ".dsh");
24140
- 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");
24141
24259
  }
24142
24260
  async loadOrCreate(deviceName) {
24143
24261
  await mkdir3(this.directory, { recursive: true, mode: 448 });
24144
24262
  await chmod3(this.directory, 448);
24145
- const devicePath = join6(this.directory, "device.json");
24146
- const keyPath = join6(this.directory, "device.key");
24263
+ const devicePath = join8(this.directory, "device.json");
24264
+ const keyPath = join8(this.directory, "device.key");
24147
24265
  const [hasDevice, hasKey] = await Promise.all([exists2(devicePath), exists2(keyPath)]);
24148
24266
  if (hasDevice !== hasKey) {
24149
24267
  throw new IdentityInvalidError("device identity is incomplete; repair it explicitly before reconnecting");
@@ -24212,7 +24330,7 @@ var IdentityStore = class {
24212
24330
  return removed;
24213
24331
  }
24214
24332
  async loadPeers() {
24215
- const path = join6(this.directory, "trusted-peers.json");
24333
+ const path = join8(this.directory, "trusted-peers.json");
24216
24334
  if (!await exists2(path)) {
24217
24335
  await atomicJsonWrite(path, { schemaVersion: 1, peers: [] }, 384);
24218
24336
  }
@@ -24228,7 +24346,7 @@ var IdentityStore = class {
24228
24346
  this.peers = peers;
24229
24347
  }
24230
24348
  async savePeers() {
24231
- await atomicJsonWrite(join6(this.directory, "trusted-peers.json"), {
24349
+ await atomicJsonWrite(join8(this.directory, "trusted-peers.json"), {
24232
24350
  schemaVersion: 1,
24233
24351
  peers: [...this.peers.values()]
24234
24352
  }, 384);
@@ -24237,7 +24355,7 @@ var IdentityStore = class {
24237
24355
  function serverStorageDirectory(root, serverUrl, role) {
24238
24356
  const origin = new URL(serverUrl).origin;
24239
24357
  const scope = createHash("sha256").update(origin).digest("hex").slice(0, 24);
24240
- return join6(root, "servers", scope, role);
24358
+ return join8(root, "servers", scope, role);
24241
24359
  }
24242
24360
  async function ensureDeviceDirectory(root, serverUrl) {
24243
24361
  const directory = serverStorageDirectory(root, serverUrl, "device");
@@ -24438,10 +24556,11 @@ var ControlStatusStream = class {
24438
24556
 
24439
24557
  // src/codex/domain.ts
24440
24558
  import { randomUUID as randomUUID3 } from "node:crypto";
24559
+ import { Buffer as Buffer4 } from "node:buffer";
24441
24560
  import { accessSync, constants, existsSync as existsSync3, readFileSync, readdirSync as readdirSync2, statSync as statSync2 } from "node:fs";
24442
24561
  import { readdir as readdir2, realpath, stat as stat4 } from "node:fs/promises";
24443
- import { homedir as homedir2 } from "node:os";
24444
- 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";
24445
24564
 
24446
24565
  // src/codex/app-server.ts
24447
24566
  import { spawn as spawn2 } from "node:child_process";
@@ -24698,6 +24817,135 @@ function isRecord10(value) {
24698
24817
  return typeof value === "object" && value !== null && !Array.isArray(value);
24699
24818
  }
24700
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
+
24701
24949
  // src/codex/peer-bridge.ts
24702
24950
  import { Buffer as Buffer3 } from "node:buffer";
24703
24951
  var streamOpenSchema = external_exports.object({
@@ -24988,6 +25236,219 @@ function concatChunks(chunks, totalBytes) {
24988
25236
  return output;
24989
25237
  }
24990
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
+
24991
25452
  // src/codex/domain.ts
24992
25453
  var APPROVAL_TTL_MS = 5 * 6e4;
24993
25454
  var DEFAULT_RESTART_DELAYS_MS = [1e3, 2e3, 4e3, 8e3, 15e3];
@@ -24996,8 +25457,6 @@ var ACCOUNT_PROBE_TIMEOUT_MS = 6e4;
24996
25457
  var CODEX_START_BUDGET_MS = 9e4;
24997
25458
  var CODEX_PAGE_LIMIT2 = 100;
24998
25459
  var MAX_CODEX_PAGES2 = 32;
24999
- var CODEX_HISTORY_PAGE_LIMIT = 25;
25000
- var MAX_CODEX_HISTORY_PAGES = 64;
25001
25460
  var CODEX_DIRECTORY_ENTRY_LIMIT = 500;
25002
25461
  var CodexRemoteDomain = class {
25003
25462
  constructor(config, logger, createAppServer = (binary, targetLogger) => new CodexAppServerClient(binary, targetLogger), restartDelaysMs = DEFAULT_RESTART_DELAYS_MS) {
@@ -25005,6 +25464,7 @@ var CodexRemoteDomain = class {
25005
25464
  this.createAppServer = createAppServer;
25006
25465
  this.restartDelaysMs = restartDelaysMs;
25007
25466
  this.config = config;
25467
+ this.historyWindows = new CodexHistoryWindows((method, params) => this.callUpstream(method, params), logger);
25008
25468
  }
25009
25469
  appServer;
25010
25470
  unsubscribeInbound;
@@ -25014,6 +25474,8 @@ var CodexRemoteDomain = class {
25014
25474
  turnOwners = /* @__PURE__ */ new Map();
25015
25475
  approvals = /* @__PURE__ */ new Map();
25016
25476
  permissionPresets = /* @__PURE__ */ new Map();
25477
+ /** Per-Thread turn index plus the tail-first full-item window. */
25478
+ historyWindows;
25017
25479
  approvalExpiryTimer;
25018
25480
  restartTimer;
25019
25481
  restartAttempt = 0;
@@ -25115,14 +25577,16 @@ var CodexRemoteDomain = class {
25115
25577
  }
25116
25578
  if (call.method === "thread/list") {
25117
25579
  const result = sanitizeThreadList(await this.callUpstream(call.method, call.params));
25580
+ const authority = await this.readWorkspaceAuthority(result.data);
25118
25581
  const authorized = filterThreadListByWorkspaceAuthority(
25119
25582
  result,
25120
- await this.readWorkspaceAuthority(result.data)
25583
+ authority,
25584
+ this.config.showUnprojectedSessions !== false
25121
25585
  );
25122
- return {
25586
+ return rebaseThreadListCwds({
25123
25587
  ...authorized,
25124
25588
  data: uniqueCodexThreadRows(authorized.data)
25125
- };
25589
+ }, authority.roots);
25126
25590
  }
25127
25591
  if (call.method === "thread/start") {
25128
25592
  const cwd = await this.requireCodexWorkspacePath(call.params.cwd);
@@ -25141,21 +25605,15 @@ var CodexRemoteDomain = class {
25141
25605
  }
25142
25606
  const threadId = threadIdFromParams(call.params);
25143
25607
  if (call.method === "dsh/sessionHistory") {
25144
- const thread = await this.readThreadForHistory(connectionId, threadId);
25145
- const page = paginateCodexNativeHistory(
25146
- projectCodexNativeHistory(thread, `codex:${threadId}`),
25147
- {
25148
- beforeSeq: optionalInteger2(call.params.beforeSeq),
25149
- throughSeq: optionalInteger2(call.params.throughSeq),
25150
- maxMessages: optionalInteger2(call.params.maxMessages)
25151
- }
25152
- );
25153
- const activeTurnId2 = typeof page.activeTurnId === "string" ? page.activeTurnId : this.turnOwners.get(threadId)?.turnId;
25154
- return {
25155
- ...page,
25156
- ...activeTurnId2 === void 0 ? {} : { activeTurnId: activeTurnId2 },
25157
- permissionPreset: this.permissionPresets.get(threadId) ?? null
25158
- };
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
+ });
25159
25617
  }
25160
25618
  if (call.method === "dsh/directoryList") {
25161
25619
  return this.listCodexDirectory(call.params.path);
@@ -25658,103 +26116,60 @@ var CodexRemoteDomain = class {
25658
26116
  return { thread: listed, ...typeof listed.cwd === "string" && listed.cwd.length > 0 ? { cwd: listed.cwd } : {} };
25659
26117
  }
25660
26118
  const thread = extractThread(result);
25661
- if (thread === void 0 || thread.id !== threadId || !isThreadAllowedByWorkspaceAuthority(thread, authority)) {
26119
+ if (thread === void 0 || thread.id !== threadId || !this.threadAllowed(thread, authority)) {
25662
26120
  throw new RpcError("CODEX_THREAD_NOT_ALLOWED", "The Codex thread is not available through this Remote Host.");
25663
26121
  }
25664
26122
  return { thread, ...typeof thread.cwd === "string" && thread.cwd.length > 0 ? { cwd: thread.cwd } : {} };
25665
26123
  }
25666
- async readThreadForHistory(connectionId, threadId) {
25667
- const { thread: metadata } = await this.readKnownThread(threadId);
25668
- try {
25669
- return { ...metadata, turns: await this.readThreadTurns(connectionId, threadId, "full") };
25670
- } catch (fullError) {
25671
- if (!isHistoryReadRecoverable(fullError)) throw fullError;
25672
- this.logHistoryFallback(connectionId, "turns-full", fullError);
25673
- }
25674
- try {
25675
- return { ...metadata, turns: await this.readThreadTurns(connectionId, threadId, "summary") };
25676
- } catch (summaryError) {
25677
- if (!isHistoryReadRecoverable(summaryError)) throw summaryError;
25678
- this.logHistoryFallback(connectionId, "turns-summary", summaryError);
25679
- }
25680
- try {
25681
- const legacyResult = await this.callUpstream("thread/read", { threadId, includeTurns: true });
25682
- const legacyThread = extractThread(legacyResult);
25683
- if (legacyThread === void 0 || legacyThread.id !== threadId) {
25684
- throw new RpcError("CODEX_INVALID_RESPONSE", "Codex App Server returned an invalid Thread history.");
25685
- }
25686
- return legacyThread;
25687
- } catch (legacyError) {
25688
- if (!isHistoryReadRecoverable(legacyError)) throw legacyError;
25689
- this.logHistoryFallback(connectionId, "thread-read-full", legacyError);
25690
- return { ...metadata, turns: [] };
25691
- }
25692
- }
25693
- async readThreadTurns(connectionId, threadId, itemsView) {
25694
- const turns = [];
25695
- let cursor2;
25696
- for (let page = 0; page < MAX_CODEX_HISTORY_PAGES; page += 1) {
25697
- 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", {
25698
26145
  threadId,
25699
- limit: CODEX_HISTORY_PAGE_LIMIT,
25700
- sortDirection: "asc",
25701
- itemsView,
25702
- ...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
25703
26152
  });
25704
- const pageResult = isRecord12(result) ? result : {};
25705
- for (const rawTurn of array2(pageResult.data)) {
25706
- if (!isRecord12(rawTurn)) continue;
25707
- const turnId = typeof rawTurn.id === "string" ? rawTurn.id : void 0;
25708
- const items = rawTurn.itemsView === "full" || turnId === void 0 ? array2(rawTurn.items) : await this.readThreadItems(connectionId, threadId, turnId, array2(rawTurn.items));
25709
- turns.push({ ...rawTurn, items });
25710
- }
25711
- cursor2 = typeof pageResult.nextCursor === "string" && pageResult.nextCursor.length > 0 ? pageResult.nextCursor : void 0;
25712
- if (cursor2 === void 0) break;
25713
- }
25714
- return turns;
25715
- }
25716
- async readThreadItems(connectionId, threadId, turnId, fallbackItems) {
25717
- const items = [];
25718
- let cursor2;
25719
- try {
25720
- for (let page = 0; page < MAX_CODEX_HISTORY_PAGES; page += 1) {
25721
- const result = await this.callUpstream("thread/items/list", {
25722
- threadId,
25723
- turnId,
25724
- limit: CODEX_HISTORY_PAGE_LIMIT,
25725
- sortDirection: "asc",
25726
- ...cursor2 === void 0 ? {} : { cursor: cursor2 }
25727
- });
25728
- const pageResult = isRecord12(result) ? result : {};
25729
- for (const entry of array2(pageResult.data)) {
25730
- if (isRecord12(entry) && entry.item !== void 0) items.push(entry.item);
25731
- }
25732
- cursor2 = typeof pageResult.nextCursor === "string" && pageResult.nextCursor.length > 0 ? pageResult.nextCursor : void 0;
25733
- if (cursor2 === void 0) break;
26153
+ if (bytes <= HISTORY_BUDGET_BYTES) {
26154
+ window.acceptedPageSize = size;
26155
+ return response;
25734
26156
  }
25735
- } catch (error) {
25736
- if (!isHistoryReadRecoverable(error)) throw error;
25737
- this.logHistoryFallback(connectionId, "items", error);
25738
- return fallbackItems;
25739
26157
  }
25740
- return items;
25741
- }
25742
- logHistoryFallback(connectionId, stage, error) {
25743
- this.logger.warn("Codex history read fallback", {
25744
- connectionId: maskId(connectionId),
25745
- stage,
25746
- code: errorCode4(error)
25747
- });
26158
+ throw new RpcError("RESPONSE_TOO_LARGE", "The Codex history page is too large for the secure Remote channel.");
25748
26159
  }
25749
26160
  async assertResultThreadAllowed(result) {
25750
26161
  const thread = extractThread(result);
25751
26162
  if (thread === void 0 || typeof thread.id !== "string") {
25752
26163
  throw new RpcError("CODEX_INVALID_RESPONSE", "Codex App Server returned an invalid thread.");
25753
26164
  }
25754
- if (!isThreadAllowedByWorkspaceAuthority(thread, await this.readWorkspaceAuthority())) {
26165
+ if (!this.threadAllowed(thread, await this.readWorkspaceAuthority())) {
25755
26166
  throw new RpcError("CODEX_THREAD_NOT_ALLOWED", "The Codex thread is not available through this Remote Host.");
25756
26167
  }
25757
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
+ }
25758
26173
  claimTurn(threadId, connectionId, method, turnId) {
25759
26174
  const owner = this.turnOwners.get(threadId);
25760
26175
  const peerDeviceId = this.peerDeviceIds.get(connectionId) ?? connectionId;
@@ -25895,7 +26310,7 @@ var CodexRemoteDomain = class {
25895
26310
  }));
25896
26311
  const thread = result.data.find((item) => item.id === threadId);
25897
26312
  if (thread !== void 0) {
25898
- return isThreadAllowedByWorkspaceAuthority(thread, authority) ? thread : void 0;
26313
+ return this.threadAllowed(thread, authority) ? thread : void 0;
25899
26314
  }
25900
26315
  cursor2 = typeof result.nextCursor === "string" && result.nextCursor.length > 0 ? result.nextCursor : void 0;
25901
26316
  if (cursor2 === void 0) break;
@@ -25906,6 +26321,14 @@ var CodexRemoteDomain = class {
25906
26321
  if (!isAbsolute2(path)) {
25907
26322
  throw new RpcError("CODEX_PATH_NOT_ALLOWED", "The CodeX working directory is not available as a Workspace.");
25908
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
+ }
25909
26332
  const paths = await this.listCodexWorkspacePaths();
25910
26333
  const lexicalCandidate = resolve(path);
25911
26334
  let candidate;
@@ -26006,7 +26429,7 @@ var CodexRemoteDomain = class {
26006
26429
  return paths;
26007
26430
  }
26008
26431
  };
26009
- 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) {
26010
26433
  if (configured !== "codex") return [configured];
26011
26434
  if (hostPlatform === "win32") {
26012
26435
  return [.../* @__PURE__ */ new Set([
@@ -26042,12 +26465,12 @@ function codexBinaryCandidates(configured, hostPlatform = process.platform, user
26042
26465
  ])];
26043
26466
  }
26044
26467
  function bundledWindowsCodex(userHome) {
26045
- return bundledWindowsCodexAt(join7(userHome, "AppData", "Local"));
26468
+ return bundledWindowsCodexAt(join9(userHome, "AppData", "Local"));
26046
26469
  }
26047
26470
  function bundledWindowsCodexAt(localAppData) {
26048
- const bin = join7(localAppData, "OpenAI", "Codex", "bin");
26471
+ const bin = join9(localAppData, "OpenAI", "Codex", "bin");
26049
26472
  try {
26050
- 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);
26051
26474
  const candidate = newest[0]?.candidate;
26052
26475
  return candidate === void 0 ? [] : [candidate];
26053
26476
  } catch {
@@ -26120,13 +26543,30 @@ function sanitizeThreadList(result) {
26120
26543
  ...typeof result.backwardsCursor === "string" && result.backwardsCursor.length > 0 ? { backwardsCursor: result.backwardsCursor } : {}
26121
26544
  };
26122
26545
  }
26123
- function filterThreadListByWorkspaceAuthority(result, authority) {
26546
+ function filterThreadListByWorkspaceAuthority(result, authority, includeUnprojected = false) {
26124
26547
  if (!isRecord12(result) || !Array.isArray(result.data)) {
26125
26548
  throw new RpcError("CODEX_INVALID_RESPONSE", "Codex App Server returned an invalid thread list.");
26126
26549
  }
26127
26550
  return {
26128
26551
  ...result,
26129
- 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
+ })
26130
26570
  };
26131
26571
  }
26132
26572
  function sanitizeProject(value) {
@@ -26283,12 +26723,6 @@ function canTryNextBinary(error) {
26283
26723
  function isRecord12(value) {
26284
26724
  return typeof value === "object" && value !== null && !Array.isArray(value);
26285
26725
  }
26286
- function array2(value) {
26287
- return Array.isArray(value) ? value : [];
26288
- }
26289
- function maskId(value) {
26290
- return value.length <= 12 ? value : `${value.slice(0, 8)}\u2026${value.slice(-4)}`;
26291
- }
26292
26726
  function isActiveWriterMessage(message) {
26293
26727
  return message.toLowerCase().includes("active writer");
26294
26728
  }
@@ -27402,12 +27836,12 @@ function isOversizedListing(value) {
27402
27836
 
27403
27837
  // src/remote-directory-browser.ts
27404
27838
  import { readdir as readdir3, stat as stat5 } from "node:fs/promises";
27405
- import { homedir as homedir3, platform as platform2 } from "node:os";
27839
+ import { homedir as homedir5, platform as platform2 } from "node:os";
27406
27840
  import { basename as basename3, dirname as dirname5, isAbsolute as isAbsolute3, parse, resolve as resolve2 } from "node:path";
27407
27841
  var MAX_ENTRIES = 500;
27408
27842
  async function listRemoteDirectory(path, signal) {
27409
27843
  signal?.throwIfAborted();
27410
- const home = resolve2(homedir3());
27844
+ const home = resolve2(homedir5());
27411
27845
  const target2 = path === void 0 || path.trim() === "" ? home : resolve2(path);
27412
27846
  if (!isAbsolute3(target2)) throw new Error("The remote directory path must be absolute.");
27413
27847
  const rows = await readdir3(target2, { withFileTypes: true });
@@ -28271,7 +28705,8 @@ function concatChunks2(chunks, totalBytes) {
28271
28705
  }
28272
28706
 
28273
28707
  // src/harness-remote-bridge.ts
28274
- import { Buffer as Buffer4 } from "node:buffer";
28708
+ import { Buffer as Buffer5 } from "node:buffer";
28709
+ import { isAbsolute as isAbsolute4, join as join10, relative as relative2, sep } from "node:path";
28275
28710
  var endpointSchema = external_exports.string().min(1).max(128).regex(/^(?:\$events(?:\/result)?|[A-Za-z0-9_$.-]+\/[A-Za-z0-9_$.-]+)$/);
28276
28711
  var callSchema3 = external_exports.object({ endpoint: endpointSchema, payload: external_exports.unknown() }).strict();
28277
28712
  var streamOpenSchema3 = external_exports.object({
@@ -28320,9 +28755,8 @@ var MAX_ACTIVE_STREAMS2 = MAX_ALPHA_STREAMS_PER_CONNECTION;
28320
28755
  var MAX_ACTIVE_TRANSFERS2 = MAX_ACTIVE_TRANSFERS_PER_DIRECTION;
28321
28756
  var INLINE_TRANSFER_RESPONSE_BYTES3 = 2 * 1024 * 1024;
28322
28757
  var STREAM_FRAME_ENVELOPE_BYTES = 4 * 1024;
28323
- var RESPONSE_ENVELOPE_BYTES = 8 * 1024;
28324
- var HISTORY_PAGE_SIZES = [120, 80, 50, 30, 20, 12, 6, 3, 1];
28325
- var MAX_HISTORY_PAGE_MESSAGES = 500;
28758
+ var RESPONSE_ENVELOPE_BYTES2 = 8 * 1024;
28759
+ var WORKSPACE_FILE_ENDPOINTS = /* @__PURE__ */ new Set(["workspaceFiles/stat", "workspaceFiles/read", "workspaceFiles/readBytes", "workspaceFiles/list"]);
28326
28760
  var REMOTE_HISTORY_CALL_ENDPOINTS = /* @__PURE__ */ new Set(["session/page", "session/history"]);
28327
28761
  var REMOTE_HISTORY_STREAM_ENDPOINTS = /* @__PURE__ */ new Set(["session/follow"]);
28328
28762
  var MAX_CACHED_HISTORY_PAGES = 64;
@@ -28403,19 +28837,22 @@ var HARNESS_REMOTE_ALLOWLIST = [
28403
28837
  ];
28404
28838
  var allowedEndpoints = new Set(HARNESS_REMOTE_ALLOWLIST);
28405
28839
  var HarnessRemoteBridge = class {
28406
- 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) {
28407
28841
  this.gateway = gateway;
28408
28842
  this.publish = publish;
28409
28843
  this.logger = logger;
28410
28844
  this.harnessVersion = harnessVersion;
28411
28845
  this.terminal = terminal;
28412
28846
  this.codexWorkspace = codexWorkspace;
28847
+ this.sessionHeaderCwd = sessionHeaderCwd;
28413
28848
  }
28414
28849
  streams = /* @__PURE__ */ new Map();
28415
28850
  incomingTransfers = /* @__PURE__ */ new Map();
28416
28851
  outgoingTransfers = /* @__PURE__ */ new Map();
28417
28852
  /** Last page size that fit one secure message, per session, so probing is paid once. */
28418
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();
28419
28856
  /**
28420
28857
  * Answer one remote Gateway call.
28421
28858
  *
@@ -28456,7 +28893,7 @@ var HarnessRemoteBridge = class {
28456
28893
  const signal = AbortSignal.timeout(6e4);
28457
28894
  try {
28458
28895
  const nativePayload = normalizeWorkspaceRequestPayload(params.endpoint, params.payload, this.harnessVersion);
28459
- 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.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);
28460
28897
  const result = params.endpoint === "directoryPicker/list" && needsDirectoryFallback(nativeResult) ? await this.directoryList(params.payload, signal) : nativeResult;
28461
28898
  this.logger?.debug("harness remote call ok", {
28462
28899
  endpoint: params.endpoint,
@@ -28583,7 +29020,7 @@ var HarnessRemoteBridge = class {
28583
29020
  return {
28584
29021
  transferId: params.transferId,
28585
29022
  index: params.index,
28586
- data: Buffer4.from(transfer.bytes.subarray(start, end)).toString("base64")
29023
+ data: Buffer5.from(transfer.bytes.subarray(start, end)).toString("base64")
28587
29024
  };
28588
29025
  }
28589
29026
  closeTransfer(input2) {
@@ -28703,7 +29140,7 @@ var HarnessRemoteBridge = class {
28703
29140
  hasMore: value?.hasMore,
28704
29141
  bytes
28705
29142
  });
28706
- if (bytes <= MAX_SECURE_MESSAGE_BYTES - RESPONSE_ENVELOPE_BYTES) {
29143
+ if (bytes <= MAX_SECURE_MESSAGE_BYTES - RESPONSE_ENVELOPE_BYTES2) {
28707
29144
  this.rememberHistoryPageSize(key, size);
28708
29145
  return wire;
28709
29146
  }
@@ -28722,7 +29159,7 @@ var HarnessRemoteBridge = class {
28722
29159
  const key = historyPageKey(payload);
28723
29160
  const requested = requestedMaxMessages(payload) ?? HISTORY_PAGE_SIZES[0];
28724
29161
  const remembered = this.historyPageSizes.get(key);
28725
- const start = Math.max(1, Math.min(requested, MAX_HISTORY_PAGE_MESSAGES, remembered ?? requested));
29162
+ const start = Math.max(1, Math.min(requested, HISTORY_PAGE_SIZES[0], remembered ?? requested));
28726
29163
  this.logger?.debug("history window", { key: key.slice(0, 120), requested, remembered, start });
28727
29164
  return { key, sizes: historyPageSizes(start) };
28728
29165
  }
@@ -28733,6 +29170,68 @@ var HarnessRemoteBridge = class {
28733
29170
  }
28734
29171
  this.historyPageSizes.set(key, size);
28735
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;
29234
+ }
28736
29235
  closeStream(input2) {
28737
29236
  const params = streamCloseSchema3.parse(input2);
28738
29237
  const active = this.streams.get(params.streamId);
@@ -28821,11 +29320,6 @@ function windowedRequest(request, maxMessages) {
28821
29320
  }
28822
29321
  return next;
28823
29322
  }
28824
- function historyPageSizes(start) {
28825
- const sizes = [start];
28826
- for (const size of HISTORY_PAGE_SIZES) if (size < start) sizes.push(size);
28827
- return sizes;
28828
- }
28829
29323
  function historyRequestOf(payload) {
28830
29324
  let node = payload;
28831
29325
  for (let depth = 0; depth < 4; depth += 1) {
@@ -28850,6 +29344,64 @@ function requestedMaxMessages(payload) {
28850
29344
  const value = historyRequestOf(payload)?.maxMessages;
28851
29345
  return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : void 0;
28852
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
+ }
28853
29405
  function historyPageKey(payload) {
28854
29406
  const request = historyRequestOf(payload);
28855
29407
  if (request === void 0) return "default";
@@ -28862,10 +29414,10 @@ function historyPageKey(payload) {
28862
29414
  }
28863
29415
  }
28864
29416
  function responseBytes(result) {
28865
- return Buffer4.byteLength(JSON.stringify(result), "utf8");
29417
+ return Buffer5.byteLength(JSON.stringify(result), "utf8");
28866
29418
  }
28867
29419
  function streamFrameBytes(frame) {
28868
- return Buffer4.byteLength(JSON.stringify(frame), "utf8");
29420
+ return Buffer5.byteLength(JSON.stringify(frame), "utf8");
28869
29421
  }
28870
29422
  function remainingIterable(iterator) {
28871
29423
  return { [Symbol.asyncIterator]: () => iterator };
@@ -28925,7 +29477,7 @@ async function dispatchCommandForHost(gateway, payload, signal, harnessVersion)
28925
29477
  );
28926
29478
  }
28927
29479
  function decodeCanonicalBase643(value) {
28928
- const bytes = Buffer4.from(value, "base64");
29480
+ const bytes = Buffer5.from(value, "base64");
28929
29481
  if (bytes.toString("base64") !== value) throw new RpcError("INVALID_MESSAGE", "The Harness Remote transfer chunk is invalid.");
28930
29482
  return bytes;
28931
29483
  }
@@ -29073,7 +29625,7 @@ import { execFileSync as execFileSync2 } from "node:child_process";
29073
29625
  // src/codex-workspace-bridge.ts
29074
29626
  import { spawn as spawn4 } from "node:child_process";
29075
29627
  import { realpath as realpath2, lstat, readdir as readdir4, readFile as readFile5, stat as stat6, watch } from "node:fs/promises";
29076
- 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";
29077
29629
  var MAX_READ_BYTES = 4 * 1024 * 1024;
29078
29630
  var MAX_INPUT_BYTES = 64 * 1024;
29079
29631
  var MAX_COLS = 240;
@@ -29160,7 +29712,7 @@ var CodexWorkspaceBridge = class {
29160
29712
  const entries = await readdir4(target2, { withFileTypes: true });
29161
29713
  const result = [];
29162
29714
  for (const entry of entries.slice(0, 500)) {
29163
- const item = join8(target2, entry.name);
29715
+ const item = join11(target2, entry.name);
29164
29716
  const info2 = await lstat(item);
29165
29717
  if (info2.isSymbolicLink()) continue;
29166
29718
  result.push({ name: entry.name, type: info2.isDirectory() ? "directory" : info2.isFile() ? "file" : "other", ...info2.isFile() ? { size: info2.size } : {} });
@@ -29382,15 +29934,15 @@ var CodexWorkspaceBridge = class {
29382
29934
  }
29383
29935
  }
29384
29936
  async safePath(root, path, directory) {
29385
- const candidate = isAbsolute4(path) ? resolve3(path) : resolve3(root, path);
29386
- const rel = relative2(root, candidate);
29387
- 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.");
29388
29940
  try {
29389
29941
  const info = await lstat(candidate);
29390
29942
  if (info.isSymbolicLink()) throw new Error();
29391
29943
  const canonical = await realpath2(candidate);
29392
- const canonicalRel = relative2(root, canonical);
29393
- if (canonicalRel.startsWith("..") || isAbsolute4(canonicalRel)) throw new Error();
29944
+ const canonicalRel = relative3(root, canonical);
29945
+ if (canonicalRel.startsWith("..") || isAbsolute5(canonicalRel)) throw new Error();
29394
29946
  if (directory && !info.isDirectory()) throw new Error();
29395
29947
  return canonical;
29396
29948
  } catch {
@@ -29531,7 +30083,7 @@ function readLimit(args) {
29531
30083
 
29532
30084
  // src/service.ts
29533
30085
  var HostPluginRuntime = class {
29534
- constructor(config, identities, apiProxy, logger, localGateway, fileViewerHost, terminalSpawner) {
30086
+ constructor(config, identities, apiProxy, logger, localGateway, fileViewerHost, terminalSpawner, sessionSource) {
29535
30087
  this.config = config;
29536
30088
  this.identities = identities;
29537
30089
  this.apiProxy = apiProxy;
@@ -29539,6 +30091,7 @@ var HostPluginRuntime = class {
29539
30091
  this.localGateway = localGateway;
29540
30092
  this.fileViewerHost = fileViewerHost;
29541
30093
  this.terminalSpawner = terminalSpawner;
30094
+ this.sessionSource = sessionSource;
29542
30095
  this.terminalEnabled = config.terminal.enabled;
29543
30096
  this.loopbackPorts = [...config.loopback.ports];
29544
30097
  this.paused = config.hostControl?.paused === true;
@@ -29563,7 +30116,8 @@ var HostPluginRuntime = class {
29563
30116
  () => this.terminalEnabled,
29564
30117
  this.codexWorkspaceState,
29565
30118
  this.terminalSpawner
29566
- )
30119
+ ),
30120
+ this.sessionSource === void 0 ? void 0 : (sessionId) => this.sessionSource?.()?.get?.(sessionId)?.header?.cwd
29567
30121
  ) : void 0;
29568
30122
  const fileViewer = new RemoteFileViewerBridge(
29569
30123
  () => this.fileViewerHost?.(),
@@ -29985,7 +30539,7 @@ function isPlainRecord(value) {
29985
30539
  // src/cli.ts
29986
30540
  import { stat as stat7 } from "node:fs/promises";
29987
30541
  import { hostname as hostname3 } from "node:os";
29988
- import { join as join9 } from "node:path";
30542
+ import { join as join12 } from "node:path";
29989
30543
  var QR_POLL_INTERVAL_MS = 2e3;
29990
30544
  var TERMINAL_QR_MARGIN = 4;
29991
30545
  async function runCli(args = process.argv.slice(2), dependencies = {}) {
@@ -30073,7 +30627,7 @@ async function status(args, runtime) {
30073
30627
  `Server: ${serverUrl}`,
30074
30628
  "Host control: enabled (dsh-TUI default)"
30075
30629
  ];
30076
- if (!await exists3(join9(directory, "device.json"))) {
30630
+ if (!await exists3(join12(directory, "device.json"))) {
30077
30631
  lines.push("Device: not initialized", "Authorization: logged out", "Credential: unavailable");
30078
30632
  write(runtime.stdout, `${lines.join("\n")}
30079
30633
 
@@ -30117,7 +30671,7 @@ async function logout(args, runtime) {
30117
30671
  const serverUrl = selectedServer();
30118
30672
  const root = new IdentityStore({ env: runtime.env }).directory;
30119
30673
  const directory = serverStorageDirectory(root, serverUrl, "host");
30120
- if (!await exists3(join9(directory, "device.json"))) {
30674
+ if (!await exists3(join12(directory, "device.json"))) {
30121
30675
  await new ServerCredentialStore(directory).clear();
30122
30676
  write(runtime.stdout, "This Host is already logged out.\n");
30123
30677
  return 0;
@@ -30782,6 +31336,7 @@ async function activate(ctx, readConfig, entryId, tuiBinding) {
30782
31336
  const nativeTypertGateway = ctx.get("typertGateway");
30783
31337
  const localTypertGateway = new TypertGatewaySwitch(nativeTypertGateway, { peer: peerFor }).local();
30784
31338
  const subprocess = ctx.get("subprocess", false);
31339
+ const sessionSource = () => ctx.get("sessions", false);
30785
31340
  const deepseekAccount = ctx.get("deepseekAccount", false);
30786
31341
  const deepseekSession = deepseekAccount === void 0 ? void 0 : {
30787
31342
  read: async () => {
@@ -30813,7 +31368,8 @@ async function activate(ctx, readConfig, entryId, tuiBinding) {
30813
31368
  logger,
30814
31369
  localTypertGateway,
30815
31370
  () => ctx.get("fileViewerHost"),
30816
- subprocess === void 0 ? void 0 : subprocessTerminalSpawner(subprocess)
31371
+ subprocess === void 0 ? void 0 : subprocessTerminalSpawner(subprocess),
31372
+ sessionSource
30817
31373
  );
30818
31374
  let clientRuntime;
30819
31375
  const hostControl = runtime;