ds-harness-remote 0.4.14 → 0.4.15

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 (82) hide show
  1. package/README.md +2 -2
  2. package/dist/client-runtime.d.ts +5 -0
  3. package/dist/client-runtime.d.ts.map +1 -1
  4. package/dist/client-runtime.js +24 -0
  5. package/dist/client-runtime.js.map +1 -1
  6. package/dist/client-secure-transport.d.ts +1 -0
  7. package/dist/client-secure-transport.d.ts.map +1 -1
  8. package/dist/client-secure-transport.js +12 -5
  9. package/dist/client-secure-transport.js.map +1 -1
  10. package/dist/client.d.ts.map +1 -1
  11. package/dist/client.github.js +259 -76
  12. package/dist/client.js +259 -76
  13. package/dist/client.js.map +1 -1
  14. package/dist/config.d.ts +12 -0
  15. package/dist/config.d.ts.map +1 -1
  16. package/dist/config.js +6 -0
  17. package/dist/config.js.map +1 -1
  18. package/dist/control-route.d.ts +10 -1
  19. package/dist/control-route.d.ts.map +1 -1
  20. package/dist/control-route.js +17 -4
  21. package/dist/control-route.js.map +1 -1
  22. package/dist/control-runtime.d.ts +8 -1
  23. package/dist/control-runtime.d.ts.map +1 -1
  24. package/dist/control-runtime.js +40 -1
  25. package/dist/control-runtime.js.map +1 -1
  26. package/dist/control-stream.d.ts +67 -0
  27. package/dist/control-stream.d.ts.map +1 -0
  28. package/dist/control-stream.js +179 -0
  29. package/dist/control-stream.js.map +1 -0
  30. package/dist/harness-api-bridge.js +3 -0
  31. package/dist/harness-api-bridge.js.map +1 -1
  32. package/dist/harness-remote-bridge.d.ts +4 -2
  33. package/dist/harness-remote-bridge.d.ts.map +1 -1
  34. package/dist/harness-remote-bridge.js +42 -4
  35. package/dist/harness-remote-bridge.js.map +1 -1
  36. package/dist/index.js +1559 -580
  37. package/dist/index.js.map +4 -4
  38. package/dist/loopback-host.d.ts +20 -0
  39. package/dist/loopback-host.d.ts.map +1 -0
  40. package/dist/loopback-host.js +220 -0
  41. package/dist/loopback-host.js.map +1 -0
  42. package/dist/loopback-preview.d.ts +19 -0
  43. package/dist/loopback-preview.d.ts.map +1 -0
  44. package/dist/loopback-preview.js +232 -0
  45. package/dist/loopback-preview.js.map +1 -0
  46. package/dist/remote-typert-gateway.js +16 -4
  47. package/dist/remote-typert-gateway.js.map +1 -1
  48. package/dist/rpc-router.d.ts +3 -1
  49. package/dist/rpc-router.d.ts.map +1 -1
  50. package/dist/rpc-router.js +10 -1
  51. package/dist/rpc-router.js.map +1 -1
  52. package/dist/server-api.d.ts +6 -3
  53. package/dist/server-api.d.ts.map +1 -1
  54. package/dist/server-api.js +50 -26
  55. package/dist/server-api.js.map +1 -1
  56. package/dist/server-connection.d.ts +1 -0
  57. package/dist/server-connection.d.ts.map +1 -1
  58. package/dist/server-connection.js +54 -7
  59. package/dist/server-connection.js.map +1 -1
  60. package/dist/server-credentials.d.ts +9 -0
  61. package/dist/server-credentials.d.ts.map +1 -1
  62. package/dist/server-credentials.js +34 -0
  63. package/dist/server-credentials.js.map +1 -1
  64. package/dist/service.d.ts +5 -0
  65. package/dist/service.d.ts.map +1 -1
  66. package/dist/service.js +19 -2
  67. package/dist/service.js.map +1 -1
  68. package/dist/status-stream.d.ts +48 -0
  69. package/dist/status-stream.d.ts.map +1 -0
  70. package/dist/status-stream.js +133 -0
  71. package/dist/status-stream.js.map +1 -0
  72. package/dist/terminal-policy.d.ts +21 -0
  73. package/dist/terminal-policy.d.ts.map +1 -0
  74. package/dist/terminal-policy.js +62 -0
  75. package/dist/terminal-policy.js.map +1 -0
  76. package/dist/tui-command.js +6 -0
  77. package/dist/tui-command.js.map +1 -1
  78. package/dist/version.d.ts +1 -1
  79. package/dist/version.js +1 -1
  80. package/dsh-plugin.json +1 -1
  81. package/package.json +36 -16
  82. package/public.d.ts +2 -0
package/dist/index.js CHANGED
@@ -1,9 +1,14 @@
1
1
  var __defProp = Object.defineProperty;
