ds-harness-remote 0.4.16 → 0.4.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/client-runtime.js +1 -1
  2. package/dist/client-runtime.js.map +1 -1
  3. package/dist/client.github.js +57 -18
  4. package/dist/client.js +57 -18
  5. package/dist/client.js.map +1 -1
  6. package/dist/codex/domain.d.ts +2 -0
  7. package/dist/codex/domain.d.ts.map +1 -1
  8. package/dist/codex/domain.js +15 -0
  9. package/dist/codex/domain.js.map +1 -1
  10. package/dist/codex/session-id.d.ts +10 -0
  11. package/dist/codex/session-id.d.ts.map +1 -0
  12. package/dist/codex/session-id.js +15 -0
  13. package/dist/codex/session-id.js.map +1 -0
  14. package/dist/codex/virtual-harness.d.ts +3 -2
  15. package/dist/codex/virtual-harness.d.ts.map +1 -1
  16. package/dist/codex/virtual-harness.js +32 -3
  17. package/dist/codex/virtual-harness.js.map +1 -1
  18. package/dist/codex-workspace-bridge.d.ts +108 -0
  19. package/dist/codex-workspace-bridge.d.ts.map +1 -0
  20. package/dist/codex-workspace-bridge.js +475 -0
  21. package/dist/codex-workspace-bridge.js.map +1 -0
  22. package/dist/harness-remote-bridge.d.ts +4 -2
  23. package/dist/harness-remote-bridge.d.ts.map +1 -1
  24. package/dist/harness-remote-bridge.js +22 -2
  25. package/dist/harness-remote-bridge.js.map +1 -1
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +616 -78
  28. package/dist/index.js.map +4 -4
  29. package/dist/service.d.ts +7 -1
  30. package/dist/service.d.ts.map +1 -1
  31. package/dist/service.js +8 -2
  32. package/dist/service.js.map +1 -1
  33. package/dist/terminal-policy.d.ts.map +1 -1
  34. package/dist/terminal-policy.js +8 -2
  35. package/dist/terminal-policy.js.map +1 -1
  36. package/dist/version.d.ts +1 -1
  37. package/dist/version.js +1 -1
  38. package/dsh-plugin.json +1 -1
  39. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4695,7 +4695,7 @@ var LoopbackHost = class {
4695
4695
  const response = handle.response;
4696
4696
  let chunk = response.read(Math.min(response.readableLength || LOOPBACK_CHUNK_BYTES, LOOPBACK_CHUNK_BYTES));
4697
4697
  if (chunk === null && !response.readableEnded && !response.destroyed) {
4698
- await new Promise((resolve3, reject) => {
4698
+ await new Promise((resolve4, reject) => {
4699
4699
  const timer = setTimeout(() => finish(new Error("timeout")), 2e4);
4700
4700
  const ready = () => finish();
4701
4701
  const fail4 = () => finish(new Error("closed"));
@@ -4705,7 +4705,7 @@ var LoopbackHost = class {
4705
4705
  response.off("end", ready);
4706
4706
  response.off("close", ready);
4707
4707
  response.off("error", fail4);
4708
- error === void 0 ? resolve3() : reject(error);
4708
+ error === void 0 ? resolve4() : reject(error);
4709
4709
  };
4710
4710
  response.once("readable", ready);
4711
4711
  response.once("end", ready);
@@ -4735,22 +4735,22 @@ var LoopbackHost = class {
4735
4735
  this.close(value.id);
4736
4736
  throw new RpcError("RATE_LIMITED", "Preview WebSocket buffer exceeded its limit.");
4737
4737
  }
4738
- await new Promise((resolve3, reject) => handle.socket.send(data2, { binary: value.binary }, (error) => error ? reject(new RpcError("LOOPBACK_CLOSED", "Preview socket closed.")) : resolve3()));
4738
+ await new Promise((resolve4, reject) => handle.socket.send(data2, { binary: value.binary }, (error) => error ? reject(new RpcError("LOOPBACK_CLOSED", "Preview socket closed.")) : resolve4()));
4739
4739
  return { sent: true };
4740
4740
  }
4741
4741
  if (value.op === "ws.read") {
4742
4742
  if (handle.reading) throw new RpcError("REQUEST_CONFLICT", "Only one preview read may be pending.");
4743
4743
  handle.reading = true;
4744
4744
  try {
4745
- if (handle.messages.length === 0 && !handle.closed) await new Promise((resolve3) => {
4745
+ if (handle.messages.length === 0 && !handle.closed) await new Promise((resolve4) => {
4746
4746
  const timer = setTimeout(() => {
4747
4747
  handle.wake = void 0;
4748
- resolve3();
4748
+ resolve4();
4749
4749
  }, 2e4);
4750
4750
  handle.wake = () => {
4751
4751
  clearTimeout(timer);
4752
4752
  handle.wake = void 0;
4753
- resolve3();
4753
+ resolve4();
4754
4754
  };
4755
4755
  });
4756
4756
  let bytes = 0;
@@ -4792,7 +4792,7 @@ var LoopbackHost = class {
4792
4792
  openHttp(value) {
4793
4793
  const body = value.body === void 0 ? void 0 : Buffer.from(value.body, "base64");
4794
4794
  if ((body?.length ?? 0) > LOOPBACK_MAX_BODY_BYTES) throw new Error("body limit");
4795
- return new Promise((resolve3, reject) => {
4795
+ return new Promise((resolve4, reject) => {
4796
4796
  const headers = Object.fromEntries(cleanHeaders(value.headers));
4797
4797
  const request = httpRequest({
4798
4798
  hostname: "127.0.0.1",
@@ -4811,7 +4811,7 @@ var LoopbackHost = class {
4811
4811
  handle.response = response;
4812
4812
  response.on("error", () => void 0);
4813
4813
  clearTimeout(timer);
4814
- resolve3({ status: response.statusCode ?? 502, headers: cleanHeaders(headerPairs(response.headers)) });
4814
+ resolve4({ status: response.statusCode ?? 502, headers: cleanHeaders(headerPairs(response.headers)) });
4815
4815
  });
4816
4816
  const timer = setTimeout(() => {
4817
4817
  request.destroy();
@@ -4826,7 +4826,7 @@ var LoopbackHost = class {
4826
4826
  });
4827
4827
  }
4828
4828
  openWs(value) {
4829
- return new Promise((resolve3, reject) => {
4829
+ return new Promise((resolve4, reject) => {
4830
4830
  const socket = new WebSocket2(`ws://127.0.0.1:${value.port}${value.path}`, value.protocols, {
4831
4831
  headers: Object.fromEntries(cleanHeaders(value.headers)),
4832
4832
  followRedirects: false,
@@ -4836,7 +4836,7 @@ var LoopbackHost = class {
4836
4836
  });
4837
4837
  const handle = { kind: "ws", socket, messages: [], bytes: 0, reading: false, closed: false, touched: Date.now() };
4838
4838
  this.handles.set(value.id, handle);
4839
- socket.once("open", () => resolve3({ protocol: socket.protocol }));
4839
+ socket.once("open", () => resolve4({ protocol: socket.protocol }));
4840
4840
  socket.on("message", (data2, binary) => {
4841
4841
  const message = { data: Buffer.from(data2).toString("base64"), binary };
4842
4842
  if (handle.bytes + message.data.length > 1024 * 1024 || handle.messages.length >= 256) {
@@ -4894,7 +4894,7 @@ var LoopbackPreview = class {
4894
4894
  for (const client of running.ws.clients) client.terminate();
4895
4895
  running.ws.close();
4896
4896
  running.server.closeAllConnections();
4897
- await new Promise((resolve3) => running.server.close(() => resolve3()));
4897
+ await new Promise((resolve4) => running.server.close(() => resolve4()));
4898
4898
  }
4899
4899
  this.servers.clear();
4900
4900
  }
@@ -4949,11 +4949,11 @@ var LoopbackPreview = class {
4949
4949
  void this.release(id4);
4950
4950
  });
4951
4951
  });
4952
- await new Promise((resolve3, reject) => {
4952
+ await new Promise((resolve4, reject) => {
4953
4953
  server.once("error", reject);
4954
4954
  server.listen(0, "127.0.0.1", () => {
4955
4955
  server.off("error", reject);
4956
- resolve3();
4956
+ resolve4();
4957
4957
  });
4958
4958
  });
4959
4959
  const address = server.address();
@@ -5019,10 +5019,10 @@ var LoopbackPreview = class {
5019
5019
  while (!signal.aborted) {
5020
5020
  const chunk = await this.client.rpc("loopback.call", { op: "http.read", id: id4 }, signal);
5021
5021
  if (chunk.data !== "" && !res.write(Buffer.from(chunk.data, "base64"))) {
5022
- await new Promise((resolve3, reject) => {
5022
+ await new Promise((resolve4, reject) => {
5023
5023
  const ready = () => {
5024
5024
  cleanup();
5025
- resolve3();
5025
+ resolve4();
5026
5026
  };
5027
5027
  const closed = () => {
5028
5028
  cleanup();
@@ -5082,7 +5082,7 @@ var LoopbackPreview = class {
5082
5082
  const result = await this.client.rpc("loopback.call", { op: "ws.read", id: id4 }, signal);
5083
5083
  for (const message of result.messages) {
5084
5084
  if (socket.bufferedAmount > 1024 * 1024) throw new Error("slow preview");
5085
- await new Promise((resolve3, reject) => socket.send(Buffer.from(message.data, "base64"), { binary: message.binary }, (error) => error ? reject(error) : resolve3()));
5085
+ await new Promise((resolve4, reject) => socket.send(Buffer.from(message.data, "base64"), { binary: message.binary }, (error) => error ? reject(error) : resolve4()));
5086
5086
  }
5087
5087
  if (result.closed) break;
5088
5088
  }
@@ -5719,17 +5719,18 @@ var RemoteClientCore = class {
5719
5719
  throw error;
5720
5720
  }
5721
5721
  }
5722
- async rpc(method, params, signal) {
5722
+ async rpc(method, params, signal, options) {
5723
5723
  if (signal?.aborted)
5724
5724
  throw rpcAbortedError(method, signal.reason);
5725
+ const timeoutMs = callTimeoutMs(this.timeoutMs, options);
5725
5726
  const request = createRpcRequest(method, params);
5726
- const result = new Promise((resolve3, reject) => {
5727
+ const result = new Promise((resolve4, reject) => {
5727
5728
  const timer = setTimeout(() => {
5728
- this.rejectPending(request.id, new RemoteClientError("RPC_TIMEOUT", `RPC ${method} timed out after ${this.timeoutMs}ms`));
5729
- }, this.timeoutMs);
5729
+ this.rejectPending(request.id, new RemoteClientError("RPC_TIMEOUT", `RPC ${method} timed out after ${timeoutMs}ms`));
5730
+ }, timeoutMs);
5730
5731
  const pending = {
5731
5732
  method,
5732
- resolve: resolve3,
5733
+ resolve: resolve4,
5733
5734
  reject,
5734
5735
  timer
5735
5736
  };
@@ -5836,6 +5837,12 @@ var RemoteClientCore = class {
5836
5837
  function rpcAbortedError(method, reason) {
5837
5838
  return new RemoteClientError("RPC_ABORTED", `RPC ${method} was aborted`, reason === void 0 ? void 0 : { cause: reason });
5838
5839
  }
5840
+ function callTimeoutMs(fallback, options) {
5841
+ const timeoutMs = options?.timeoutMs;
5842
+ if (typeof timeoutMs !== "number")
5843
+ return fallback;
5844
+ return Number.isSafeInteger(timeoutMs) && timeoutMs > 0 && timeoutMs <= 2147483647 ? timeoutMs : fallback;
5845
+ }
5839
5846
  function transportSendError(error) {
5840
5847
  return error instanceof Error ? error : new Error("remote transport send failed", { cause: error });
5841
5848
  }
@@ -5846,7 +5853,7 @@ async function waitForRelayCapacity(socket) {
5846
5853
  while ((socket.bufferedAmount ?? 0) > 512 * 1024) {
5847
5854
  if (socket.readyState !== 1 || Date.now() - started > 1e4)
5848
5855
  throw new Error("Relay consumer is too slow or disconnected");
5849
- await new Promise((resolve3) => setTimeout(resolve3, 10));
5856
+ await new Promise((resolve4) => setTimeout(resolve4, 10));
5850
5857
  }
5851
5858
  if (socket.readyState !== 1)
5852
5859
  throw new Error("Relay transport closed");
@@ -6305,8 +6312,8 @@ var RtcDataChannelTransport = class {
6305
6312
  if (this.connectPromise !== void 0)
6306
6313
  return this.connectPromise;
6307
6314
  this.armAbort(signal);
6308
- this.connectPromise = new Promise((resolve3, reject) => {
6309
- this.openResolve = resolve3;
6315
+ this.connectPromise = new Promise((resolve4, reject) => {
6316
+ this.openResolve = resolve4;
6310
6317
  this.openReject = reject;
6311
6318
  this.negotiateTimer = setTimeout(() => {
6312
6319
  void this.failOpenAfterStats(new RtcConnectError("RTC_CONNECT_TIMEOUT", `WebRTC negotiation timed out after ${this.negotiateTimeoutMs}ms.`));
@@ -6521,9 +6528,9 @@ var RtcDataChannelTransport = class {
6521
6528
  if (this.closed || this.opened)
6522
6529
  return;
6523
6530
  this.opened = true;
6524
- const resolve3 = this.openResolve;
6531
+ const resolve4 = this.openResolve;
6525
6532
  this.clearNegotiation();
6526
- void this.resolveSelectedTransport().then(() => resolve3?.());
6533
+ void this.resolveSelectedTransport().then(() => resolve4?.());
6527
6534
  };
6528
6535
  channel.onmessage = (event) => {
6529
6536
  if (this.closed || !this.opened)
@@ -6917,7 +6924,7 @@ function asError(error) {
6917
6924
  return error instanceof Error ? error : new RtcConnectError("RTC_FAILED", "WebRTC negotiation failed.");
6918
6925
  }
6919
6926
  function sleep(ms) {
6920
- return new Promise((resolve3) => setTimeout(resolve3, ms));
6927
+ return new Promise((resolve4) => setTimeout(resolve4, ms));
6921
6928
  }
6922
6929
 
6923
6930
  // ../webrtc/dist/adaptive-transport.js
@@ -6955,8 +6962,8 @@ var AdaptiveTransport = class extends BaseTransport {
6955
6962
  this.socket = new WebSocket(this.url);
6956
6963
  this.controlFrameLimits = {};
6957
6964
  this.socket.binaryType = "arraybuffer";
6958
- await new Promise((resolve3, reject) => {
6959
- this.readyResolve = resolve3;
6965
+ await new Promise((resolve4, reject) => {
6966
+ this.readyResolve = resolve4;
6960
6967
  this.readyReject = reject;
6961
6968
  this.handshakeTimer = setTimeout(() => this.failConnection(new Error("Adaptive control handshake timed out")), this.options.handshakeTimeoutMs ?? 15e3);
6962
6969
  const socket = this.socket;
@@ -14558,7 +14565,7 @@ var ClientSecureTransport = class {
14558
14565
  }
14559
14566
  };
14560
14567
  async function waitForResponder(inner, noise) {
14561
- await new Promise((resolve3, reject) => {
14568
+ await new Promise((resolve4, reject) => {
14562
14569
  let settled = false;
14563
14570
  const timer = setTimeout(() => finish(new Error("Noise IK handshake timed out.")), 1e4);
14564
14571
  const unsubscribe = inner.onHandshake((step, data2) => {
@@ -14577,7 +14584,7 @@ async function waitForResponder(inner, noise) {
14577
14584
  settled = true;
14578
14585
  clearTimeout(timer);
14579
14586
  unsubscribe();
14580
- if (error === void 0) resolve3();
14587
+ if (error === void 0) resolve4();
14581
14588
  else reject(error);
14582
14589
  };
14583
14590
  void inner.sendHandshake(1, noise.writeHandshake()).catch((error) => {
@@ -15022,7 +15029,7 @@ var AsyncFrameQueue = class {
15022
15029
  continue;
15023
15030
  }
15024
15031
  if (this.closed) return;
15025
- const next = await new Promise((resolve3) => this.waiters.push(resolve3));
15032
+ const next = await new Promise((resolve4) => this.waiters.push(resolve4));
15026
15033
  if (next.done) return;
15027
15034
  yield next.value;
15028
15035
  }
@@ -15387,7 +15394,7 @@ var AsyncValueQueue = class {
15387
15394
  if (this.error !== void 0) throw this.error;
15388
15395
  return;
15389
15396
  }
15390
- const next = await new Promise((resolve3) => this.waiters.push(resolve3));
15397
+ const next = await new Promise((resolve4) => this.waiters.push(resolve4));
15391
15398
  if (next.done) {
15392
15399
  if (this.error !== void 0) throw this.error;
15393
15400
  return;
@@ -15497,6 +15504,29 @@ var MAX_CODEX_IMAGE_BASE64 = 288 * 1024 * 1024;
15497
15504
  var CODEX_IMAGE_MEDIA_TYPES2 = /* @__PURE__ */ new Set(["image/png", "image/jpeg", "image/webp", "image/gif"]);
15498
15505
  var CODEX_IMAGE_ATTACHMENT_PREFIX = "codex-image:";
15499
15506
  var DATA_IMAGE_URL2 = /^data:(image\/(?:png|jpeg|webp|gif));base64,([A-Za-z0-9+/]+={0,2})$/u;
15507
+ var HOST_WORKSPACE_ENDPOINTS = /* @__PURE__ */ new Set([
15508
+ "workspaceFiles/list",
15509
+ "workspaceFiles/stat",
15510
+ "workspaceFiles/read",
15511
+ "workspaceFiles/readBytes",
15512
+ "workspaceFiles/readAll",
15513
+ "workspaceFiles/readRelated",
15514
+ "terminal/environment",
15515
+ "terminal/shells",
15516
+ "terminal/list",
15517
+ "terminal/create",
15518
+ "terminal/write",
15519
+ "terminal/resize",
15520
+ "terminal/rename",
15521
+ "terminal/close"
15522
+ ]);
15523
+ var HOST_WORKSPACE_STREAM_ENDPOINTS = /* @__PURE__ */ new Set(["workspaceFiles/changes", "terminal/follow", "terminal/retain"]);
15524
+ function isHostWorkspaceEndpoint(endpoint) {
15525
+ return HOST_WORKSPACE_ENDPOINTS.has(endpoint);
15526
+ }
15527
+ function isHostWorkspaceStreamEndpoint(endpoint) {
15528
+ return HOST_WORKSPACE_STREAM_ENDPOINTS.has(endpoint);
15529
+ }
15500
15530
  function codexProjectWorkspaceId(projectId) {
15501
15531
  return `${CODEX_WORKSPACE_PREFIX}project:${projectId}`;
15502
15532
  }
@@ -15504,10 +15534,11 @@ async function discoverCodexVirtualWorkspaces(client, signal) {
15504
15534
  return (await loadCatalog(client, signal)).workspaces;
15505
15535
  }
15506
15536
  var CodexVirtualHarness = class _CodexVirtualHarness {
15507
- constructor(client, host, sessionGeneration = "legacy") {
15537
+ constructor(client, host, sessionGeneration = "legacy", hostCarrier) {
15508
15538
  this.client = client;
15509
15539
  this.host = host;
15510
15540
  this.sessionGeneration = sessionGeneration;
15541
+ this.hostCarrier = hostCarrier;
15511
15542
  this.api = this.createApiProxy();
15512
15543
  }
15513
15544
  api;
@@ -15531,8 +15562,8 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
15531
15562
  commandSeq = 0;
15532
15563
  selectedWorkspaceId;
15533
15564
  closed = false;
15534
- static remote(core, host, sessionGeneration = "legacy") {
15535
- return new _CodexVirtualHarness(new CodexRemoteClient(core), host, sessionGeneration);
15565
+ static remote(core, host, sessionGeneration = "legacy", hostCarrier) {
15566
+ return new _CodexVirtualHarness(new CodexRemoteClient(core), host, sessionGeneration, hostCarrier);
15536
15567
  }
15537
15568
  async workspaces(signal) {
15538
15569
  return (await this.refreshCatalog(signal)).workspaces;
@@ -15579,6 +15610,10 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
15579
15610
  }
15580
15611
  async dispatch(endpoint, payload, signal) {
15581
15612
  try {
15613
+ if (isHostWorkspaceEndpoint(endpoint)) {
15614
+ if (this.hostCarrier === void 0) return fail("method-not-found", `CodeX virtual Harness does not implement ${endpoint}.`);
15615
+ return await this.hostCarrier.dispatch(endpoint, payload, signal);
15616
+ }
15582
15617
  const args = carrierArgs(payload);
15583
15618
  switch (endpoint) {
15584
15619
  case "$events/result":
@@ -15665,6 +15700,16 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
15665
15700
  }
15666
15701
  }
15667
15702
  async open(endpoint, payload, signal) {
15703
+ if (isHostWorkspaceStreamEndpoint(endpoint)) {
15704
+ if (this.hostCarrier === void 0) {
15705
+ throw Object.assign(new Error(`CodeX virtual Harness does not implement stream ${endpoint}.`), {
15706
+ isDSHRemoteError: true,
15707
+ code: "method-not-found",
15708
+ details: {}
15709
+ });
15710
+ }
15711
+ return this.hostCarrier.open(endpoint, payload, signal);
15712
+ }
15668
15713
  const args = carrierArgs(payload);
15669
15714
  if (endpoint === "workspace/follow") return this.workspaceFollow(signal);
15670
15715
  if (endpoint === "session/control") return this.sessionControl(signal);
@@ -17983,7 +18028,7 @@ var AsyncValueQueue2 = class {
17983
18028
  continue;
17984
18029
  }
17985
18030
  if (this.closed) return;
17986
- const next = await new Promise((resolve3) => this.waiters.push(resolve3));
18031
+ const next = await new Promise((resolve4) => this.waiters.push(resolve4));
17987
18032
  if (next.done) return;
17988
18033
  yield next.value;
17989
18034
  }
@@ -18027,7 +18072,7 @@ var ServerCredentialStore = class {
18027
18072
  } catch (error) {
18028
18073
  if (!(error instanceof Error) || !("code" in error) || error.code !== "EEXIST") throw error;
18029
18074
  if (Date.now() >= deadline) throw new ServerCredentialsBusyError();
18030
- await new Promise((resolve3) => setTimeout(resolve3, 50));
18075
+ await new Promise((resolve4) => setTimeout(resolve4, 50));
18031
18076
  }
18032
18077
  }
18033
18078
  try {
@@ -18198,7 +18243,7 @@ function normalizeServerUrl(value) {
18198
18243
  }
18199
18244
 
18200
18245
  // src/version.ts
18201
- var PLUGIN_VERSION = "0.4.16";
18246
+ var PLUGIN_VERSION = "0.4.17";
18202
18247
 
18203
18248
  // src/server-api.ts
18204
18249
  var TERMINAL_CONTROL_CHARACTERS = /[\u0000-\u001f\u007f-\u009f]/u;
@@ -18867,10 +18912,10 @@ function isUsableExternalNode(candidate, requireFrom) {
18867
18912
  }
18868
18913
  function isExecutableFile(candidate) {
18869
18914
  try {
18870
- const stat6 = statSync(candidate);
18871
- if (!stat6.isFile()) return false;
18915
+ const stat7 = statSync(candidate);
18916
+ if (!stat7.isFile()) return false;
18872
18917
  if (process.platform === "win32") return true;
18873
- return (stat6.mode & 73) !== 0;
18918
+ return (stat7.mode & 73) !== 0;
18874
18919
  } catch {
18875
18920
  return false;
18876
18921
  }
@@ -18959,8 +19004,8 @@ var ExternalNativePeerConnection = class {
18959
19004
  request(method, payload) {
18960
19005
  if (this.closed) return Promise.reject(new Error("native rtc helper is closed"));
18961
19006
  const id4 = this.nextRequestId++;
18962
- const promise = new Promise((resolve3, reject) => {
18963
- this.pending.set(id4, { resolve: resolve3, reject });
19007
+ const promise = new Promise((resolve4, reject) => {
19008
+ this.pending.set(id4, { resolve: resolve4, reject });
18964
19009
  });
18965
19010
  this.write({ id: id4, method, payload });
18966
19011
  return promise;
@@ -19754,10 +19799,10 @@ function parseRouteTarget(value) {
19754
19799
  async function detectRouteHostIpv4(target2, timeoutMs) {
19755
19800
  const socket = createSocket("udp4");
19756
19801
  try {
19757
- return await new Promise((resolve3, reject) => {
19802
+ return await new Promise((resolve4, reject) => {
19758
19803
  const timer = setTimeout(() => {
19759
19804
  cleanup();
19760
- resolve3(void 0);
19805
+ resolve4(void 0);
19761
19806
  }, timeoutMs);
19762
19807
  const cleanup = () => {
19763
19808
  clearTimeout(timer);
@@ -19771,7 +19816,7 @@ async function detectRouteHostIpv4(target2, timeoutMs) {
19771
19816
  socket.connect(target2.port, target2.host, () => {
19772
19817
  cleanup();
19773
19818
  const address = socket.address();
19774
- resolve3(typeof address === "string" ? void 0 : address.address);
19819
+ resolve4(typeof address === "string" ? void 0 : address.address);
19775
19820
  });
19776
19821
  });
19777
19822
  } finally {
@@ -20097,7 +20142,7 @@ var ClientModeRuntime = class {
20097
20142
  const virtual = CodexVirtualHarness.remote(remote.client, {
20098
20143
  deviceId: remote.target.deviceId,
20099
20144
  name: remote.target.name
20100
- }, harnessSessionGeneration(this.host?.localHarnessVersion?.()));
20145
+ }, harnessSessionGeneration(this.host?.localHarnessVersion?.()), new RemoteTypertGateway2(remote.client));
20101
20146
  let workspace;
20102
20147
  try {
20103
20148
  workspace = await virtual.selectWorkspace(workspaceId, signal);
@@ -20870,7 +20915,7 @@ async function probeRemoteHostFeatures(client, clientVersion) {
20870
20915
  }
20871
20916
  async function waitForCodexFrames(stream, signal) {
20872
20917
  if (signal?.aborted) throw new ClientModeError("RPC_ABORTED", "The Codex event poll was cancelled.");
20873
- await new Promise((resolve3, reject) => {
20918
+ await new Promise((resolve4, reject) => {
20874
20919
  const previousWake = stream.wake;
20875
20920
  const timer = setTimeout(done, 25e3);
20876
20921
  const onAbort = () => {
@@ -20884,7 +20929,7 @@ async function waitForCodexFrames(stream, signal) {
20884
20929
  }
20885
20930
  function done() {
20886
20931
  cleanup();
20887
- resolve3();
20932
+ resolve4();
20888
20933
  }
20889
20934
  stream.wake = () => {
20890
20935
  previousWake();
@@ -21608,6 +21653,13 @@ function redact(value, key = "") {
21608
21653
  return Object.fromEntries(Object.entries(value).map(([childKey, child]) => [childKey, redact(child, childKey)]));
21609
21654
  }
21610
21655
 
21656
+ // src/codex/session-id.ts
21657
+ function parseCodexSessionId(value) {
21658
+ if (typeof value !== "string") return void 0;
21659
+ const match = /^codex:([A-Za-z0-9][A-Za-z0-9._-]{0,255})$/.exec(value);
21660
+ return match === null ? void 0 : { sessionId: value, threadId: match[1] };
21661
+ }
21662
+
21611
21663
  // src/terminal-policy.ts
21612
21664
  var TERMINAL_CALLS = /* @__PURE__ */ new Set([
21613
21665
  "terminal/environment",
@@ -21620,7 +21672,7 @@ var TERMINAL_CALLS = /* @__PURE__ */ new Set([
21620
21672
  "terminal/close"
21621
21673
  ]);
21622
21674
  var TERMINAL_STREAMS = /* @__PURE__ */ new Set(["terminal/follow", "terminal/retain"]);
21623
- var id2 = external_exports.string().regex(/^[\w-]{1,128}$/);
21675
+ var id2 = external_exports.string().regex(/^[A-Za-z0-9_:-]{1,256}$/);
21624
21676
  var TerminalPolicy = class {
21625
21677
  constructor(enabled, deviceId, owners) {
21626
21678
  this.enabled = enabled;
@@ -21634,7 +21686,10 @@ var TerminalPolicy = class {
21634
21686
  "Remote terminal is disabled on this Host. Enable Remote terminal in the Host Remote settings; the switch saves and applies immediately. / \u8FDC\u7A0B\u7EC8\u7AEF\u672A\u5F00\u542F\uFF0C\u8BF7\u5728 Host \u7684 Remote \u8BBE\u7F6E\u4E2D\u5F00\u542F\u300C\u8FDC\u7A0B\u7EC8\u7AEF\u300D\uFF0C\u5F00\u5173\u5207\u6362\u540E\u7ACB\u5373\u4FDD\u5B58\u5E76\u751F\u6548\u3002"
21635
21687
  );
21636
21688
  const args = external_exports.object({ args: external_exports.record(external_exports.unknown()) }).strict().parse(payload).args;
21637
- const sessionId = id2.parse(args.agentId ?? args.sessionId);
21689
+ const rawSessionId = args.agentId ?? args.sessionId;
21690
+ const sessionId = id2.parse(rawSessionId);
21691
+ const codexSession = parseCodexSessionId(sessionId);
21692
+ void codexSession;
21638
21693
  if (endpoint === "terminal/environment" || endpoint === "terminal/shells" || endpoint === "terminal/list") return {};
21639
21694
  const request = endpoint === "terminal/create" ? external_exports.object({ id: id2 }).passthrough().parse(args.request) : void 0;
21640
21695
  const terminalId = id2.parse(request?.id ?? args.id);
@@ -22334,7 +22389,7 @@ var HostServerConnection = class {
22334
22389
  this.controlFrameLimits = {};
22335
22390
  let acknowledged = false;
22336
22391
  let messageQueue = Promise.resolve();
22337
- await new Promise((resolve3, reject) => {
22392
+ await new Promise((resolve4, reject) => {
22338
22393
  let settled = false;
22339
22394
  const helloTimer = setTimeout(() => socket.close(4001, "hello timeout"), 1e4);
22340
22395
  const finish = (error) => {
@@ -22343,7 +22398,7 @@ var HostServerConnection = class {
22343
22398
  clearTimeout(helloTimer);
22344
22399
  this.online = false;
22345
22400
  if (this.socket === socket) this.socket = void 0;
22346
- void this.dropTunnels().finally(() => error === void 0 ? resolve3() : reject(error));
22401
+ void this.dropTunnels().finally(() => error === void 0 ? resolve4() : reject(error));
22347
22402
  };
22348
22403
  socket.onopen = () => {
22349
22404
  this.sendControl("hello", {
@@ -22881,14 +22936,14 @@ var HostServerConnection = class {
22881
22936
  waitBeforeRetry(baseDelay) {
22882
22937
  const spread = baseDelay * this.config.reconnect.jitter;
22883
22938
  const delay = Math.max(0, Math.round(baseDelay - spread + Math.random() * spread * 2));
22884
- return new Promise((resolve3) => {
22939
+ return new Promise((resolve4) => {
22885
22940
  const timer = setTimeout(() => {
22886
22941
  this.retryWake = void 0;
22887
- resolve3();
22942
+ resolve4();
22888
22943
  }, delay);
22889
22944
  this.retryWake = () => {
22890
22945
  clearTimeout(timer);
22891
- resolve3();
22946
+ resolve4();
22892
22947
  };
22893
22948
  });
22894
22949
  }
@@ -23921,7 +23976,7 @@ function historyRequestPayload(payload, maxMessages) {
23921
23976
  return { ...payload, maxMessages };
23922
23977
  }
23923
23978
  function withTimeout(promise, ms, message) {
23924
- return new Promise((resolve3, reject) => {
23979
+ return new Promise((resolve4, reject) => {
23925
23980
  const timer = setTimeout(() => {
23926
23981
  reject(new RpcError("TIMEOUT", message, void 0, true));
23927
23982
  }, ms);
@@ -23929,7 +23984,7 @@ function withTimeout(promise, ms, message) {
23929
23984
  promise.then(
23930
23985
  (value) => {
23931
23986
  clearTimeout(timer);
23932
- resolve3(value);
23987
+ resolve4(value);
23933
23988
  },
23934
23989
  (error) => {
23935
23990
  clearTimeout(timer);
@@ -24032,6 +24087,7 @@ var HARNESS_REMOTE_ALLOWLIST = [
24032
24087
  "messageFeedback/list",
24033
24088
  "messageFeedback/put",
24034
24089
  "pluginInventory/list",
24090
+ "permissionPresets/catalog",
24035
24091
  "session/attachment",
24036
24092
  "session/cancel",
24037
24093
  "session/canOpenWorkspacePath",
@@ -24075,12 +24131,13 @@ var HARNESS_REMOTE_ALLOWLIST = [
24075
24131
  ];
24076
24132
  var allowedEndpoints = new Set(HARNESS_REMOTE_ALLOWLIST);
24077
24133
  var HarnessRemoteBridge = class {
24078
- constructor(gateway, publish, logger, harnessVersion, terminal = new TerminalPolicy(() => false, "", /* @__PURE__ */ new Map())) {
24134
+ constructor(gateway, publish, logger, harnessVersion, terminal = new TerminalPolicy(() => false, "", /* @__PURE__ */ new Map()), codexWorkspace) {
24079
24135
  this.gateway = gateway;
24080
24136
  this.publish = publish;
24081
24137
  this.logger = logger;
24082
24138
  this.harnessVersion = harnessVersion;
24083
24139
  this.terminal = terminal;
24140
+ this.codexWorkspace = codexWorkspace;
24084
24141
  }
24085
24142
  streams = /* @__PURE__ */ new Map();
24086
24143
  incomingTransfers = /* @__PURE__ */ new Map();
@@ -24096,6 +24153,18 @@ var HarnessRemoteBridge = class {
24096
24153
  }
24097
24154
  }
24098
24155
  const reservation = params.endpoint.startsWith("terminal/") ? this.terminal.check(params.endpoint, params.payload) : void 0;
24156
+ let codexResult;
24157
+ if (this.codexWorkspace !== void 0) {
24158
+ try {
24159
+ codexResult = await this.codexWorkspace.call(params.endpoint, params.payload, AbortSignal.timeout(6e4));
24160
+ } catch (error) {
24161
+ if (reservation !== void 0) {
24162
+ this.terminal.result(params.endpoint, params.payload, { ok: false, error: { code: "FAILED", message: "", details: {} } }, reservation);
24163
+ }
24164
+ throw error;
24165
+ }
24166
+ }
24167
+ if (codexResult !== void 0) return reservation === void 0 ? codexResult : this.terminal.result(params.endpoint, params.payload, codexResult, reservation);
24099
24168
  if (params.endpoint === "session/canOpenWorkspacePath") {
24100
24169
  return { ok: true, value: true };
24101
24170
  }
@@ -24248,7 +24317,7 @@ var HarnessRemoteBridge = class {
24248
24317
  this.streams.set(params.streamId, { controller });
24249
24318
  let source;
24250
24319
  try {
24251
- source = await this.gateway.open(params.endpoint, params.payload, controller.signal);
24320
+ source = (this.codexWorkspace === void 0 ? void 0 : await this.codexWorkspace.open(params.endpoint, params.payload, controller.signal)) ?? await this.gateway.open(params.endpoint, params.payload, controller.signal);
24252
24321
  controller.signal.throwIfAborted();
24253
24322
  } catch (error) {
24254
24323
  this.streams.delete(params.streamId);
@@ -24273,6 +24342,7 @@ var HarnessRemoteBridge = class {
24273
24342
  this.incomingTransfers.clear();
24274
24343
  this.outgoingTransfers.clear();
24275
24344
  for (const [, stream] of streams) stream.controller.abort(reason);
24345
+ await this.codexWorkspace?.closeAll();
24276
24346
  }
24277
24347
  assertAllowed(endpoint) {
24278
24348
  if (!allowedEndpoints.has(endpoint) && !TERMINAL_CALLS.has(endpoint) && !TERMINAL_STREAMS.has(endpoint)) {
@@ -24445,15 +24515,15 @@ var CodexAppServerClient = class {
24445
24515
  const child = this.process;
24446
24516
  this.process = void 0;
24447
24517
  if (child === void 0 || child.exitCode !== null || child.killed) return;
24448
- await new Promise((resolve3) => {
24518
+ await new Promise((resolve4) => {
24449
24519
  const timer = setTimeout(() => {
24450
24520
  child.kill("SIGKILL");
24451
- resolve3();
24521
+ resolve4();
24452
24522
  }, 2e3);
24453
24523
  timer.unref?.();
24454
24524
  child.once("exit", () => {
24455
24525
  clearTimeout(timer);
24456
- resolve3();
24526
+ resolve4();
24457
24527
  });
24458
24528
  child.kill("SIGTERM");
24459
24529
  });
@@ -24509,13 +24579,13 @@ var CodexAppServerClient = class {
24509
24579
  }
24510
24580
  request(method, params, timeoutMs) {
24511
24581
  const id4 = this.nextId++;
24512
- const result = new Promise((resolve3, reject) => {
24582
+ const result = new Promise((resolve4, reject) => {
24513
24583
  const timer = setTimeout(() => {
24514
24584
  this.pending.delete(id4);
24515
24585
  reject(new CodexAppServerError("CODEX_REQUEST_TIMEOUT", "Codex App Server request timed out."));
24516
24586
  }, timeoutMs);
24517
24587
  timer.unref?.();
24518
- this.pending.set(id4, { resolve: resolve3, reject, timer });
24588
+ this.pending.set(id4, { resolve: resolve4, reject, timer });
24519
24589
  });
24520
24590
  try {
24521
24591
  this.write({ id: id4, method, params });
@@ -25053,6 +25123,19 @@ var CodexRemoteDomain = class {
25053
25123
  ...this.unavailableCode === void 0 ? {} : { error: this.unavailableCode }
25054
25124
  };
25055
25125
  }
25126
+ /** Resolve a thread cwd for Host-owned workspace/terminal carriers. */
25127
+ async resolveThreadWorkspace(connectionId, threadId) {
25128
+ if (!this.peers.has(connectionId)) throw new RpcError("CODEX_THREAD_UNAVAILABLE", "The CodeX thread is not available on this connection.");
25129
+ try {
25130
+ const known = await this.readKnownThread(threadId);
25131
+ return known.cwd;
25132
+ } catch (error) {
25133
+ if (error instanceof RpcError && error.code === "CODEX_THREAD_NOT_ALLOWED") {
25134
+ throw new RpcError("CODEX_THREAD_UNAVAILABLE", "The CodeX thread is not available.");
25135
+ }
25136
+ throw new RpcError("CODEX_THREAD_UNAVAILABLE", "The CodeX thread is not available.");
25137
+ }
25138
+ }
25056
25139
  createPeer(context, publish) {
25057
25140
  if (!this.config.enabled) return void 0;
25058
25141
  const bridge = new CodexPeerBridge(this, context, publish, this.logger);
@@ -26127,7 +26210,7 @@ var StdioAcpAdapter = class {
26127
26210
  const c = this.ensure();
26128
26211
  const id4 = this.nextId++;
26129
26212
  c.stdin.write(JSON.stringify({ jsonrpc: "2.0", id: id4, method, params }) + "\n");
26130
- return new Promise((resolve3, reject) => {
26213
+ return new Promise((resolve4, reject) => {
26131
26214
  let buf = "";
26132
26215
  const onData = async (d) => {
26133
26216
  buf += d;
@@ -26138,7 +26221,7 @@ var StdioAcpAdapter = class {
26138
26221
  const m = JSON.parse(l);
26139
26222
  if (m.id === id4) {
26140
26223
  c.stdout.off("data", onData);
26141
- m.error ? reject(new Error(m.error.message ?? "ACP error")) : resolve3(m.result);
26224
+ m.error ? reject(new Error(m.error.message ?? "ACP error")) : resolve4(m.result);
26142
26225
  } else if (m.method && onNotification) await onNotification(m.params);
26143
26226
  } catch {
26144
26227
  }
@@ -26152,14 +26235,460 @@ var StdioAcpAdapter = class {
26152
26235
 
26153
26236
  // src/service.ts
26154
26237
  import { execFileSync as execFileSync2 } from "node:child_process";
26238
+
26239
+ // src/codex-workspace-bridge.ts
26240
+ import { spawn as spawn4 } from "node:child_process";
26241
+ import { realpath as realpath2, lstat, readdir as readdir3, readFile as readFile4, stat as stat5, watch } from "node:fs/promises";
26242
+ import { isAbsolute as isAbsolute4, join as join6, relative as relative2, resolve as resolve3 } from "node:path";
26243
+ var MAX_READ_BYTES = 4 * 1024 * 1024;
26244
+ var MAX_INPUT_BYTES = 64 * 1024;
26245
+ var MAX_COLS = 240;
26246
+ var MAX_ROWS = 100;
26247
+ var MAX_TERMINALS = 256;
26248
+ var MAX_SCREEN_BYTES = 256 * 1024;
26249
+ var TERMINAL_SCROLLBACK = 2e3;
26250
+ var TERMINAL_TYPE = "xterm-256color";
26251
+ var CodexWorkspaceState = class {
26252
+ terminals = /* @__PURE__ */ new Map();
26253
+ };
26254
+ var DEFAULT_SHELL = process.platform === "win32" ? { path: "cmd.exe", args: [], name: "Command Prompt" } : { path: "/bin/sh", args: [], name: "sh" };
26255
+ var AsyncQueue = class {
26256
+ values = [];
26257
+ waiters = [];
26258
+ ended = false;
26259
+ push(value) {
26260
+ if (this.ended) return;
26261
+ const waiter = this.waiters.shift();
26262
+ if (waiter) waiter({ done: false, value });
26263
+ else this.values.push(value);
26264
+ }
26265
+ end() {
26266
+ this.ended = true;
26267
+ while (this.waiters.length) this.waiters.shift()({ done: true, value: void 0 });
26268
+ }
26269
+ next() {
26270
+ const value = this.values.shift();
26271
+ if (value !== void 0) return Promise.resolve({ done: false, value });
26272
+ if (this.ended) return Promise.resolve({ done: true, value: void 0 });
26273
+ return new Promise((resolve4) => this.waiters.push(resolve4));
26274
+ }
26275
+ [Symbol.asyncIterator]() {
26276
+ return this;
26277
+ }
26278
+ };
26279
+ var CodexWorkspaceBridge = class {
26280
+ constructor(resolveCwd, terminalEnabled, state = new CodexWorkspaceState(), spawnTerminal = pipeTerminalSpawner()) {
26281
+ this.resolveCwd = resolveCwd;
26282
+ this.terminalEnabled = terminalEnabled;
26283
+ this.terminals = state.terminals;
26284
+ this.spawnTerminal = spawnTerminal;
26285
+ }
26286
+ terminals;
26287
+ ownedSubscribers = /* @__PURE__ */ new Set();
26288
+ spawnTerminal;
26289
+ isCodeXScope(value) {
26290
+ return parseCodexSessionId(value) !== void 0;
26291
+ }
26292
+ async call(endpoint, payload, signal) {
26293
+ const args = argsOf(payload);
26294
+ const scope = args.workspaceFileScopeId;
26295
+ const session = args.agentId ?? args.sessionId;
26296
+ const raw = scope ?? session;
26297
+ const codex = parseCodexSessionId(raw);
26298
+ if (codex === void 0) {
26299
+ if (typeof raw === "string" && raw.startsWith("codex:")) throw new RpcError("CODEX_SESSION_INVALID", "The CodeX session identifier is invalid.");
26300
+ return void 0;
26301
+ }
26302
+ if (endpoint.startsWith("workspaceFiles/")) return this.fileCall(endpoint, codex.sessionId, args, signal);
26303
+ if (endpoint.startsWith("terminal/")) return this.terminalCall(endpoint, codex.sessionId, args, signal);
26304
+ return void 0;
26305
+ }
26306
+ async open(endpoint, payload, signal) {
26307
+ const args = argsOf(payload);
26308
+ const raw = args.workspaceFileScopeId ?? args.agentId ?? args.sessionId;
26309
+ const codex = parseCodexSessionId(raw);
26310
+ if (codex === void 0) {
26311
+ if (typeof raw === "string" && raw.startsWith("codex:")) throw new RpcError("CODEX_SESSION_INVALID", "The CodeX session identifier is invalid.");
26312
+ return void 0;
26313
+ }
26314
+ if (endpoint === "workspaceFiles/changes") return this.watchChanges(codex.sessionId, args, signal);
26315
+ if (endpoint === "terminal/retain") return this.retain(codex.sessionId, args, signal);
26316
+ if (endpoint === "terminal/follow") return this.follow(codex.sessionId, args, signal);
26317
+ return void 0;
26318
+ }
26319
+ async closeAll() {
26320
+ for (const queue of this.ownedSubscribers) queue.end();
26321
+ this.ownedSubscribers.clear();
26322
+ }
26323
+ async fileCall(endpoint, sessionId, args, signal) {
26324
+ const root = await this.rootFor(sessionId, signal);
26325
+ const path = typeof args.path === "string" ? args.path : ".";
26326
+ const target2 = await this.safePath(root, path, endpoint === "workspaceFiles/list");
26327
+ try {
26328
+ if (endpoint === "workspaceFiles/list") {
26329
+ const entries = await readdir3(target2, { withFileTypes: true });
26330
+ const result = [];
26331
+ for (const entry of entries.slice(0, 500)) {
26332
+ const item = join6(target2, entry.name);
26333
+ const info2 = await lstat(item);
26334
+ if (info2.isSymbolicLink()) continue;
26335
+ result.push({ name: entry.name, type: info2.isDirectory() ? "directory" : info2.isFile() ? "file" : "other", ...info2.isFile() ? { size: info2.size } : {} });
26336
+ }
26337
+ return { ok: true, value: { path, entries: result, truncated: entries.length > 500 } };
26338
+ }
26339
+ const info = await stat5(target2);
26340
+ if (endpoint === "workspaceFiles/stat") return { ok: true, value: { path, type: info.isDirectory() ? "directory" : info.isFile() ? "file" : "other", size: info.size, modifiedAt: info.mtimeMs } };
26341
+ if (!info.isFile()) throw new RpcError("CODEX_WORKSPACE_INVALID_PATH", "The requested workspace path is not a file.");
26342
+ const offset = readOffset(args);
26343
+ const limit = readLimit(args);
26344
+ const bytes = await readFile4(target2);
26345
+ if (bytes.byteLength > MAX_READ_BYTES) throw new RpcError("CODEX_WORKSPACE_TOO_LARGE", "The requested workspace file is too large.");
26346
+ if (endpoint === "workspaceFiles/readBytes") {
26347
+ const slice2 = bytes.subarray(offset, Math.min(offset + limit, bytes.length));
26348
+ return { ok: true, value: { path, offset, bytes: slice2.toString("base64"), eof: offset + slice2.length >= bytes.length } };
26349
+ }
26350
+ const text = bytes.toString("utf8");
26351
+ const slice = text.slice(offset, offset + limit);
26352
+ return { ok: true, value: { absolutePath: target2, version: `${info.mtimeMs}:${info.size}`, text: slice, offset, lines: slice.split("\n").length, eof: offset + slice.length >= text.length } };
26353
+ } catch (error) {
26354
+ if (error instanceof RpcError) throw error;
26355
+ throw new RpcError("CODEX_WORKSPACE_UNAVAILABLE", "The CodeX workspace file is unavailable.");
26356
+ }
26357
+ }
26358
+ async watchChanges(sessionId, args, signal) {
26359
+ const root = await this.rootFor(sessionId, signal);
26360
+ const target2 = await this.safePath(root, typeof args.path === "string" ? args.path : ".", true);
26361
+ const queue = new AsyncQueue();
26362
+ const watcher = watch(target2, { recursive: false });
26363
+ const abort = () => {
26364
+ watcher.return?.();
26365
+ queue.end();
26366
+ };
26367
+ signal.addEventListener("abort", abort, { once: true });
26368
+ void (async () => {
26369
+ try {
26370
+ for await (const event of watcher) queue.push({ type: event.eventType, path: event.filename ?? "" });
26371
+ } catch {
26372
+ } finally {
26373
+ signal.removeEventListener("abort", abort);
26374
+ queue.end();
26375
+ }
26376
+ })();
26377
+ return queue;
26378
+ }
26379
+ /**
26380
+ * `terminal/retain` only acknowledges the retention window: it never takes
26381
+ * input ownership and never replays output. Recovery reads the next snapshot.
26382
+ */
26383
+ async retain(sessionId, args, signal) {
26384
+ this.assertTerminalEnabled();
26385
+ await this.rootFor(sessionId, signal);
26386
+ this.requireTerminal(sessionId, String(args.id));
26387
+ const queue = new AsyncQueue();
26388
+ queue.push({ type: "retained" });
26389
+ this.ownedSubscribers.add(queue);
26390
+ signal.addEventListener("abort", () => {
26391
+ this.ownedSubscribers.delete(queue);
26392
+ queue.end();
26393
+ }, { once: true });
26394
+ return queue;
26395
+ }
26396
+ /** `terminal/follow` takes input ownership and starts with a screen snapshot. */
26397
+ async follow(sessionId, args, signal) {
26398
+ this.assertTerminalEnabled();
26399
+ await this.rootFor(sessionId, signal);
26400
+ const attachmentId = typeof args.attachmentId === "string" && args.attachmentId.length > 0 ? args.attachmentId : void 0;
26401
+ if (attachmentId === void 0) throw new RpcError("INVALID_MESSAGE", "The terminal attachment is invalid.");
26402
+ const terminal = this.requireTerminal(sessionId, String(args.id));
26403
+ const queue = new AsyncQueue();
26404
+ if (terminal.controllerId !== attachmentId) {
26405
+ terminal.controllerId = attachmentId;
26406
+ this.emit(terminal, { type: "state", info: terminalInfo(terminal) });
26407
+ }
26408
+ queue.push({ type: "snapshot", sequence: terminal.sequence, screen: screenOf(terminal), info: terminalInfo(terminal) });
26409
+ terminal.subscribers.add(queue);
26410
+ this.ownedSubscribers.add(queue);
26411
+ signal.addEventListener("abort", () => {
26412
+ terminal.subscribers.delete(queue);
26413
+ this.ownedSubscribers.delete(queue);
26414
+ queue.end();
26415
+ if (terminal.controllerId === attachmentId) {
26416
+ terminal.controllerId = void 0;
26417
+ this.emit(terminal, { type: "state", info: terminalInfo(terminal) });
26418
+ }
26419
+ }, { once: true });
26420
+ return queue;
26421
+ }
26422
+ async terminalCall(endpoint, sessionId, args, signal) {
26423
+ this.assertTerminalEnabled();
26424
+ const cwd = await this.rootFor(sessionId, signal);
26425
+ if (endpoint === "terminal/environment") {
26426
+ return { ok: true, value: { cwd, maxInputBytes: MAX_INPUT_BYTES, maxCols: MAX_COLS, maxRows: MAX_ROWS, scrollback: TERMINAL_SCROLLBACK } };
26427
+ }
26428
+ if (endpoint === "terminal/shells") return { ok: true, value: [shellInfo(DEFAULT_SHELL)] };
26429
+ if (endpoint === "terminal/list") return { ok: true, value: [...this.terminals.values()].filter((item) => item.sessionId === sessionId).map(terminalInfo) };
26430
+ if (endpoint === "terminal/create") return this.createTerminal(sessionId, cwd, args);
26431
+ const id4 = stringId(args.id);
26432
+ const terminal = this.requireTerminal(sessionId, id4);
26433
+ if (endpoint === "terminal/write") {
26434
+ const data2 = typeof args.data === "string" ? args.data : "";
26435
+ if (Buffer.byteLength(data2) > MAX_INPUT_BYTES) throw new RpcError("INVALID_MESSAGE", "Terminal input is too large.");
26436
+ if (terminal.process === void 0) throw new RpcError("CODEX_TERMINAL_NOT_FOUND", "The CodeX terminal is no longer available.");
26437
+ await terminal.process.write(data2);
26438
+ return { ok: true };
26439
+ }
26440
+ if (endpoint === "terminal/resize") {
26441
+ terminal.cols = bounded(args.cols, terminal.cols, MAX_COLS);
26442
+ terminal.rows = bounded(args.rows, terminal.rows, MAX_ROWS);
26443
+ await terminal.process?.resize(terminal.cols, terminal.rows);
26444
+ return { ok: true };
26445
+ }
26446
+ if (endpoint === "terminal/rename") {
26447
+ if (typeof args.title === "string" && args.title.length > 0) terminal.title = args.title.slice(0, 128);
26448
+ return { ok: true };
26449
+ }
26450
+ if (endpoint === "terminal/close") {
26451
+ this.disposeTerminal(terminal);
26452
+ this.terminals.delete(`${sessionId}/${id4}`);
26453
+ return { ok: true };
26454
+ }
26455
+ throw new RpcError("METHOD_NOT_FOUND", "The requested terminal method does not exist.");
26456
+ }
26457
+ async createTerminal(sessionId, cwd, args) {
26458
+ const request = isRecord13(args.request) ? args.request : {};
26459
+ const id4 = stringId(request.id);
26460
+ if ([...this.terminals.values()].some((item) => item.sessionId === sessionId && item.id === id4)) throw new RpcError("REQUEST_CONFLICT", "The terminal id is already active.");
26461
+ if (this.terminals.size >= MAX_TERMINALS) throw new RpcError("RATE_LIMITED", "Too many remote terminals are active.", void 0, true);
26462
+ if (request.shellPath !== void 0 && request.shellPath !== DEFAULT_SHELL.path) throw new RpcError("INVALID_MESSAGE", "The requested shell is not available for this workspace.");
26463
+ const cols = bounded(request.cols, 80, MAX_COLS);
26464
+ const rows = bounded(request.rows, 24, MAX_ROWS);
26465
+ const terminal = {
26466
+ sessionId,
26467
+ id: id4,
26468
+ title: DEFAULT_SHELL.name,
26469
+ shell: DEFAULT_SHELL,
26470
+ cwd,
26471
+ cols,
26472
+ rows,
26473
+ sequence: 0,
26474
+ screen: [],
26475
+ screenBytes: 0,
26476
+ truncated: false,
26477
+ subscribers: /* @__PURE__ */ new Set(),
26478
+ state: "running",
26479
+ exitCode: null
26480
+ };
26481
+ this.terminals.set(`${sessionId}/${id4}`, terminal);
26482
+ try {
26483
+ terminal.process = await this.spawnTerminal({
26484
+ argv: [DEFAULT_SHELL.path, ...DEFAULT_SHELL.args],
26485
+ cwd,
26486
+ cols,
26487
+ rows,
26488
+ terminalType: TERMINAL_TYPE,
26489
+ env: { DSH_SESSION_ID: sessionId }
26490
+ });
26491
+ } catch {
26492
+ this.terminals.delete(`${sessionId}/${id4}`);
26493
+ throw new RpcError("CODEX_TERMINAL_UNAVAILABLE", "The CodeX terminal could not be started.");
26494
+ }
26495
+ void this.pump(terminal, terminal.process);
26496
+ return { ok: true, value: terminalInfo(terminal) };
26497
+ }
26498
+ /** Streams process output as ordered `output` frames, then reports the exit. */
26499
+ async pump(terminal, process2) {
26500
+ try {
26501
+ for await (const chunk of process2.output) {
26502
+ if (chunk.length === 0) continue;
26503
+ terminal.sequence += 1;
26504
+ this.appendScreen(terminal, chunk);
26505
+ this.emit(terminal, { type: "output", sequence: terminal.sequence, data: chunk });
26506
+ }
26507
+ } catch {
26508
+ terminal.state = "failed";
26509
+ terminal.error = "The terminal output stream failed.";
26510
+ }
26511
+ const outcome = await process2.completed;
26512
+ terminal.exitCode = outcome.exitCode;
26513
+ if (terminal.state === "running") terminal.state = "exited";
26514
+ this.emit(terminal, { type: "state", info: terminalInfo(terminal) });
26515
+ this.endSubscribers(terminal);
26516
+ }
26517
+ appendScreen(terminal, chunk) {
26518
+ terminal.screen.push(chunk);
26519
+ terminal.screenBytes += Buffer.byteLength(chunk);
26520
+ while (terminal.screenBytes > MAX_SCREEN_BYTES && terminal.screen.length > 1) {
26521
+ terminal.screenBytes -= Buffer.byteLength(terminal.screen.shift());
26522
+ terminal.truncated = true;
26523
+ }
26524
+ }
26525
+ async rootFor(sessionId, signal) {
26526
+ const parsed = parseCodexSessionId(sessionId);
26527
+ if (!parsed) throw new RpcError("CODEX_SESSION_INVALID", "The CodeX session identifier is invalid.");
26528
+ const cwd = await this.resolveCwd(parsed.threadId, signal);
26529
+ if (!cwd) throw new RpcError("CODEX_WORKSPACE_UNAVAILABLE", "The CodeX thread has no available workspace.");
26530
+ try {
26531
+ const root = await realpath2(cwd);
26532
+ const info = await stat5(root);
26533
+ if (!info.isDirectory()) throw new Error();
26534
+ return root;
26535
+ } catch {
26536
+ throw new RpcError("CODEX_WORKSPACE_UNAVAILABLE", "The CodeX workspace is unavailable.");
26537
+ }
26538
+ }
26539
+ async safePath(root, path, directory) {
26540
+ if (isAbsolute4(path)) throw new RpcError("CODEX_WORKSPACE_PATH_DENIED", "The requested workspace path is outside the CodeX workspace.");
26541
+ const candidate = resolve3(root, path);
26542
+ const rel = relative2(root, candidate);
26543
+ if (rel.startsWith("..") || isAbsolute4(rel)) throw new RpcError("CODEX_WORKSPACE_PATH_DENIED", "The requested workspace path is outside the CodeX workspace.");
26544
+ try {
26545
+ const info = await lstat(candidate);
26546
+ if (info.isSymbolicLink()) throw new Error();
26547
+ const canonical = await realpath2(candidate);
26548
+ const canonicalRel = relative2(root, canonical);
26549
+ if (canonicalRel.startsWith("..") || isAbsolute4(canonicalRel)) throw new Error();
26550
+ if (directory && !info.isDirectory()) throw new Error();
26551
+ return canonical;
26552
+ } catch {
26553
+ throw new RpcError("CODEX_WORKSPACE_PATH_DENIED", "The requested workspace path is unavailable.");
26554
+ }
26555
+ }
26556
+ emit(terminal, value) {
26557
+ for (const subscriber of terminal.subscribers) subscriber.push(value);
26558
+ }
26559
+ endSubscribers(terminal) {
26560
+ for (const subscriber of terminal.subscribers) subscriber.end();
26561
+ terminal.subscribers.clear();
26562
+ }
26563
+ requireTerminal(sessionId, id4) {
26564
+ const terminal = this.terminals.get(`${sessionId}/${id4}`);
26565
+ if (!terminal) throw new RpcError("CODEX_TERMINAL_NOT_FOUND", "The CodeX terminal is no longer available.");
26566
+ return terminal;
26567
+ }
26568
+ assertTerminalEnabled() {
26569
+ if (!this.terminalEnabled()) throw new RpcError("TERMINAL_DISABLED", "Remote terminal is disabled on this Host.");
26570
+ }
26571
+ disposeTerminal(terminal) {
26572
+ void Promise.resolve(terminal.process?.terminate()).catch(() => void 0);
26573
+ if (terminal.state === "running") {
26574
+ terminal.state = "exited";
26575
+ this.emit(terminal, { type: "state", info: terminalInfo(terminal) });
26576
+ }
26577
+ this.endSubscribers(terminal);
26578
+ }
26579
+ };
26580
+ function pipeTerminalSpawner() {
26581
+ return async (spec) => {
26582
+ const child = spawn4(spec.argv[0], spec.argv.slice(1), {
26583
+ cwd: spec.cwd,
26584
+ stdio: "pipe",
26585
+ windowsHide: true,
26586
+ env: { ...process.env, ...spec.env }
26587
+ });
26588
+ const queue = new AsyncQueue();
26589
+ child.stdout.on("data", (data2) => queue.push(Buffer.from(data2).toString("utf8")));
26590
+ child.stderr.on("data", (data2) => queue.push(Buffer.from(data2).toString("utf8")));
26591
+ child.on("error", () => queue.end());
26592
+ const completed = new Promise((resolve4) => {
26593
+ child.on("error", () => {
26594
+ queue.end();
26595
+ resolve4({ exitCode: null });
26596
+ });
26597
+ child.on("exit", (code) => {
26598
+ queue.end();
26599
+ resolve4({ exitCode: code });
26600
+ });
26601
+ });
26602
+ return {
26603
+ output: queue,
26604
+ write: (data2) => {
26605
+ child.stdin.write(data2);
26606
+ },
26607
+ resize: () => void 0,
26608
+ terminate: () => {
26609
+ if (!child.killed) child.kill();
26610
+ },
26611
+ completed
26612
+ };
26613
+ };
26614
+ }
26615
+ function subprocessTerminalSpawner(subprocess) {
26616
+ return async (spec) => {
26617
+ const handle = await subprocess.spawnTerminal({
26618
+ argv: [...spec.argv],
26619
+ cwd: spec.cwd,
26620
+ cols: spec.cols,
26621
+ rows: spec.rows,
26622
+ terminalType: spec.terminalType,
26623
+ env: spec.env
26624
+ });
26625
+ const queue = new AsyncQueue();
26626
+ handle.output.setEncoding?.("utf8");
26627
+ handle.output.on("data", (chunk) => queue.push(typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8")));
26628
+ handle.output.once("close", () => queue.end());
26629
+ handle.output.once("error", () => queue.end());
26630
+ return {
26631
+ output: queue,
26632
+ write: (data2) => handle.write(data2),
26633
+ resize: (cols, rows) => handle.resize(cols, rows),
26634
+ terminate: () => handle.terminate(),
26635
+ completed: handle.done.then((outcome) => ({ exitCode: typeof outcome?.exitCode === "number" ? outcome.exitCode : null })).catch(() => ({ exitCode: null }))
26636
+ };
26637
+ };
26638
+ }
26639
+ function shellInfo(shell) {
26640
+ return { path: shell.path, args: [...shell.args], name: shell.name };
26641
+ }
26642
+ function terminalInfo(terminal) {
26643
+ return {
26644
+ id: terminal.id,
26645
+ title: terminal.title,
26646
+ shell: shellInfo(terminal.shell),
26647
+ cwd: terminal.cwd,
26648
+ cols: terminal.cols,
26649
+ rows: terminal.rows,
26650
+ state: terminal.state,
26651
+ exitCode: terminal.exitCode,
26652
+ ...terminal.error === void 0 ? {} : { error: terminal.error },
26653
+ ...terminal.controllerId === void 0 ? {} : { controllerId: terminal.controllerId }
26654
+ };
26655
+ }
26656
+ function screenOf(terminal) {
26657
+ return (terminal.truncated ? "\x1Bc" : "") + terminal.screen.join("");
26658
+ }
26659
+ function argsOf(payload) {
26660
+ const value = isRecord13(payload) ? payload : {};
26661
+ return isRecord13(value.args) ? value.args : value;
26662
+ }
26663
+ function isRecord13(value) {
26664
+ return typeof value === "object" && value !== null && !Array.isArray(value);
26665
+ }
26666
+ function stringId(value) {
26667
+ if (typeof value !== "string" || !/^[A-Za-z0-9_.:-]{1,128}$/.test(value)) throw new RpcError("INVALID_MESSAGE", "The terminal identifier is invalid.");
26668
+ return value;
26669
+ }
26670
+ function bounded(value, fallback, max) {
26671
+ return typeof value === "number" && Number.isInteger(value) && value > 0 ? Math.min(value, max) : fallback;
26672
+ }
26673
+ function readOffset(args) {
26674
+ const range = isRecord13(args.range) ? args.range : args;
26675
+ return typeof range.offset === "number" && Number.isInteger(range.offset) && range.offset >= 0 ? range.offset : 0;
26676
+ }
26677
+ function readLimit(args) {
26678
+ const range = isRecord13(args.range) ? args.range : args;
26679
+ return typeof range.limit === "number" && Number.isInteger(range.limit) && range.limit > 0 ? Math.min(range.limit, MAX_READ_BYTES) : MAX_READ_BYTES;
26680
+ }
26681
+
26682
+ // src/service.ts
26155
26683
  var HostPluginRuntime = class {
26156
- constructor(config, identities, apiProxy, logger, localGateway, fileViewerHost) {
26684
+ constructor(config, identities, apiProxy, logger, localGateway, fileViewerHost, terminalSpawner) {
26157
26685
  this.config = config;
26158
26686
  this.identities = identities;
26159
26687
  this.apiProxy = apiProxy;
26160
26688
  this.logger = logger;
26161
26689
  this.localGateway = localGateway;
26162
26690
  this.fileViewerHost = fileViewerHost;
26691
+ this.terminalSpawner = terminalSpawner;
26163
26692
  this.terminalEnabled = config.terminal.enabled;
26164
26693
  this.loopback = new LoopbackHost(config.loopback.ports);
26165
26694
  this.codex = new CodexRemoteDomain(config.codex, logger);
@@ -26177,7 +26706,13 @@ var HostPluginRuntime = class {
26177
26706
  (event, data2) => send(createEvent(event, data2)),
26178
26707
  this.logger,
26179
26708
  this.harnessVersion,
26180
- new TerminalPolicy(() => this.terminalEnabled, context.peerDeviceId, this.terminalOwners)
26709
+ new TerminalPolicy(() => this.terminalEnabled, context.peerDeviceId, this.terminalOwners),
26710
+ new CodexWorkspaceBridge(
26711
+ (threadId, signal) => this.codex.resolveThreadWorkspace(context.connectionId, threadId),
26712
+ () => this.terminalEnabled,
26713
+ this.codexWorkspaceState,
26714
+ this.terminalSpawner
26715
+ )
26181
26716
  ) : void 0;
26182
26717
  const fileViewer = new RemoteFileViewerBridge(
26183
26718
  () => this.fileViewerHost?.(),
@@ -26215,6 +26750,7 @@ var HostPluginRuntime = class {
26215
26750
  harnessVersion;
26216
26751
  closed = false;
26217
26752
  codex;
26753
+ codexWorkspaceState = new CodexWorkspaceState();
26218
26754
  localCodexPeer;
26219
26755
  localCodexPublish = async () => void 0;
26220
26756
  setTerminalEnabled(enabled) {
@@ -26498,9 +27034,9 @@ function isPlainRecord(value) {
26498
27034
  }
26499
27035
 
26500
27036
  // src/cli.ts
26501
- import { stat as stat5 } from "node:fs/promises";
27037
+ import { stat as stat6 } from "node:fs/promises";
26502
27038
  import { hostname as hostname3 } from "node:os";
26503
- import { join as join6 } from "node:path";
27039
+ import { join as join7 } from "node:path";
26504
27040
  var QR_POLL_INTERVAL_MS = 2e3;
26505
27041
  var TERMINAL_QR_MARGIN = 4;
26506
27042
  async function runCli(args = process.argv.slice(2), dependencies = {}) {
@@ -26577,7 +27113,7 @@ async function status(args, runtime) {
26577
27113
  `Server: ${serverUrl}`,
26578
27114
  "Host control: enabled (dsh-TUI default)"
26579
27115
  ];
26580
- if (!await exists3(join6(directory, "device.json"))) {
27116
+ if (!await exists3(join7(directory, "device.json"))) {
26581
27117
  lines.push("Device: not initialized", "Authorization: logged out", "Credential: unavailable");
26582
27118
  write(runtime.stdout, `${lines.join("\n")}
26583
27119
 
@@ -26621,7 +27157,7 @@ async function logout(args, runtime) {
26621
27157
  const serverUrl = selectedServer();
26622
27158
  const root = new IdentityStore({ env: runtime.env }).directory;
26623
27159
  const directory = serverStorageDirectory(root, serverUrl, "host");
26624
- if (!await exists3(join6(directory, "device.json"))) {
27160
+ if (!await exists3(join7(directory, "device.json"))) {
26625
27161
  await new ServerCredentialStore(directory).clear();
26626
27162
  write(runtime.stdout, "This Host is already logged out.\n");
26627
27163
  return 0;
@@ -26663,7 +27199,7 @@ function resolveDependencies(input2) {
26663
27199
  stdout: input2.stdout ?? process.stdout,
26664
27200
  stderr: input2.stderr ?? process.stderr,
26665
27201
  now: input2.now ?? Date.now,
26666
- wait: input2.wait ?? ((milliseconds) => new Promise((resolve3) => setTimeout(resolve3, milliseconds))),
27202
+ wait: input2.wait ?? ((milliseconds) => new Promise((resolve4) => setTimeout(resolve4, milliseconds))),
26667
27203
  renderQr: input2.renderQr ?? renderTerminalQr,
26668
27204
  createIdentityStore: input2.createIdentityStore ?? ((options) => new IdentityStore(options)),
26669
27205
  createHostApi: input2.createHostApi ?? ((serverUrl, store) => new HostServerApi(serverUrl, store))
@@ -26749,7 +27285,7 @@ function terminalLink(url, target2) {
26749
27285
  }
26750
27286
  async function exists3(path) {
26751
27287
  try {
26752
- await stat5(path);
27288
+ await stat6(path);
26753
27289
  return true;
26754
27290
  } catch (error) {
26755
27291
  if (error instanceof Error && "code" in error && error.code === "ENOENT") return false;
@@ -27108,7 +27644,7 @@ function errorCode4(error) {
27108
27644
  return "CONNECTION_FAILED";
27109
27645
  }
27110
27646
  function wait(milliseconds) {
27111
- return new Promise((resolve3) => setTimeout(resolve3, milliseconds));
27647
+ return new Promise((resolve4) => setTimeout(resolve4, milliseconds));
27112
27648
  }
27113
27649
 
27114
27650
  // src/remote-file-content-provider.ts
@@ -27277,13 +27813,15 @@ async function activate(ctx, input2, tuiBinding) {
27277
27813
  const apiProxy = ctx.get("apiProxy");
27278
27814
  const nativeTypertGateway = ctx.get("typertGateway");
27279
27815
  const localTypertGateway = new TypertGatewaySwitch(nativeTypertGateway).local();
27816
+ const subprocess = ctx.get("subprocess", false);
27280
27817
  const runtime = new HostPluginRuntime(
27281
27818
  config,
27282
27819
  hostIdentities,
27283
27820
  apiProxy,
27284
27821
  logger,
27285
27822
  localTypertGateway,
27286
- () => ctx.get("fileViewerHost")
27823
+ () => ctx.get("fileViewerHost"),
27824
+ subprocess === void 0 ? void 0 : subprocessTerminalSpawner(subprocess)
27287
27825
  );
27288
27826
  let clientRuntime;
27289
27827
  const hostControl = runtime;