2
- var __export = (target, all) => {
2
+ var __export = (target2, all) => {
3
3
  for (var name2 in all)
4
- __defProp(target, name2, { get: all[name2], enumerable: true });
4
+ __defProp(target2, name2, { get: all[name2], enumerable: true });
5
5
  };
6
6
 
7
+ // src/loopback-preview.ts
8
+ import { randomBytes, randomUUID } from "node:crypto";
9
+ import { createServer } from "node:http";
10
+ import { WebSocketServer } from "ws";
11
+
7
12
  // ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.js
8
13
  var external_exports = {};
9
14
  __export(external_exports, {
@@ -208,15 +213,15 @@ var ZodParsedType = util.arrayToEnum([
208
213
  "map",
209
214
  "set"
210
215
  ]);
211
- var getParsedType = (data) => {
212
- const t = typeof data;
216
+ var getParsedType = (data2) => {
217
+ const t = typeof data2;
213
218
  switch (t) {
214
219
  case "undefined":
215
220
  return ZodParsedType.undefined;
216
221
  case "string":
217
222
  return ZodParsedType.string;
218
223
  case "number":
219
- return Number.isNaN(data) ? ZodParsedType.nan : ZodParsedType.number;
224
+ return Number.isNaN(data2) ? ZodParsedType.nan : ZodParsedType.number;
220
225
  case "boolean":
221
226
  return ZodParsedType.boolean;
222
227
  case "function":
@@ -226,22 +231,22 @@ var getParsedType = (data) => {
226
231
  case "symbol":
227
232
  return ZodParsedType.symbol;
228
233
  case "object":
229
- if (Array.isArray(data)) {
234
+ if (Array.isArray(data2)) {
230
235
  return ZodParsedType.array;
231
236
  }
232
- if (data === null) {
237
+ if (data2 === null) {
233
238
  return ZodParsedType.null;
234
239
  }
235
- if (data.then && typeof data.then === "function" && data.catch && typeof data.catch === "function") {
240
+ if (data2.then && typeof data2.then === "function" && data2.catch && typeof data2.catch === "function") {
236
241
  return ZodParsedType.promise;
237
242
  }
238
- if (typeof Map !== "undefined" && data instanceof Map) {
243
+ if (typeof Map !== "undefined" && data2 instanceof Map) {
239
244
  return ZodParsedType.map;
240
245
  }
241
- if (typeof Set !== "undefined" && data instanceof Set) {
246
+ if (typeof Set !== "undefined" && data2 instanceof Set) {
242
247
  return ZodParsedType.set;
243
248
  }
244
- if (typeof Date !== "undefined" && data instanceof Date) {
249
+ if (typeof Date !== "undefined" && data2 instanceof Date) {
245
250
  return ZodParsedType.date;
246
251
  }
247
252
  return ZodParsedType.object;
@@ -482,7 +487,7 @@ function getErrorMap() {
482
487
 
483
488
  // ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js
484
489
  var makeIssue = (params) => {
485
- const { data, path, errorMaps, issueData } = params;
490
+ const { data: data2, path, errorMaps, issueData } = params;
486
491
  const fullPath = [...path, ...issueData.path || []];
487
492
  const fullIssue = {
488
493
  ...issueData,
@@ -498,7 +503,7 @@ var makeIssue = (params) => {
498
503
  let errorMessage = "";
499
504
  const maps = errorMaps.filter((m) => !!m).slice().reverse();
500
505
  for (const map of maps) {
501
- errorMessage = map(fullIssue, { data, defaultError: errorMessage }).message;
506
+ errorMessage = map(fullIssue, { data: data2, defaultError: errorMessage }).message;
502
507
  }
503
508
  return {
504
509
  ...issueData,
@@ -700,13 +705,13 @@ var ZodType = class {
700
705
  const result = this._parse(input2);
701
706
  return Promise.resolve(result);
702
707
  }
703
- parse(data, params) {
704
- const result = this.safeParse(data, params);
708
+ parse(data2, params) {
709
+ const result = this.safeParse(data2, params);
705
710
  if (result.success)
706
711
  return result.data;
707
712
  throw result.error;
708
713
  }
709
- safeParse(data, params) {
714
+ safeParse(data2, params) {
710
715
  const ctx = {
711
716
  common: {
712
717
  issues: [],
@@ -716,13 +721,13 @@ var ZodType = class {
716
721
  path: params?.path || [],
717
722
  schemaErrorMap: this._def.errorMap,
718
723
  parent: null,
719
- data,
720
- parsedType: getParsedType(data)
724
+ data: data2,
725
+ parsedType: getParsedType(data2)
721
726
  };
722
- const result = this._parseSync({ data, path: ctx.path, parent: ctx });
727
+ const result = this._parseSync({ data: data2, path: ctx.path, parent: ctx });
723
728
  return handleResult(ctx, result);
724
729
  }
725
- "~validate"(data) {
730
+ "~validate"(data2) {
726
731
  const ctx = {
727
732
  common: {
728
733
  issues: [],
@@ -731,12 +736,12 @@ var ZodType = class {
731
736
  path: [],
732
737
  schemaErrorMap: this._def.errorMap,
733
738
  parent: null,
734
- data,
735
- parsedType: getParsedType(data)
739
+ data: data2,
740
+ parsedType: getParsedType(data2)
736
741
  };
737
742
  if (!this["~standard"].async) {
738
743
  try {
739
- const result = this._parseSync({ data, path: [], parent: ctx });
744
+ const result = this._parseSync({ data: data2, path: [], parent: ctx });
740
745
  return isValid(result) ? {
741
746
  value: result.value
742
747
  } : {
@@ -752,19 +757,19 @@ var ZodType = class {
752
757
  };
753
758
  }
754
759
  }
755
- return this._parseAsync({ data, path: [], parent: ctx }).then((result) => isValid(result) ? {
760
+ return this._parseAsync({ data: data2, path: [], parent: ctx }).then((result) => isValid(result) ? {
756
761
  value: result.value
757
762
  } : {
758
763
  issues: ctx.common.issues
759
764
  });
760
765
  }
761
- async parseAsync(data, params) {
762
- const result = await this.safeParseAsync(data, params);
766
+ async parseAsync(data2, params) {
767
+ const result = await this.safeParseAsync(data2, params);
763
768
  if (result.success)
764
769
  return result.data;
765
770
  throw result.error;
766
771
  }
767
- async safeParseAsync(data, params) {
772
+ async safeParseAsync(data2, params) {
768
773
  const ctx = {
769
774
  common: {
770
775
  issues: [],
@@ -774,10 +779,10 @@ var ZodType = class {
774
779
  path: params?.path || [],
775
780
  schemaErrorMap: this._def.errorMap,
776
781
  parent: null,
777
- data,
778
- parsedType: getParsedType(data)
782
+ data: data2,
783
+ parsedType: getParsedType(data2)
779
784
  };
780
- const maybeAsyncResult = this._parse({ data, path: ctx.path, parent: ctx });
785
+ const maybeAsyncResult = this._parse({ data: data2, path: ctx.path, parent: ctx });
781
786
  const result = await (isAsync(maybeAsyncResult) ? maybeAsyncResult : Promise.resolve(maybeAsyncResult));
782
787
  return handleResult(ctx, result);
783
788
  }
@@ -798,8 +803,8 @@ var ZodType = class {
798
803
  ...getIssueProperties(val)
799
804
  });
800
805
  if (typeof Promise !== "undefined" && result instanceof Promise) {
801
- return result.then((data) => {
802
- if (!data) {
806
+ return result.then((data2) => {
807
+ if (!data2) {
803
808
  setError();
804
809
  return false;
805
810
  } else {
@@ -865,7 +870,7 @@ var ZodType = class {
865
870
  this["~standard"] = {
866
871
  version: 1,
867
872
  vendor: "zod",
868
- validate: (data) => this["~validate"](data)
873
+ validate: (data2) => this["~validate"](data2)
869
874
  };
870
875
  }
871
876
  optional() {
@@ -929,8 +934,8 @@ var ZodType = class {
929
934
  description
930
935
  });
931
936
  }
932
- pipe(target) {
933
- return ZodPipeline.create(this, target);
937
+ pipe(target2) {
938
+ return ZodPipeline.create(this, target2);
934
939
  }
935
940
  readonly() {
936
941
  return ZodReadonly.create(this);
@@ -1325,7 +1330,7 @@ var ZodString = class _ZodString extends ZodType {
1325
1330
  return { status: status2.value, value: input2.data };
1326
1331
  }
1327
1332
  _regex(regex, validation, message) {
1328
- return this.refinement((data) => regex.test(data), {
1333
+ return this.refinement((data2) => regex.test(data2), {
1329
1334
  validation,
1330
1335
  code: ZodIssueCode.invalid_string,
1331
1336
  ...errorUtil.errToObj(message)
@@ -3541,8 +3546,8 @@ var ZodPromise = class extends ZodType {
3541
3546
  return INVALID;
3542
3547
  }
3543
3548
  const promisified = ctx.parsedType === ZodParsedType.promise ? ctx.data : Promise.resolve(ctx.data);
3544
- return OK(promisified.then((data) => {
3545
- return this._def.type.parseAsync(data, {
3549
+ return OK(promisified.then((data2) => {
3550
+ return this._def.type.parseAsync(data2, {
3546
3551
  path: ctx.path,
3547
3552
  errorMap: ctx.common.contextualErrorMap
3548
3553
  });
@@ -3736,12 +3741,12 @@ ZodNullable.create = (type, params) => {
3736
3741
  var ZodDefault = class extends ZodType {
3737
3742
  _parse(input2) {
3738
3743
  const { ctx } = this._processInputParams(input2);
3739
- let data = ctx.data;
3744
+ let data2 = ctx.data;
3740
3745
  if (ctx.parsedType === ZodParsedType.undefined) {
3741
- data = this._def.defaultValue();
3746
+ data2 = this._def.defaultValue();
3742
3747
  }
3743
3748
  return this._def.innerType._parse({
3744
- data,
3749
+ data: data2,
3745
3750
  path: ctx.path,
3746
3751
  parent: ctx
3747
3752
  });
@@ -3836,9 +3841,9 @@ var BRAND = Symbol("zod_brand");
3836
3841
  var ZodBranded = class extends ZodType {
3837
3842
  _parse(input2) {
3838
3843
  const { ctx } = this._processInputParams(input2);
3839
- const data = ctx.data;
3844
+ const data2 = ctx.data;
3840
3845
  return this._def.type._parse({
3841
- data,
3846
+ data: data2,
3842
3847
  path: ctx.path,
3843
3848
  parent: ctx
3844
3849
  });
@@ -3905,13 +3910,13 @@ var ZodPipeline = class _ZodPipeline extends ZodType {
3905
3910
  var ZodReadonly = class extends ZodType {
3906
3911
  _parse(input2) {
3907
3912
  const result = this._def.innerType._parse(input2);
3908
- const freeze = (data) => {
3909
- if (isValid(data)) {
3910
- data.value = Object.freeze(data.value);
3913
+ const freeze = (data2) => {
3914
+ if (isValid(data2)) {
3915
+ data2.value = Object.freeze(data2.value);
3911
3916
  }
3912
- return data;
3917
+ return data2;
3913
3918
  };
3914
- return isAsync(result) ? result.then((data) => freeze(data)) : freeze(result);
3919
+ return isAsync(result) ? result.then((data2) => freeze(data2)) : freeze(result);
3915
3920
  }
3916
3921
  unwrap() {
3917
3922
  return this._def.innerType;
@@ -3924,26 +3929,26 @@ ZodReadonly.create = (type, params) => {
3924
3929
  ...processCreateParams(params)
3925
3930
  });
3926
3931
  };
3927
- function cleanParams(params, data) {
3928
- const p = typeof params === "function" ? params(data) : typeof params === "string" ? { message: params } : params;
3932
+ function cleanParams(params, data2) {
3933
+ const p = typeof params === "function" ? params(data2) : typeof params === "string" ? { message: params } : params;
3929
3934
  const p2 = typeof p === "string" ? { message: p } : p;
3930
3935
  return p2;
3931
3936
  }
3932
3937
  function custom(check, _params = {}, fatal) {
3933
3938
  if (check)
3934
- return ZodAny.create().superRefine((data, ctx) => {
3935
- const r = check(data);
3939
+ return ZodAny.create().superRefine((data2, ctx) => {
3940
+ const r = check(data2);
3936
3941
  if (r instanceof Promise) {
3937
3942
  return r.then((r2) => {
3938
3943
  if (!r2) {
3939
- const params = cleanParams(_params, data);
3944
+ const params = cleanParams(_params, data2);
3940
3945
  const _fatal = params.fatal ?? fatal ?? true;
3941
3946
  ctx.addIssue({ code: "custom", ...params, fatal: _fatal });
3942
3947
  }
3943
3948
  });
3944
3949
  }
3945
3950
  if (!r) {
3946
- const params = cleanParams(_params, data);
3951
+ const params = cleanParams(_params, data2);
3947
3952
  const _fatal = params.fatal ?? fatal ?? true;
3948
3953
  ctx.addIssue({ code: "custom", ...params, fatal: _fatal });
3949
3954
  }
@@ -3995,7 +4000,7 @@ var ZodFirstPartyTypeKind;
3995
4000
  })(ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {}));
3996
4001
  var instanceOfType = (cls, params = {
3997
4002
  message: `Input not instance of ${cls.name}`
3998
- }) => custom((data) => data instanceof cls, params);
4003
+ }) => custom((data2) => data2 instanceof cls, params);
3999
4004
  var stringType = ZodString.create;
4000
4005
  var numberType = ZodNumber.create;
4001
4006
  var nanType = ZodNaN.create;
@@ -4045,6 +4050,28 @@ var coerce = {
4045
4050
  };
4046
4051
  var NEVER = INVALID;
4047
4052
 
4053
+ // ../protocol/dist/loopback.js
4054
+ var LOOPBACK_CHUNK_BYTES = 64 * 1024;
4055
+ var LOOPBACK_MAX_BODY_BYTES = 1024 * 1024;
4056
+ var LOOPBACK_MAX_WS_BYTES = 256 * 1024;
4057
+ var LOOPBACK_MAX_CONNECTIONS = 16;
4058
+ var id = external_exports.string().uuid();
4059
+ var data = external_exports.string().max(Math.ceil(LOOPBACK_MAX_BODY_BYTES / 3) * 4).regex(/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/);
4060
+ var target = {
4061
+ port: external_exports.number().int().min(1024).max(65535),
4062
+ path: external_exports.string().min(1).max(8192).regex(/^\/(?!\/)[^\x00-\x20\x7f\\]*$/),
4063
+ headers: external_exports.array(external_exports.tuple([external_exports.string().regex(/^[!#$%&'*+.^_`|~0-9A-Za-z-]{1,128}$/), external_exports.string().max(8192).regex(/^[^\x00-\x08\x0a-\x1f\x7f]*$/)])).max(64)
4064
+ };
4065
+ var loopbackRequestSchema = external_exports.discriminatedUnion("op", [
4066
+ external_exports.object({ op: external_exports.literal("describe") }).strict(),
4067
+ external_exports.object({ op: external_exports.literal("http.open"), id, ...target, method: external_exports.enum(["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]), body: data.optional() }).strict(),
4068
+ external_exports.object({ op: external_exports.literal("http.read"), id }).strict(),
4069
+ external_exports.object({ op: external_exports.literal("close"), id }).strict(),
4070
+ external_exports.object({ op: external_exports.literal("ws.open"), id, ...target, protocols: external_exports.array(external_exports.string().regex(/^[!#$%&'*+.^_`|~0-9A-Za-z-]{1,128}$/)).max(16) }).strict(),
4071
+ external_exports.object({ op: external_exports.literal("ws.read"), id }).strict(),
4072
+ external_exports.object({ op: external_exports.literal("ws.send"), id, data, binary: external_exports.boolean() }).strict()
4073
+ ]);
4074
+
4048
4075
  // ../protocol/dist/index.js
4049
4076
  var PROTOCOL_VERSION = 1;
4050
4077
  var MAX_CONTROL_FRAME_BYTES = 64 * 1024;
@@ -4114,6 +4141,7 @@ var rpcMethods = [
4114
4141
  "harness.remote.transfer.close",
4115
4142
  "harness.remote.stream.open",
4116
4143
  "harness.remote.stream.close",
4144
+ "loopback.call",
4117
4145
  "fileviewer.call",
4118
4146
  "codex.app.call",
4119
4147
  "codex.app.respond",
@@ -4404,26 +4432,26 @@ var rpcErrorPayloadSchema = external_exports.object({
4404
4432
  retryable: external_exports.boolean().optional(),
4405
4433
  details: external_exports.unknown().optional()
4406
4434
  });
4407
- function createMessage(type, payload, id2 = cryptoRandomId()) {
4435
+ function createMessage(type, payload, id4 = cryptoRandomId()) {
4408
4436
  return {
4409
4437
  v: PROTOCOL_VERSION,
4410
- id: id2,
4438
+ id: id4,
4411
4439
  type,
4412
4440
  timestamp: Date.now(),
4413
4441
  payload
4414
4442
  };
4415
4443
  }
4416
- function createControlFrame(type, payload, id2 = cryptoRandomId()) {
4444
+ function createControlFrame(type, payload, id4 = cryptoRandomId()) {
4417
4445
  return {
4418
4446
  v: PROTOCOL_VERSION,
4419
- id: id2,
4447
+ id: id4,
4420
4448
  type,
4421
4449
  timestamp: Date.now(),
4422
4450
  payload
4423
4451
  };
4424
4452
  }
4425
- function createRpcRequest(method, params, id2) {
4426
- return createMessage("rpc.request", { method, params }, id2);
4453
+ function createRpcRequest(method, params, id4) {
4454
+ return createMessage("rpc.request", { method, params }, id4);
4427
4455
  }
4428
4456
  function createRpcResponse(requestId, result) {
4429
4457
  return createMessage("rpc.response", { requestId, result });
@@ -4431,8 +4459,8 @@ function createRpcResponse(requestId, result) {
4431
4459
  function createRpcError(requestId, code, message, details, retryable) {
4432
4460
  return createMessage("rpc.error", { requestId, code, message, details, retryable });
4433
4461
  }
4434
- function createEvent(event, data, options = {}) {
4435
- return createMessage("event", { event, data, ...options });
4462
+ function createEvent(event, data2, options = {}) {
4463
+ return createMessage("event", { event, data: data2, ...options });
4436
4464
  }
4437
4465
  function parseRemoteMessage(input2) {
4438
4466
  return remoteMessageSchema.parse(input2);
@@ -4451,11 +4479,11 @@ function encodeControlFrame(frame, limits = {}) {
4451
4479
  assertControlFrameSize(new TextEncoder().encode(encoded).byteLength, parsed.type, limits);
4452
4480
  return encoded;
4453
4481
  }
4454
- function decodeControlFrame(data, limits = {}) {
4455
- const encoded = typeof data === "string" ? new TextEncoder().encode(data) : data;
4482
+ function decodeControlFrame(data2, limits = {}) {
4483
+ const encoded = typeof data2 === "string" ? new TextEncoder().encode(data2) : data2;
4456
4484
  if (encoded.byteLength > MAX_RELAY_FRAME_BYTES)
4457
4485
  throw new Error("Control frame exceeds the Relay frame limit.");
4458
- const text = typeof data === "string" ? data : new TextDecoder("utf-8", { fatal: true }).decode(data);
4486
+ const text = typeof data2 === "string" ? data2 : new TextDecoder("utf-8", { fatal: true }).decode(data2);
4459
4487
  const frame = parseControlFrame(JSON.parse(text));
4460
4488
  assertControlFrameSize(encoded.byteLength, frame.type, limits);
4461
4489
  return frame;
@@ -4463,8 +4491,8 @@ function decodeControlFrame(data, limits = {}) {
4463
4491
  function encodeMessage(message) {
4464
4492
  return new TextEncoder().encode(JSON.stringify(message));
4465
4493
  }
4466
- function decodeMessage(data) {
4467
- const text = typeof data === "string" ? data : new TextDecoder().decode(data);
4494
+ function decodeMessage(data2) {
4495
+ const text = typeof data2 === "string" ? data2 : new TextDecoder().decode(data2);
4468
4496
  return parseRemoteMessage(JSON.parse(text));
4469
4497
  }
4470
4498
  var SecureMessageCodec = class {
@@ -4571,6 +4599,504 @@ function cryptoRandomId() {
4571
4599
  return `msg_${Date.now().toString(36)}_${Math.random().toString(36).slice(2)}`;
4572
4600
  }
4573
4601
 
4602
+ // src/loopback-host.ts
4603
+ import { request as httpRequest } from "node:http";
4604
+ import { WebSocket as WebSocket2 } from "ws";
4605
+
4606
+ // src/safe-error.ts
4607
+ var RpcError = class extends Error {
4608
+ constructor(code, message, details, retryable = false) {
4609
+ super(message);
4610
+ this.code = code;
4611
+ this.details = details;
4612
+ this.retryable = retryable;
4613
+ }
4614
+ };
4615
+ function safeErrorCode(error) {
4616
+ if (error instanceof RpcError) return error.code;
4617
+ if (error instanceof external_exports.ZodError) return "INVALID_MESSAGE";
4618
+ return "INTERNAL_ERROR";
4619
+ }
4620
+
4621
+ // src/loopback-host.ts
4622
+ var HOP_HEADERS = /* @__PURE__ */ new Set([
4623
+ "connection",
4624
+ "keep-alive",
4625
+ "proxy-authenticate",
4626
+ "proxy-authorization",
4627
+ "te",
4628
+ "trailer",
4629
+ "transfer-encoding",
4630
+ "upgrade",
4631
+ "host",
4632
+ "content-length",
4633
+ "forwarded",
4634
+ "x-forwarded-for",
4635
+ "x-forwarded-host",
4636
+ "x-forwarded-proto"
4637
+ ]);
4638
+ function cleanHeaders(headers) {
4639
+ const removed = new Set(HOP_HEADERS);
4640
+ for (const [name2, value] of headers) if (name2.toLowerCase() === "connection") {
4641
+ for (const token of value.split(",")) removed.add(token.trim().toLowerCase());
4642
+ }
4643
+ return headers.filter(([name2]) => !removed.has(name2.toLowerCase()) && !name2.toLowerCase().startsWith("sec-websocket-"));
4644
+ }
4645
+ function headerPairs(headers) {
4646
+ return Object.entries(headers).flatMap(([name2, value]) => value === void 0 ? [] : (Array.isArray(value) ? value : [value]).map((item) => [name2, item]));
4647
+ }
4648
+ var LoopbackHost = class {
4649
+ handles = /* @__PURE__ */ new Map();
4650
+ ports;
4651
+ closed = false;
4652
+ timer;
4653
+ constructor(ports) {
4654
+ this.ports = [...ports];
4655
+ this.timer = setInterval(() => {
4656
+ for (const [id4, handle] of this.handles) if (Date.now() - handle.touched > 6e4) this.close(id4);
4657
+ }, 1e4);
4658
+ this.timer.unref();
4659
+ }
4660
+ setPorts(ports) {
4661
+ this.ports = [...ports];
4662
+ }
4663
+ hasPorts() {
4664
+ return this.ports.length > 0;
4665
+ }
4666
+ async call(input2) {
4667
+ if (this.closed) throw new RpcError("TRANSPORT_CLOSED", "Preview connection closed.");
4668
+ const value = loopbackRequestSchema.parse(input2);
4669
+ if (value.op === "describe") return { ports: [...this.ports] };
4670
+ if (value.op === "close") {
4671
+ this.close(value.id);
4672
+ return { closed: true };
4673
+ }
4674
+ if (value.op === "http.open" || value.op === "ws.open") {
4675
+ if (!this.ports.includes(value.port)) throw new RpcError(
4676
+ "LOOPBACK_PORT_DENIED",
4677
+ "This preview port is not allowed. Add it to loopback.ports in the Host Remote settings and save the access settings. / \u8BF7\u5728 Host Remote \u8BBE\u7F6E\u4E2D\u5141\u8BB8\u6B64\u9884\u89C8\u7AEF\u53E3\u5E76\u4FDD\u5B58\u8BBF\u95EE\u8BBE\u7F6E\u3002"
4678
+ );
4679
+ if (this.handles.has(value.id)) throw new RpcError("REQUEST_CONFLICT", "Preview handle is already in use.");
4680
+ if (this.handles.size >= LOOPBACK_MAX_CONNECTIONS) throw new RpcError("RATE_LIMITED", "Too many active preview requests.");
4681
+ try {
4682
+ return value.op === "http.open" ? await this.openHttp(value) : await this.openWs(value);
4683
+ } catch {
4684
+ this.close(value.id);
4685
+ throw new RpcError("LOOPBACK_UNAVAILABLE", "The allowed loopback service did not respond. Check that it is running on the Host.");
4686
+ }
4687
+ }
4688
+ const handle = this.handles.get(value.id);
4689
+ if (handle === void 0) throw new RpcError("LOOPBACK_CLOSED", "Preview request closed; reload the preview.");
4690
+ handle.touched = Date.now();
4691
+ if (value.op === "http.read" && handle.kind === "http") {
4692
+ if (handle.reading) throw new RpcError("REQUEST_CONFLICT", "Only one preview read may be pending.");
4693
+ handle.reading = true;
4694
+ try {
4695
+ const response = handle.response;
4696
+ let chunk = response.read(Math.min(response.readableLength || LOOPBACK_CHUNK_BYTES, LOOPBACK_CHUNK_BYTES));
4697
+ if (chunk === null && !response.readableEnded && !response.destroyed) {
4698
+ await new Promise((resolve3, reject) => {
4699
+ const timer = setTimeout(() => finish(new Error("timeout")), 2e4);
4700
+ const ready = () => finish();
4701
+ const fail4 = () => finish(new Error("closed"));
4702
+ const finish = (error) => {
4703
+ clearTimeout(timer);
4704
+ response.off("readable", ready);
4705
+ response.off("end", ready);
4706
+ response.off("close", ready);
4707
+ response.off("error", fail4);
4708
+ error === void 0 ? resolve3() : reject(error);
4709
+ };
4710
+ response.once("readable", ready);
4711
+ response.once("end", ready);
4712
+ response.once("close", ready);
4713
+ response.once("error", fail4);
4714
+ });
4715
+ chunk = response.read(Math.min(response.readableLength || LOOPBACK_CHUNK_BYTES, LOOPBACK_CHUNK_BYTES));
4716
+ if (chunk === null && response.readableLength > 0) chunk = response.read(Math.min(response.readableLength, LOOPBACK_CHUNK_BYTES));
4717
+ }
4718
+ const done = chunk === null && (response.readableEnded || response.destroyed);
4719
+ if (done && !response.complete) throw new Error("truncated");
4720
+ handle.total += chunk?.length ?? 0;
4721
+ if (handle.total > 64 * 1024 * 1024) throw new Error("size");
4722
+ if (done) this.close(value.id);
4723
+ return { data: chunk?.toString("base64") ?? "", done };
4724
+ } catch {
4725
+ this.close(value.id);
4726
+ throw new RpcError("LOOPBACK_READ_FAILED", "Preview response ended or exceeded its limit; reload to retry.");
4727
+ } finally {
4728
+ handle.reading = false;
4729
+ }
4730
+ }
4731
+ if (handle.kind === "ws") {
4732
+ if (value.op === "ws.send") {
4733
+ const data2 = Buffer.from(value.data, "base64");
4734
+ if (data2.length > LOOPBACK_MAX_WS_BYTES || handle.socket.bufferedAmount > 1024 * 1024) {
4735
+ this.close(value.id);
4736
+ throw new RpcError("RATE_LIMITED", "Preview WebSocket buffer exceeded its limit.");
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()));
4739
+ return { sent: true };
4740
+ }
4741
+ if (value.op === "ws.read") {
4742
+ if (handle.reading) throw new RpcError("REQUEST_CONFLICT", "Only one preview read may be pending.");
4743
+ handle.reading = true;
4744
+ try {
4745
+ if (handle.messages.length === 0 && !handle.closed) await new Promise((resolve3) => {
4746
+ const timer = setTimeout(() => {
4747
+ handle.wake = void 0;
4748
+ resolve3();
4749
+ }, 2e4);
4750
+ handle.wake = () => {
4751
+ clearTimeout(timer);
4752
+ handle.wake = void 0;
4753
+ resolve3();
4754
+ };
4755
+ });
4756
+ let bytes = 0;
4757
+ const messages = [];
4758
+ while (handle.messages.length > 0 && bytes < LOOPBACK_MAX_WS_BYTES) {
4759
+ const message = handle.messages.shift();
4760
+ bytes += message.data.length;
4761
+ messages.push(message);
4762
+ }
4763
+ handle.bytes -= bytes;
4764
+ const closed = handle.closed && handle.messages.length === 0;
4765
+ if (closed) this.close(value.id);
4766
+ return { messages, closed };
4767
+ } finally {
4768
+ handle.reading = false;
4769
+ }
4770
+ }
4771
+ }
4772
+ throw new RpcError("INVALID_MESSAGE", "Preview handle kind does not match the operation.");
4773
+ }
4774
+ closeAll() {
4775
+ this.closed = true;
4776
+ clearInterval(this.timer);
4777
+ for (const id4 of this.handles.keys()) this.close(id4);
4778
+ }
4779
+ close(id4) {
4780
+ const handle = this.handles.get(id4);
4781
+ this.handles.delete(id4);
4782
+ if (handle?.kind === "http") {
4783
+ handle.response?.destroy();
4784
+ handle.request.destroy();
4785
+ }
4786
+ if (handle?.kind === "ws") {
4787
+ handle.closed = true;
4788
+ handle.wake?.();
4789
+ handle.socket.terminate();
4790
+ }
4791
+ }
4792
+ openHttp(value) {
4793
+ const body = value.body === void 0 ? void 0 : Buffer.from(value.body, "base64");
4794
+ if ((body?.length ?? 0) > LOOPBACK_MAX_BODY_BYTES) throw new Error("body limit");
4795
+ return new Promise((resolve3, reject) => {
4796
+ const headers = Object.fromEntries(cleanHeaders(value.headers));
4797
+ const request = httpRequest({
4798
+ hostname: "127.0.0.1",
4799
+ port: value.port,
4800
+ method: value.method,
4801
+ path: value.path,
4802
+ headers,
4803
+ agent: false,
4804
+ maxHeaderSize: 32 * 1024
4805
+ }, (response) => {
4806
+ const handle = this.handles.get(value.id);
4807
+ if (handle?.kind !== "http") {
4808
+ response.destroy();
4809
+ return;
4810
+ }
4811
+ handle.response = response;
4812
+ response.on("error", () => void 0);
4813
+ clearTimeout(timer);
4814
+ resolve3({ status: response.statusCode ?? 502, headers: cleanHeaders(headerPairs(response.headers)) });
4815
+ });
4816
+ const timer = setTimeout(() => {
4817
+ request.destroy();
4818
+ reject(new Error("timeout"));
4819
+ }, 2e4);
4820
+ request.on("error", () => {
4821
+ clearTimeout(timer);
4822
+ reject(new Error("upstream"));
4823
+ });
4824
+ this.handles.set(value.id, { kind: "http", request, reading: false, total: 0, touched: Date.now() });
4825
+ request.end(body);
4826
+ });
4827
+ }
4828
+ openWs(value) {
4829
+ return new Promise((resolve3, reject) => {
4830
+ const socket = new WebSocket2(`ws://127.0.0.1:${value.port}${value.path}`, value.protocols, {
4831
+ headers: Object.fromEntries(cleanHeaders(value.headers)),
4832
+ followRedirects: false,
4833
+ handshakeTimeout: 15e3,
4834
+ maxPayload: LOOPBACK_MAX_WS_BYTES,
4835
+ perMessageDeflate: false
4836
+ });
4837
+ const handle = { kind: "ws", socket, messages: [], bytes: 0, reading: false, closed: false, touched: Date.now() };
4838
+ this.handles.set(value.id, handle);
4839
+ socket.once("open", () => resolve3({ protocol: socket.protocol }));
4840
+ socket.on("message", (data2, binary) => {
4841
+ const message = { data: Buffer.from(data2).toString("base64"), binary };
4842
+ if (handle.bytes + message.data.length > 1024 * 1024 || handle.messages.length >= 256) {
4843
+ this.close(value.id);
4844
+ return;
4845
+ }
4846
+ handle.messages.push(message);
4847
+ handle.bytes += message.data.length;
4848
+ handle.wake?.();
4849
+ });
4850
+ socket.on("close", () => {
4851
+ handle.closed = true;
4852
+ handle.wake?.();
4853
+ reject(new Error("closed"));
4854
+ });
4855
+ socket.on("error", () => {
4856
+ handle.closed = true;
4857
+ handle.wake?.();
4858
+ reject(new Error("upstream"));
4859
+ });
4860
+ });
4861
+ }
4862
+ };
4863
+
4864
+ // src/loopback-preview.ts
4865
+ var LoopbackPreview = class {
4866
+ constructor(client) {
4867
+ this.client = client;
4868
+ }
4869
+ servers = /* @__PURE__ */ new Map();
4870
+ sockets = /* @__PURE__ */ new Set();
4871
+ lifetime = new AbortController();
4872
+ async open(port) {
4873
+ if (!Number.isInteger(port) || port < 1024 || port > 65535) throw new RpcError("INVALID_MESSAGE", "Enter a port between 1024 and 65535.");
4874
+ this.lifetime.signal.throwIfAborted();
4875
+ const description = await this.client.rpc("loopback.call", { op: "describe" }, this.lifetime.signal);
4876
+ if (!description.ports.includes(port)) throw new RpcError("LOOPBACK_PORT_DENIED", "Allow this port in the Host Remote settings (loopback.ports), then save the access settings. / \u8BF7\u5728 Host Remote \u8BBE\u7F6E\u4E2D\u5141\u8BB8\u6B64\u7AEF\u53E3\uFF0C\u7136\u540E\u4FDD\u5B58\u8BBF\u95EE\u8BBE\u7F6E\u3002");
4877
+ let pending = this.servers.get(port);
4878
+ if (pending === void 0) {
4879
+ if (this.servers.size >= 16) throw new RpcError("RATE_LIMITED", "Too many preview origins.");
4880
+ pending = this.start(port);
4881
+ this.servers.set(port, pending);
4882
+ void pending.catch(() => {
4883
+ this.servers.delete(port);
4884
+ });
4885
+ }
4886
+ return { url: (await pending).url };
4887
+ }
4888
+ async close() {
4889
+ this.lifetime.abort();
4890
+ for (const socket of this.sockets) socket.destroy();
4891
+ for (const pending of this.servers.values()) {
4892
+ const running = await pending.catch(() => void 0);
4893
+ if (running === void 0) continue;
4894
+ for (const client of running.ws.clients) client.terminate();
4895
+ running.ws.close();
4896
+ running.server.closeAllConnections();
4897
+ await new Promise((resolve3) => running.server.close(() => resolve3()));
4898
+ }
4899
+ this.servers.clear();
4900
+ }
4901
+ async start(port) {
4902
+ const hostname4 = `dsh-${randomBytes(24).toString("hex")}.localhost`;
4903
+ let authority = "";
4904
+ const accepted = (req) => req.headers.host === authority && (req.headers.origin === void 0 || req.headers.origin === `http://${authority}`) && req.headers["service-worker"] === void 0;
4905
+ const server = createServer((req, res) => {
4906
+ if (!accepted(req)) {
4907
+ res.writeHead(403);
4908
+ res.end("Preview origin denied.");
4909
+ return;
4910
+ }
4911
+ void this.http(port, authority, req, res);
4912
+ });
4913
+ server.requestTimeout = 3e4;
4914
+ server.headersTimeout = 15e3;
4915
+ server.maxHeadersCount = 64;
4916
+ server.on("connection", (socket) => {
4917
+ if (this.sockets.size >= 64 || this.lifetime.signal.aborted) {
4918
+ socket.destroy();
4919
+ return;
4920
+ }
4921
+ this.sockets.add(socket);
4922
+ socket.on("close", () => this.sockets.delete(socket));
4923
+ });
4924
+ const selectedProtocols = /* @__PURE__ */ new WeakMap();
4925
+ const ws = new WebSocketServer({
4926
+ noServer: true,
4927
+ maxPayload: LOOPBACK_MAX_WS_BYTES,
4928
+ perMessageDeflate: false,
4929
+ handleProtocols: (_protocols, req) => selectedProtocols.get(req) || false
4930
+ });
4931
+ server.on("upgrade", (req, socket, head) => {
4932
+ if (!accepted(req)) {
4933
+ socket.destroy();
4934
+ return;
4935
+ }
4936
+ const id4 = randomUUID();
4937
+ const headers = this.headers(req, port);
4938
+ const protocols = (req.headers["sec-websocket-protocol"] ?? "").split(",").map((item) => item.trim()).filter(Boolean);
4939
+ void this.client.rpc("loopback.call", { op: "ws.open", id: id4, port, path: req.url ?? "/", headers, protocols }, this.lifetime.signal).then((result) => {
4940
+ if (socket.destroyed || this.lifetime.signal.aborted) {
4941
+ socket.destroy();
4942
+ void this.release(id4);
4943
+ return;
4944
+ }
4945
+ selectedProtocols.set(req, result.protocol);
4946
+ ws.handleUpgrade(req, socket, head, (local) => this.websocket(id4, local));
4947
+ }).catch(() => {
4948
+ socket.destroy();
4949
+ void this.release(id4);
4950
+ });
4951
+ });
4952
+ await new Promise((resolve3, reject) => {
4953
+ server.once("error", reject);
4954
+ server.listen(0, "127.0.0.1", () => {
4955
+ server.off("error", reject);
4956
+ resolve3();
4957
+ });
4958
+ });
4959
+ const address = server.address();
4960
+ if (address === null || typeof address === "string") {
4961
+ server.close();
4962
+ throw new Error("Preview listener unavailable");
4963
+ }
4964
+ authority = `${hostname4}:${address.port}`;
4965
+ if (this.lifetime.signal.aborted) {
4966
+ server.close();
4967
+ ws.close();
4968
+ throw new Error("Preview closed");
4969
+ }
4970
+ return { server, ws, url: `http://${authority}/` };
4971
+ }
4972
+ headers(req, port) {
4973
+ return cleanHeaders(headerPairs(req.headers)).filter(([name2]) => !["origin", "referer"].includes(name2.toLowerCase())).concat(req.headers.origin === void 0 ? [] : [["origin", `http://127.0.0.1:${port}`]]);
4974
+ }
4975
+ async http(port, authority, req, res) {
4976
+ const id4 = randomUUID();
4977
+ const controller = new AbortController();
4978
+ const abort = () => {
4979
+ controller.abort();
4980
+ void this.release(id4);
4981
+ };
4982
+ res.once("close", abort);
4983
+ const signal = AbortSignal.any([controller.signal, this.lifetime.signal]);
4984
+ try {
4985
+ const chunks = [];
4986
+ let length = 0;
4987
+ for await (const chunk of req) {
4988
+ length += chunk.length;
4989
+ if (length > LOOPBACK_MAX_BODY_BYTES) throw new RpcError("PAYLOAD_TOO_LARGE", "Preview request body is limited to 1 MiB.");
4990
+ chunks.push(chunk);
4991
+ }
4992
+ const head = await this.client.rpc("loopback.call", {
4993
+ op: "http.open",
4994
+ id: id4,
4995
+ port,
4996
+ path: req.url ?? "/",
4997
+ method: req.method ?? "GET",
4998
+ headers: this.headers(req, port),
4999
+ ...length === 0 ? {} : { body: Buffer.concat(chunks).toString("base64") }
5000
+ }, signal);
5001
+ const headers = {};
5002
+ for (let [name2, value] of cleanHeaders(head.headers)) {
5003
+ name2 = name2.toLowerCase();
5004
+ if (name2 === "location") {
5005
+ const url = new URL(value, `http://127.0.0.1:${port}/`);
5006
+ if (!["127.0.0.1", "localhost", "[::1]"].includes(url.hostname) || Number(url.port || 80) !== port || url.protocol !== "http:") {
5007
+ throw new Error("Redirect is outside the authorized preview service");
5008
+ }
5009
+ value = `http://${authority}${url.pathname}${url.search}${url.hash}`;
5010
+ }
5011
+ if (name2 === "set-cookie") {
5012
+ value = value.replace(/;\s*domain=[^;]*/ig, "");
5013
+ const existing = headers[name2];
5014
+ headers[name2] = [...Array.isArray(existing) ? existing : [], value];
5015
+ } else if (!["service-worker-allowed", "clear-site-data", "alt-svc"].includes(name2)) headers[name2] = value;
5016
+ }
5017
+ headers["referrer-policy"] = "no-referrer";
5018
+ res.writeHead(head.status, headers);
5019
+ while (!signal.aborted) {
5020
+ const chunk = await this.client.rpc("loopback.call", { op: "http.read", id: id4 }, signal);
5021
+ if (chunk.data !== "" && !res.write(Buffer.from(chunk.data, "base64"))) {
5022
+ await new Promise((resolve3, reject) => {
5023
+ const ready = () => {
5024
+ cleanup();
5025
+ resolve3();
5026
+ };
5027
+ const closed = () => {
5028
+ cleanup();
5029
+ reject(new Error("closed"));
5030
+ };
5031
+ const cleanup = () => {
5032
+ res.off("drain", ready);
5033
+ res.off("close", closed);
5034
+ };
5035
+ res.once("drain", ready);
5036
+ res.once("close", closed);
5037
+ });
5038
+ }
5039
+ if (chunk.done) break;
5040
+ }
5041
+ res.end();
5042
+ } catch {
5043
+ if (!res.headersSent) {
5044
+ res.writeHead(502, { "Content-Type": "text/plain; charset=utf-8" });
5045
+ res.end("Remote preview unavailable. Check the Host service and connection, then reload.");
5046
+ } else res.destroy();
5047
+ } finally {
5048
+ res.off("close", abort);
5049
+ await this.release(id4);
5050
+ }
5051
+ }
5052
+ websocket(id4, socket) {
5053
+ let input2 = Promise.resolve();
5054
+ let queued = 0;
5055
+ let count = 0;
5056
+ const controller = new AbortController();
5057
+ const signal = AbortSignal.any([controller.signal, this.lifetime.signal]);
5058
+ const close = () => {
5059
+ controller.abort();
5060
+ socket.terminate();
5061
+ void this.release(id4);
5062
+ };
5063
+ socket.on("error", close);
5064
+ socket.on("close", close);
5065
+ socket.on("message", (data2, binary) => {
5066
+ const bytes = Buffer.from(data2);
5067
+ queued += bytes.length;
5068
+ count += 1;
5069
+ if (queued > 1024 * 1024 || count > 256) {
5070
+ close();
5071
+ return;
5072
+ }
5073
+ input2 = input2.then(async () => {
5074
+ await this.client.rpc("loopback.call", { op: "ws.send", id: id4, data: bytes.toString("base64"), binary }, signal);
5075
+ queued -= bytes.length;
5076
+ count -= 1;
5077
+ }).catch(close);
5078
+ });
5079
+ void (async () => {
5080
+ try {
5081
+ while (!signal.aborted) {
5082
+ const result = await this.client.rpc("loopback.call", { op: "ws.read", id: id4 }, signal);
5083
+ for (const message of result.messages) {
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()));
5086
+ }
5087
+ if (result.closed) break;
5088
+ }
5089
+ } catch {
5090
+ } finally {
5091
+ close();
5092
+ }
5093
+ })();
5094
+ }
5095
+ async release(id4) {
5096
+ if (!this.lifetime.signal.aborted) await this.client.rpc("loopback.call", { op: "close", id: id4 }).catch(() => void 0);
5097
+ }
5098
+ };
5099
+
4574
5100
  // ../client-core/dist/remote-gateway.js
4575
5101
  var DIRECT_REMOTE_CALL_BYTES = 2 * 1024 * 1024;
4576
5102
  var RemoteGatewayError = class extends Error {
@@ -4787,12 +5313,12 @@ function deriveCodexCwdWorkspaces(values) {
4787
5313
  continue;
4788
5314
  }
4789
5315
  const baseId = `cwd-${hashWorkspacePath(key)}`;
4790
- let id2 = baseId;
4791
- for (let suffix = 2; usedIds.has(id2); suffix += 1)
4792
- id2 = `${baseId}-${suffix}`;
4793
- usedIds.add(id2);
5316
+ let id4 = baseId;
5317
+ for (let suffix = 2; usedIds.has(id4); suffix += 1)
5318
+ id4 = `${baseId}-${suffix}`;
5319
+ usedIds.add(id4);
4794
5320
  byPath.set(key, {
4795
- id: id2,
5321
+ id: id4,
4796
5322
  name: workspaceBasename(path),
4797
5323
  path,
4798
5324
  position: byPath.size,
@@ -4966,16 +5492,16 @@ function parseDataImageUrl(value) {
4966
5492
  if (match === null)
4967
5493
  return void 0;
4968
5494
  const mediaType = match[1];
4969
- const data = match[2];
4970
- if (!CODEX_IMAGE_MEDIA_TYPES.has(mediaType) || !isCanonicalBase64(data))
5495
+ const data2 = match[2];
5496
+ if (!CODEX_IMAGE_MEDIA_TYPES.has(mediaType) || !isCanonicalBase64(data2))
4971
5497
  return void 0;
4972
- return { url: `data:${mediaType};base64,${data}`, mediaType, data };
5498
+ return { url: `data:${mediaType};base64,${data2}`, mediaType, data: data2 };
4973
5499
  }
4974
5500
  function isCanonicalBase64(value) {
4975
5501
  return value.length >= 4 && value.length % 4 === 0 && /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/u.test(value);
4976
5502
  }
4977
- function sniffImageMediaType(data) {
4978
- const bytes = base64PrefixBytes(data, 32);
5503
+ function sniffImageMediaType(data2) {
5504
+ const bytes = base64PrefixBytes(data2, 32);
4979
5505
  if (bytes.length >= 8 && bytes[0] === 137 && bytes[1] === 80 && bytes[2] === 78 && bytes[3] === 71 && bytes[4] === 13 && bytes[5] === 10 && bytes[6] === 26 && bytes[7] === 10)
4980
5506
  return "image/png";
4981
5507
  if (bytes.length >= 3 && bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255)
@@ -5181,7 +5707,7 @@ var RemoteClientCore = class {
5181
5707
  if (this.unsubscribeTransport !== void 0)
5182
5708
  return;
5183
5709
  this.closeNotified = false;
5184
- this.unsubscribeTransport = this.transport.onMessage((data) => this.handleMessage(data));
5710
+ this.unsubscribeTransport = this.transport.onMessage((data2) => this.handleMessage(data2));
5185
5711
  this.unsubscribeClose = this.transport.onClose?.(() => this.handleTransportClose());
5186
5712
  try {
5187
5713
  await this.transport.connect();
@@ -5252,8 +5778,8 @@ var RemoteClientCore = class {
5252
5778
  this.rejectAllPending((pending) => new RemoteClientError("TRANSPORT_CLOSED", `RPC ${pending.method} terminated because the remote transport closed`));
5253
5779
  this.notifyClose();
5254
5780
  }
5255
- handleMessage(data) {
5256
- const message = decodeMessage(data);
5781
+ handleMessage(data2) {
5782
+ const message = decodeMessage(data2);
5257
5783
  if (message.type === "rpc.response")
5258
5784
  this.handleResponse(message);
5259
5785
  if (message.type === "rpc.error")
@@ -5314,6 +5840,18 @@ function transportSendError(error) {
5314
5840
  return error instanceof Error ? error : new Error("remote transport send failed", { cause: error });
5315
5841
  }
5316
5842
 
5843
+ // ../webrtc/dist/websocket-backpressure.js
5844
+ async function waitForRelayCapacity(socket) {
5845
+ const started = Date.now();
5846
+ while ((socket.bufferedAmount ?? 0) > 512 * 1024) {
5847
+ if (socket.readyState !== 1 || Date.now() - started > 1e4)
5848
+ throw new Error("Relay consumer is too slow or disconnected");
5849
+ await new Promise((resolve3) => setTimeout(resolve3, 10));
5850
+ }
5851
+ if (socket.readyState !== 1)
5852
+ throw new Error("Relay transport closed");
5853
+ }
5854
+
5317
5855
  // ../webrtc/dist/transport.js
5318
5856
  var BaseTransport = class {
5319
5857
  handlers = /* @__PURE__ */ new Set();
@@ -5326,9 +5864,9 @@ var BaseTransport = class {
5326
5864
  this.closeHandlers.add(cb);
5327
5865
  return () => this.closeHandlers.delete(cb);
5328
5866
  }
5329
- emit(data) {
5867
+ emit(data2) {
5330
5868
  for (const handler of this.handlers)
5331
- handler(data);
5869
+ handler(data2);
5332
5870
  }
5333
5871
  emitClose() {
5334
5872
  for (const handler of this.closeHandlers)
@@ -5571,8 +6109,8 @@ function adaptDataChannel(raw) {
5571
6109
  get onbufferedamountlow() {
5572
6110
  return raw.onbufferedamountlow;
5573
6111
  },
5574
- send(data) {
5575
- raw.send(data);
6112
+ send(data2) {
6113
+ raw.send(data2);
5576
6114
  },
5577
6115
  close() {
5578
6116
  raw.close();
@@ -5591,19 +6129,19 @@ var MAX_ASSEMBLY_AGE_MS = 3e4;
5591
6129
  var RtcChunkCodec = class {
5592
6130
  nextMessageId = 1;
5593
6131
  assemblies = /* @__PURE__ */ new Map();
5594
- encode(data) {
5595
- if (data.byteLength <= RTC_CHUNK_PAYLOAD_BYTES)
5596
- return [data];
5597
- if (data.byteLength > RTC_CHUNK_MAX_MESSAGE_BYTES) {
6132
+ encode(data2) {
6133
+ if (data2.byteLength <= RTC_CHUNK_PAYLOAD_BYTES)
6134
+ return [data2];
6135
+ if (data2.byteLength > RTC_CHUNK_MAX_MESSAGE_BYTES) {
5598
6136
  throw new Error("WebRTC transport message exceeds the reassembly limit.");
5599
6137
  }
5600
6138
  const messageId = this.nextMessageId;
5601
6139
  this.nextMessageId = messageId === 4294967295 ? 1 : messageId + 1;
5602
- const total = Math.ceil(data.byteLength / RTC_CHUNK_PAYLOAD_BYTES);
6140
+ const total = Math.ceil(data2.byteLength / RTC_CHUNK_PAYLOAD_BYTES);
5603
6141
  const frames = [];
5604
6142
  for (let index = 0; index < total; index += 1) {
5605
6143
  const start = index * RTC_CHUNK_PAYLOAD_BYTES;
5606
- const chunk = data.subarray(start, Math.min(data.byteLength, start + RTC_CHUNK_PAYLOAD_BYTES));
6144
+ const chunk = data2.subarray(start, Math.min(data2.byteLength, start + RTC_CHUNK_PAYLOAD_BYTES));
5607
6145
  const frame = new Uint8Array(RTC_CHUNK_HEADER_BYTES + chunk.byteLength);
5608
6146
  frame.set(RTC_CHUNK_MAGIC);
5609
6147
  const view = new DataView(frame.buffer);
@@ -5789,11 +6327,11 @@ var RtcDataChannelTransport = class {
5789
6327
  else
5790
6328
  void this.handleIce(signal.candidate);
5791
6329
  }
5792
- async send(data) {
6330
+ async send(data2) {
5793
6331
  const channel = this.requireOpenChannel();
5794
6332
  if (channel.readyState !== "open")
5795
6333
  throw new Error("WebRTC data channel is not open.");
5796
- for (const frame of this.outgoing.encode(data)) {
6334
+ for (const frame of this.outgoing.encode(data2)) {
5797
6335
  try {
5798
6336
  channel.send(toArrayBuffer(frame));
5799
6337
  } catch (error) {
@@ -5801,7 +6339,7 @@ var RtcDataChannelTransport = class {
5801
6339
  throw error;
5802
6340
  }
5803
6341
  }
5804
- this.bytesSent += data.byteLength;
6342
+ this.bytesSent += data2.byteLength;
5805
6343
  this.armWatchdog(channel);
5806
6344
  }
5807
6345
  onMessage(handler) {
@@ -5990,8 +6528,8 @@ var RtcDataChannelTransport = class {
5990
6528
  channel.onmessage = (event) => {
5991
6529
  if (this.closed || !this.opened)
5992
6530
  return;
5993
- const data = event.data;
5994
- const frame = typeof data === "string" ? new TextEncoder().encode(data) : new Uint8Array(data);
6531
+ const data2 = event.data;
6532
+ const frame = typeof data2 === "string" ? new TextEncoder().encode(data2) : new Uint8Array(data2);
5995
6533
  this.bytesReceived += frame.byteLength;
5996
6534
  try {
5997
6535
  const message = this.incoming.decode(frame);
@@ -6182,7 +6720,7 @@ function inspectSelectedPath(stats) {
6182
6720
  }
6183
6721
  }
6184
6722
  const pairs = [
6185
- ...[...selectedPairIds].map((id2) => candidatePairs.get(id2)).filter((pair) => pair !== void 0),
6723
+ ...[...selectedPairIds].map((id4) => candidatePairs.get(id4)).filter((pair) => pair !== void 0),
6186
6724
  ...selectedPairs
6187
6725
  ];
6188
6726
  for (const pair of pairs) {
@@ -6370,10 +6908,10 @@ function requireSdp(description) {
6370
6908
  }
6371
6909
  return description.sdp;
6372
6910
  }
6373
- function toArrayBuffer(data) {
6374
- if (data.byteOffset === 0 && data.byteLength === data.buffer.byteLength)
6375
- return data.buffer;
6376
- return data.slice().buffer;
6911
+ function toArrayBuffer(data2) {
6912
+ if (data2.byteOffset === 0 && data2.byteLength === data2.buffer.byteLength)
6913
+ return data2.buffer;
6914
+ return data2.slice().buffer;
6377
6915
  }
6378
6916
  function asError(error) {
6379
6917
  return error instanceof Error ? error : new RtcConnectError("RTC_FAILED", "WebRTC negotiation failed.");
@@ -6445,26 +6983,27 @@ var AdaptiveTransport = class extends BaseTransport {
6445
6983
  };
6446
6984
  });
6447
6985
  }
6448
- async send(data) {
6986
+ async send(data2) {
6449
6987
  if (this.connectionId === void 0)
6450
6988
  throw new Error("adaptive transport has not been authorized");
6451
6989
  if (this.dataMode === "webrtc") {
6452
6990
  const rtc = this.rtc;
6453
6991
  if (rtc === void 0)
6454
6992
  throw new Error("webrtc data channel is not available");
6455
- this.bytesSent += data.byteLength;
6456
- await rtc.send(data);
6993
+ this.bytesSent += data2.byteLength;
6994
+ await rtc.send(data2);
6457
6995
  return;
6458
6996
  }
6459
6997
  if (this.socket?.readyState !== WebSocket.OPEN)
6460
6998
  throw new Error("adaptive transport is not connected");
6999
+ await waitForRelayCapacity(this.socket);
6461
7000
  this.sendControl("relay", {
6462
7001
  connectionId: this.connectionId,
6463
7002
  targetDeviceId: this.options.targetDeviceId,
6464
7003
  counter: this.relayCounter,
6465
- ciphertext: toBase64Url(data)
7004
+ ciphertext: toBase64Url(data2)
6466
7005
  });
6467
- this.bytesSent += data.byteLength;
7006
+ this.bytesSent += data2.byteLength;
6468
7007
  this.relayCounter += 1;
6469
7008
  }
6470
7009
  connectionInfo() {
@@ -6488,7 +7027,7 @@ var AdaptiveTransport = class extends BaseTransport {
6488
7027
  ...this.rtc === void 0 ? {} : { webRtc: await this.rtc.connectionDetails() }
6489
7028
  };
6490
7029
  }
6491
- async sendHandshake(step, data) {
7030
+ async sendHandshake(step, data2) {
6492
7031
  if (this.socket?.readyState !== WebSocket.OPEN || this.connectionId === void 0) {
6493
7032
  throw new Error("adaptive transport has not been authorized");
6494
7033
  }
@@ -6496,7 +7035,7 @@ var AdaptiveTransport = class extends BaseTransport {
6496
7035
  connectionId: this.connectionId,
6497
7036
  targetDeviceId: this.options.targetDeviceId,
6498
7037
  step,
6499
- data: toBase64Url(data)
7038
+ data: toBase64Url(data2)
6500
7039
  });
6501
7040
  }
6502
7041
  onHandshake(cb) {
@@ -6580,9 +7119,9 @@ var AdaptiveTransport = class extends BaseTransport {
6580
7119
  if (payload.connectionId !== this.connectionId || payload.targetDeviceId !== this.options.deviceId || !Number.isSafeInteger(payload.step) || typeof payload.data !== "string") {
6581
7120
  throw new Error("Received a secure handshake frame for an unknown connection");
6582
7121
  }
6583
- const data = fromBase64Url(payload.data);
7122
+ const data2 = fromBase64Url(payload.data);
6584
7123
  for (const handler of this.handshakeHandlers)
6585
- handler(payload.step, data);
7124
+ handler(payload.step, data2);
6586
7125
  return;
6587
7126
  }
6588
7127
  if (frame.type === "signal.answer") {
@@ -6611,9 +7150,9 @@ var AdaptiveTransport = class extends BaseTransport {
6611
7150
  if (payload.connectionId !== this.connectionId || payload.targetDeviceId !== this.options.deviceId || !Number.isSafeInteger(payload.counter) || typeof payload.ciphertext !== "string") {
6612
7151
  throw new Error("Received a relay frame for an unknown connection");
6613
7152
  }
6614
- const data = fromBase64Url(payload.ciphertext);
6615
- this.bytesReceived += data.byteLength;
6616
- this.emit(data);
7153
+ const data2 = fromBase64Url(payload.ciphertext);
7154
+ this.bytesReceived += data2.byteLength;
7155
+ this.emit(data2);
6617
7156
  }
6618
7157
  async negotiate() {
6619
7158
  if (this.connectionId === void 0)
@@ -6705,9 +7244,9 @@ var AdaptiveTransport = class extends BaseTransport {
6705
7244
  }
6706
7245
  });
6707
7246
  this.rtc = rtc;
6708
- rtc.onMessage((data) => {
6709
- this.bytesReceived += data.byteLength;
6710
- this.emit(data);
7247
+ rtc.onMessage((data2) => {
7248
+ this.bytesReceived += data2.byteLength;
7249
+ this.emit(data2);
6711
7250
  });
6712
7251
  rtc.onClose(() => {
6713
7252
  if (this.rtc === rtc && this.dataMode === "webrtc")
@@ -6802,6 +7341,27 @@ function negotiateTimeout(serverMs, localMs) {
6802
7341
  return Math.max(serverMs, localMs);
6803
7342
  }
6804
7343
 
7344
+ // ../webrtc/dist/serial-send.js
7345
+ var SerialSend = class {
7346
+ tail = Promise.resolve();
7347
+ bytes = 0;
7348
+ count = 0;
7349
+ async run(bytes, send) {
7350
+ if (this.bytes + bytes > 8 * 1024 * 1024 || this.count >= 256)
7351
+ throw new Error("Encrypted send queue exceeds its limit");
7352
+ this.bytes += bytes;
7353
+ this.count += 1;
7354
+ const task = this.tail.then(send);
7355
+ this.tail = task.catch(() => void 0);
7356
+ try {
7357
+ await task;
7358
+ } finally {
7359
+ this.bytes -= bytes;
7360
+ this.count -= 1;
7361
+ }
7362
+ }
7363
+ };
7364
+
6805
7365
  // src/api-proxy-switch.ts
6806
7366
  var SWITCHED_DOMAINS = [
6807
7367
  "sessions",
@@ -6855,10 +7415,10 @@ var ApiProxySwitch = class {
6855
7415
  });
6856
7416
  this.installed = true;
6857
7417
  }
6858
- selectRemote(api, target) {
7418
+ selectRemote(api, target2) {
6859
7419
  if (!this.installed) throw new Error("The Harness API switch is not installed.");
6860
7420
  this.remote = api;
6861
- this.target = { ...target };
7421
+ this.target = { ...target2 };
6862
7422
  this.mode = "remote";
6863
7423
  }
6864
7424
  selectLocal() {
@@ -6904,7 +7464,7 @@ import * as nc from "node:crypto";
6904
7464
  var crypto = nc && typeof nc === "object" && "webcrypto" in nc ? nc.webcrypto : nc && typeof nc === "object" && "randomBytes" in nc ? nc : void 0;
6905
7465
 
6906
7466
  // ../../node_modules/.pnpm/@noble+ciphers@1.3.0/node_modules/@noble/ciphers/esm/webcrypto.js
6907
- function randomBytes(bytesLength = 32) {
7467
+ function randomBytes2(bytesLength = 32) {
6908
7468
  if (crypto && typeof crypto.getRandomValues === "function") {
6909
7469
  return crypto.getRandomValues(new Uint8Array(bytesLength));
6910
7470
  }
@@ -7004,11 +7564,11 @@ function utf8ToBytes(str) {
7004
7564
  throw new Error("string expected");
7005
7565
  return new Uint8Array(new TextEncoder().encode(str));
7006
7566
  }
7007
- function toBytes(data) {
7008
- if (typeof data === "string")
7009
- data = utf8ToBytes(data);
7010
- abytes(data);
7011
- return data;
7567
+ function toBytes(data2) {
7568
+ if (typeof data2 === "string")
7569
+ data2 = utf8ToBytes(data2);
7570
+ abytes(data2);
7571
+ return data2;
7012
7572
  }
7013
7573
  function concatBytes(...arrays) {
7014
7574
  let sum = 0;
@@ -7035,7 +7595,7 @@ function createHasher(hashCons) {
7035
7595
  hashC.create = () => hashCons();
7036
7596
  return hashC;
7037
7597
  }
7038
- function randomBytes2(bytesLength = 32) {
7598
+ function randomBytes3(bytesLength = 32) {
7039
7599
  if (crypto2 && typeof crypto2.getRandomValues === "function") {
7040
7600
  return crypto2.getRandomValues(new Uint8Array(bytesLength));
7041
7601
  }
@@ -7072,21 +7632,21 @@ var HashMD = class extends Hash {
7072
7632
  this.buffer = new Uint8Array(blockLen);
7073
7633
  this.view = createView(this.buffer);
7074
7634
  }
7075
- update(data) {
7635
+ update(data2) {
7076
7636
  aexists(this);
7077
- data = toBytes(data);
7078
- abytes(data);
7637
+ data2 = toBytes(data2);
7638
+ abytes(data2);
7079
7639
  const { view, buffer, blockLen } = this;
7080
- const len = data.length;
7640
+ const len = data2.length;
7081
7641
  for (let pos = 0; pos < len; ) {
7082
7642
  const take = Math.min(blockLen - this.pos, len - pos);
7083
7643
  if (take === blockLen) {
7084
- const dataView = createView(data);
7644
+ const dataView = createView(data2);
7085
7645
  for (; blockLen <= len - pos; pos += blockLen)
7086
7646
  this.process(dataView, pos);
7087
7647
  continue;
7088
7648
  }
7089
- buffer.set(data.subarray(pos, pos + take), this.pos);
7649
+ buffer.set(data2.subarray(pos, pos + take), this.pos);
7090
7650
  this.pos += take;
7091
7651
  pos += take;
7092
7652
  if (this.pos === blockLen) {
@@ -7094,7 +7654,7 @@ var HashMD = class extends Hash {
7094
7654
  this.pos = 0;
7095
7655
  }
7096
7656
  }
7097
- this.length += data.length;
7657
+ this.length += data2.length;
7098
7658
  this.roundClean();
7099
7659
  return this;
7100
7660
  }
@@ -8471,13 +9031,13 @@ function eddsa(Point, cHash, eddsaOpts = {}) {
8471
9031
  });
8472
9032
  const { prehash } = eddsaOpts;
8473
9033
  const { BASE, Fp: Fp2, Fn: Fn2 } = Point;
8474
- const randomBytes7 = eddsaOpts.randomBytes || randomBytes2;
9034
+ const randomBytes8 = eddsaOpts.randomBytes || randomBytes3;
8475
9035
  const adjustScalarBytes3 = eddsaOpts.adjustScalarBytes || ((bytes) => bytes);
8476
- const domain = eddsaOpts.domain || ((data, ctx, phflag) => {
9036
+ const domain = eddsaOpts.domain || ((data2, ctx, phflag) => {
8477
9037
  _abool2(phflag, "phflag");
8478
9038
  if (ctx.length || phflag)
8479
9039
  throw new Error("Contexts/pre-hash are not supported");
8480
- return data;
9040
+ return data2;
8481
9041
  });
8482
9042
  function modN_LE(hash) {
8483
9043
  return Fn2.create(bytesToNumberLE(hash));
@@ -8553,7 +9113,7 @@ function eddsa(Point, cHash, eddsaOpts = {}) {
8553
9113
  signature: 2 * _size,
8554
9114
  seed: _size
8555
9115
  };
8556
- function randomSecretKey(seed = randomBytes7(lengths.seed)) {
9116
+ function randomSecretKey(seed = randomBytes8(lengths.seed)) {
8557
9117
  return _abytes2(seed, lengths.seed, "seed");
8558
9118
  }
8559
9119
  function keygen(seed) {
@@ -8672,7 +9232,7 @@ function montgomery(curveDef) {
8672
9232
  const is25519 = type === "x25519";
8673
9233
  if (!is25519 && type !== "x448")
8674
9234
  throw new Error("invalid type");
8675
- const randomBytes_ = rand || randomBytes2;
9235
+ const randomBytes_ = rand || randomBytes3;
8676
9236
  const montgomeryBits = is25519 ? 255 : 448;
8677
9237
  const fieldLen = is25519 ? 32 : 56;
8678
9238
  const Gu = is25519 ? BigInt(9) : BigInt(5);
@@ -9321,8 +9881,8 @@ var SymmetricState = class {
9321
9881
  }
9322
9882
  this.ck.set(this.h);
9323
9883
  }
9324
- mixHash(data) {
9325
- this.h.set(this.H.hash(concat(this.h, data)).subarray(0, this.H.hashLen));
9884
+ mixHash(data2) {
9885
+ this.h.set(this.H.hash(concat(this.h, data2)).subarray(0, this.H.hashLen));
9326
9886
  }
9327
9887
  mixKey(inputKeyMaterial) {
9328
9888
  const [ck, temp] = this.H.hkdf(this.ck, inputKeyMaterial);
@@ -9345,13 +9905,13 @@ var SymmetricState = class {
9345
9905
  }
9346
9906
  this.mixHash(out);
9347
9907
  }
9348
- decryptAndHash(data, out) {
9908
+ decryptAndHash(data2, out) {
9349
9909
  if (this.cipherstate) {
9350
- this.cipherstate.decryptWithAd(this.h, data, out);
9910
+ this.cipherstate.decryptWithAd(this.h, data2, out);
9351
9911
  } else {
9352
- out.set(data);
9912
+ out.set(data2);
9353
9913
  }
9354
- this.mixHash(data);
9914
+ this.mixHash(data2);
9355
9915
  }
9356
9916
  split() {
9357
9917
  if (!this.hasKey()) {
@@ -9751,7 +10311,7 @@ function createHasher2(hashCons, info = {}) {
9751
10311
  Object.assign(hashC, info);
9752
10312
  return Object.freeze(hashC);
9753
10313
  }
9754
- function randomBytes3(bytesLength = 32) {
10314
+ function randomBytes4(bytesLength = 32) {
9755
10315
  anumber2(bytesLength, "bytesLength");
9756
10316
  const cr = typeof globalThis === "object" ? globalThis.crypto : null;
9757
10317
  if (typeof cr?.getRandomValues !== "function")
@@ -9794,22 +10354,22 @@ var HashMD2 = class {
9794
10354
  this.buffer = new Uint8Array(blockLen);
9795
10355
  this.view = createView2(this.buffer);
9796
10356
  }
9797
- update(data) {
10357
+ update(data2) {
9798
10358
  aexists2(this);
9799
- abytes2(data);
10359
+ abytes2(data2);
9800
10360
  const { view, buffer, blockLen } = this;
9801
- const len = data.length;
10361
+ const len = data2.length;
9802
10362
  let processed = false;
9803
10363
  for (let pos = 0; pos < len; ) {
9804
10364
  const take = Math.min(blockLen - this.pos, len - pos);
9805
10365
  if (take === blockLen) {
9806
- const dataView = createView2(data);
10366
+ const dataView = createView2(data2);
9807
10367
  for (; blockLen <= len - pos; pos += blockLen)
9808
10368
  this.process(dataView, pos);
9809
10369
  processed = true;
9810
10370
  continue;
9811
10371
  }
9812
- buffer.set(pos === 0 && take === len ? data : data.subarray(pos, pos + take), this.pos);
10372
+ buffer.set(pos === 0 && take === len ? data2 : data2.subarray(pos, pos + take), this.pos);
9813
10373
  this.pos += take;
9814
10374
  pos += take;
9815
10375
  if (this.pos === blockLen) {
@@ -9818,7 +10378,7 @@ var HashMD2 = class {
9818
10378
  processed = true;
9819
10379
  }
9820
10380
  }
9821
- this.length += data.length;
10381
+ this.length += data2.length;
9822
10382
  if (processed)
9823
10383
  this.roundClean();
9824
10384
  return this;
@@ -10318,7 +10878,7 @@ function afunction(value, title) {
10318
10878
  var bytesToHex3 = bytesToHex2;
10319
10879
  var hexToBytes3 = (hex) => hexToBytes2(hex);
10320
10880
  var isBytes3 = isBytes2;
10321
- var randomBytes4 = (bytesLength) => randomBytes3(bytesLength);
10881
+ var randomBytes5 = (bytesLength) => randomBytes4(bytesLength);
10322
10882
  var _0n7 = /* @__PURE__ */ BigInt(0);
10323
10883
  var _1n7 = /* @__PURE__ */ BigInt(1);
10324
10884
  var atitle2 = (title) => title ? `"${title}" ` : "";
@@ -10877,18 +11437,18 @@ function validateTableBytes(numPoints, fpBytes) {
10877
11437
  if (bytes > TABLE_BYTES_MAX)
10878
11438
  throw new Error("invalid window size: table would need ~" + Math.ceil(bytes / 2 ** 20) + " MiB, max " + TABLE_BYTES_MAX / 2 ** 20 + " MiB");
10879
11439
  }
10880
- function probeRandomBytes(randomBytes7, length) {
10881
- if (randomBytes7 === void 0)
11440
+ function probeRandomBytes(randomBytes8, length) {
11441
+ if (randomBytes8 === void 0)
10882
11442
  return void 0;
10883
- afunction(randomBytes7, "randomBytes");
11443
+ afunction(randomBytes8, "randomBytes");
10884
11444
  try {
10885
- const probe = randomBytes7(length);
11445
+ const probe = randomBytes8(length);
10886
11446
  if (!isBytes3(probe) || probe.length !== length)
10887
11447
  return void 0;
10888
11448
  } catch {
10889
11449
  return void 0;
10890
11450
  }
10891
- return randomBytes7;
11451
+ return randomBytes8;
10892
11452
  }
10893
11453
  function validateMSMPoints2(points, c) {
10894
11454
  aarray(points, "points");
@@ -10981,9 +11541,9 @@ var ScalarMultiplier = class {
10981
11541
  baseCanBeBlinded;
10982
11542
  bits;
10983
11543
  // Parametrized with a given Point class (not individual point)
10984
- constructor(Point, randomBytes7) {
11544
+ constructor(Point, randomBytes8) {
10985
11545
  validatePointCons(Point);
10986
- this.randomBytes = probeRandomBytes(randomBytes7, BLIND_BYTES);
11546
+ this.randomBytes = probeRandomBytes(randomBytes8, BLIND_BYTES);
10987
11547
  this.Point = Point;
10988
11548
  this.BASE = Point.BASE;
10989
11549
  this.ZERO = Point.ZERO;
@@ -11254,7 +11814,7 @@ function edwards2(params, extraOpts = {}) {
11254
11814
  if (FpLegendre2(Fp2, CURVE.d) !== -1)
11255
11815
  throw new Error("edwards: CURVE.d must be a non-square in Fp for complete addition formulas");
11256
11816
  validateObject2(opts, {}, { uvRatio: "function", randomBytes: "function" });
11257
- const randomBytes7 = opts.randomBytes === void 0 ? randomBytes4 : opts.randomBytes;
11817
+ const randomBytes8 = opts.randomBytes === void 0 ? randomBytes5 : opts.randomBytes;
11258
11818
  const MASK = _2n6 << BigInt(Fp2.BYTES * 8) - _1n10;
11259
11819
  function isOdd(n) {
11260
11820
  if (!Fp2.isOdd)
@@ -11515,7 +12075,7 @@ function edwards2(params, extraOpts = {}) {
11515
12075
  }
11516
12076
  }
11517
12077
  const normalize = (points) => normalizeZ2(Point, points);
11518
- const wnaf = new ScalarMultiplier(Point, randomBytes7);
12078
+ const wnaf = new ScalarMultiplier(Point, randomBytes8);
11519
12079
  if (wnaf.bits >= 6)
11520
12080
  Point.BASE.precompute(6);
11521
12081
  Object.freeze(Point.prototype);
@@ -11558,7 +12118,7 @@ function montgomery2(curveDef) {
11558
12118
  const is25519 = type === "x25519";
11559
12119
  if (!is25519 && type !== "x448")
11560
12120
  throw new Error("invalid type");
11561
- const randomBytes_ = rand === void 0 ? randomBytes4 : rand;
12121
+ const randomBytes_ = rand === void 0 ? randomBytes5 : rand;
11562
12122
  const montgomeryBits = is25519 ? 255 : 448;
11563
12123
  const swap = cswap(P);
11564
12124
  const fieldLen = is25519 ? 32 : 56;
@@ -11847,11 +12407,11 @@ var NqHandshake = class {
11847
12407
  isFinished() {
11848
12408
  return this.status === 2;
11849
12409
  }
11850
- mixHash(data) {
11851
- this.symmetric.mixHash(data);
12410
+ mixHash(data2) {
12411
+ this.symmetric.mixHash(data2);
11852
12412
  }
11853
- mixKeyAndHash(data) {
11854
- this.symmetric.mixKeyAndHash(data);
12413
+ mixKeyAndHash(data2) {
12414
+ this.symmetric.mixKeyAndHash(data2);
11855
12415
  }
11856
12416
  pushPsk(psk) {
11857
12417
  if (psk.length !== PSK_LEN) {
@@ -12525,13 +13085,13 @@ var _BLAKE2 = class {
12525
13085
  this.buffer = new Uint8Array(blockLen);
12526
13086
  this.buffer32 = u32(this.buffer);
12527
13087
  }
12528
- update(data) {
13088
+ update(data2) {
12529
13089
  aexists2(this);
12530
- abytes2(data);
13090
+ abytes2(data2);
12531
13091
  const { blockLen, buffer, buffer32 } = this;
12532
- const len = data.length;
12533
- const offset = data.byteOffset;
12534
- const buf = data.buffer;
13092
+ const len = data2.length;
13093
+ const offset = data2.byteOffset;
13094
+ const buf = data2.buffer;
12535
13095
  for (let pos = 0; pos < len; ) {
12536
13096
  if (this.pos === blockLen) {
12537
13097
  swap32IfBE(buffer32);
@@ -12551,7 +13111,7 @@ var _BLAKE2 = class {
12551
13111
  swap32IfBE(data32);
12552
13112
  continue;
12553
13113
  }
12554
- buffer.set(pos === 0 && take === len ? data : data.subarray(pos, pos + take), this.pos);
13114
+ buffer.set(pos === 0 && take === len ? data2 : data2.subarray(pos, pos + take), this.pos);
12555
13115
  this.pos += take;
12556
13116
  this.length += take;
12557
13117
  pos += take;
@@ -12935,8 +13495,8 @@ var hmac = /* @__PURE__ */ (() => {
12935
13495
 
12936
13496
  // ../../node_modules/.pnpm/@lukeburns+clatterjs@1.0.0/node_modules/@lukeburns/clatterjs/dist/crypto/hash.js
12937
13497
  function makeHash(name2, hash, hashLen, blockLen) {
12938
- const doHash = (data) => hash.create().update(data).digest();
12939
- const hmac1 = (key, data) => hmac(hash, key, data);
13498
+ const doHash = (data2) => hash.create().update(data2).digest();
13499
+ const hmac1 = (key, data2) => hmac(hash, key, data2);
12940
13500
  const hmacMany = (key, parts) => hmac1(key, parts.length === 1 ? parts[0] : concatBytes2(...parts));
12941
13501
  return {
12942
13502
  name: name2,
@@ -13089,20 +13649,20 @@ var wrapCipher = /* @__NO_SIDE_EFFECTS__ */ (params, constructor) => {
13089
13649
  };
13090
13650
  let called = false;
13091
13651
  const wrCipher = {
13092
- encrypt(data, output) {
13652
+ encrypt(data2, output) {
13093
13653
  if (called)
13094
13654
  throw new Error("cannot encrypt() twice with same key + nonce");
13095
13655
  called = true;
13096
- abytes4(data, void 0, "data");
13656
+ abytes4(data2, void 0, "data");
13097
13657
  checkOutput(cipher.encrypt.length, output);
13098
- return cipher.encrypt(data, output);
13658
+ return cipher.encrypt(data2, output);
13099
13659
  },
13100
- decrypt(data, output) {
13101
- abytes4(data, void 0, "data");
13102
- if (tagl && data.length < tagl)
13660
+ decrypt(data2, output) {
13661
+ abytes4(data2, void 0, "data");
13662
+ if (tagl && data2.length < tagl)
13103
13663
  throw new Error('"ciphertext" expected length >= tagLength=' + tagl);
13104
13664
  checkOutput(cipher.decrypt.length, output);
13105
- return cipher.decrypt(data, output);
13665
+ return cipher.decrypt(data2, output);
13106
13666
  }
13107
13667
  };
13108
13668
  return wrCipher;
@@ -13146,12 +13706,12 @@ var BLOCK_LEN = 64;
13146
13706
  var BLOCK_LEN32 = 16;
13147
13707
  var MAX_COUNTER = /* @__PURE__ */ (() => 2 ** 32 - 1)();
13148
13708
  var U32_EMPTY = /* @__PURE__ */ Uint32Array.of();
13149
- function runCipher(core, sigma, key, nonce, data, output, counter, rounds) {
13150
- const len = data.length;
13709
+ function runCipher(core, sigma, key, nonce, data2, output, counter, rounds) {
13710
+ const len = data2.length;
13151
13711
  const block = new Uint8Array(BLOCK_LEN);
13152
13712
  const b32 = u322(block);
13153
- const isAligned = isLE2 && isAligned32(data) && isAligned32(output);
13154
- const d32 = isAligned ? u322(data) : U32_EMPTY;
13713
+ const isAligned = isLE2 && isAligned32(data2) && isAligned32(output);
13714
+ const d32 = isAligned ? u322(data2) : U32_EMPTY;
13155
13715
  const o32 = isAligned ? u322(output) : U32_EMPTY;
13156
13716
  if (!isLE2) {
13157
13717
  for (let pos = 0; pos < len; counter++) {
@@ -13162,7 +13722,7 @@ function runCipher(core, sigma, key, nonce, data, output, counter, rounds) {
13162
13722
  const take = Math.min(BLOCK_LEN, len - pos);
13163
13723
  for (let j = 0, posj; j < take; j++) {
13164
13724
  posj = pos + j;
13165
- output[posj] = data[posj] ^ block[j];
13725
+ output[posj] = data2[posj] ^ block[j];
13166
13726
  }
13167
13727
  pos += take;
13168
13728
  }
@@ -13186,7 +13746,7 @@ function runCipher(core, sigma, key, nonce, data, output, counter, rounds) {
13186
13746
  }
13187
13747
  for (let j = 0, posj; j < take; j++) {
13188
13748
  posj = pos + j;
13189
- output[posj] = data[posj] ^ block[j];
13749
+ output[posj] = data2[posj] ^ block[j];
13190
13750
  }
13191
13751
  pos += take;
13192
13752
  }
@@ -13199,11 +13759,11 @@ function createCipher(core, opts) {
13199
13759
  anumber4(rounds);
13200
13760
  abool2(counterRight);
13201
13761
  abool2(allowShortKeys);
13202
- return (key, nonce, data, output, counter = 0) => {
13762
+ return (key, nonce, data2, output, counter = 0) => {
13203
13763
  abytes4(key, void 0, "key");
13204
13764
  abytes4(nonce, void 0, "nonce");
13205
- abytes4(data, void 0, "data");
13206
- const len = data.length;
13765
+ abytes4(data2, void 0, "data");
13766
+ const len = data2.length;
13207
13767
  output = getOutput(len, output, false);
13208
13768
  anumber4(counter);
13209
13769
  if (counter < 0 || counter >= MAX_COUNTER)
@@ -13254,7 +13814,7 @@ function createCipher(core, opts) {
13254
13814
  }
13255
13815
  const n32 = swap32IfBE2(u322(nonce));
13256
13816
  try {
13257
- runCipher(core, sigma, k32, n32, data, output, counter, rounds);
13817
+ runCipher(core, sigma, k32, n32, data2, output, counter, rounds);
13258
13818
  return output;
13259
13819
  } finally {
13260
13820
  clean3(...toClean);
@@ -13301,7 +13861,7 @@ var Poly1305 = class {
13301
13861
  for (let i = 0; i < 8; i++)
13302
13862
  this.pad[i] = u8to16(key, 16 + 2 * i);
13303
13863
  }
13304
- process(data, offset, isLast = false) {
13864
+ process(data2, offset, isLast = false) {
13305
13865
  const hibit = isLast ? 0 : 1 << 11;
13306
13866
  const { h, r } = this;
13307
13867
  const r0 = r[0];
@@ -13314,14 +13874,14 @@ var Poly1305 = class {
13314
13874
  const r7 = r[7];
13315
13875
  const r8 = r[8];
13316
13876
  const r9 = r[9];
13317
- const t0 = u8to16(data, offset + 0);
13318
- const t1 = u8to16(data, offset + 2);
13319
- const t2 = u8to16(data, offset + 4);
13320
- const t3 = u8to16(data, offset + 6);
13321
- const t4 = u8to16(data, offset + 8);
13322
- const t5 = u8to16(data, offset + 10);
13323
- const t6 = u8to16(data, offset + 12);
13324
- const t7 = u8to16(data, offset + 14);
13877
+ const t0 = u8to16(data2, offset + 0);
13878
+ const t1 = u8to16(data2, offset + 2);
13879
+ const t2 = u8to16(data2, offset + 4);
13880
+ const t3 = u8to16(data2, offset + 6);
13881
+ const t4 = u8to16(data2, offset + 8);
13882
+ const t5 = u8to16(data2, offset + 10);
13883
+ const t6 = u8to16(data2, offset + 12);
13884
+ const t7 = u8to16(data2, offset + 14);
13325
13885
  let h0 = h[0] + (t0 & 8191);
13326
13886
  let h1 = h[1] + ((t0 >>> 13 | t1 << 3) & 8191);
13327
13887
  let h2 = h[2] + ((t1 >>> 10 | t2 << 6) & 8191);
@@ -13457,20 +14017,20 @@ var Poly1305 = class {
13457
14017
  }
13458
14018
  clean3(g);
13459
14019
  }
13460
- update(data) {
14020
+ update(data2) {
13461
14021
  aexists3(this);
13462
- abytes4(data);
13463
- data = copyBytes4(data);
14022
+ abytes4(data2);
14023
+ data2 = copyBytes4(data2);
13464
14024
  const { buffer, blockLen } = this;
13465
- const len = data.length;
14025
+ const len = data2.length;
13466
14026
  for (let pos = 0; pos < len; ) {
13467
14027
  const take = Math.min(blockLen - this.pos, len - pos);
13468
14028
  if (take === blockLen) {
13469
14029
  for (; blockLen <= len - pos; pos += blockLen)
13470
- this.process(data, pos);
14030
+ this.process(data2, pos);
13471
14031
  continue;
13472
14032
  }
13473
- buffer.set(data.subarray(pos, pos + take), this.pos);
14033
+ buffer.set(data2.subarray(pos, pos + take), this.pos);
13474
14034
  this.pos += take;
13475
14035
  pos += take;
13476
14036
  if (this.pos === blockLen) {
@@ -13644,9 +14204,9 @@ var _poly1305_aead = (xorStream) => (key, nonce, AAD) => {
13644
14204
  },
13645
14205
  decrypt(ciphertext, output) {
13646
14206
  output = getOutput(ciphertext.length - tagLength, output, false);
13647
- const data = ciphertext.subarray(0, -tagLength);
14207
+ const data2 = ciphertext.subarray(0, -tagLength);
13648
14208
  const passedTag = ciphertext.subarray(-tagLength);
13649
- const tag = computeTag(xorStream, key, nonce, data, AAD);
14209
+ const tag = computeTag(xorStream, key, nonce, data2, AAD);
13650
14210
  if (!equalBytes2(passedTag, tag)) {
13651
14211
  clean3(tag);
13652
14212
  throw new Error("invalid tag");
@@ -13765,7 +14325,7 @@ var NoiseIkSession = class {
13765
14325
  ...initiator ? { rs: Uint8Array.from(this.expectedRemoteStatic) } : {},
13766
14326
  cipher: chachaPoly,
13767
14327
  hash: sha256H,
13768
- rng: options.random ?? randomBytes
14328
+ rng: options.random ?? randomBytes2
13769
14329
  });
13770
14330
  if (this.handshake.getName() !== NOISE_IK_PROTOCOL) {
13771
14331
  throw new NoiseSessionError("NOISE_SUITE_MISMATCH", "The Noise provider selected an unexpected cipher suite.");
@@ -13915,6 +14475,7 @@ var ClientSecureTransport = class {
13915
14475
  incoming = new SecureMessageCodec();
13916
14476
  outgoing = new SecureMessageCodec();
13917
14477
  closed = false;
14478
+ sends = new SerialSend();
13918
14479
  async connect() {
13919
14480
  this.closed = false;
13920
14481
  this.incoming.reset();
@@ -13942,12 +14503,14 @@ var ClientSecureTransport = class {
13942
14503
  throw error;
13943
14504
  }
13944
14505
  }
13945
- async send(data) {
13946
- const plaintextFrames = this.outgoing.encode(data);
14506
+ async send(data2) {
14507
+ if (data2.byteLength > MAX_SECURE_MESSAGE_BYTES) throw new Error("Secure message exceeds the reassembly limit.");
14508
+ const noise = this.requireNoise();
13947
14509
  try {
13948
- for (const plaintext of plaintextFrames) {
13949
- await this.inner.send(this.requireNoise().encrypt(plaintext));
13950
- }
14510
+ await this.sends.run(data2.byteLength, async () => {
14511
+ if (this.closed || this.noise !== noise) throw new Error("Secure transport replaced");
14512
+ for (const plaintext of this.outgoing.encode(data2)) await this.inner.send(noise.encrypt(plaintext));
14513
+ });
13951
14514
  } catch (error) {
13952
14515
  await this.close().catch(() => void 0);
13953
14516
  throw error;
@@ -13955,11 +14518,11 @@ var ClientSecureTransport = class {
13955
14518
  }
13956
14519
  onMessage(handler) {
13957
14520
  this.unsubscribeInner?.();
13958
- this.unsubscribeInner = this.inner.onMessage((data) => {
14521
+ this.unsubscribeInner = this.inner.onMessage((data2) => {
13959
14522
  const noise = this.noise;
13960
14523
  if (noise === void 0 || !noise.complete || this.closed) return;
13961
14524
  try {
13962
- const message = this.incoming.decode(noise.decrypt(data));
14525
+ const message = this.incoming.decode(noise.decrypt(data2));
13963
14526
  if (message !== void 0) handler(message);
13964
14527
  } catch {
13965
14528
  void this.close();
@@ -13998,11 +14561,11 @@ async function waitForResponder(inner, noise) {
13998
14561
  await new Promise((resolve3, reject) => {
13999
14562
  let settled = false;
14000
14563
  const timer = setTimeout(() => finish(new Error("Noise IK handshake timed out.")), 1e4);
14001
- const unsubscribe = inner.onHandshake((step, data) => {
14564
+ const unsubscribe = inner.onHandshake((step, data2) => {
14002
14565
  if (settled) return;
14003
14566
  try {
14004
14567
  if (step !== 2) throw new Error("Noise IK responder sent an out-of-order handshake message.");
14005
- noise.readHandshake(data);
14568
+ noise.readHandshake(data2);
14006
14569
  if (!noise.complete) throw new Error("Noise IK handshake did not complete.");
14007
14570
  finish();
14008
14571
  } catch (error) {
@@ -14025,16 +14588,19 @@ async function waitForResponder(inner, noise) {
14025
14588
 
14026
14589
  // src/control-route.ts
14027
14590
  var CONTROL_RPC_PREFIX = "/ds-harness-remote";
14591
+ var STATUS_STREAM_ENDPOINT = "status.events";
14592
+ var STATUS_STREAM_PATH = `${CONTROL_RPC_PREFIX}/${STATUS_STREAM_ENDPOINT}`;
14028
14593
  var ENDPOINT_SEGMENT_PATTERN = /^[A-Za-z0-9_$.-]+$/;
14029
14594
  var INVALID_REQUEST_RPC_ID = "invalid-request";
14030
- function registerControlRoute(connection, handler, webServer) {
14595
+ function registerControlRoute(connection, handler, webServer, statusStream) {
14031
14596
  if (webServer !== void 0 && connection.requestRejection !== void 0) {
14032
14597
  const dispose = webServer.register({
14033
14598
  kind: "prefix",
14034
14599
  path: CONTROL_RPC_PREFIX,
14035
- handler: (req, res) => handleControlRequest(connection, handler, req, res)
14600
+ handler: (req, res) => handleControlRequest(connection, handler, req, res, statusStream)
14036
14601
  });
14037
14602
  return async () => {
14603
+ statusStream?.close();
14038
14604
  await dispose();
14039
14605
  };
14040
14606
  }
@@ -14042,7 +14608,7 @@ function registerControlRoute(connection, handler, webServer) {
14042
14608
  authority: "loopback"
14043
14609
  });
14044
14610
  }
14045
- async function handleControlRequest(connection, handler, req, res) {
14611
+ async function handleControlRequest(connection, handler, req, res, statusStream) {
14046
14612
  const rejection = connection.requestRejection?.(req);
14047
14613
  if (rejection !== void 0) {
14048
14614
  res.writeHead(rejection);
@@ -14050,6 +14616,10 @@ async function handleControlRequest(connection, handler, req, res) {
14050
14616
  return;
14051
14617
  }
14052
14618
  const endpoint = endpointFromPath(CONTROL_RPC_PREFIX, new URL(req.url ?? "/", "http://dsh.internal").pathname);
14619
+ if (req.method === "GET" && endpoint === STATUS_STREAM_ENDPOINT && statusStream !== void 0) {
14620
+ await statusStream.handle(res);
14621
+ return;
14622
+ }
14053
14623
  if (req.method !== "POST" || endpoint === void 0) {
14054
14624
  writeText(res, 404, "not found");
14055
14625
  return;
@@ -14141,12 +14711,12 @@ function writeText(res, status2, body) {
14141
14711
  }
14142
14712
 
14143
14713
  // src/ids.ts
14144
- import { randomBytes as randomBytes6 } from "node:crypto";
14714
+ import { randomBytes as randomBytes7 } from "node:crypto";
14145
14715
  function uuidV7(now = Date.now()) {
14146
14716
  if (!Number.isSafeInteger(now) || now < 0 || now > 281474976710655) {
14147
14717
  throw new RangeError("UUIDv7 timestamp must be a non-negative 48-bit integer");
14148
14718
  }
14149
- const bytes = randomBytes6(16);
14719
+ const bytes = randomBytes7(16);
14150
14720
  let timestamp = BigInt(now);
14151
14721
  for (let index = 5; index >= 0; index -= 1) {
14152
14722
  bytes[index] = Number(timestamp & 0xffn);
@@ -14460,13 +15030,13 @@ var AsyncFrameQueue = class {
14460
15030
  };
14461
15031
  function routeStreamEvent(event, streamId, queue) {
14462
15032
  if (event.event === "harness.api.frame") {
14463
- const data = event.data;
14464
- if (data.streamId !== streamId || typeof data.frame !== "object" || data.frame === null || typeof data.frame.rpcId !== "string" || !("payload" in data.frame)) return;
14465
- queue.push(data.frame);
15033
+ const data2 = event.data;
15034
+ if (data2.streamId !== streamId || typeof data2.frame !== "object" || data2.frame === null || typeof data2.frame.rpcId !== "string" || !("payload" in data2.frame)) return;
15035
+ queue.push(data2.frame);
14466
15036
  }
14467
15037
  if (event.event === "harness.api.stream.closed") {
14468
- const data = event.data;
14469
- if (data.streamId === streamId) queue.close();
15038
+ const data2 = event.data;
15039
+ if (data2.streamId === streamId) queue.close();
14470
15040
  }
14471
15041
  }
14472
15042
  function bytesToBase642(bytes) {
@@ -14575,8 +15145,8 @@ function normalizeEvent(value) {
14575
15145
  const { sourceEventSeqs: _sourceEventSeqs, ...rest } = next;
14576
15146
  next = rest;
14577
15147
  }
14578
- const data = normalizeEventData(type, next.data);
14579
- if (data !== next.data) next = { ...next, data };
15148
+ const data2 = normalizeEventData(type, next.data);
15149
+ if (data2 !== next.data) next = { ...next, data: data2 };
14580
15150
  const surfaceOp = normalizeSurfaceOp(next.surfaceOp);
14581
15151
  if (surfaceOp !== next.surfaceOp) next = { ...next, surfaceOp };
14582
15152
  return next;
@@ -14774,14 +15344,26 @@ var RemoteTypertGateway2 = class {
14774
15344
  };
14775
15345
  var AsyncValueQueue = class {
14776
15346
  values = [];
15347
+ bytes = 0;
14777
15348
  waiters = [];
14778
15349
  closed = false;
14779
15350
  error;
14780
15351
  push(value) {
14781
15352
  if (this.closed) return;
14782
15353
  const waiter = this.waiters.shift();
14783
- if (waiter === void 0) this.values.push(value);
14784
- else waiter({ done: false, value });
15354
+ if (waiter !== void 0) {
15355
+ waiter({ done: false, value });
15356
+ return;
15357
+ }
15358
+ const bytes = new TextEncoder().encode(JSON.stringify(value) ?? "").byteLength;
15359
+ if (this.values.length >= 256 || this.bytes + bytes > 4 * 1024 * 1024) {
15360
+ this.values.length = 0;
15361
+ this.bytes = 0;
15362
+ this.fail(new RemoteClientError("TRANSPORT_CLOSED", "Remote stream consumer is too slow; reconnect to recover."));
15363
+ return;
15364
+ }
15365
+ this.bytes += bytes;
15366
+ this.values.push({ value, bytes });
14785
15367
  }
14786
15368
  close() {
14787
15369
  if (this.closed) return;
@@ -14796,7 +15378,9 @@ var AsyncValueQueue = class {
14796
15378
  async *[Symbol.asyncIterator]() {
14797
15379
  while (true) {
14798
15380
  if (this.values.length > 0) {
14799
- yield this.values.shift();
15381
+ const entry = this.values.shift();
15382
+ this.bytes -= entry.bytes;
15383
+ yield entry.value;
14800
15384
  continue;
14801
15385
  }
14802
15386
  if (this.closed) {
@@ -14814,15 +15398,15 @@ var AsyncValueQueue = class {
14814
15398
  };
14815
15399
  function routeStreamEvent2(event, streamId, queue) {
14816
15400
  if (event.event === "harness.remote.frame") {
14817
- const data2 = event.data;
14818
- if (data2.streamId === streamId && data2.hasValue === true) queue.push(data2.value);
15401
+ const data3 = event.data;
15402
+ if (data3.streamId === streamId && data3.hasValue === true) queue.push(data3.value);
14819
15403
  return;
14820
15404
  }
14821
15405
  if (event.event !== "harness.remote.stream.closed") return;
14822
- const data = event.data;
14823
- if (data.streamId !== streamId) return;
14824
- if (data.reason === "failed") {
14825
- const failure2 = data.failure;
15406
+ const data2 = event.data;
15407
+ if (data2.streamId !== streamId) return;
15408
+ if (data2.reason === "failed") {
15409
+ const failure2 = data2.failure;
14826
15410
  queue.fail(remoteFailure({
14827
15411
  code: typeof failure2?.code === "string" ? failure2.code : "internal",
14828
15412
  message: typeof failure2?.message === "string" ? failure2.message : "The remote Harness stream failed.",
@@ -15589,7 +16173,7 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
15589
16173
  follow.liveToolResultSeq.delete(itemId);
15590
16174
  follow.liveToolResultSeq.delete(`${itemId}:call`);
15591
16175
  }
15592
- pushEvent(follow, type, data, view, placement) {
16176
+ pushEvent(follow, type, data2, view, placement) {
15593
16177
  const seq = follow.nextSeq++;
15594
16178
  const event = {
15595
16179
  type,
@@ -15597,7 +16181,7 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
15597
16181
  time: Date.now(),
15598
16182
  data: adaptEventData(
15599
16183
  type,
15600
- type === "assistant/message" && this.sessionGeneration === "v3" && follow.assistantAttempt !== void 0 ? { ...record2(data), stream: follow.assistantAttempt.stream } : data,
16184
+ type === "assistant/message" && this.sessionGeneration === "v3" && follow.assistantAttempt !== void 0 ? { ...record2(data2), stream: follow.assistantAttempt.stream } : data2,
15601
16185
  this.sessionGeneration
15602
16186
  ),
15603
16187
  ...isSurfaceEvent(type) ? placement === void 0 ? { surfaceOp: "append" } : {
@@ -15625,9 +16209,9 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
15625
16209
  for (const block of collectImageBlocks(value)) {
15626
16210
  const attachment = record2(block.attachment);
15627
16211
  const attachmentId = string(attachment.attachmentId);
15628
- const data = string(block.data);
15629
- if (attachmentId === void 0 || data === void 0 || !attachmentId.startsWith(CODEX_IMAGE_ATTACHMENT_PREFIX)) continue;
15630
- this.imageAttachments.set(attachmentId, { sessionId, attachment, data });
16212
+ const data2 = string(block.data);
16213
+ if (attachmentId === void 0 || data2 === void 0 || !attachmentId.startsWith(CODEX_IMAGE_ATTACHMENT_PREFIX)) continue;
16214
+ this.imageAttachments.set(attachmentId, { sessionId, attachment, data: data2 });
15631
16215
  }
15632
16216
  }
15633
16217
  ensureStreamBlock(follow, key, itemId, kind) {
@@ -16287,7 +16871,7 @@ async function loadCatalog(client, signal, pendingThreads) {
16287
16871
  if (cursor2 === void 0) break;
16288
16872
  }
16289
16873
  if (pendingThreads !== void 0) {
16290
- const listedIds = new Set(threads.map((thread) => string(thread.id)).filter((id2) => id2 !== void 0));
16874
+ const listedIds = new Set(threads.map((thread) => string(thread.id)).filter((id4) => id4 !== void 0));
16291
16875
  for (const [threadId, thread] of pendingThreads) {
16292
16876
  if (listedIds.has(threadId)) pendingThreads.delete(threadId);
16293
16877
  else threads.unshift(thread);
@@ -16362,8 +16946,8 @@ async function loadModelDirectory(client, signal) {
16362
16946
  for (const value of array(result.data)) {
16363
16947
  const source = record2(value);
16364
16948
  if (source.hidden === true) continue;
16365
- const id2 = string(source.model) ?? string(source.id);
16366
- if (id2 === void 0 || models.has(id2)) continue;
16949
+ const id4 = string(source.model) ?? string(source.id);
16950
+ if (id4 === void 0 || models.has(id4)) continue;
16367
16951
  const efforts = array(source.supportedReasoningEfforts).map((value2) => {
16368
16952
  const effort = record2(value2);
16369
16953
  const effortId = string(effort.reasoningEffort);
@@ -16378,8 +16962,8 @@ async function loadModelDirectory(client, signal) {
16378
16962
  const defaultEffort2 = string(source.defaultReasoningEffort);
16379
16963
  const description = string(source.description);
16380
16964
  const model = {
16381
- id: id2,
16382
- name: string(source.displayName) ?? id2,
16965
+ id: id4,
16966
+ name: string(source.displayName) ?? id4,
16383
16967
  ...description === void 0 ? {} : { description },
16384
16968
  ...efforts.length === 0 ? {} : {
16385
16969
  reasoning: {
@@ -16388,8 +16972,8 @@ async function loadModelDirectory(client, signal) {
16388
16972
  }
16389
16973
  }
16390
16974
  };
16391
- models.set(id2, model);
16392
- if (source.isDefault === true) defaultModelId = id2;
16975
+ models.set(id4, model);
16976
+ if (source.isDefault === true) defaultModelId = id4;
16393
16977
  }
16394
16978
  cursor2 = typeof result.nextCursor === "string" && result.nextCursor.length > 0 ? result.nextCursor : void 0;
16395
16979
  if (cursor2 === void 0) break;
@@ -16412,7 +16996,7 @@ function projectCodexNativeHistory(thread, sessionId, sessionGeneration = "legac
16412
16996
  let seq = 0;
16413
16997
  let turnNumber = 0;
16414
16998
  const active = activeTurnId(thread.turns);
16415
- const append = (type, data, time = Date.now(), view, sourceEventSeqs) => {
16999
+ const append = (type, data2, time = Date.now(), view, sourceEventSeqs) => {
16416
17000
  const eventSeq = seq++;
16417
17001
  entries.push({
16418
17002
  type: "event",
@@ -16420,7 +17004,7 @@ function projectCodexNativeHistory(thread, sessionId, sessionGeneration = "legac
16420
17004
  type,
16421
17005
  seq: eventSeq,
16422
17006
  time,
16423
- data: adaptEventData(type, data, sessionGeneration),
17007
+ data: adaptEventData(type, data2, sessionGeneration),
16424
17008
  ...sourceEventSeqs === void 0 ? {} : { sourceEventSeqs },
16425
17009
  ...isSurfaceEvent(type) ? { surfaceOp: "append" } : {}
16426
17010
  },
@@ -16510,9 +17094,9 @@ function paginateCodexNativeHistory(history, request) {
16510
17094
  ...history.activeTurnId === void 0 ? {} : { activeTurnId: history.activeTurnId }
16511
17095
  };
16512
17096
  }
16513
- function adaptEventData(type, data, sessionGeneration) {
16514
- if (sessionGeneration !== "v3" || type !== "assistant/message") return data;
16515
- const value = record2(data);
17097
+ function adaptEventData(type, data2, sessionGeneration) {
17098
+ if (sessionGeneration !== "v3" || type !== "assistant/message") return data2;
17099
+ const value = record2(data2);
16516
17100
  return Array.isArray(value.stream) ? value : { ...value, stream: [] };
16517
17101
  }
16518
17102
  function adaptCodexHistoryPage(value, sessionGeneration) {
@@ -16546,14 +17130,14 @@ function adaptCodexHistoryPage(value, sessionGeneration) {
16546
17130
  }
16547
17131
  function itemEvents(item, turn, step, requestId, selection = modelSelection(), sessionId = CODEX_SESSION_PREFIX, supplementalImages = []) {
16548
17132
  const type = string(item.type);
16549
- const id2 = string(item.id) ?? `${turn}:${step}:${hashString(JSON.stringify(item))}`;
17133
+ const id4 = string(item.id) ?? `${turn}:${step}:${hashString(JSON.stringify(item))}`;
16550
17134
  const text = itemText2(item);
16551
17135
  if (type === "userMessage") return [{
16552
17136
  type: "user/message",
16553
17137
  data: {
16554
- id: id2,
17138
+ id: id4,
16555
17139
  role: "user",
16556
- content: messageContent(item, `${sessionId}:${id2}`, "text", text),
17140
+ content: messageContent(item, `${sessionId}:${id4}`, "text", text),
16557
17141
  source: requestId === void 0 || requestId === "" ? { kind: "user" } : { kind: "user", rpcId: requestId }
16558
17142
  }
16559
17143
  }];
@@ -16563,11 +17147,11 @@ function itemEvents(item, turn, step, requestId, selection = modelSelection(), s
16563
17147
  turn,
16564
17148
  step,
16565
17149
  message: {
16566
- id: id2,
17150
+ id: id4,
16567
17151
  role: "assistant",
16568
17152
  content: messageContent(
16569
17153
  item,
16570
- `${sessionId}:${id2}`,
17154
+ `${sessionId}:${id4}`,
16571
17155
  type === "reasoning" ? "reasoning" : "text",
16572
17156
  text,
16573
17157
  type === "agentMessage" ? supplementalImages : []
@@ -16582,10 +17166,10 @@ function itemEvents(item, turn, step, requestId, selection = modelSelection(), s
16582
17166
  return [
16583
17167
  {
16584
17168
  type: "tool/call",
16585
- data: { turn, step, callId: id2, name: name2, arguments: JSON.stringify(args) },
17169
+ data: { turn, step, callId: id4, name: name2, arguments: JSON.stringify(args) },
16586
17170
  view: { for: "call", view: toolCallView(item, type, args) }
16587
17171
  },
16588
- toolResultEvent(item, turn, step, id2, toolResultText(item, type, text))
17172
+ toolResultEvent(item, turn, step, id4, toolResultText(item, type, text))
16589
17173
  ];
16590
17174
  }
16591
17175
  if (type === "error") return [{
@@ -16594,7 +17178,7 @@ function itemEvents(item, turn, step, requestId, selection = modelSelection(), s
16594
17178
  turn,
16595
17179
  step,
16596
17180
  message: {
16597
- id: id2,
17181
+ id: id4,
16598
17182
  role: "assistant",
16599
17183
  content: [{ type: "text", text: text ?? "CodeX reported an error." }],
16600
17184
  source: { kind: "model", provider: selection.provider, model: selection.model }
@@ -16681,19 +17265,19 @@ function parseImageBlock(block) {
16681
17265
  const url = string(block.url) ?? string(block.image_url) ?? string(record2(block.image_url).url);
16682
17266
  const parsed = url === void 0 ? void 0 : parseDataImageUrl2(url);
16683
17267
  if (parsed !== void 0) return parsed;
16684
- const data = string(block.data);
16685
- if (data === void 0 || !isCanonicalBase642(data)) return void 0;
16686
- if (data.length > MAX_CODEX_IMAGE_BASE64) return void 0;
16687
- const mediaType = string(block.mediaType) ?? string(block.mimeType) ?? sniffImageMediaType2(data);
16688
- return mediaType !== void 0 && CODEX_IMAGE_MEDIA_TYPES2.has(mediaType) ? { mediaType, data, url: `data:${mediaType};base64,${data}` } : void 0;
17268
+ const data2 = string(block.data);
17269
+ if (data2 === void 0 || !isCanonicalBase642(data2)) return void 0;
17270
+ if (data2.length > MAX_CODEX_IMAGE_BASE64) return void 0;
17271
+ const mediaType = string(block.mediaType) ?? string(block.mimeType) ?? sniffImageMediaType2(data2);
17272
+ return mediaType !== void 0 && CODEX_IMAGE_MEDIA_TYPES2.has(mediaType) ? { mediaType, data: data2, url: `data:${mediaType};base64,${data2}` } : void 0;
16689
17273
  }
16690
17274
  function parseDataImageUrl2(value) {
16691
17275
  const match = DATA_IMAGE_URL2.exec(value);
16692
17276
  if (match === null) return void 0;
16693
17277
  const mediaType = match[1];
16694
- const data = match[2];
16695
- if (!CODEX_IMAGE_MEDIA_TYPES2.has(mediaType) || !isCanonicalBase642(data)) return void 0;
16696
- return { mediaType, data, url: `data:${mediaType};base64,${data}` };
17278
+ const data2 = match[2];
17279
+ if (!CODEX_IMAGE_MEDIA_TYPES2.has(mediaType) || !isCanonicalBase642(data2)) return void 0;
17280
+ return { mediaType, data: data2, url: `data:${mediaType};base64,${data2}` };
16697
17281
  }
16698
17282
  function collectImageBlocks(value, output = []) {
16699
17283
  if (Array.isArray(value)) {
@@ -16714,8 +17298,8 @@ function base64ByteLength(value) {
16714
17298
  const padding = value.endsWith("==") ? 2 : value.endsWith("=") ? 1 : 0;
16715
17299
  return Math.max(1, Math.floor(value.length * 3 / 4) - padding);
16716
17300
  }
16717
- function imageDimensions(mediaType, data) {
16718
- const bytes = base64PrefixBytes2(data, 256 * 1024);
17301
+ function imageDimensions(mediaType, data2) {
17302
+ const bytes = base64PrefixBytes2(data2, 256 * 1024);
16719
17303
  const parsed = mediaType === "image/png" ? pngDimensions(bytes) : mediaType === "image/jpeg" ? jpegDimensions(bytes) : mediaType === "image/gif" ? gifDimensions(bytes) : mediaType === "image/webp" ? webpDimensions(bytes) : void 0;
16720
17304
  return parsed ?? { width: 1, height: 1 };
16721
17305
  }
@@ -16768,8 +17352,8 @@ function webpDimensions(bytes) {
16768
17352
  }
16769
17353
  return void 0;
16770
17354
  }
16771
- function sniffImageMediaType2(data) {
16772
- const bytes = base64PrefixBytes2(data, 32);
17355
+ function sniffImageMediaType2(data2) {
17356
+ const bytes = base64PrefixBytes2(data2, 32);
16773
17357
  if (pngDimensions(bytes) !== void 0) return "image/png";
16774
17358
  if (bytes.length >= 3 && bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255) return "image/jpeg";
16775
17359
  if (gifDimensions(bytes) !== void 0) return "image/gif";
@@ -16812,7 +17396,7 @@ function stringFromBytes(bytes, start, end) {
16812
17396
  for (let index = start; index < end; index += 1) output += String.fromCharCode(bytes[index]);
16813
17397
  return output;
16814
17398
  }
16815
- function toolResultEvent(item, turn, step, id2, resultText) {
17399
+ function toolResultEvent(item, turn, step, id4, resultText) {
16816
17400
  const type = string(item.type) ?? "unknown";
16817
17401
  return {
16818
17402
  type: "tool/result",
@@ -16820,23 +17404,23 @@ function toolResultEvent(item, turn, step, id2, resultText) {
16820
17404
  turn,
16821
17405
  step,
16822
17406
  message: {
16823
- id: `${id2}:result`,
17407
+ id: `${id4}:result`,
16824
17408
  role: "user",
16825
17409
  content: [{
16826
17410
  type: "tool-result",
16827
- toolCallId: id2,
17411
+ toolCallId: id4,
16828
17412
  content: [{ type: "text", text: resultText }],
16829
17413
  ...item.status === "failed" || item.success === false ? { isError: true } : {}
16830
17414
  }],
16831
- source: { kind: "tool", callId: id2 }
17415
+ source: { kind: "tool", callId: id4 }
16832
17416
  }
16833
17417
  },
16834
17418
  view: { for: "result", view: toolResultView(item, type, resultText) }
16835
17419
  };
16836
17420
  }
16837
17421
  function toolResultContentReplacement(event) {
16838
- const data = record2(event.data);
16839
- const message = record2(data.message);
17422
+ const data2 = record2(event.data);
17423
+ const message = record2(data2.message);
16840
17424
  const content = array(message.content);
16841
17425
  const result = record2(content[0]);
16842
17426
  if (result.isError === void 0) return event;
@@ -16844,7 +17428,7 @@ function toolResultContentReplacement(event) {
16844
17428
  return {
16845
17429
  ...event,
16846
17430
  data: {
16847
- ...data,
17431
+ ...data2,
16848
17432
  message: { ...message, content: [stableResult] }
16849
17433
  }
16850
17434
  };
@@ -16978,12 +17562,12 @@ function projectWorkspace(project) {
16978
17562
  }
16979
17563
  function projectCodexProject(value, fallbackPosition) {
16980
17564
  const source = record2(value);
16981
- const id2 = string(source.id);
16982
- if (id2 === void 0) return void 0;
17565
+ const id4 = string(source.id);
17566
+ if (id4 === void 0) return void 0;
16983
17567
  const roots = array(source.roots).map((root) => string(record2(root).path)).filter((path) => path !== void 0 && path.length > 0).map((path) => ({ path }));
16984
17568
  if (roots.length === 0) return void 0;
16985
17569
  return {
16986
- id: id2,
17570
+ id: id4,
16987
17571
  name: string(source.name) ?? basename(roots[0].path),
16988
17572
  roots,
16989
17573
  position: finiteNumber(source.position) ?? fallbackPosition,
@@ -17409,6 +17993,105 @@ var AsyncValueQueue2 = class {
17409
17993
  // src/server-api.ts
17410
17994
  import { platform } from "node:os";
17411
17995
 
17996
+ // src/server-credentials.ts
17997
+ import { chmod, mkdir, readFile as readFile2, rename, rm, stat, writeFile } from "node:fs/promises";
17998
+ import { dirname as dirname2, join as join2 } from "node:path";
17999
+ var credentialSchema = external_exports.object({
18000
+ schemaVersion: external_exports.literal(1),
18001
+ serverUrl: external_exports.string().url(),
18002
+ deviceId: external_exports.string().min(1),
18003
+ authorizationMethod: external_exports.enum(["account", "host_registration_code", "owned_device"]),
18004
+ account: external_exports.string().min(1).max(254).optional(),
18005
+ accessToken: external_exports.string().min(16),
18006
+ accessTokenExpiresAt: external_exports.number().int().positive(),
18007
+ refreshToken: external_exports.string().min(16),
18008
+ refreshTokenExpiresAt: external_exports.number().int().positive()
18009
+ }).strict();
18010
+ var ServerCredentialStore = class {
18011
+ path;
18012
+ constructor(directory) {
18013
+ this.path = join2(directory, "server-credentials.json");
18014
+ }
18015
+ /** Serialize the complete read/refresh/write transaction across processes.
18016
+ * Never steal an old lock: a suspended owner may still consume a one-use token.
18017
+ * After a crash, stop all instances before removing the orphaned lock.
18018
+ */
18019
+ async withRefreshLock(operation) {
18020
+ const lock = `${this.path}.refresh-lock`;
18021
+ await mkdir(dirname2(this.path), { recursive: true, mode: 448 });
18022
+ const deadline = Date.now() + 15e3;
18023
+ for (; ; ) {
18024
+ try {
18025
+ await mkdir(lock, { mode: 448 });
18026
+ break;
18027
+ } catch (error) {
18028
+ if (!(error instanceof Error) || !("code" in error) || error.code !== "EEXIST") throw error;
18029
+ if (Date.now() >= deadline) throw new ServerCredentialsBusyError();
18030
+ await new Promise((resolve3) => setTimeout(resolve3, 50));
18031
+ }
18032
+ }
18033
+ try {
18034
+ return await operation();
18035
+ } finally {
18036
+ await rm(lock, { recursive: true });
18037
+ }
18038
+ }
18039
+ async load(serverUrl, deviceId) {
18040
+ if (!await exists(this.path)) return void 0;
18041
+ await assertPrivateMode(this.path);
18042
+ let parsed;
18043
+ try {
18044
+ parsed = credentialSchema.parse(JSON.parse(await readFile2(this.path, "utf8")));
18045
+ } catch (error) {
18046
+ throw new ServerCredentialsInvalidError(`server credentials are invalid: ${safeMessage(error)}`);
18047
+ }
18048
+ return parsed.serverUrl === serverUrl && parsed.deviceId === deviceId ? parsed : void 0;
18049
+ }
18050
+ async save(credentials) {
18051
+ const record6 = credentialSchema.parse({ schemaVersion: 1, ...credentials });
18052
+ await atomicWrite(this.path, `${JSON.stringify(record6, null, 2)}
18053
+ `);
18054
+ return record6;
18055
+ }
18056
+ async clear() {
18057
+ await rm(this.path, { force: true });
18058
+ }
18059
+ };
18060
+ var ServerCredentialsInvalidError = class extends Error {
18061
+ code = "SERVER_CREDENTIALS_INVALID";
18062
+ };
18063
+ var ServerCredentialsBusyError = class extends Error {
18064
+ code = "SERVER_CREDENTIALS_BUSY";
18065
+ constructor() {
18066
+ super("Credential refresh is locked. Stop other instances; after a crash, stop all instances before removing server-credentials.json.refresh-lock and authorizing again.");
18067
+ }
18068
+ };
18069
+ async function atomicWrite(path, contents) {
18070
+ await mkdir(dirname2(path), { recursive: true, mode: 448 });
18071
+ const temporary = `${path}.${process.pid}.${uuidV7()}.tmp`;
18072
+ await writeFile(temporary, contents, { encoding: "utf8", mode: 384, flag: "wx" });
18073
+ await chmod(temporary, 384);
18074
+ await rename(temporary, path);
18075
+ await chmod(path, 384);
18076
+ }
18077
+ async function assertPrivateMode(path) {
18078
+ if (process.platform === "win32") return;
18079
+ const mode = (await stat(path)).mode & 511;
18080
+ if ((mode & 63) !== 0) throw new ServerCredentialsInvalidError("server credentials permissions must be 0600");
18081
+ }
18082
+ async function exists(path) {
18083
+ try {
18084
+ await stat(path);
18085
+ return true;
18086
+ } catch (error) {
18087
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return false;
18088
+ throw error;
18089
+ }
18090
+ }
18091
+ function safeMessage(error) {
18092
+ return error instanceof Error ? error.message : "invalid credential data";
18093
+ }
18094
+
17412
18095
  // src/config.ts
17413
18096
  import { hostname } from "node:os";
17414
18097
  import s from "@deepseek-ai/schemastery";
@@ -17418,6 +18101,8 @@ var Config = s.object({
17418
18101
  role: s.union(["host", "client", "both"]),
17419
18102
  serverUrl: s.string(),
17420
18103
  deviceName: s.string(),
18104
+ terminal: s.object({ enabled: s.boolean() }),
18105
+ loopback: s.object({ ports: s.array(s.number()) }),
17421
18106
  forceRelay: s.boolean(),
17422
18107
  logLevel: s.union(["debug", "info", "warn", "error"]),
17423
18108
  reconnect: s.union([
@@ -17447,6 +18132,8 @@ var configSchema = external_exports.object({
17447
18132
  role: external_exports.enum(["host", "client", "both"]).optional(),
17448
18133
  serverUrl: external_exports.string().url().optional(),
17449
18134
  deviceName: external_exports.string().trim().min(1).max(80).optional(),
18135
+ terminal: external_exports.object({ enabled: external_exports.boolean().optional() }).strict().optional(),
18136
+ loopback: external_exports.object({ ports: external_exports.array(external_exports.number().int().min(1024).max(65535)).max(16).optional() }).strict().optional(),
17450
18137
  forceRelay: external_exports.boolean().optional(),
17451
18138
  logLevel: external_exports.enum(["debug", "info", "warn", "error"]).optional(),
17452
18139
  reconnect: reconnectSchema.optional(),
@@ -17471,6 +18158,8 @@ function resolveConfig(input2 = {}, env = process.env) {
17471
18158
  role: parsed.role ?? "host",
17472
18159
  ...serverUrl === void 0 ? {} : { serverUrl },
17473
18160
  deviceName: parsed.deviceName ?? hostname(),
18161
+ terminal: { enabled: parsed.terminal?.enabled ?? false },
18162
+ loopback: { ports: [...new Set(parsed.loopback?.ports ?? [])] },
17474
18163
  forceRelay: parsed.forceRelay ?? false,
17475
18164
  logLevel: parsed.logLevel ?? "info",
17476
18165
  reconnect: {
@@ -17483,10 +18172,10 @@ function resolveConfig(input2 = {}, env = process.env) {
17483
18172
  enabled: parsed.codex?.enabled ?? true,
17484
18173
  binary: parsed.codex?.binary ?? "codex"
17485
18174
  },
17486
- acp: { enabled: parsed.acp?.enabled ?? true, backends: [.../* @__PURE__ */ new Set(["codex", "cursor", "kimi", ...parsed.acp?.backends?.map((item) => item.id) ?? []])].map((id2) => {
17487
- const d = parsed.acp?.backends?.find((x) => x.id === id2);
17488
- const legacy = parsed.acp?.backend === id2 ? parsed.acp : void 0;
17489
- return { id: id2, enabled: d?.enabled ?? legacy?.enabled ?? true, command: d?.command ?? legacy?.command ?? { codex: "codex", cursor: "agent", kimi: "kimi" }[id2] ?? id2, args: d?.args ?? legacy?.args ?? ["acp"], ...d?.cwd ?? legacy?.cwd ? { cwd: d?.cwd ?? legacy?.cwd } : {} };
18175
+ acp: { enabled: parsed.acp?.enabled ?? true, backends: [.../* @__PURE__ */ new Set(["codex", "cursor", "kimi", ...parsed.acp?.backends?.map((item) => item.id) ?? []])].map((id4) => {
18176
+ const d = parsed.acp?.backends?.find((x) => x.id === id4);
18177
+ const legacy = parsed.acp?.backend === id4 ? parsed.acp : void 0;
18178
+ return { id: id4, enabled: d?.enabled ?? legacy?.enabled ?? true, command: d?.command ?? legacy?.command ?? { codex: "codex", cursor: "agent", kimi: "kimi" }[id4] ?? id4, args: d?.args ?? legacy?.args ?? ["acp"], ...d?.cwd ?? legacy?.cwd ? { cwd: d?.cwd ?? legacy?.cwd } : {} };
17490
18179
  }) }
17491
18180
  };
17492
18181
  }
@@ -17509,7 +18198,7 @@ function normalizeServerUrl(value) {
17509
18198
  }
17510
18199
 
17511
18200
  // src/version.ts
17512
- var PLUGIN_VERSION = "0.4.14";
18201
+ var PLUGIN_VERSION = "0.4.15";
17513
18202
 
17514
18203
  // src/server-api.ts
17515
18204
  var TERMINAL_CONTROL_CHARACTERS = /[\u0000-\u001f\u007f-\u009f]/u;
@@ -17668,22 +18357,48 @@ var HostServerApi = class {
17668
18357
  this.credentials = await this.credentialsPromise;
17669
18358
  return this.credentials;
17670
18359
  }
17671
- async refreshCredentials() {
18360
+ async refreshCredentials(rejectedAccessToken = this.credentials?.accessToken) {
17672
18361
  const identity = this.requireIdentity();
17673
- const stored = await this.store.load(this.baseUrl, identity.deviceId);
17674
- if (stored === void 0 || stored.refreshTokenExpiresAt <= Date.now()) return this.register(identity);
17675
- const tokens = await this.publicRequest("/api/v1/auth/refresh", {
17676
- method: "POST",
17677
- body: JSON.stringify({ deviceId: identity.deviceId, refreshToken: stored.refreshToken })
18362
+ this.credentials = await this.withRefreshLock(async () => {
18363
+ const stored = await this.store.load(this.baseUrl, identity.deviceId);
18364
+ if (stored === void 0 || stored.refreshTokenExpiresAt <= Date.now()) return this.register(identity);
18365
+ if (stored.accessToken !== rejectedAccessToken && stored.accessTokenExpiresAt > Date.now() + 3e4) {
18366
+ return stored;
18367
+ }
18368
+ return this.rotateCredentials(identity, stored);
17678
18369
  });
17679
- this.credentials = await this.store.save({
18370
+ return this.credentials;
18371
+ }
18372
+ async withRefreshLock(operation) {
18373
+ try {
18374
+ return await this.store.withRefreshLock(operation);
18375
+ } catch (error) {
18376
+ if (error instanceof ServerCredentialsBusyError) {
18377
+ throw new ServerApiError(error.code, error.message, false);
18378
+ }
18379
+ throw error;
18380
+ }
18381
+ }
18382
+ async rotateCredentials(identity, stored) {
18383
+ let tokens;
18384
+ try {
18385
+ tokens = await this.publicRequest("/api/v1/auth/refresh", {
18386
+ method: "POST",
18387
+ body: JSON.stringify({ deviceId: identity.deviceId, refreshToken: stored.refreshToken })
18388
+ });
18389
+ } catch (error) {
18390
+ if (error instanceof ServerApiError) {
18391
+ throw new ServerApiError(error.code, error.message, error.retryable, error.status, "credential_refresh");
18392
+ }
18393
+ throw error;
18394
+ }
18395
+ return this.store.save({
17680
18396
  serverUrl: this.baseUrl,
17681
18397
  deviceId: identity.deviceId,
17682
18398
  authorizationMethod: stored.authorizationMethod,
17683
18399
  ...stored.account === void 0 ? {} : { account: stored.account },
17684
18400
  ...validateTokens(tokens)
17685
18401
  });
17686
- return this.credentials;
17687
18402
  }
17688
18403
  async listDevices() {
17689
18404
  const result = await this.request("/api/v1/devices");
@@ -17709,21 +18424,13 @@ var HostServerApi = class {
17709
18424
  return { online: result.online, ...typeof result.lastSeenAt === "number" ? { lastSeenAt: result.lastSeenAt } : {} };
17710
18425
  }
17711
18426
  async loadOrIssue(identity) {
17712
- const stored = await this.store.load(this.baseUrl, identity.deviceId);
17713
- if (stored === void 0 || stored.refreshTokenExpiresAt <= Date.now() + 3e4) {
17714
- return this.register(identity);
17715
- }
17716
- if (stored.accessTokenExpiresAt > Date.now() + 3e4) return stored;
17717
- const tokens = await this.publicRequest("/api/v1/auth/refresh", {
17718
- method: "POST",
17719
- body: JSON.stringify({ deviceId: identity.deviceId, refreshToken: stored.refreshToken })
17720
- });
17721
- return this.store.save({
17722
- serverUrl: this.baseUrl,
17723
- deviceId: identity.deviceId,
17724
- authorizationMethod: stored.authorizationMethod,
17725
- ...stored.account === void 0 ? {} : { account: stored.account },
17726
- ...validateTokens(tokens)
18427
+ return this.withRefreshLock(async () => {
18428
+ const stored = await this.store.load(this.baseUrl, identity.deviceId);
18429
+ if (stored === void 0 || stored.refreshTokenExpiresAt <= Date.now() + 3e4) {
18430
+ return this.register(identity);
18431
+ }
18432
+ if (stored.accessTokenExpiresAt > Date.now() + 3e4) return stored;
18433
+ return this.rotateCredentials(identity, stored);
17727
18434
  });
17728
18435
  }
17729
18436
  async register(identity, authorization) {
@@ -17816,11 +18523,12 @@ var ClientServerApi = class extends HostServerApi {
17816
18523
  }
17817
18524
  };
17818
18525
  var ServerApiError = class extends Error {
17819
- constructor(code, message, retryable, status2) {
18526
+ constructor(code, message, retryable, status2, phase) {
17820
18527
  super(message);
17821
18528
  this.code = code;
17822
18529
  this.retryable = retryable;
17823
18530
  this.status = status2;
18531
+ this.phase = phase;
17824
18532
  }
17825
18533
  };
17826
18534
  function validateTokens(value) {
@@ -17991,12 +18699,12 @@ var TypertGatewaySwitch = class {
17991
18699
  }
17992
18700
  this.installed = true;
17993
18701
  }
17994
- selectRemote(remote, support = ALL_REMOTE_COMMANDS, target) {
18702
+ selectRemote(remote, support = ALL_REMOTE_COMMANDS, target2) {
17995
18703
  if (!this.installed) throw new Error("The Typert gateway switch is not installed.");
17996
18704
  this.remoteInvoke = typeof remote === "function" ? remote : (request) => remote.invoke(request);
17997
18705
  this.remoteTarget = typeof remote === "function" ? void 0 : remote;
17998
18706
  this.remoteSupport = { ...support };
17999
- this.target = target === void 0 ? void 0 : { ...target };
18707
+ this.target = target2 === void 0 ? void 0 : { ...target2 };
18000
18708
  }
18001
18709
  selectLocal() {
18002
18710
  this.remoteInvoke = void 0;
@@ -18063,7 +18771,7 @@ import { networkInterfaces } from "node:os";
18063
18771
  // src/native-rtc-helper.ts
18064
18772
  import { spawn, spawnSync } from "node:child_process";
18065
18773
  import { existsSync, readdirSync, realpathSync, statSync } from "node:fs";
18066
- import { delimiter, dirname as dirname2, join as join2 } from "node:path";
18774
+ import { delimiter, dirname as dirname3, join as join3 } from "node:path";
18067
18775
  import { fileURLToPath } from "node:url";
18068
18776
  var cachedExternalFactory;
18069
18777
  var cachedExternalFactoryResolved = false;
@@ -18114,21 +18822,21 @@ function nodeBinaryCandidates() {
18114
18822
  add3(process.env.DSH_REMOTE_NODE);
18115
18823
  add3(process.env.NODE);
18116
18824
  add3(process.execPath);
18117
- for (const part of (process.env.PATH ?? "").split(delimiter)) add3(join2(part, process.platform === "win32" ? "node.exe" : "node"));
18825
+ for (const part of (process.env.PATH ?? "").split(delimiter)) add3(join3(part, process.platform === "win32" ? "node.exe" : "node"));
18118
18826
  add3("/opt/homebrew/bin/node");
18119
18827
  add3("/usr/local/bin/node");
18120
18828
  add3("/usr/bin/node");
18121
- add3(join2(process.env.HOME ?? "", ".volta", "bin", process.platform === "win32" ? "node.exe" : "node"));
18122
- add3(join2(process.env.HOME ?? "", ".asdf", "shims", process.platform === "win32" ? "node.exe" : "node"));
18123
- add3(join2(process.env.HOME ?? "", ".local", "bin", process.platform === "win32" ? "node.exe" : "node"));
18829
+ add3(join3(process.env.HOME ?? "", ".volta", "bin", process.platform === "win32" ? "node.exe" : "node"));
18830
+ add3(join3(process.env.HOME ?? "", ".asdf", "shims", process.platform === "win32" ? "node.exe" : "node"));
18831
+ add3(join3(process.env.HOME ?? "", ".local", "bin", process.platform === "win32" ? "node.exe" : "node"));
18124
18832
  for (const nvmNode of nvmNodeCandidates()) add3(nvmNode);
18125
18833
  return candidates;
18126
18834
  }
18127
18835
  function nvmNodeCandidates() {
18128
- const root = join2(process.env.HOME ?? "", ".nvm", "versions", "node");
18836
+ const root = join3(process.env.HOME ?? "", ".nvm", "versions", "node");
18129
18837
  if (root === "" || !existsSync(root)) return [];
18130
18838
  try {
18131
- return readdirSync(root).map((version) => join2(root, version, "bin", process.platform === "win32" ? "node.exe" : "node")).sort((left, right) => right.localeCompare(left, "en", { numeric: true }));
18839
+ return readdirSync(root).map((version) => join3(root, version, "bin", process.platform === "win32" ? "node.exe" : "node")).sort((left, right) => right.localeCompare(left, "en", { numeric: true }));
18132
18840
  } catch {
18133
18841
  return [];
18134
18842
  }
@@ -18150,7 +18858,7 @@ function isUsableExternalNode(candidate, requireFrom) {
18150
18858
  "console.log(`node:${process.versions.node}`);"
18151
18859
  ].join("")
18152
18860
  ], {
18153
- cwd: dirname2(requireFrom),
18861
+ cwd: dirname3(requireFrom),
18154
18862
  env,
18155
18863
  encoding: "utf8",
18156
18864
  timeout: 3e3
@@ -18172,7 +18880,7 @@ var ExternalNativePeerConnection = class {
18172
18880
  this.nodeBinary = nodeBinary;
18173
18881
  this.requireFrom = requireFrom;
18174
18882
  this.child = spawn(this.nodeBinary, ["--input-type=module", "--eval", HELPER_SOURCE], {
18175
- cwd: dirname2(this.requireFrom),
18883
+ cwd: dirname3(this.requireFrom),
18176
18884
  env: { ...process.env, DSH_REMOTE_RTC_HELPER_REQUIRE_FROM: this.requireFrom },
18177
18885
  stdio: ["pipe", "pipe", "pipe"]
18178
18886
  });
@@ -18241,8 +18949,8 @@ var ExternalNativePeerConnection = class {
18241
18949
  }, 1e3);
18242
18950
  timer.unref?.();
18243
18951
  }
18244
- sendChannelData(channelId, data) {
18245
- const payload = typeof data === "string" ? { channelId, text: data } : { channelId, base64: Buffer.from(data).toString("base64") };
18952
+ sendChannelData(channelId, data2) {
18953
+ const payload = typeof data2 === "string" ? { channelId, text: data2 } : { channelId, base64: Buffer.from(data2).toString("base64") };
18246
18954
  this.notify("channelSend", payload);
18247
18955
  }
18248
18956
  closeChannel(channelId) {
@@ -18250,11 +18958,11 @@ var ExternalNativePeerConnection = class {
18250
18958
  }
18251
18959
  request(method, payload) {
18252
18960
  if (this.closed) return Promise.reject(new Error("native rtc helper is closed"));
18253
- const id2 = this.nextRequestId++;
18961
+ const id4 = this.nextRequestId++;
18254
18962
  const promise = new Promise((resolve3, reject) => {
18255
- this.pending.set(id2, { resolve: resolve3, reject });
18963
+ this.pending.set(id4, { resolve: resolve3, reject });
18256
18964
  });
18257
- this.write({ id: id2, method, payload });
18965
+ this.write({ id: id4, method, payload });
18258
18966
  return promise;
18259
18967
  }
18260
18968
  notify(method, payload) {
@@ -18358,8 +19066,8 @@ var ExternalNativeDataChannel = class {
18358
19066
  onclose = null;
18359
19067
  onerror = null;
18360
19068
  onbufferedamountlow = null;
18361
- send(data) {
18362
- this.owner.sendChannelData(this.channelId, data);
19069
+ send(data2) {
19070
+ this.owner.sendChannelData(this.channelId, data2);
18363
19071
  }
18364
19072
  close() {
18365
19073
  this.owner.closeChannel(this.channelId);
@@ -18372,8 +19080,8 @@ var ExternalNativeDataChannel = class {
18372
19080
  if (previous !== "closed" && readyState === "closed") this.onclose?.();
18373
19081
  if (bufferedAmount === 0) this.onbufferedamountlow?.();
18374
19082
  }
18375
- emitMessage(data) {
18376
- this.onmessage?.({ data });
19083
+ emitMessage(data2) {
19084
+ this.onmessage?.({ data: data2 });
18377
19085
  }
18378
19086
  emitError() {
18379
19087
  this.onerror?.();
@@ -18850,10 +19558,10 @@ function adaptDataChannel2(raw) {
18850
19558
  return raw.onerror;
18851
19559
  },
18852
19560
  onbufferedamountlow: null,
18853
- send(data) {
18854
- const bytes = typeof data === "string" ? Buffer.byteLength(data) : data.byteLength;
19561
+ send(data2) {
19562
+ const bytes = typeof data2 === "string" ? Buffer.byteLength(data2) : data2.byteLength;
18855
19563
  try {
18856
- raw.send(typeof data === "string" ? data : Buffer.from(data));
19564
+ raw.send(typeof data2 === "string" ? data2 : Buffer.from(data2));
18857
19565
  } catch (error) {
18858
19566
  console.error("[werift-send-error] bytes=" + bytes, error instanceof Error ? error.message : error);
18859
19567
  throw error;
@@ -18916,17 +19624,17 @@ function adaptNativeDataChannel(raw) {
18916
19624
  get onbufferedamountlow() {
18917
19625
  return raw.onbufferedamountlow;
18918
19626
  },
18919
- send(data) {
18920
- raw.send(typeof data === "string" ? data : Buffer.from(data));
19627
+ send(data2) {
19628
+ raw.send(typeof data2 === "string" ? data2 : Buffer.from(data2));
18921
19629
  },
18922
19630
  close() {
18923
19631
  raw.close();
18924
19632
  }
18925
19633
  };
18926
19634
  }
18927
- function toArrayBuffer2(data) {
18928
- if (typeof data === "string") return data;
18929
- return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
19635
+ function toArrayBuffer2(data2) {
19636
+ if (typeof data2 === "string") return data2;
19637
+ return data2.buffer.slice(data2.byteOffset, data2.byteOffset + data2.byteLength);
18930
19638
  }
18931
19639
  function normalizeNativeCandidate(candidate) {
18932
19640
  const json = typeof candidate.toJSON === "function" ? candidate.toJSON() : {
@@ -18940,11 +19648,11 @@ function normalizeNativeCandidate(candidate) {
18940
19648
  sdpMLineIndex: normalizeSdpMLineIndex(json.sdpMLineIndex)
18941
19649
  };
18942
19650
  }
18943
- function normalizeNativeMessageData(data) {
18944
- if (typeof data === "string") return data;
18945
- if (data instanceof ArrayBuffer) return data;
18946
- if (ArrayBuffer.isView(data)) {
18947
- return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
19651
+ function normalizeNativeMessageData(data2) {
19652
+ if (typeof data2 === "string") return data2;
19653
+ if (data2 instanceof ArrayBuffer) return data2;
19654
+ if (ArrayBuffer.isView(data2)) {
19655
+ return data2.buffer.slice(data2.byteOffset, data2.byteOffset + data2.byteLength);
18948
19656
  }
18949
19657
  return new Uint8Array().buffer;
18950
19658
  }
@@ -18974,8 +19682,8 @@ function detectHostIpv4Candidates(interfaces, preferredHostIpv4Candidates = [])
18974
19682
  return [...new Set(candidates.map((candidate) => candidate.ip))];
18975
19683
  }
18976
19684
  async function detectRouteHostIpv4Candidates(targets, timeoutMs = 500) {
18977
- const detected = await Promise.all(targets.map(async (target) => {
18978
- const routeTarget = parseRouteTarget(target);
19685
+ const detected = await Promise.all(targets.map(async (target2) => {
19686
+ const routeTarget = parseRouteTarget(target2);
18979
19687
  return routeTarget === void 0 ? void 0 : await detectRouteHostIpv4(routeTarget, timeoutMs).catch(() => void 0);
18980
19688
  }));
18981
19689
  return [...new Set(detected.filter((ip) => ip !== void 0 && isUsableIpv4(ip)))];
@@ -19043,7 +19751,7 @@ function parseRouteTarget(value) {
19043
19751
  return void 0;
19044
19752
  }
19045
19753
  }
19046
- async function detectRouteHostIpv4(target, timeoutMs) {
19754
+ async function detectRouteHostIpv4(target2, timeoutMs) {
19047
19755
  const socket = createSocket("udp4");
19048
19756
  try {
19049
19757
  return await new Promise((resolve3, reject) => {
@@ -19060,7 +19768,7 @@ async function detectRouteHostIpv4(target, timeoutMs) {
19060
19768
  reject(error);
19061
19769
  };
19062
19770
  socket.once("error", onError);
19063
- socket.connect(target.port, target.host, () => {
19771
+ socket.connect(target2.port, target2.host, () => {
19064
19772
  cleanup();
19065
19773
  const address = socket.address();
19066
19774
  resolve3(typeof address === "string" ? void 0 : address.address);
@@ -19135,6 +19843,7 @@ var ClientModeRuntime = class {
19135
19843
  this.proxySwitch = apiProxy === void 0 ? void 0 : new ApiProxySwitch(apiProxy);
19136
19844
  this.gatewaySwitch = new TypertGatewaySwitch(typertGateway);
19137
19845
  }
19846
+ preview;
19138
19847
  identity;
19139
19848
  connected;
19140
19849
  pendingWorkspaceSelection;
@@ -19186,6 +19895,11 @@ var ClientModeRuntime = class {
19186
19895
  ...this.host === void 0 ? {} : { host: this.host.hostStatus() }
19187
19896
  };
19188
19897
  }
19898
+ async closePreview() {
19899
+ const preview = this.preview;
19900
+ this.preview = void 0;
19901
+ await preview?.close();
19902
+ }
19189
19903
  async detailedStatus() {
19190
19904
  const connected = this.connected;
19191
19905
  if (connected === void 0 || this.identity === void 0) return this.status();
@@ -19251,6 +19965,7 @@ var ClientModeRuntime = class {
19251
19965
  this.pendingWorkspaceSelection = void 0;
19252
19966
  await this.closeCodexVirtual();
19253
19967
  this.proxySwitch?.selectLocal();
19968
+ await this.closePreview();
19254
19969
  this.gatewaySwitch.selectLocal();
19255
19970
  await this.closeCodexStreams(previous?.client);
19256
19971
  await previous?.client.close().catch(() => void 0);
@@ -19272,6 +19987,7 @@ var ClientModeRuntime = class {
19272
19987
  if (mode === "local") {
19273
19988
  await this.closeCodexVirtual();
19274
19989
  this.proxySwitch?.selectLocal();
19990
+ await this.closePreview();
19275
19991
  this.gatewaySwitch.selectLocal();
19276
19992
  const previous2 = this.connected;
19277
19993
  this.connected = void 0;
@@ -19294,6 +20010,7 @@ var ClientModeRuntime = class {
19294
20010
  throw error;
19295
20011
  }
19296
20012
  const previous = this.connected;
20013
+ await this.closePreview();
19297
20014
  this.connected = next;
19298
20015
  this.clearConnectionProgress(next.progressRunId);
19299
20016
  this.pendingWorkspaceSelection = void 0;
@@ -19429,9 +20146,11 @@ var ClientModeRuntime = class {
19429
20146
  return this.status();
19430
20147
  }
19431
20148
  async close() {
20149
+ await this.closePreview();
19432
20150
  if (this.closed) return;
19433
20151
  this.closed = true;
19434
20152
  this.proxySwitch?.selectLocal();
20153
+ await this.closePreview();
19435
20154
  this.gatewaySwitch.selectLocal();
19436
20155
  this.pendingWorkspaceSelection = void 0;
19437
20156
  await this.closeCodexVirtual();
@@ -19514,24 +20233,24 @@ var ClientModeRuntime = class {
19514
20233
  this.codexStreams.set(value.streamId, stream);
19515
20234
  return { opened: true, streamId: value.streamId, threadId: value.threadId };
19516
20235
  }
19517
- publishLocalCodexFrame = async (event, data) => {
19518
- const streamId = data.streamId;
20236
+ publishLocalCodexFrame = async (event, data2) => {
20237
+ const streamId = data2.streamId;
19519
20238
  const stream = this.codexStreams.get(streamId);
19520
20239
  if (stream === void 0 || stream.target.kind !== "local") return;
19521
20240
  if (event === "codex.app.frame") {
19522
- this.appendCodexFrame(stream, data);
20241
+ this.appendCodexFrame(stream, data2);
19523
20242
  return;
19524
20243
  }
19525
- const closed = data;
20244
+ const closed = data2;
19526
20245
  stream.closed = typeof closed.reason === "string" ? closed.reason : "closed";
19527
20246
  stream.wake();
19528
20247
  };
19529
- appendCodexFrame(stream, data) {
19530
- if (!isRecord7(data) || !isRecord7(data.frame) || typeof data.frame.method !== "string") return;
20248
+ appendCodexFrame(stream, data2) {
20249
+ if (!isRecord7(data2) || !isRecord7(data2.frame) || typeof data2.frame.method !== "string") return;
19531
20250
  if (stream.frames.length >= 256) {
19532
20251
  stream.closed = "overflow";
19533
20252
  } else {
19534
- stream.frames.push({ method: data.frame.method, params: data.frame.params });
20253
+ stream.frames.push({ method: data2.frame.method, params: data2.frame.params });
19535
20254
  }
19536
20255
  stream.wake();
19537
20256
  }
@@ -19585,16 +20304,16 @@ var ClientModeRuntime = class {
19585
20304
  }));
19586
20305
  }
19587
20306
  selectRemoteTarget(remote, transport = this.selectHarnessRemoteTransport(remote)) {
19588
- const target = { deviceId: remote.target.deviceId, name: remote.target.name };
20307
+ const target2 = { deviceId: remote.target.deviceId, name: remote.target.name };
19589
20308
  if (transport === "remoteGateway") {
19590
- this.gatewaySwitch.selectRemote(this.remoteTypertGateway(remote), void 0, target);
20309
+ this.gatewaySwitch.selectRemote(this.remoteTypertGateway(remote), void 0, target2);
19591
20310
  return;
19592
20311
  }
19593
- this.proxySwitch?.selectRemote(new RemoteHarnessApiProxy(remote.client).api, target);
20312
+ this.proxySwitch?.selectRemote(new RemoteHarnessApiProxy(remote.client).api, target2);
19594
20313
  this.gatewaySwitch.selectRemote((request) => invokeRemoteCommand(remote.client, request), {
19595
20314
  execute: true,
19596
20315
  list: remote.features.commandList
19597
- }, target);
20316
+ }, target2);
19598
20317
  }
19599
20318
  remoteTypertGateway(remote) {
19600
20319
  const localSessionGeneration = harnessSessionGeneration(this.host?.localHarnessVersion?.());
@@ -19604,13 +20323,13 @@ var ClientModeRuntime = class {
19604
20323
  );
19605
20324
  }
19606
20325
  selectCodexTarget(virtual, remote) {
19607
- const target = { deviceId: remote.target.deviceId, name: remote.target.name };
20326
+ const target2 = { deviceId: remote.target.deviceId, name: remote.target.name };
19608
20327
  if (this.gatewaySwitch.supportsCarrier()) {
19609
- this.gatewaySwitch.selectRemote(virtual, void 0, target);
20328
+ this.gatewaySwitch.selectRemote(virtual, void 0, target2);
19610
20329
  return;
19611
20330
  }
19612
- this.proxySwitch.selectRemote(virtual.api, target);
19613
- this.gatewaySwitch.selectRemote((request) => virtual.invoke(request), { execute: true, list: true }, target);
20331
+ this.proxySwitch.selectRemote(virtual.api, target2);
20332
+ this.gatewaySwitch.selectRemote((request) => virtual.invoke(request), { execute: true, list: true }, target2);
19614
20333
  }
19615
20334
  async closeCodexVirtual() {
19616
20335
  const virtual = this.codexVirtual;
@@ -19656,14 +20375,14 @@ var ClientModeRuntime = class {
19656
20375
  throw new ClientModeError("MEMBERSHIP_REQUIRED", "The selected Host is not authorized for this account.");
19657
20376
  }
19658
20377
  this.updateConnectionProgress(progressRunId, "authorizing-peer");
19659
- const target = await this.authorizeHostPeer(serverDevice);
20378
+ const target2 = await this.authorizeHostPeer(serverDevice);
19660
20379
  const presence = await this.server.presenceFor(targetDeviceId);
19661
20380
  if (!presence.online) throw new ClientModeError("HOST_OFFLINE", "The selected Host is offline.", true);
19662
20381
  const credentials = await this.server.authenticate(identity);
19663
20382
  const rtcFactory = this.config.forceRelay ? void 0 : await this.rtcFactoryProvider({ routeTargets: [this.server.baseUrl] }).catch(() => void 0);
19664
20383
  if (!this.config.forceRelay && rtcFactory === void 0) {
19665
20384
  this.logger.warn("remote Harness WebRTC backend unavailable; using relay", {
19666
- targetDeviceId: shortId(target.deviceId)
20385
+ targetDeviceId: shortId(target2.deviceId)
19667
20386
  });
19668
20387
  }
19669
20388
  let webRtcFallback = false;
@@ -19682,13 +20401,13 @@ var ClientModeRuntime = class {
19682
20401
  onWebRtcFallback: (error, diagnostics) => {
19683
20402
  webRtcFallback = true;
19684
20403
  this.logger.warn(attempt === "direct" ? "remote Harness direct WebRTC failed; trying TURN" : "remote Harness TURN WebRTC failed; using relay", {
19685
- targetDeviceId: shortId(target.deviceId),
20404
+ targetDeviceId: shortId(target2.deviceId),
19686
20405
  attempt,
19687
20406
  reason: diagnosticReason(error)
19688
20407
  });
19689
20408
  if (diagnostics !== void 0) {
19690
20409
  this.logger.debug("remote Harness WebRTC fallback diagnostics", {
19691
- targetDeviceId: shortId(target.deviceId),
20410
+ targetDeviceId: shortId(target2.deviceId),
19692
20411
  attempt,
19693
20412
  ...webrtcDiagnosticsLogFields(diagnostics)
19694
20413
  });
@@ -19703,7 +20422,7 @@ var ClientModeRuntime = class {
19703
20422
  const stopProgressTimer = this.beginAttemptProgress(progressRunId, attempt);
19704
20423
  try {
19705
20424
  transport = createTransport(attempt);
19706
- client = new RemoteClientCore(new ClientSecureTransport(transport, identity, target), 6e4);
20425
+ client = new RemoteClientCore(new ClientSecureTransport(transport, identity, target2), 6e4);
19707
20426
  await client.connect();
19708
20427
  signal?.throwIfAborted();
19709
20428
  } finally {
@@ -19715,7 +20434,7 @@ var ClientModeRuntime = class {
19715
20434
  transport = void 0;
19716
20435
  if (attempt === "turn") {
19717
20436
  this.logger.info("remote Harness relay fallback re-established", {
19718
- targetDeviceId: shortId(target.deviceId)
20437
+ targetDeviceId: shortId(target2.deviceId)
19719
20438
  });
19720
20439
  }
19721
20440
  }
@@ -19732,6 +20451,7 @@ var ClientModeRuntime = class {
19732
20451
  );
19733
20452
  connectedClient.onClose(() => {
19734
20453
  if (this.connected?.client !== connectedClient) return;
20454
+ void this.closePreview();
19735
20455
  this.connected = void 0;
19736
20456
  this.connectionProgress = void 0;
19737
20457
  this.pendingWorkspaceSelection = void 0;
@@ -19740,12 +20460,12 @@ var ClientModeRuntime = class {
19740
20460
  this.gatewaySwitch.selectLocal();
19741
20461
  void connectedClient.close().catch(() => void 0);
19742
20462
  this.logger.warn("remote Harness transport closed; falling back to local mode", {
19743
- targetDeviceId: shortId(target.deviceId)
20463
+ targetDeviceId: shortId(target2.deviceId)
19744
20464
  });
19745
20465
  });
19746
20466
  const connectionDetails = await connectedTransport.connectionDetails().catch(() => void 0);
19747
20467
  this.logger.info("remote Harness transport ready", {
19748
- targetDeviceId: shortId(target.deviceId),
20468
+ targetDeviceId: shortId(target2.deviceId),
19749
20469
  transport: connectedClient.getStats().mode,
19750
20470
  ...connectionDetails === void 0 ? {} : {
19751
20471
  preferredTransports: connectionDetails.preferredTransports,
@@ -19755,14 +20475,14 @@ var ClientModeRuntime = class {
19755
20475
  });
19756
20476
  if (connectionDetails?.webRtc?.diagnostics !== void 0) {
19757
20477
  this.logger.debug("remote Harness transport diagnostics", {
19758
- targetDeviceId: shortId(target.deviceId),
20478
+ targetDeviceId: shortId(target2.deviceId),
19759
20479
  ...webrtcDiagnosticsLogFields(connectionDetails.webRtc.diagnostics)
19760
20480
  });
19761
20481
  }
19762
20482
  const features = await probeRemoteHostFeatures(connectedClient, serverDevice.clientVersion);
19763
20483
  return {
19764
20484
  client: connectedClient,
19765
- target,
20485
+ target: target2,
19766
20486
  transport: connectedTransport,
19767
20487
  features,
19768
20488
  progressRunId,
@@ -19779,6 +20499,7 @@ var ClientModeRuntime = class {
19779
20499
  if (this.connected?.target.deviceId === targetDeviceId) return this.connected;
19780
20500
  const next = await this.connect(targetDeviceId, signal);
19781
20501
  const previous = this.connected;
20502
+ await this.closePreview();
19782
20503
  this.connected = next;
19783
20504
  this.clearConnectionProgress(next.progressRunId);
19784
20505
  await previous?.client.close().catch(() => void 0);
@@ -19835,6 +20556,12 @@ var ClientModeRuntime = class {
19835
20556
  }
19836
20557
  return ok2(await this.pollClientOAuthQrLogin(value.qrId));
19837
20558
  }
20559
+ if (endpoint === "preview.open") {
20560
+ const remote = this.activeRemote();
20561
+ if (remote === void 0) throw new ClientModeError("TRANSPORT_CLOSED", "Connect to a Remote Host first.");
20562
+ this.preview ??= new LoopbackPreview(remote.client);
20563
+ return ok2(await this.preview.open(record3(payload).port));
20564
+ }
19838
20565
  if (endpoint === "directory.list") {
19839
20566
  const value = record3(payload);
19840
20567
  if (typeof value.targetDeviceId !== "string") throw new ClientModeError("INVALID_MESSAGE", "A Host is required.");
@@ -20099,6 +20826,7 @@ function remoteHostFeatures(clientVersion) {
20099
20826
  return {
20100
20827
  commandList: isVersionAtLeast(clientVersion, REMOTE_COMMAND_LIST_MIN_VERSION),
20101
20828
  fileViewer: isVersionAtLeast(clientVersion, REMOTE_FILE_VIEWER_MIN_VERSION),
20829
+ terminal: false,
20102
20830
  apiProxy: true,
20103
20831
  remoteGateway: false,
20104
20832
  codex: false
@@ -20120,6 +20848,7 @@ async function probeRemoteHostFeatures(client, clientVersion) {
20120
20848
  const apiProxy = capabilities.has("harness.api.v1");
20121
20849
  const remoteV1 = capabilities.has("harness.remote.v1");
20122
20850
  const remoteV3 = capabilities.has("harness.remote.v3");
20851
+ const terminal = capabilities.has("harness.terminal.v1");
20123
20852
  const codex = capabilities.has("codex.appserver.v1");
20124
20853
  if (remoteV1 && remoteV3) {
20125
20854
  throw new ClientModeError("INVALID_MESSAGE", "The remote Host advertised conflicting Harness Session formats.");
@@ -20132,6 +20861,7 @@ async function probeRemoteHostFeatures(client, clientVersion) {
20132
20861
  return {
20133
20862
  commandList: remoteGateway || apiProxy && fallback.commandList,
20134
20863
  fileViewer: capabilities.has("fileviewer.read.v1"),
20864
+ terminal,
20135
20865
  apiProxy,
20136
20866
  remoteGateway,
20137
20867
  ...sessionFormat === void 0 ? {} : { sessionFormat },
@@ -20202,9 +20932,9 @@ import { execFileSync } from "node:child_process";
20202
20932
 
20203
20933
  // src/identity-store.ts
20204
20934
  import { createHash } from "node:crypto";
20205
- import { chmod, mkdir, readFile as readFile2, rename, rm, stat, writeFile } from "node:fs/promises";
20935
+ import { chmod as chmod2, mkdir as mkdir2, readFile as readFile3, rename as rename2, rm as rm2, stat as stat2, writeFile as writeFile2 } from "node:fs/promises";
20206
20936
  import { homedir } from "node:os";
20207
- import { dirname as dirname3, join as join3 } from "node:path";
20937
+ import { dirname as dirname4, join as join4 } from "node:path";
20208
20938
  var identitySchema = external_exports.object({
20209
20939
  schemaVersion: external_exports.literal(1),
20210
20940
  deviceId: external_exports.string().uuid(),
@@ -20233,15 +20963,15 @@ var IdentityStore = class {
20233
20963
  peers = /* @__PURE__ */ new Map();
20234
20964
  constructor(options = {}) {
20235
20965
  const env = options.env ?? process.env;
20236
- const dshHome = env.DSH_HOME || join3(options.homeDirectory ?? homedir(), ".dsh");
20237
- this.directory = options.directory ?? join3(dshHome, "remote");
20966
+ const dshHome = env.DSH_HOME || join4(options.homeDirectory ?? homedir(), ".dsh");
20967
+ this.directory = options.directory ?? join4(dshHome, "remote");
20238
20968
  }
20239
20969
  async loadOrCreate(deviceName) {
20240
- await mkdir(this.directory, { recursive: true, mode: 448 });
20241
- await chmod(this.directory, 448);
20242
- const devicePath = join3(this.directory, "device.json");
20243
- const keyPath = join3(this.directory, "device.key");
20244
- const [hasDevice, hasKey] = await Promise.all([exists(devicePath), exists(keyPath)]);
20970
+ await mkdir2(this.directory, { recursive: true, mode: 448 });
20971
+ await chmod2(this.directory, 448);
20972
+ const devicePath = join4(this.directory, "device.json");
20973
+ const keyPath = join4(this.directory, "device.key");
20974
+ const [hasDevice, hasKey] = await Promise.all([exists2(devicePath), exists2(keyPath)]);
20245
20975
  if (hasDevice !== hasKey) {
20246
20976
  throw new IdentityInvalidError("device identity is incomplete; repair it explicitly before reconnecting");
20247
20977
  }
@@ -20252,10 +20982,10 @@ var IdentityStore = class {
20252
20982
  await atomicTextWrite(keyPath, `${keys.privateKey}
20253
20983
  `, 384);
20254
20984
  }
20255
- await assertPrivateMode(keyPath);
20985
+ await assertPrivateMode2(keyPath);
20256
20986
  try {
20257
- let record6 = identitySchema.parse(JSON.parse(await readFile2(devicePath, "utf8")));
20258
- const privateKey = (await readFile2(keyPath, "utf8")).trim();
20987
+ let record6 = identitySchema.parse(JSON.parse(await readFile3(devicePath, "utf8")));
20988
+ const privateKey = (await readFile3(keyPath, "utf8")).trim();
20259
20989
  const regenerated = generateKeyPair(fromBase64Url2(privateKey));
20260
20990
  if (regenerated.publicKey !== record6.publicKey) {
20261
20991
  throw new IdentityInvalidError("device public and private keys do not match");
@@ -20277,7 +21007,7 @@ var IdentityStore = class {
20277
21007
  return this.identity;
20278
21008
  }
20279
21009
  async reset(deviceName) {
20280
- await rm(this.directory, { recursive: true, force: true });
21010
+ await rm2(this.directory, { recursive: true, force: true });
20281
21011
  this.identity = void 0;
20282
21012
  this.peers.clear();
20283
21013
  return this.loadOrCreate(deviceName);
@@ -20309,11 +21039,11 @@ var IdentityStore = class {
20309
21039
  return removed;
20310
21040
  }
20311
21041
  async loadPeers() {
20312
- const path = join3(this.directory, "trusted-peers.json");
20313
- if (!await exists(path)) {
21042
+ const path = join4(this.directory, "trusted-peers.json");
21043
+ if (!await exists2(path)) {
20314
21044
  await atomicJsonWrite(path, { schemaVersion: 1, peers: [] }, 384);
20315
21045
  }
20316
- const parsed = trustedPeersSchema.parse(JSON.parse(await readFile2(path, "utf8")));
21046
+ const parsed = trustedPeersSchema.parse(JSON.parse(await readFile3(path, "utf8")));
20317
21047
  const peers = /* @__PURE__ */ new Map();
20318
21048
  for (const peer of parsed.peers) {
20319
21049
  if (peer.fingerprint !== fingerprint(peer.publicKey)) {
@@ -20325,7 +21055,7 @@ var IdentityStore = class {
20325
21055
  this.peers = peers;
20326
21056
  }
20327
21057
  async savePeers() {
20328
- await atomicJsonWrite(join3(this.directory, "trusted-peers.json"), {
21058
+ await atomicJsonWrite(join4(this.directory, "trusted-peers.json"), {
20329
21059
  schemaVersion: 1,
20330
21060
  peers: [...this.peers.values()]
20331
21061
  }, 384);
@@ -20334,15 +21064,15 @@ var IdentityStore = class {
20334
21064
  function serverStorageDirectory(root, serverUrl, role) {
20335
21065
  const origin = new URL(serverUrl).origin;
20336
21066
  const scope = createHash("sha256").update(origin).digest("hex").slice(0, 24);
20337
- return join3(root, "servers", scope, role);
21067
+ return join4(root, "servers", scope, role);
20338
21068
  }
20339
21069
  function fingerprint(publicKey) {
20340
21070
  const compact = createHash("sha256").update(fromBase64Url2(publicKey)).digest("hex").slice(0, 12).toUpperCase();
20341
21071
  return compact.match(/.{1,4}/g).join(" ");
20342
21072
  }
20343
- async function assertPrivateMode(path) {
21073
+ async function assertPrivateMode2(path) {
20344
21074
  if (process.platform === "win32") return;
20345
- const mode = (await stat(path)).mode & 511;
21075
+ const mode = (await stat2(path)).mode & 511;
20346
21076
  if ((mode & 63) !== 0) {
20347
21077
  throw new IdentityInvalidError(`private key permissions must be 0600, got ${mode.toString(8).padStart(3, "0")}`);
20348
21078
  }
@@ -20352,16 +21082,16 @@ async function atomicJsonWrite(path, value, mode) {
20352
21082
  `, mode);
20353
21083
  }
20354
21084
  async function atomicTextWrite(path, value, mode) {
20355
- await mkdir(dirname3(path), { recursive: true, mode: 448 });
21085
+ await mkdir2(dirname4(path), { recursive: true, mode: 448 });
20356
21086
  const temporary = `${path}.${process.pid}.${uuidV7()}.tmp`;
20357
- await writeFile(temporary, value, { encoding: "utf8", mode, flag: "wx" });
20358
- await chmod(temporary, mode);
20359
- await rename(temporary, path);
20360
- await chmod(path, mode);
21087
+ await writeFile2(temporary, value, { encoding: "utf8", mode, flag: "wx" });
21088
+ await chmod2(temporary, mode);
21089
+ await rename2(temporary, path);
21090
+ await chmod2(path, mode);
20361
21091
  }
20362
- async function exists(path) {
21092
+ async function exists2(path) {
20363
21093
  try {
20364
- await stat(path);
21094
+ await stat2(path);
20365
21095
  return true;
20366
21096
  } catch (error) {
20367
21097
  if (isNodeError(error) && error.code === "ENOENT") return false;
@@ -20375,74 +21105,148 @@ function safeErrorMessage(error) {
20375
21105
  return error instanceof Error ? error.message : "invalid identity data";
20376
21106
  }
20377
21107
 
20378
- // src/server-credentials.ts
20379
- import { chmod as chmod2, mkdir as mkdir2, readFile as readFile3, rename as rename2, rm as rm2, stat as stat2, writeFile as writeFile2 } from "node:fs/promises";
20380
- import { dirname as dirname4, join as join4 } from "node:path";
20381
- var credentialSchema = external_exports.object({
20382
- schemaVersion: external_exports.literal(1),
20383
- serverUrl: external_exports.string().url(),
20384
- deviceId: external_exports.string().min(1),
20385
- authorizationMethod: external_exports.enum(["account", "host_registration_code", "owned_device"]),
20386
- account: external_exports.string().min(1).max(254).optional(),
20387
- accessToken: external_exports.string().min(16),
20388
- accessTokenExpiresAt: external_exports.number().int().positive(),
20389
- refreshToken: external_exports.string().min(16),
20390
- refreshTokenExpiresAt: external_exports.number().int().positive()
20391
- }).strict();
20392
- var ServerCredentialStore = class {
20393
- path;
20394
- constructor(directory) {
20395
- this.path = join4(directory, "server-credentials.json");
21108
+ // src/control-stream.ts
21109
+ var STATUS_STREAM_SAMPLE_INTERVAL_MS = 1500;
21110
+ var STATUS_STREAM_HEARTBEAT_INTERVAL_MS = 15e3;
21111
+ var STATUS_STREAM_RETRY_MS = 3e3;
21112
+ var KEEP_ALIVE_FRAME = ": keep-alive\n\n";
21113
+ var ControlStatusStream = class {
21114
+ /**
21115
+ * @param readStatus - reads the current status value, the same value the unary control endpoint returns.
21116
+ * @param options - sampling, keep-alive, and reconnect periods.
21117
+ */
21118
+ constructor(readStatus, options = {}) {
21119
+ this.readStatus = readStatus;
21120
+ this.sampleIntervalMs = options.sampleIntervalMs ?? STATUS_STREAM_SAMPLE_INTERVAL_MS;
21121
+ this.heartbeatIntervalMs = options.heartbeatIntervalMs ?? STATUS_STREAM_HEARTBEAT_INTERVAL_MS;
21122
+ this.retryMs = options.retryMs ?? STATUS_STREAM_RETRY_MS;
21123
+ }
21124
+ subscribers = /* @__PURE__ */ new Set();
21125
+ sampleIntervalMs;
21126
+ heartbeatIntervalMs;
21127
+ retryMs;
21128
+ timer;
21129
+ sampling = false;
21130
+ payload;
21131
+ writtenAt = 0;
21132
+ closed = false;
21133
+ /**
21134
+ * Write the SSE response head, the current status as its first frame, and keep
21135
+ * pushing until the client disconnects or {@link close} runs.
21136
+ * @param response - the loopback route response, owned for the connection lifetime.
21137
+ */
21138
+ async handle(response) {
21139
+ if (this.closed) {
21140
+ response.writeHead(503);
21141
+ response.end();
21142
+ return;
21143
+ }
21144
+ response.writeHead(200, {
21145
+ "content-type": "text/event-stream",
21146
+ "cache-control": "no-store",
21147
+ connection: "keep-alive"
21148
+ });
21149
+ try {
21150
+ response.write(`retry: ${String(this.retryMs)}
21151
+
21152
+ `);
21153
+ } catch {
21154
+ return;
21155
+ }
21156
+ const subscriber = { response, payload: void 0 };
21157
+ this.subscribers.add(subscriber);
21158
+ const detach = () => {
21159
+ this.detach(subscriber);
21160
+ };
21161
+ response.on("close", detach);
21162
+ response.on("error", detach);
21163
+ this.startSampling();
21164
+ this.write(await this.readSnapshot() ?? this.payload);
20396
21165
  }
20397
- async load(serverUrl, deviceId) {
20398
- if (!await exists2(this.path)) return void 0;
20399
- await assertPrivateMode2(this.path);
20400
- let parsed;
21166
+ /** End every open stream and stop sampling. */
21167
+ close() {
21168
+ this.closed = true;
21169
+ this.stopSampling();
21170
+ for (const subscriber of [...this.subscribers]) {
21171
+ try {
21172
+ subscriber.response.end();
21173
+ } catch {
21174
+ }
21175
+ }
21176
+ this.subscribers.clear();
21177
+ this.payload = void 0;
21178
+ }
21179
+ /** Forget one subscriber; the last one to leave also stops the sampler. */
21180
+ detach(subscriber) {
21181
+ this.subscribers.delete(subscriber);
21182
+ if (this.subscribers.size === 0) this.stopSampling();
21183
+ }
21184
+ startSampling() {
21185
+ if (this.closed || this.timer !== void 0) return;
21186
+ this.timer = setInterval(() => {
21187
+ void this.sample();
21188
+ }, this.sampleIntervalMs);
21189
+ }
21190
+ stopSampling() {
21191
+ if (this.timer === void 0) return;
21192
+ clearInterval(this.timer);
21193
+ this.timer = void 0;
21194
+ }
21195
+ async sample() {
21196
+ if (this.sampling || this.closed) return;
21197
+ this.sampling = true;
20401
21198
  try {
20402
- parsed = credentialSchema.parse(JSON.parse(await readFile3(this.path, "utf8")));
20403
- } catch (error) {
20404
- throw new ServerCredentialsInvalidError(`server credentials are invalid: ${safeMessage(error)}`);
21199
+ const snapshot = await this.readSnapshot();
21200
+ if (snapshot !== void 0 && !this.closed) this.write(snapshot);
21201
+ } finally {
21202
+ this.sampling = false;
20405
21203
  }
20406
- return parsed.serverUrl === serverUrl && parsed.deviceId === deviceId ? parsed : void 0;
20407
21204
  }
20408
- async save(credentials) {
20409
- const record6 = credentialSchema.parse({ schemaVersion: 1, ...credentials });
20410
- await atomicWrite(this.path, `${JSON.stringify(record6, null, 2)}
20411
- `);
20412
- return record6;
21205
+ async readSnapshot() {
21206
+ try {
21207
+ const value = await this.readStatus();
21208
+ return value === void 0 ? void 0 : JSON.stringify(value) ?? void 0;
21209
+ } catch {
21210
+ return void 0;
21211
+ }
20413
21212
  }
20414
- async clear() {
20415
- await rm2(this.path, { force: true });
21213
+ /**
21214
+ * Write the payload to every subscriber that has not seen it, or one
21215
+ * keep-alive comment when the whole connection set is idle.
21216
+ */
21217
+ write(snapshot) {
21218
+ if (snapshot === void 0 || this.subscribers.size === 0) return;
21219
+ const now = Date.now();
21220
+ const fresh = [...this.subscribers].filter((subscriber) => subscriber.payload !== snapshot);
21221
+ if (fresh.length > 0) {
21222
+ const frame = `data: ${snapshot}
21223
+
21224
+ `;
21225
+ for (const subscriber of fresh) {
21226
+ if (this.writeChunk(subscriber, frame)) subscriber.payload = snapshot;
21227
+ else this.detach(subscriber);
21228
+ }
21229
+ this.payload = snapshot;
21230
+ this.writtenAt = now;
21231
+ return;
21232
+ }
21233
+ if (now - this.writtenAt < this.heartbeatIntervalMs) return;
21234
+ for (const subscriber of [...this.subscribers]) {
21235
+ if (!this.writeChunk(subscriber, KEEP_ALIVE_FRAME)) this.detach(subscriber);
21236
+ }
21237
+ this.writtenAt = now;
20416
21238
  }
20417
- };
20418
- var ServerCredentialsInvalidError = class extends Error {
20419
- code = "SERVER_CREDENTIALS_INVALID";
20420
- };
20421
- async function atomicWrite(path, contents) {
20422
- await mkdir2(dirname4(path), { recursive: true, mode: 448 });
20423
- const temporary = `${path}.${process.pid}.${uuidV7()}.tmp`;
20424
- await writeFile2(temporary, contents, { encoding: "utf8", mode: 384, flag: "wx" });
20425
- await chmod2(temporary, 384);
20426
- await rename2(temporary, path);
20427
- await chmod2(path, 384);
20428
- }
20429
- async function assertPrivateMode2(path) {
20430
- if (process.platform === "win32") return;
20431
- const mode = (await stat2(path)).mode & 511;
20432
- if ((mode & 63) !== 0) throw new ServerCredentialsInvalidError("server credentials permissions must be 0600");
20433
- }
20434
- async function exists2(path) {
20435
- try {
20436
- await stat2(path);
20437
- return true;
20438
- } catch (error) {
20439
- if (error instanceof Error && "code" in error && error.code === "ENOENT") return false;
20440
- throw error;
21239
+ /** @returns true when the chunk reached a live response. */
21240
+ writeChunk(subscriber, chunk) {
21241
+ if (subscriber.response.destroyed || subscriber.response.writableEnded) return false;
21242
+ try {
21243
+ subscriber.response.write(chunk);
21244
+ return true;
21245
+ } catch {
21246
+ return false;
21247
+ }
20441
21248
  }
20442
- }
20443
- function safeMessage(error) {
20444
- return error instanceof Error ? error.message : "invalid credential data";
20445
- }
21249
+ };
20446
21250
 
20447
21251
  // src/control-runtime.ts
20448
21252
  var PluginControlRuntime = class {
@@ -20454,10 +21258,27 @@ var PluginControlRuntime = class {
20454
21258
  this.host = host;
20455
21259
  }
20456
21260
  register(connection, webServer) {
20457
- return registerControlRoute(connection, (endpoint, payload, signal) => this.handle(endpoint, payload, signal), webServer);
21261
+ const statusStream = new ControlStatusStream(() => this.streamStatus());
21262
+ return registerControlRoute(
21263
+ connection,
21264
+ (endpoint, payload, signal) => this.handle(endpoint, payload, signal),
21265
+ webServer,
21266
+ statusStream
21267
+ );
21268
+ }
21269
+ /**
21270
+ * Read the value the status event stream pushes. It resolves through the same
21271
+ * endpoint handler as the unary `status` control call, so a pushed status and
21272
+ * a polled one can never diverge.
21273
+ */
21274
+ async streamStatus() {
21275
+ const result = await this.handle("status", {}, new AbortController().signal);
21276
+ if (!result.ok) throw new ClientModeError("STATUS_UNAVAILABLE", result.error.message);
21277
+ return result.value;
20458
21278
  }
20459
21279
  async handle(endpoint, payload, signal) {
20460
21280
  try {
21281
+ if (endpoint === "settings.development.set") return ok3(await this.setDevelopment(payload));
20461
21282
  if (endpoint === "settings.get") return ok3(await this.settingsView());
20462
21283
  if (endpoint === "settings.configure") return ok3(await this.configure(payload));
20463
21284
  if (endpoint === "settings.server.set") return ok3(await this.setServer(payload));
@@ -20561,6 +21382,23 @@ var PluginControlRuntime = class {
20561
21382
  await this.settings.replace({ ...current, role });
20562
21383
  return this.settingsView();
20563
21384
  }
21385
+ async setDevelopment(payload) {
21386
+ if (this.settings === void 0) throw new ClientModeError("SETTINGS_UNAVAILABLE", "DSH user settings are unavailable in this profile.");
21387
+ const value = record4(payload);
21388
+ if (value.terminalEnabled !== void 0 && typeof value.terminalEnabled !== "boolean") throw new ClientModeError("INVALID_MESSAGE", "The terminal switch must be a boolean.");
21389
+ if (value.ports !== void 0 && (!Array.isArray(value.ports) || value.ports.some((port) => !Number.isInteger(port)))) throw new ClientModeError("INVALID_MESSAGE", "Loopback ports must be integers.");
21390
+ if (value.terminalEnabled === void 0 && value.ports === void 0) throw new ClientModeError("INVALID_MESSAGE", "A terminal switch or loopback ports are required.");
21391
+ const current = editableConfig(resolveConfig(this.settings.get()));
21392
+ const next = resolveConfig({
21393
+ ...current,
21394
+ terminal: { enabled: value.terminalEnabled === void 0 ? current.terminal?.enabled ?? false : value.terminalEnabled },
21395
+ loopback: { ports: value.ports === void 0 ? current.loopback?.ports ?? [] : value.ports }
21396
+ });
21397
+ await this.settings.replace(editableConfig(next));
21398
+ if (value.terminalEnabled !== void 0) this.host?.setTerminalEnabled?.(value.terminalEnabled);
21399
+ if (value.ports !== void 0) this.host?.setLoopbackPorts?.(value.ports);
21400
+ return this.settingsView();
21401
+ }
20564
21402
  async setCodex(payload) {
20565
21403
  if (this.settings === void 0) {
20566
21404
  throw new ClientModeError("SETTINGS_UNAVAILABLE", "DSH user settings are unavailable in this profile.");
@@ -20601,10 +21439,10 @@ var PluginControlRuntime = class {
20601
21439
  }
20602
21440
  async removeAcp(payload) {
20603
21441
  if (this.settings === void 0) throw new ClientModeError("SETTINGS_UNAVAILABLE", "DSH user settings are unavailable in this profile.");
20604
- const id2 = record4(payload).id;
20605
- if (typeof id2 !== "string" || ["codex", "cursor", "kimi"].includes(id2)) throw new ClientModeError("INVALID_MESSAGE", "Only custom ACP backends can be removed.");
21442
+ const id4 = record4(payload).id;
21443
+ if (typeof id4 !== "string" || ["codex", "cursor", "kimi"].includes(id4)) throw new ClientModeError("INVALID_MESSAGE", "Only custom ACP backends can be removed.");
20606
21444
  const current = resolveConfig(this.settings.get());
20607
- const backends = (current.acp?.backends ?? []).filter((item) => item.id !== id2);
21445
+ const backends = (current.acp?.backends ?? []).filter((item) => item.id !== id4);
20608
21446
  await this.settings.replace(editableConfig({ ...current, acp: { enabled: current.acp?.enabled ?? true, backends } }));
20609
21447
  return this.settingsView();
20610
21448
  }
@@ -20698,6 +21536,8 @@ function editableConfig(config) {
20698
21536
  enabled: config.enabled,
20699
21537
  role: config.role,
20700
21538
  ...config.serverUrl === void 0 ? {} : { serverUrl: config.serverUrl },
21539
+ terminal: config.terminal,
21540
+ loopback: config.loopback,
20701
21541
  forceRelay: config.forceRelay,
20702
21542
  logLevel: config.logLevel,
20703
21543
  reconnect: config.reconnect.enabled ? {
@@ -20768,8 +21608,70 @@ function redact(value, key = "") {
20768
21608
  return Object.fromEntries(Object.entries(value).map(([childKey, child]) => [childKey, redact(child, childKey)]));
20769
21609
  }
20770
21610
 
21611
+ // src/terminal-policy.ts
21612
+ var TERMINAL_CALLS = /* @__PURE__ */ new Set([
21613
+ "terminal/environment",
21614
+ "terminal/shells",
21615
+ "terminal/list",
21616
+ "terminal/create",
21617
+ "terminal/write",
21618
+ "terminal/resize",
21619
+ "terminal/rename",
21620
+ "terminal/close"
21621
+ ]);
21622
+ var TERMINAL_STREAMS = /* @__PURE__ */ new Set(["terminal/follow", "terminal/retain"]);
21623
+ var id2 = external_exports.string().regex(/^[\w-]{1,128}$/);
21624
+ var TerminalPolicy = class {
21625
+ constructor(enabled, deviceId, owners) {
21626
+ this.enabled = enabled;
21627
+ this.deviceId = deviceId;
21628
+ this.owners = owners;
21629
+ }
21630
+ attachments = /* @__PURE__ */ new Map();
21631
+ check(endpoint, payload) {
21632
+ if (!(typeof this.enabled === "function" ? this.enabled() : this.enabled)) throw new RpcError(
21633
+ "TERMINAL_DISABLED",
21634
+ "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
+ );
21636
+ 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);
21638
+ if (endpoint === "terminal/environment" || endpoint === "terminal/shells" || endpoint === "terminal/list") return {};
21639
+ const request = endpoint === "terminal/create" ? external_exports.object({ id: id2 }).passthrough().parse(args.request) : void 0;
21640
+ const terminalId = id2.parse(request?.id ?? args.id);
21641
+ const key = `${sessionId}/${terminalId}`;
21642
+ if (endpoint === "terminal/create") {
21643
+ if (this.owners.has(key) && this.owners.get(key) !== this.deviceId) this.deny();
21644
+ if (!this.owners.has(key) && this.owners.size >= 256) throw new RpcError("RATE_LIMITED", "Too many retained remote terminals.");
21645
+ const created = !this.owners.has(key);
21646
+ this.owners.set(key, this.deviceId);
21647
+ return { key, created };
21648
+ }
21649
+ if (this.owners.get(key) !== this.deviceId) this.deny();
21650
+ if (endpoint === "terminal/follow") this.attachments.set(key, id2.parse(args.attachmentId));
21651
+ if (endpoint === "terminal/write" || endpoint === "terminal/resize") {
21652
+ if (this.attachments.get(key) !== id2.parse(args.attachmentId)) this.deny();
21653
+ }
21654
+ return { key };
21655
+ }
21656
+ result(endpoint, payload, result, reservation) {
21657
+ if (reservation.key !== void 0 && (!result.ok && reservation.created || result.ok && endpoint === "terminal/close")) {
21658
+ this.owners.delete(reservation.key);
21659
+ this.attachments.delete(reservation.key);
21660
+ }
21661
+ if (endpoint === "terminal/list" && result.ok && Array.isArray(result.value)) {
21662
+ const args = payload.args;
21663
+ const session = args.sessionId ?? args.agentId;
21664
+ return { ok: true, value: result.value.filter((value) => typeof value?.id === "string" && this.owners.get(`${session}/${value.id}`) === this.deviceId) };
21665
+ }
21666
+ return result;
21667
+ }
21668
+ deny() {
21669
+ throw new RpcError("PERMISSION_DENIED", "This terminal or input attachment belongs to another connection or device.");
21670
+ }
21671
+ };
21672
+
20771
21673
  // src/service.ts
20772
- import { randomUUID as randomUUID2 } from "node:crypto";
21674
+ import { randomUUID as randomUUID3 } from "node:crypto";
20773
21675
 
20774
21676
  // src/connection-controller.ts
20775
21677
  var ConnectionController = class {
@@ -20953,21 +21855,6 @@ function shortId2(value) {
20953
21855
  return value.length <= 12 ? value : `${value.slice(0, 8)}\u2026${value.slice(-4)}`;
20954
21856
  }
20955
21857
 
20956
- // src/safe-error.ts
20957
- var RpcError = class extends Error {
20958
- constructor(code, message, details, retryable = false) {
20959
- super(message);
20960
- this.code = code;
20961
- this.details = details;
20962
- this.retryable = retryable;
20963
- }
20964
- };
20965
- function safeErrorCode(error) {
20966
- if (error instanceof RpcError) return error.code;
20967
- if (error instanceof external_exports.ZodError) return "INVALID_MESSAGE";
20968
- return "INTERNAL_ERROR";
20969
- }
20970
-
20971
21858
  // src/rpc-router.ts
20972
21859
  var wireRequestSchema = external_exports.object({ method: external_exports.string().min(1), params: external_exports.unknown() }).strict();
20973
21860
  var emptyParamsSchema = external_exports.object({}).strict();
@@ -20990,6 +21877,7 @@ var apiMethods = /* @__PURE__ */ new Set([
20990
21877
  "harness.remote.transfer.close",
20991
21878
  "harness.remote.stream.open",
20992
21879
  "harness.remote.stream.close",
21880
+ "loopback.call",
20993
21881
  "fileviewer.call",
20994
21882
  "codex.app.call",
20995
21883
  "codex.app.respond",
@@ -21019,7 +21907,7 @@ var HOST_CAPABILITIES = [
21019
21907
  "agent.acp.v1"
21020
21908
  ];
21021
21909
  var RpcRouter = class {
21022
- constructor(harnessApi, maxPending = 128, logger, fileViewer, harnessRemote, capabilities = () => HOST_CAPABILITIES, codex, acp) {
21910
+ constructor(harnessApi, maxPending = 128, logger, fileViewer, harnessRemote, capabilities = () => HOST_CAPABILITIES, codex, acp, loopback) {
21023
21911
  this.harnessApi = harnessApi;
21024
21912
  this.maxPending = maxPending;
21025
21913
  this.logger = logger;
@@ -21028,9 +21916,11 @@ var RpcRouter = class {
21028
21916
  this.capabilities = capabilities;
21029
21917
  this.codex = codex;
21030
21918
  this.acp = acp;
21919
+ this.loopback = loopback;
21031
21920
  }
21032
21921
  active = 0;
21033
21922
  async closePeerStreams() {
21923
+ this.loopback?.closeAll();
21034
21924
  await Promise.all([
21035
21925
  this.harnessApi?.closeAll(),
21036
21926
  this.harnessRemote?.closeAll(),
@@ -21112,6 +22002,10 @@ var RpcRouter = class {
21112
22002
  return this.requireRemoteGateway().openStream(params);
21113
22003
  case "harness.remote.stream.close":
21114
22004
  return this.requireRemoteGateway().closeStream(params);
22005
+ case "loopback.call": {
22006
+ if (this.loopback === void 0) throw new RpcError("FEATURE_NOT_SUPPORTED", "Loopback preview is unavailable on this Host.");
22007
+ return this.loopback.call(params);
22008
+ }
21115
22009
  case "fileviewer.call": {
21116
22010
  if (this.fileViewer === void 0) {
21117
22011
  throw new RpcError("FILE_VIEWER_UNAVAILABLE", "The Remote Host does not have DSH File Viewer available.");
@@ -21332,6 +22226,7 @@ var HostServerConnection = class {
21332
22226
  lastActiveAt;
21333
22227
  reconnectRequested = false;
21334
22228
  resumeQueued = false;
22229
+ authRecoveryAttempted = false;
21335
22230
  rtcFactory;
21336
22231
  negotiatedCapabilities = ["transport.relay"];
21337
22232
  controlFrameLimits = {};
@@ -21395,14 +22290,23 @@ var HostServerConnection = class {
21395
22290
  }
21396
22291
  async run() {
21397
22292
  let delayMs = this.config.reconnect.initialDelayMs;
22293
+ this.authRecoveryAttempted = false;
21398
22294
  while (!this.stopped) {
21399
22295
  try {
21400
22296
  await this.connectOnce();
21401
22297
  delayMs = this.config.reconnect.initialDelayMs;
21402
22298
  } catch (error) {
21403
22299
  const code = errorCode2(error);
22300
+ if (code === "CREDENTIALS_REFRESHED") continue;
21404
22301
  this.terminalError = code;
21405
- this.logger.warn("server control connection failed", { code, retryable: isRetryable(error) });
22302
+ this.logger.warn("server control connection failed", {
22303
+ code,
22304
+ retryable: isRetryable(error),
22305
+ ...error instanceof ServerApiError && error.phase !== void 0 ? { phase: error.phase } : {}
22306
+ });
22307
+ if (TERMINAL_AUTH_ERRORS.has(code)) {
22308
+ this.logger.warn(code === "CONNECTION_REPLACED" ? "Another instance is using this Host identity. Stop it or use a separate DSH_HOME; automatic reconnect is paused." : code === "SERVER_CREDENTIALS_BUSY" ? "Credential refresh is locked. Stop other instances; after a crash, stop all instances before removing server-credentials.json.refresh-lock and authorizing again." : "Host authorization failed. Run /remote login or authorize this Host again in Remote settings.");
22309
+ }
21406
22310
  if (TERMINAL_AUTH_ERRORS.has(code) || !this.config.reconnect.enabled) return;
21407
22311
  }
21408
22312
  if (this.stopped) return;
@@ -21458,6 +22362,7 @@ var HostServerConnection = class {
21458
22362
  this.lastActiveAt = Date.now();
21459
22363
  if (frame.type === "hello.ack") {
21460
22364
  const payload = requireHelloAck(frame.payload);
22365
+ this.authRecoveryAttempted = false;
21461
22366
  this.controlFrameLimits = {
21462
22367
  maxControlFrameBytes: payload.maxControlFrameBytes,
21463
22368
  maxRelayFrameBytes: payload.maxRelayFrameBytes
@@ -21494,13 +22399,28 @@ var HostServerConnection = class {
21494
22399
  socket.onclose = (event) => {
21495
22400
  const close = async () => {
21496
22401
  await messageQueue.catch(() => void 0);
22402
+ if (this.stopped) {
22403
+ finish();
22404
+ return;
22405
+ }
22406
+ if (event.code === 4003) {
22407
+ finish(new ControlConnectionError("CONNECTION_REPLACED", "Another instance connected with this Host identity."));
22408
+ return;
22409
+ }
21497
22410
  if (event.code === 4002) {
22411
+ if (this.authRecoveryAttempted) {
22412
+ finish(new ControlConnectionError("AUTH_INVALID", "Server rejected refreshed credentials."));
22413
+ return;
22414
+ }
21498
22415
  try {
21499
- await this.api.refreshCredentials();
22416
+ await this.api.refreshCredentials(credentials.accessToken);
21500
22417
  } catch (error) {
21501
22418
  finish(asError2(error));
21502
22419
  return;
21503
22420
  }
22421
+ this.authRecoveryAttempted = true;
22422
+ finish(new ControlConnectionError("CREDENTIALS_REFRESHED", "Retry hello with refreshed credentials."));
22423
+ return;
21504
22424
  }
21505
22425
  if (event.code === 4004) {
21506
22426
  finish(new ControlConnectionError("DEVICE_REVOKED", "The Server revoked this Host device."));
@@ -21748,7 +22668,7 @@ var HostServerConnection = class {
21748
22668
  }
21749
22669
  });
21750
22670
  tunnel.rtc = rtc;
21751
- rtc.onMessage((data) => tunnel.channel?.receive(void 0, data));
22671
+ rtc.onMessage((data2) => tunnel.channel?.receive(void 0, data2));
21752
22672
  rtc.onClose(() => {
21753
22673
  void this.handleRtcFailed(tunnel, rtc, new Error("WebRTC data channel closed."));
21754
22674
  });
@@ -21905,6 +22825,10 @@ var HostServerConnection = class {
21905
22825
  async sendRelay(tunnel, ciphertext) {
21906
22826
  const counter = Number(tunnel.noise.sendingCounter() - 1n);
21907
22827
  if (!Number.isSafeInteger(counter) || counter < 0) throw new ControlConnectionError("FRAME_TOO_LARGE", "Noise transport counter overflowed.");
22828
+ const socket = this.socket;
22829
+ if (socket === void 0) throw new Error("Relay transport closed");
22830
+ await waitForRelayCapacity(socket);
22831
+ if (this.socket !== socket) throw new Error("Relay transport replaced");
21908
22832
  this.sendControl("relay", {
21909
22833
  connectionId: tunnel.connectionId,
21910
22834
  targetDeviceId: tunnel.peer.deviceId,
@@ -21970,6 +22894,8 @@ var HostServerConnection = class {
21970
22894
  }
21971
22895
  };
21972
22896
  var TERMINAL_AUTH_ERRORS = /* @__PURE__ */ new Set([
22897
+ "CONNECTION_REPLACED",
22898
+ "SERVER_CREDENTIALS_BUSY",
21973
22899
  "ACCOUNT_AUTH_REQUIRED",
21974
22900
  "AUTH_INVALID",
21975
22901
  "DEVICE_OWNERSHIP_REQUIRED",
@@ -21990,6 +22916,7 @@ var ServerNoiseChannel = class {
21990
22916
  this.peerDeviceId = tunnel.peer.deviceId;
21991
22917
  this.peerIdentityKey = tunnel.peer.publicKey;
21992
22918
  }
22919
+ sends = new SerialSend();
21993
22920
  security;
21994
22921
  peerDeviceId;
21995
22922
  peerIdentityKey;
@@ -22000,11 +22927,12 @@ var ServerNoiseChannel = class {
22000
22927
  closed = false;
22001
22928
  async send(message) {
22002
22929
  if (this.closed) throw new Error("secure channel is closed");
22003
- const plaintextFrames = this.outgoing.encode(encodeMessage(message));
22930
+ const encoded = encodeMessage(message);
22004
22931
  try {
22005
- for (const plaintext of plaintextFrames) {
22006
- await this.transmit(this.tunnel.noise.encrypt(plaintext));
22007
- }
22932
+ await this.sends.run(encoded.byteLength, async () => {
22933
+ if (this.closed) throw new Error("Secure channel closed");
22934
+ for (const plaintext of this.outgoing.encode(encoded)) await this.transmit(this.tunnel.noise.encrypt(plaintext));
22935
+ });
22008
22936
  } catch (error) {
22009
22937
  await this.close().catch(() => void 0);
22010
22938
  throw error;
@@ -22049,10 +22977,10 @@ function websocketUrl2(baseUrl) {
22049
22977
  url.pathname = `${url.pathname.replace(/\/$/, "")}/ws/v1/connect`;
22050
22978
  return url.toString();
22051
22979
  }
22052
- function decodeControl(data, limits) {
22053
- if (typeof data !== "string") throw new ControlConnectionError("INVALID_MESSAGE", "Server control frames must be text JSON.");
22980
+ function decodeControl(data2, limits) {
22981
+ if (typeof data2 !== "string") throw new ControlConnectionError("INVALID_MESSAGE", "Server control frames must be text JSON.");
22054
22982
  try {
22055
- return decodeControlFrame(data, limits);
22983
+ return decodeControlFrame(data2, limits);
22056
22984
  } catch {
22057
22985
  throw new ControlConnectionError("INVALID_MESSAGE", "Server sent an invalid control frame.");
22058
22986
  }
@@ -22158,10 +23086,11 @@ function errorCode2(error) {
22158
23086
  return error instanceof ServerApiError || error instanceof ControlConnectionError ? error.code : "CONNECTION_FAILED";
22159
23087
  }
22160
23088
  function isRetryable(error) {
22161
- return error instanceof ServerApiError ? error.retryable : errorCode2(error) !== "DEVICE_REVOKED";
23089
+ return !TERMINAL_AUTH_ERRORS.has(errorCode2(error)) && (!(error instanceof ServerApiError) || error.retryable);
22162
23090
  }
22163
23091
  function closeCode(code) {
22164
23092
  if (code === 4002) return "AUTH_INVALID";
23093
+ if (code === 4003) return "CONNECTION_REPLACED";
22165
23094
  if (code === 4004) return "DEVICE_REVOKED";
22166
23095
  if (code === 4007) return "RATE_LIMITED";
22167
23096
  if (code === 4011) return "UNSUPPORTED_VERSION";
@@ -22176,13 +23105,13 @@ var MAX_ENTRIES = 500;
22176
23105
  async function listRemoteDirectory(path, signal) {
22177
23106
  signal?.throwIfAborted();
22178
23107
  const home = resolve(homedir2());
22179
- const target = path === void 0 || path.trim() === "" ? home : resolve(path);
22180
- if (!isAbsolute2(target)) throw new Error("The remote directory path must be absolute.");
22181
- const rows = await readdir(target, { withFileTypes: true });
23108
+ const target2 = path === void 0 || path.trim() === "" ? home : resolve(path);
23109
+ if (!isAbsolute2(target2)) throw new Error("The remote directory path must be absolute.");
23110
+ const rows = await readdir(target2, { withFileTypes: true });
22182
23111
  const directories = [];
22183
23112
  for (const row of rows) {
22184
23113
  signal?.throwIfAborted();
22185
- const child = resolve(target, row.name);
23114
+ const child = resolve(target2, row.name);
22186
23115
  let directory = row.isDirectory();
22187
23116
  if (!directory && row.isSymbolicLink()) directory = await stat3(child).then((value) => value.isDirectory()).catch(() => false);
22188
23117
  if (!directory) continue;
@@ -22190,9 +23119,9 @@ async function listRemoteDirectory(path, signal) {
22190
23119
  }
22191
23120
  directories.sort((left, right) => left.name.localeCompare(right.name, void 0, { sensitivity: "base" }));
22192
23121
  return {
22193
- path: target,
23122
+ path: target2,
22194
23123
  home,
22195
- crumbs: crumbs(target),
23124
+ crumbs: crumbs(target2),
22196
23125
  entries: directories.slice(0, MAX_ENTRIES),
22197
23126
  truncated: directories.length > MAX_ENTRIES
22198
23127
  };
@@ -22899,6 +23828,9 @@ function disableRemoteSettingsDocument(response) {
22899
23828
  }
22900
23829
  var EMPTY_SETTINGS_NAMESPACES = /* @__PURE__ */ new Set();
22901
23830
  async function assertRegisteredSettingsNamespace(api, ns) {
23831
+ if (ns === "ds-harness-remote" || ns === "dsh-remote") {
23832
+ throw new RpcError("PERMISSION_DENIED", "Remote access settings can only be changed locally on the Host.");
23833
+ }
22902
23834
  const allowed = await registeredSettingsNamespaces(api);
22903
23835
  if (!allowed.has(ns)) throw deniedSettingsNamespace(ns);
22904
23836
  }
@@ -23124,6 +24056,15 @@ var HARNESS_REMOTE_ALLOWLIST = [
23124
24056
  "subagents/interruptByParent",
23125
24057
  "subagents/list",
23126
24058
  "subagents/prompt",
24059
+ "workspaceFiles/list",
24060
+ "workspaceFiles/stat",
24061
+ "workspaceFiles/read",
24062
+ "workspaceFiles/readBytes",
24063
+ "workspaceFiles/readAll",
24064
+ "workspaceFiles/readRelated",
24065
+ "workspaceFiles/changes",
24066
+ "officeToPdf/render",
24067
+ "officeToPdf/generation",
23127
24068
  "workspace/archiveSession",
23128
24069
  "workspace/create",
23129
24070
  "workspace/delete",
@@ -23134,11 +24075,12 @@ var HARNESS_REMOTE_ALLOWLIST = [
23134
24075
  ];
23135
24076
  var allowedEndpoints = new Set(HARNESS_REMOTE_ALLOWLIST);
23136
24077
  var HarnessRemoteBridge = class {
23137
- constructor(gateway, publish, logger, harnessVersion) {
24078
+ constructor(gateway, publish, logger, harnessVersion, terminal = new TerminalPolicy(() => false, "", /* @__PURE__ */ new Map())) {
23138
24079
  this.gateway = gateway;
23139
24080
  this.publish = publish;
23140
24081
  this.logger = logger;
23141
24082
  this.harnessVersion = harnessVersion;
24083
+ this.terminal = terminal;
23142
24084
  }
23143
24085
  streams = /* @__PURE__ */ new Map();
23144
24086
  incomingTransfers = /* @__PURE__ */ new Map();
@@ -23146,6 +24088,14 @@ var HarnessRemoteBridge = class {
23146
24088
  async call(input2) {
23147
24089
  const params = callSchema3.parse(input2);
23148
24090
  this.assertAllowed(params.endpoint);
24091
+ if (TERMINAL_STREAMS.has(params.endpoint)) throw new RpcError("METHOD_NOT_ALLOWED", "Use a stream for this terminal endpoint.");
24092
+ if (["settings/update", "settings/replace", "settings/mutate"].includes(params.endpoint)) {
24093
+ const args = requestArgs(params.payload);
24094
+ if (args.ns === "ds-harness-remote" || args.ns === "dsh-remote") {
24095
+ throw new RpcError("PERMISSION_DENIED", "Remote access settings can only be changed locally on the Host.");
24096
+ }
24097
+ }
24098
+ const reservation = params.endpoint.startsWith("terminal/") ? this.terminal.check(params.endpoint, params.payload) : void 0;
23149
24099
  if (params.endpoint === "session/canOpenWorkspacePath") {
23150
24100
  return { ok: true, value: true };
23151
24101
  }
@@ -23158,8 +24108,9 @@ var HarnessRemoteBridge = class {
23158
24108
  endpoint: params.endpoint,
23159
24109
  durationMs: Math.round(performance.now() - startedAt)
23160
24110
  });
23161
- return result;
24111
+ return reservation === void 0 ? result : this.terminal.result(params.endpoint, params.payload, result, reservation);
23162
24112
  } catch (error) {
24113
+ if (reservation !== void 0) this.terminal.result(params.endpoint, params.payload, { ok: false, error: { code: "FAILED", message: "", details: {} } }, reservation);
23163
24114
  if (params.endpoint === "directoryPicker/list") {
23164
24115
  const result = await this.directoryList(params.payload, signal);
23165
24116
  this.logger?.debug("harness remote call ok", {
@@ -23286,13 +24237,24 @@ var HarnessRemoteBridge = class {
23286
24237
  async openStream(input2) {
23287
24238
  const params = streamOpenSchema2.parse(input2);
23288
24239
  this.assertAllowed(params.endpoint);
24240
+ if (params.endpoint.startsWith("settings/")) throw new RpcError("METHOD_NOT_ALLOWED", "Settings endpoints are not streams.");
24241
+ if (TERMINAL_CALLS.has(params.endpoint)) throw new RpcError("METHOD_NOT_ALLOWED", "This terminal endpoint is not a stream.");
24242
+ if (TERMINAL_STREAMS.has(params.endpoint)) this.terminal.check(params.endpoint, params.payload);
23289
24243
  if (this.streams.has(params.streamId)) throw new RpcError("REQUEST_CONFLICT", "The Harness Remote stream is already open.");
23290
24244
  if (this.streams.size >= MAX_ACTIVE_STREAMS) {
23291
24245
  throw new RpcError("RATE_LIMITED", "Too many Harness Remote streams are open.", void 0, true);
23292
24246
  }
23293
24247
  const controller = new AbortController();
23294
- const source = await this.gateway.open(params.endpoint, params.payload, controller.signal);
23295
24248
  this.streams.set(params.streamId, { controller });
24249
+ let source;
24250
+ try {
24251
+ source = await this.gateway.open(params.endpoint, params.payload, controller.signal);
24252
+ controller.signal.throwIfAborted();
24253
+ } catch (error) {
24254
+ this.streams.delete(params.streamId);
24255
+ controller.abort();
24256
+ throw error;
24257
+ }
23296
24258
  void this.pump(params.streamId, source, controller.signal);
23297
24259
  return { opened: true, streamId: params.streamId };
23298
24260
  }
@@ -23313,7 +24275,7 @@ var HarnessRemoteBridge = class {
23313
24275
  for (const [, stream] of streams) stream.controller.abort(reason);
23314
24276
  }
23315
24277
  assertAllowed(endpoint) {
23316
- if (!allowedEndpoints.has(endpoint)) {
24278
+ if (!allowedEndpoints.has(endpoint) && !TERMINAL_CALLS.has(endpoint) && !TERMINAL_STREAMS.has(endpoint)) {
23317
24279
  throw new RpcError("METHOD_NOT_ALLOWED", "The requested Harness Remote endpoint is not allowed.");
23318
24280
  }
23319
24281
  if (endpoint === "$events" && !this.gateway.supportsCarrier) {
@@ -23347,8 +24309,8 @@ var HarnessRemoteBridge = class {
23347
24309
  }
23348
24310
  pruneTransfers() {
23349
24311
  const cutoff = Date.now() - TRANSFER_IDLE_MS;
23350
- for (const [id2, transfer] of this.incomingTransfers) if (transfer.touchedAt < cutoff) this.incomingTransfers.delete(id2);
23351
- for (const [id2, transfer] of this.outgoingTransfers) if (transfer.touchedAt < cutoff) this.outgoingTransfers.delete(id2);
24312
+ for (const [id4, transfer] of this.incomingTransfers) if (transfer.touchedAt < cutoff) this.incomingTransfers.delete(id4);
24313
+ for (const [id4, transfer] of this.outgoingTransfers) if (transfer.touchedAt < cutoff) this.outgoingTransfers.delete(id4);
23352
24314
  }
23353
24315
  };
23354
24316
  async function dispatchCommandForHost(gateway, payload, signal, harnessVersion) {
@@ -23407,7 +24369,7 @@ function isRecord9(value) {
23407
24369
  }
23408
24370
 
23409
24371
  // src/codex/domain.ts
23410
- import { randomUUID } from "node:crypto";
24372
+ import { randomUUID as randomUUID2 } from "node:crypto";
23411
24373
  import { readdir as readdir2, realpath, stat as stat4 } from "node:fs/promises";
23412
24374
  import { homedir as homedir3 } from "node:os";
23413
24375
  import { basename as basename3, isAbsolute as isAbsolute3, join as join5, relative, resolve as resolve2 } from "node:path";
@@ -23461,11 +24423,11 @@ var CodexAppServerClient = class {
23461
24423
  if (!this.ready) throw new CodexAppServerError("CODEX_UNAVAILABLE", "Codex App Server is not ready.");
23462
24424
  return this.request(method, params, timeoutMs);
23463
24425
  }
23464
- async respond(id2, result) {
23465
- this.write({ id: id2, result });
24426
+ async respond(id4, result) {
24427
+ this.write({ id: id4, result });
23466
24428
  }
23467
- async respondError(id2, code, message) {
23468
- this.write({ id: id2, error: { code, message } });
24429
+ async respondError(id4, code, message) {
24430
+ this.write({ id: id4, error: { code, message } });
23469
24431
  }
23470
24432
  onInbound(handler) {
23471
24433
  this.inboundHandlers.add(handler);
@@ -23546,19 +24508,19 @@ var CodexAppServerClient = class {
23546
24508
  }
23547
24509
  }
23548
24510
  request(method, params, timeoutMs) {
23549
- const id2 = this.nextId++;
24511
+ const id4 = this.nextId++;
23550
24512
  const result = new Promise((resolve3, reject) => {
23551
24513
  const timer = setTimeout(() => {
23552
- this.pending.delete(id2);
24514
+ this.pending.delete(id4);
23553
24515
  reject(new CodexAppServerError("CODEX_REQUEST_TIMEOUT", "Codex App Server request timed out."));
23554
24516
  }, timeoutMs);
23555
24517
  timer.unref?.();
23556
- this.pending.set(id2, { resolve: resolve3, reject, timer });
24518
+ this.pending.set(id4, { resolve: resolve3, reject, timer });
23557
24519
  });
23558
24520
  try {
23559
- this.write({ id: id2, method, params });
24521
+ this.write({ id: id4, method, params });
23560
24522
  } catch (error) {
23561
- const pending = this.takePending(id2);
24523
+ const pending = this.takePending(id4);
23562
24524
  pending?.reject(error instanceof Error ? error : new Error("Codex App Server write failed."));
23563
24525
  }
23564
24526
  return result;
@@ -23629,15 +24591,15 @@ var CodexAppServerClient = class {
23629
24591
  this.failureNotified = true;
23630
24592
  for (const handler of this.unavailableHandlers) handler(code);
23631
24593
  }
23632
- takePending(id2) {
23633
- const pending = this.pending.get(id2);
24594
+ takePending(id4) {
24595
+ const pending = this.pending.get(id4);
23634
24596
  if (pending === void 0) return void 0;
23635
- this.pending.delete(id2);
24597
+ this.pending.delete(id4);
23636
24598
  clearTimeout(pending.timer);
23637
24599
  return pending;
23638
24600
  }
23639
24601
  failPending(error) {
23640
- for (const id2 of [...this.pending.keys()]) this.takePending(id2)?.reject(error);
24602
+ for (const id4 of [...this.pending.keys()]) this.takePending(id4)?.reject(error);
23641
24603
  }
23642
24604
  };
23643
24605
  function safeUpstreamError(value) {
@@ -23651,7 +24613,7 @@ function isRecord10(value) {
23651
24613
  }
23652
24614
 
23653
24615
  // src/codex/method-policy.ts
23654
- var id = external_exports.string().min(1).max(256);
24616
+ var id3 = external_exports.string().min(1).max(256);
23655
24617
  var cursor = external_exports.string().min(1).max(4096).nullable().optional();
23656
24618
  var textInput = external_exports.object({
23657
24619
  type: external_exports.literal("text"),
@@ -23700,9 +24662,9 @@ var schemas = {
23700
24662
  useStateDbOnly: external_exports.boolean().optional(),
23701
24663
  searchTerm: external_exports.string().max(1024).optional()
23702
24664
  }).strict(),
23703
- "thread/read": external_exports.object({ threadId: id, includeTurns: external_exports.boolean().optional() }).strict(),
24665
+ "thread/read": external_exports.object({ threadId: id3, includeTurns: external_exports.boolean().optional() }).strict(),
23704
24666
  "dsh/sessionHistory": external_exports.object({
23705
- threadId: id,
24667
+ threadId: id3,
23706
24668
  beforeSeq: external_exports.number().int().nonnegative().optional(),
23707
24669
  throughSeq: external_exports.number().int().min(-1).optional(),
23708
24670
  maxMessages: external_exports.number().int().min(1).max(200).optional()
@@ -23717,21 +24679,21 @@ var schemas = {
23717
24679
  permissionPreset: permissionPreset.optional()
23718
24680
  }).strict(),
23719
24681
  "thread/resume": external_exports.object({
23720
- threadId: id,
24682
+ threadId: id3,
23721
24683
  model: external_exports.string().min(1).max(128).optional(),
23722
24684
  permissionPreset: permissionPreset.optional()
23723
24685
  }).strict(),
23724
24686
  "thread/fork": external_exports.object({
23725
- threadId: id,
23726
- lastTurnId: id.optional(),
24687
+ threadId: id3,
24688
+ lastTurnId: id3.optional(),
23727
24689
  permissionPreset: permissionPreset.optional()
23728
24690
  }).strict(),
23729
- "thread/name/set": external_exports.object({ threadId: id, name: external_exports.string().trim().min(1).max(256) }).strict(),
23730
- "thread/archive": external_exports.object({ threadId: id }).strict(),
23731
- "thread/unarchive": external_exports.object({ threadId: id }).strict(),
23732
- "thread/unsubscribe": external_exports.object({ threadId: id }).strict(),
24691
+ "thread/name/set": external_exports.object({ threadId: id3, name: external_exports.string().trim().min(1).max(256) }).strict(),
24692
+ "thread/archive": external_exports.object({ threadId: id3 }).strict(),
24693
+ "thread/unarchive": external_exports.object({ threadId: id3 }).strict(),
24694
+ "thread/unsubscribe": external_exports.object({ threadId: id3 }).strict(),
23733
24695
  "turn/start": external_exports.object({
23734
- threadId: id,
24696
+ threadId: id3,
23735
24697
  input,
23736
24698
  model: external_exports.string().min(1).max(128).optional(),
23737
24699
  effort: external_exports.enum(["none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra"]).optional(),
@@ -23739,8 +24701,8 @@ var schemas = {
23739
24701
  personality: external_exports.string().min(1).max(64).optional(),
23740
24702
  permissionPreset: permissionPreset.optional()
23741
24703
  }).strict(),
23742
- "turn/steer": external_exports.object({ threadId: id, input, expectedTurnId: id }).strict(),
23743
- "turn/interrupt": external_exports.object({ threadId: id, turnId: id }).strict()
24704
+ "turn/steer": external_exports.object({ threadId: id3, input, expectedTurnId: id3 }).strict(),
24705
+ "turn/interrupt": external_exports.object({ threadId: id3, turnId: id3 }).strict()
23744
24706
  };
23745
24707
  var CODEX_APP_ALLOWLIST = Object.freeze(Object.keys(schemas));
23746
24708
  function parseCodexCall(method, params) {
@@ -23958,14 +24920,14 @@ var CodexPeerBridge = class {
23958
24920
  if (this.closed) return;
23959
24921
  const streamIds = [...this.streams.entries()].filter(([, targetThreadId]) => targetThreadId === threadId).map(([streamId]) => streamId);
23960
24922
  for (const streamId of streamIds) {
23961
- const data = { streamId, frame };
23962
- if (new TextEncoder().encode(JSON.stringify(data)).byteLength > MAX_SECURE_MESSAGE_BYTES) {
24923
+ const data2 = { streamId, frame };
24924
+ if (new TextEncoder().encode(JSON.stringify(data2)).byteLength > MAX_SECURE_MESSAGE_BYTES) {
23963
24925
  this.streams.delete(streamId);
23964
24926
  await this.publish("codex.app.stream.closed", { streamId, reason: "failed" });
23965
24927
  this.logger?.warn("Codex stream closed after oversized frame", { streamId });
23966
24928
  continue;
23967
24929
  }
23968
- await this.publish("codex.app.frame", data);
24930
+ await this.publish("codex.app.frame", data2);
23969
24931
  }
23970
24932
  }
23971
24933
  async failStreams(reason = "failed") {
@@ -23994,11 +24956,11 @@ var CodexPeerBridge = class {
23994
24956
  }
23995
24957
  pruneTransfers() {
23996
24958
  const staleBefore = Date.now() - TRANSFER_IDLE_MS;
23997
- for (const [id2, transfer] of this.incomingTransfers) {
23998
- if (transfer.touchedAt < staleBefore) this.incomingTransfers.delete(id2);
24959
+ for (const [id4, transfer] of this.incomingTransfers) {
24960
+ if (transfer.touchedAt < staleBefore) this.incomingTransfers.delete(id4);
23999
24961
  }
24000
- for (const [id2, transfer] of this.outgoingTransfers) {
24001
- if (transfer.touchedAt < staleBefore) this.outgoingTransfers.delete(id2);
24962
+ for (const [id4, transfer] of this.outgoingTransfers) {
24963
+ if (transfer.touchedAt < staleBefore) this.outgoingTransfers.delete(id4);
24002
24964
  }
24003
24965
  }
24004
24966
  requireOpen() {
@@ -24465,7 +25427,7 @@ var CodexRemoteDomain = class {
24465
25427
  await appServer.respond(message.id, { decision: "decline" });
24466
25428
  return;
24467
25429
  }
24468
- const requestHandle = randomUUID();
25430
+ const requestHandle = randomUUID2();
24469
25431
  this.approvals.set(requestHandle, {
24470
25432
  upstreamId: message.id,
24471
25433
  connectionId: owner.connectionId,
@@ -24773,14 +25735,14 @@ var CodexRemoteDomain = class {
24773
25735
  }
24774
25736
  }
24775
25737
  async listCodexDirectory(path) {
24776
- const target = await this.resolveCodexDirectory(path);
24777
- const rows = await readdir2(target.path, { withFileTypes: true }).catch(() => void 0);
25738
+ const target2 = await this.resolveCodexDirectory(path);
25739
+ const rows = await readdir2(target2.path, { withFileTypes: true }).catch(() => void 0);
24778
25740
  if (rows === void 0) {
24779
25741
  throw new RpcError("CODEX_PATH_NOT_ALLOWED", "The CodeX working directory is not available as a Workspace.");
24780
25742
  }
24781
25743
  const entries = [];
24782
25744
  for (const row of rows) {
24783
- const child = resolve2(target.path, row.name);
25745
+ const child = resolve2(target2.path, row.name);
24784
25746
  let directory = row.isDirectory();
24785
25747
  if (!directory && row.isSymbolicLink()) {
24786
25748
  directory = await stat4(child).then((value) => value.isDirectory()).catch(() => false);
@@ -24789,7 +25751,7 @@ var CodexRemoteDomain = class {
24789
25751
  try {
24790
25752
  const canonicalChild = await realpath(child);
24791
25753
  if (!(await stat4(canonicalChild)).isDirectory()) continue;
24792
- if (!containsCodexPath(target.canonicalRoot, canonicalChild)) continue;
25754
+ if (!containsCodexPath(target2.canonicalRoot, canonicalChild)) continue;
24793
25755
  } catch {
24794
25756
  continue;
24795
25757
  }
@@ -24801,9 +25763,9 @@ var CodexRemoteDomain = class {
24801
25763
  }
24802
25764
  entries.sort((left, right) => left.name.localeCompare(right.name, void 0, { sensitivity: "base" }));
24803
25765
  return {
24804
- path: target.path,
24805
- home: target.root,
24806
- crumbs: codexDirectoryCrumbs(target.root, target.path),
25766
+ path: target2.path,
25767
+ home: target2.root,
25768
+ crumbs: codexDirectoryCrumbs(target2.root, target2.path),
24807
25769
  entries: entries.slice(0, CODEX_DIRECTORY_ENTRY_LIMIT),
24808
25770
  truncated: entries.length > CODEX_DIRECTORY_ENTRY_LIMIT
24809
25771
  };
@@ -24883,7 +25845,7 @@ function sanitizeThreadList(result) {
24883
25845
  if (!isRecord12(result) || !Array.isArray(result.data)) {
24884
25846
  throw new RpcError("CODEX_INVALID_RESPONSE", "Codex App Server returned an invalid thread list.");
24885
25847
  }
24886
- const data = result.data.flatMap((value) => {
25848
+ const data2 = result.data.flatMap((value) => {
24887
25849
  if (!isRecord12(value) || typeof value.id !== "string") return [];
24888
25850
  return [{
24889
25851
  id: value.id,
@@ -24900,7 +25862,7 @@ function sanitizeThreadList(result) {
24900
25862
  }];
24901
25863
  });
24902
25864
  return {
24903
- data,
25865
+ data: data2,
24904
25866
  ...typeof result.nextCursor === "string" && result.nextCursor.length > 0 ? { nextCursor: result.nextCursor } : { nextCursor: null },
24905
25867
  ...typeof result.backwardsCursor === "string" && result.backwardsCursor.length > 0 ? { backwardsCursor: result.backwardsCursor } : {}
24906
25868
  };
@@ -24934,9 +25896,9 @@ function sanitizeProjectList(result) {
24934
25896
  if (!isRecord12(result) || !Array.isArray(result.data)) {
24935
25897
  throw new RpcError("CODEX_INVALID_RESPONSE", "Codex App Server returned an invalid project list.");
24936
25898
  }
24937
- const data = result.data.flatMap((value) => sanitizeProject(value) ?? []);
25899
+ const data2 = result.data.flatMap((value) => sanitizeProject(value) ?? []);
24938
25900
  return {
24939
- data,
25901
+ data: data2,
24940
25902
  ...typeof result.nextCursor === "string" && result.nextCursor.length > 0 ? { nextCursor: result.nextCursor } : { nextCursor: null }
24941
25903
  };
24942
25904
  }
@@ -25163,8 +26125,8 @@ var StdioAcpAdapter = class {
25163
26125
  }
25164
26126
  call(method, params, onNotification) {
25165
26127
  const c = this.ensure();
25166
- const id2 = this.nextId++;
25167
- c.stdin.write(JSON.stringify({ jsonrpc: "2.0", id: id2, method, params }) + "\n");
26128
+ const id4 = this.nextId++;
26129
+ c.stdin.write(JSON.stringify({ jsonrpc: "2.0", id: id4, method, params }) + "\n");
25168
26130
  return new Promise((resolve3, reject) => {
25169
26131
  let buf = "";
25170
26132
  const onData = async (d) => {
@@ -25174,7 +26136,7 @@ var StdioAcpAdapter = class {
25174
26136
  for (const l of lines) {
25175
26137
  try {
25176
26138
  const m = JSON.parse(l);
25177
- if (m.id === id2) {
26139
+ if (m.id === id4) {
25178
26140
  c.stdout.off("data", onData);
25179
26141
  m.error ? reject(new Error(m.error.message ?? "ACP error")) : resolve3(m.result);
25180
26142
  } else if (m.method && onNotification) await onNotification(m.params);
@@ -25198,11 +26160,13 @@ var HostPluginRuntime = class {
25198
26160
  this.logger = logger;
25199
26161
  this.localGateway = localGateway;
25200
26162
  this.fileViewerHost = fileViewerHost;
26163
+ this.terminalEnabled = config.terminal.enabled;
26164
+ this.loopback = new LoopbackHost(config.loopback.ports);
25201
26165
  this.codex = new CodexRemoteDomain(config.codex, logger);
25202
26166
  this.connections = new ConnectionController(this.identities, (context, send) => {
25203
26167
  const harnessApi = this.apiProxy === void 0 ? void 0 : new HarnessApiBridge(
25204
26168
  this.apiProxy,
25205
- (event, data) => send(createEvent(event, data)),
26169
+ (event, data2) => send(createEvent(event, data2)),
25206
26170
  void 0,
25207
26171
  this.logger,
25208
26172
  this.localGateway,
@@ -25210,9 +26174,10 @@ var HostPluginRuntime = class {
25210
26174
  );
25211
26175
  const harnessRemote = this.localGateway?.supportsCarrier === true ? new HarnessRemoteBridge(
25212
26176
  this.localGateway,
25213
- (event, data) => send(createEvent(event, data)),
26177
+ (event, data2) => send(createEvent(event, data2)),
25214
26178
  this.logger,
25215
- this.harnessVersion
26179
+ this.harnessVersion,
26180
+ new TerminalPolicy(() => this.terminalEnabled, context.peerDeviceId, this.terminalOwners)
25216
26181
  ) : void 0;
25217
26182
  const fileViewer = new RemoteFileViewerBridge(
25218
26183
  () => this.fileViewerHost?.(),
@@ -25220,7 +26185,7 @@ var HostPluginRuntime = class {
25220
26185
  );
25221
26186
  const codex = this.codex.createPeer(
25222
26187
  context,
25223
- (event, data) => send(createEvent(event, data))
26188
+ (event, data2) => send(createEvent(event, data2))
25224
26189
  );
25225
26190
  const adapters = (config.acp?.backends ?? []).filter((item) => item.enabled && this.acpAvailable(item.command)).map((item) => new StdioAcpAdapter(item));
25226
26191
  const acp = config.acp?.enabled && adapters.length > 0 ? new AcpGateway(adapters) : void 0;
@@ -25232,7 +26197,8 @@ var HostPluginRuntime = class {
25232
26197
  harnessRemote,
25233
26198
  () => this.hostCapabilities(),
25234
26199
  codex,
25235
- acp
26200
+ acp,
26201
+ this.loopback
25236
26202
  );
25237
26203
  }, this.logger);
25238
26204
  if (config.serverUrl !== void 0) {
@@ -25240,6 +26206,9 @@ var HostPluginRuntime = class {
25240
26206
  }
25241
26207
  }
25242
26208
  connections;
26209
+ terminalOwners = /* @__PURE__ */ new Map();
26210
+ terminalEnabled;
26211
+ loopback;
25243
26212
  identity;
25244
26213
  serverApi;
25245
26214
  serverConnection;
@@ -25248,6 +26217,12 @@ var HostPluginRuntime = class {
25248
26217
  codex;
25249
26218
  localCodexPeer;
25250
26219
  localCodexPublish = async () => void 0;
26220
+ setTerminalEnabled(enabled) {
26221
+ this.terminalEnabled = enabled;
26222
+ }
26223
+ setLoopbackPorts(ports) {
26224
+ this.loopback.setPorts(ports);
26225
+ }
25251
26226
  async start() {
25252
26227
  if (this.closed) throw new Error("remote runtime is closed");
25253
26228
  this.identity = await this.identities.loadOrCreate(this.config.deviceName);
@@ -25455,7 +26430,7 @@ var HostPluginRuntime = class {
25455
26430
  let reportedVersion;
25456
26431
  let errorCode5;
25457
26432
  try {
25458
- const response = await this.apiProxy?.host.describe({ rpcId: randomUUID2(), payload: {} });
26433
+ const response = await this.apiProxy?.host.describe({ rpcId: randomUUID3(), payload: {} });
25459
26434
  if (response === void 0) throw new Error("ApiProxy is unavailable");
25460
26435
  if (!response.result.ok) {
25461
26436
  errorCode5 = response.result.error.code;
@@ -25472,12 +26447,14 @@ var HostPluginRuntime = class {
25472
26447
  }
25473
26448
  hostCapabilities() {
25474
26449
  const capabilities = [];
26450
+ if (this.loopback.hasPorts()) capabilities.push("loopback.http-ws.v1");
25475
26451
  if (this.localGateway?.supportsCarrier === true) {
25476
26452
  capabilities.push(
25477
26453
  harnessSessionGeneration(this.harnessVersion) === "v3" ? "harness.remote.v3" : "harness.remote.v1",
25478
26454
  "harness.remote.transfer.v1"
25479
26455
  );
25480
26456
  }
26457
+ if (this.localGateway?.supportsCarrier && this.terminalEnabled) capabilities.push("harness.terminal.v1");
25481
26458
  if (this.apiProxy !== void 0) {
25482
26459
  capabilities.push("harness.api.v1", "harness.api.transfer.v1");
25483
26460
  }
@@ -25505,7 +26482,7 @@ var HostPluginRuntime = class {
25505
26482
  const peer = this.codex.createPeer({
25506
26483
  connectionId: `loopback:${identity.deviceId}`,
25507
26484
  peerDeviceId: identity.deviceId
25508
- }, (event, data) => this.localCodexPublish(event, data));
26485
+ }, (event, data2) => this.localCodexPublish(event, data2));
25509
26486
  if (peer === void 0) {
25510
26487
  throw new RpcError("CODEX_UNAVAILABLE", "Local CodeX is disabled or unavailable on this Host.");
25511
26488
  }
@@ -25763,11 +26740,11 @@ function helpText() {
25763
26740
  ""
25764
26741
  ].join("\n");
25765
26742
  }
25766
- function write(target, value) {
25767
- target.write(value);
26743
+ function write(target2, value) {
26744
+ target2.write(value);
25768
26745
  }
25769
- function terminalLink(url, target) {
25770
- if (target.isTTY !== true) return url;
26746
+ function terminalLink(url, target2) {
26747
+ if (target2.isTTY !== true) return url;
25771
26748
  return `\x1B]8;;${url}\x07${url}\x1B]8;;\x07`;
25772
26749
  }
25773
26750
  async function exists3(path) {
@@ -25828,10 +26805,10 @@ function installTuiRemoteCommand(ctx, resolveTarget) {
25828
26805
  return { kind: "success" };
25829
26806
  }
25830
26807
  if (command === "logout" && args.length === 1) {
25831
- const target = resolveTarget();
25832
- if (target === void 0) return remoteNotReady();
26808
+ const target2 = resolveTarget();
26809
+ if (target2 === void 0) return remoteNotReady();
25833
26810
  try {
25834
- await target.runtime.clearHostAuthorization();
26811
+ await target2.runtime.clearHostAuthorization();
25835
26812
  return {
25836
26813
  kind: "success",
25837
26814
  text: "Remote Host logged out and its local device identity was rotated."
@@ -25928,11 +26905,11 @@ function remoteCommandChildren(canonicalPath) {
25928
26905
  }
25929
26906
  return [];
25930
26907
  }
25931
- function remoteStatusLines(target) {
25932
- if (target === void 0) {
26908
+ function remoteStatusLines(target2) {
26909
+ if (target2 === void 0) {
25933
26910
  return ["Remote Host is disabled or still starting. Check the ds-harness-remote settings and retry."];
25934
26911
  }
25935
- const { runtime, config } = target;
26912
+ const { runtime, config } = target2;
25936
26913
  const status2 = runtime.hostStatus();
25937
26914
  const diagnostics = runtime.diagnostics();
25938
26915
  const codex = runtime.codexStatus();
@@ -25944,6 +26921,8 @@ function remoteStatusLines(target) {
25944
26921
  `Device: ${status2.deviceId ?? "not initialized"}`,
25945
26922
  `Authorization: ${status2.authorized ? status2.account === void 0 ? "logged in" : `logged in (${status2.account})` : "logged out"}`,
25946
26923
  `Server connection: ${connection}`,
26924
+ ...status2.error === void 0 ? [] : [`Connection error: ${status2.error}`],
26925
+ ...status2.error === "CONNECTION_REPLACED" ? ["Another instance is using this Host identity. Stop it or use a separate DSH_HOME before reconnecting."] : status2.error === "SERVER_CREDENTIALS_BUSY" ? ["Credential refresh is locked. Stop all instances before removing an orphaned server-credentials.json.refresh-lock and authorizing again."] : status2.accountRequired ? ["Authorize again with /remote login [github|zhihu]."] : [],
25947
26926
  `Harness Remote API: ${capabilities.has("harness.api.v1") ? "available (ApiProxy)" : capabilities.has("harness.remote.v3") ? "available (Typert Remote Session V3)" : capabilities.has("harness.remote.v1") ? "available (Typert Remote)" : "unavailable"}`,
25948
26927
  `Remote clients: ${diagnostics.activeConnections}`,
25949
26928
  `Codex Remote: ${codex.enabled ? codex.state : "disabled"}`,
@@ -25951,8 +26930,8 @@ function remoteStatusLines(target) {
25951
26930
  "Commands: /remote login [github|zhihu] \xB7 /remote status \xB7 /remote logout"
25952
26931
  ];
25953
26932
  }
25954
- function formatRemoteStatusInline(target) {
25955
- return remoteStatusLines(target).filter(Boolean).join(" \xB7 ");
26933
+ function formatRemoteStatusInline(target2) {
26934
+ return remoteStatusLines(target2).filter(Boolean).join(" \xB7 ");
25956
26935
  }
25957
26936
  function remoteCommandUsage() {
25958
26937
  return [
@@ -25993,16 +26972,16 @@ var RemoteLoginController = class {
25993
26972
  }
25994
26973
  async run(attempt, provider) {
25995
26974
  try {
25996
- const target = this.resolveTarget();
25997
- if (target === void 0) throw new Error("REMOTE_NOT_READY");
25998
- const session = await target.runtime.startHostOAuthQrLogin(provider);
26975
+ const target2 = this.resolveTarget();
26976
+ if (target2 === void 0) throw new Error("REMOTE_NOT_READY");
26977
+ const session = await target2.runtime.startHostOAuthQrLogin(provider);
25999
26978
  const [qr, compactQr] = await Promise.all([
26000
26979
  renderTerminalQr(session.scanUrl),
26001
26980
  renderCompactTerminalQr(session.scanUrl)
26002
26981
  ]);
26003
26982
  if (attempt !== this.attempt) return;
26004
26983
  this.update({ phase: "waiting", provider, qr, compactQr, scanUrl: session.scanUrl });
26005
- await this.poll(attempt, provider, session, target.runtime);
26984
+ await this.poll(attempt, provider, session, target2.runtime);
26006
26985
  } catch (error) {
26007
26986
  if (attempt !== this.attempt) return;
26008
26987
  this.update({ phase: "error", provider, error: errorCode4(error) });