ds-harness-remote 0.4.13 → 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 (94) hide show
  1. package/README.md +2 -2
  2. package/dist/acp.d.ts +75 -0
  3. package/dist/acp.d.ts.map +1 -0
  4. package/dist/acp.js +61 -0
  5. package/dist/acp.js.map +1 -0
  6. package/dist/client-runtime.d.ts +11 -0
  7. package/dist/client-runtime.d.ts.map +1 -1
  8. package/dist/client-runtime.js +24 -0
  9. package/dist/client-runtime.js.map +1 -1
  10. package/dist/client-secure-transport.d.ts +1 -0
  11. package/dist/client-secure-transport.d.ts.map +1 -1
  12. package/dist/client-secure-transport.js +12 -5
  13. package/dist/client-secure-transport.js.map +1 -1
  14. package/dist/client.d.ts.map +1 -1
  15. package/dist/client.github.js +474 -76
  16. package/dist/client.js +474 -76
  17. package/dist/client.js.map +1 -1
  18. package/dist/codex/method-policy.d.ts +10 -10
  19. package/dist/config.d.ts +36 -0
  20. package/dist/config.d.ts.map +1 -1
  21. package/dist/config.js +9 -0
  22. package/dist/config.js.map +1 -1
  23. package/dist/connection-controller.d.ts +5 -0
  24. package/dist/connection-controller.d.ts.map +1 -1
  25. package/dist/connection-controller.js +16 -1
  26. package/dist/connection-controller.js.map +1 -1
  27. package/dist/control-route.d.ts +10 -1
  28. package/dist/control-route.d.ts.map +1 -1
  29. package/dist/control-route.js +17 -4
  30. package/dist/control-route.js.map +1 -1
  31. package/dist/control-runtime.d.ts +12 -1
  32. package/dist/control-runtime.d.ts.map +1 -1
  33. package/dist/control-runtime.js +95 -1
  34. package/dist/control-runtime.js.map +1 -1
  35. package/dist/control-stream.d.ts +67 -0
  36. package/dist/control-stream.d.ts.map +1 -0
  37. package/dist/control-stream.js +179 -0
  38. package/dist/control-stream.js.map +1 -0
  39. package/dist/harness-api-bridge.d.ts.map +1 -1
  40. package/dist/harness-api-bridge.js +32 -8
  41. package/dist/harness-api-bridge.js.map +1 -1
  42. package/dist/harness-remote-bridge.d.ts +5 -2
  43. package/dist/harness-remote-bridge.d.ts.map +1 -1
  44. package/dist/harness-remote-bridge.js +85 -5
  45. package/dist/harness-remote-bridge.js.map +1 -1
  46. package/dist/index.d.ts +2 -0
  47. package/dist/index.d.ts.map +1 -1
  48. package/dist/index.js +1872 -588
  49. package/dist/index.js.map +4 -4
  50. package/dist/loopback-host.d.ts +20 -0
  51. package/dist/loopback-host.d.ts.map +1 -0
  52. package/dist/loopback-host.js +220 -0
  53. package/dist/loopback-host.js.map +1 -0
  54. package/dist/loopback-preview.d.ts +19 -0
  55. package/dist/loopback-preview.d.ts.map +1 -0
  56. package/dist/loopback-preview.js +232 -0
  57. package/dist/loopback-preview.js.map +1 -0
  58. package/dist/remote-typert-gateway.js +16 -4
  59. package/dist/remote-typert-gateway.js.map +1 -1
  60. package/dist/rpc-router.d.ts +7 -2
  61. package/dist/rpc-router.d.ts.map +1 -1
  62. package/dist/rpc-router.js +27 -1
  63. package/dist/rpc-router.js.map +1 -1
  64. package/dist/server-api.d.ts +6 -3
  65. package/dist/server-api.d.ts.map +1 -1
  66. package/dist/server-api.js +50 -26
  67. package/dist/server-api.js.map +1 -1
  68. package/dist/server-connection.d.ts +1 -0
  69. package/dist/server-connection.d.ts.map +1 -1
  70. package/dist/server-connection.js +54 -7
  71. package/dist/server-connection.js.map +1 -1
  72. package/dist/server-credentials.d.ts +9 -0
  73. package/dist/server-credentials.d.ts.map +1 -1
  74. package/dist/server-credentials.js +34 -0
  75. package/dist/server-credentials.js.map +1 -1
  76. package/dist/service.d.ts +14 -0
  77. package/dist/service.d.ts.map +1 -1
  78. package/dist/service.js +48 -2
  79. package/dist/service.js.map +1 -1
  80. package/dist/status-stream.d.ts +48 -0
  81. package/dist/status-stream.d.ts.map +1 -0
  82. package/dist/status-stream.js +133 -0
  83. package/dist/status-stream.js.map +1 -0
  84. package/dist/terminal-policy.d.ts +21 -0
  85. package/dist/terminal-policy.d.ts.map +1 -0
  86. package/dist/terminal-policy.js +62 -0
  87. package/dist/terminal-policy.js.map +1 -0
  88. package/dist/tui-command.js +6 -0
  89. package/dist/tui-command.js.map +1 -1
  90. package/dist/version.d.ts +1 -1
  91. package/dist/version.js +1 -1
  92. package/dsh-plugin.json +1 -1
  93. package/package.json +44 -24
  94. 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;
@@ -4066,6 +4093,8 @@ var MAX_RPC_TEXT_INPUT_BYTES = 64 * 1024;
4066
4093
  var MAX_FILEVIEWER_RANGE_BYTES = 512 * 1024;
4067
4094
  var MIN_REPLAY_WINDOW_MS = 15 * 6e4;
4068
4095
  var MAX_ALPHA_STREAMS_PER_CONNECTION = 16;
4096
+ var MAX_ACP_PROMPT_BYTES = 64 * 1024;
4097
+ var MAX_ACP_UPDATE_BYTES = 512 * 1024;
4069
4098
  var SECURE_FRAGMENT_MAGIC = new Uint8Array([68, 83, 72, 70]);
4070
4099
  var SECURE_FRAGMENT_VERSION = 1;
4071
4100
  var SECURE_FRAGMENT_HEADER_BYTES = 17;
@@ -4112,6 +4141,7 @@ var rpcMethods = [
4112
4141
  "harness.remote.transfer.close",
4113
4142
  "harness.remote.stream.open",
4114
4143
  "harness.remote.stream.close",
4144
+ "loopback.call",
4115
4145
  "fileviewer.call",
4116
4146
  "codex.app.call",
4117
4147
  "codex.app.respond",
@@ -4121,7 +4151,14 @@ var rpcMethods = [
4121
4151
  "codex.app.transfer.chunk",
4122
4152
  "codex.app.transfer.commit",
4123
4153
  "codex.app.transfer.read",
4124
- "codex.app.transfer.close"
4154
+ "codex.app.transfer.close",
4155
+ "acp.initialize",
4156
+ "acp.session.new",
4157
+ "acp.session.load",
4158
+ "acp.session.prompt",
4159
+ "acp.session.respond_permission",
4160
+ "acp.session.cancel",
4161
+ "acp.session.set_mode"
4125
4162
  ];
4126
4163
  var errorCodes = [
4127
4164
  // Protocol / Version
@@ -4395,26 +4432,26 @@ var rpcErrorPayloadSchema = external_exports.object({
4395
4432
  retryable: external_exports.boolean().optional(),
4396
4433
  details: external_exports.unknown().optional()
4397
4434
  });
4398
- function createMessage(type, payload, id2 = cryptoRandomId()) {
4435
+ function createMessage(type, payload, id4 = cryptoRandomId()) {
4399
4436
  return {
4400
4437
  v: PROTOCOL_VERSION,
4401
- id: id2,
4438
+ id: id4,
4402
4439
  type,
4403
4440
  timestamp: Date.now(),
4404
4441
  payload
4405
4442
  };
4406
4443
  }
4407
- function createControlFrame(type, payload, id2 = cryptoRandomId()) {
4444
+ function createControlFrame(type, payload, id4 = cryptoRandomId()) {
4408
4445
  return {
4409
4446
  v: PROTOCOL_VERSION,
4410
- id: id2,
4447
+ id: id4,
4411
4448
  type,
4412
4449
  timestamp: Date.now(),
4413
4450
  payload
4414
4451
  };
4415
4452
  }
4416
- function createRpcRequest(method, params, id2) {
4417
- return createMessage("rpc.request", { method, params }, id2);
4453
+ function createRpcRequest(method, params, id4) {
4454
+ return createMessage("rpc.request", { method, params }, id4);
4418
4455
  }
4419
4456
  function createRpcResponse(requestId, result) {
4420
4457
  return createMessage("rpc.response", { requestId, result });
@@ -4422,8 +4459,8 @@ function createRpcResponse(requestId, result) {
4422
4459
  function createRpcError(requestId, code, message, details, retryable) {
4423
4460
  return createMessage("rpc.error", { requestId, code, message, details, retryable });
4424
4461
  }
4425
- function createEvent(event, data, options = {}) {
4426
- return createMessage("event", { event, data, ...options });
4462
+ function createEvent(event, data2, options = {}) {
4463
+ return createMessage("event", { event, data: data2, ...options });
4427
4464
  }
4428
4465
  function parseRemoteMessage(input2) {
4429
4466
  return remoteMessageSchema.parse(input2);
@@ -4442,11 +4479,11 @@ function encodeControlFrame(frame, limits = {}) {
4442
4479
  assertControlFrameSize(new TextEncoder().encode(encoded).byteLength, parsed.type, limits);
4443
4480
  return encoded;
4444
4481
  }
4445
- function decodeControlFrame(data, limits = {}) {
4446
- 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;
4447
4484
  if (encoded.byteLength > MAX_RELAY_FRAME_BYTES)
4448
4485
  throw new Error("Control frame exceeds the Relay frame limit.");
4449
- 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);
4450
4487
  const frame = parseControlFrame(JSON.parse(text));
4451
4488
  assertControlFrameSize(encoded.byteLength, frame.type, limits);
4452
4489
  return frame;
@@ -4454,8 +4491,8 @@ function decodeControlFrame(data, limits = {}) {
4454
4491
  function encodeMessage(message) {
4455
4492
  return new TextEncoder().encode(JSON.stringify(message));
4456
4493
  }
4457
- function decodeMessage(data) {
4458
- 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);
4459
4496
  return parseRemoteMessage(JSON.parse(text));
4460
4497
  }
4461
4498
  var SecureMessageCodec = class {
@@ -4562,6 +4599,504 @@ function cryptoRandomId() {
4562
4599
  return `msg_${Date.now().toString(36)}_${Math.random().toString(36).slice(2)}`;
4563
4600
  }
4564
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
+
4565
5100
  // ../client-core/dist/remote-gateway.js
4566
5101
  var DIRECT_REMOTE_CALL_BYTES = 2 * 1024 * 1024;
4567
5102
  var RemoteGatewayError = class extends Error {
@@ -4778,12 +5313,12 @@ function deriveCodexCwdWorkspaces(values) {
4778
5313
  continue;
4779
5314
  }
4780
5315
  const baseId = `cwd-${hashWorkspacePath(key)}`;
4781
- let id2 = baseId;
4782
- for (let suffix = 2; usedIds.has(id2); suffix += 1)
4783
- id2 = `${baseId}-${suffix}`;
4784
- 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);
4785
5320
  byPath.set(key, {
4786
- id: id2,
5321
+ id: id4,
4787
5322
  name: workspaceBasename(path),
4788
5323
  path,
4789
5324
  position: byPath.size,
@@ -4957,16 +5492,16 @@ function parseDataImageUrl(value) {
4957
5492
  if (match === null)
4958
5493
  return void 0;
4959
5494
  const mediaType = match[1];
4960
- const data = match[2];
4961
- if (!CODEX_IMAGE_MEDIA_TYPES.has(mediaType) || !isCanonicalBase64(data))
5495
+ const data2 = match[2];
5496
+ if (!CODEX_IMAGE_MEDIA_TYPES.has(mediaType) || !isCanonicalBase64(data2))
4962
5497
  return void 0;
4963
- return { url: `data:${mediaType};base64,${data}`, mediaType, data };
5498
+ return { url: `data:${mediaType};base64,${data2}`, mediaType, data: data2 };
4964
5499
  }
4965
5500
  function isCanonicalBase64(value) {
4966
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);
4967
5502
  }
4968
- function sniffImageMediaType(data) {
4969
- const bytes = base64PrefixBytes(data, 32);
5503
+ function sniffImageMediaType(data2) {
5504
+ const bytes = base64PrefixBytes(data2, 32);
4970
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)
4971
5506
  return "image/png";
4972
5507
  if (bytes.length >= 3 && bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255)
@@ -5172,7 +5707,7 @@ var RemoteClientCore = class {
5172
5707
  if (this.unsubscribeTransport !== void 0)
5173
5708
  return;
5174
5709
  this.closeNotified = false;
5175
- this.unsubscribeTransport = this.transport.onMessage((data) => this.handleMessage(data));
5710
+ this.unsubscribeTransport = this.transport.onMessage((data2) => this.handleMessage(data2));
5176
5711
  this.unsubscribeClose = this.transport.onClose?.(() => this.handleTransportClose());
5177
5712
  try {
5178
5713
  await this.transport.connect();
@@ -5243,8 +5778,8 @@ var RemoteClientCore = class {
5243
5778
  this.rejectAllPending((pending) => new RemoteClientError("TRANSPORT_CLOSED", `RPC ${pending.method} terminated because the remote transport closed`));
5244
5779
  this.notifyClose();
5245
5780
  }
5246
- handleMessage(data) {
5247
- const message = decodeMessage(data);
5781
+ handleMessage(data2) {
5782
+ const message = decodeMessage(data2);
5248
5783
  if (message.type === "rpc.response")
5249
5784
  this.handleResponse(message);
5250
5785
  if (message.type === "rpc.error")
@@ -5305,6 +5840,18 @@ function transportSendError(error) {
5305
5840
  return error instanceof Error ? error : new Error("remote transport send failed", { cause: error });
5306
5841
  }
5307
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
+
5308
5855
  // ../webrtc/dist/transport.js
5309
5856
  var BaseTransport = class {
5310
5857
  handlers = /* @__PURE__ */ new Set();
@@ -5317,9 +5864,9 @@ var BaseTransport = class {
5317
5864
  this.closeHandlers.add(cb);
5318
5865
  return () => this.closeHandlers.delete(cb);
5319
5866
  }
5320
- emit(data) {
5867
+ emit(data2) {
5321
5868
  for (const handler of this.handlers)
5322
- handler(data);
5869
+ handler(data2);
5323
5870
  }
5324
5871
  emitClose() {
5325
5872
  for (const handler of this.closeHandlers)
@@ -5562,8 +6109,8 @@ function adaptDataChannel(raw) {
5562
6109
  get onbufferedamountlow() {
5563
6110
  return raw.onbufferedamountlow;
5564
6111
  },
5565
- send(data) {
5566
- raw.send(data);
6112
+ send(data2) {
6113
+ raw.send(data2);
5567
6114
  },
5568
6115
  close() {
5569
6116
  raw.close();
@@ -5582,19 +6129,19 @@ var MAX_ASSEMBLY_AGE_MS = 3e4;
5582
6129
  var RtcChunkCodec = class {
5583
6130
  nextMessageId = 1;
5584
6131
  assemblies = /* @__PURE__ */ new Map();
5585
- encode(data) {
5586
- if (data.byteLength <= RTC_CHUNK_PAYLOAD_BYTES)
5587
- return [data];
5588
- 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) {
5589
6136
  throw new Error("WebRTC transport message exceeds the reassembly limit.");
5590
6137
  }
5591
6138
  const messageId = this.nextMessageId;
5592
6139
  this.nextMessageId = messageId === 4294967295 ? 1 : messageId + 1;
5593
- const total = Math.ceil(data.byteLength / RTC_CHUNK_PAYLOAD_BYTES);
6140
+ const total = Math.ceil(data2.byteLength / RTC_CHUNK_PAYLOAD_BYTES);
5594
6141
  const frames = [];
5595
6142
  for (let index = 0; index < total; index += 1) {
5596
6143
  const start = index * RTC_CHUNK_PAYLOAD_BYTES;
5597
- 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));
5598
6145
  const frame = new Uint8Array(RTC_CHUNK_HEADER_BYTES + chunk.byteLength);
5599
6146
  frame.set(RTC_CHUNK_MAGIC);
5600
6147
  const view = new DataView(frame.buffer);
@@ -5780,11 +6327,11 @@ var RtcDataChannelTransport = class {
5780
6327
  else
5781
6328
  void this.handleIce(signal.candidate);
5782
6329
  }
5783
- async send(data) {
6330
+ async send(data2) {
5784
6331
  const channel = this.requireOpenChannel();
5785
6332
  if (channel.readyState !== "open")
5786
6333
  throw new Error("WebRTC data channel is not open.");
5787
- for (const frame of this.outgoing.encode(data)) {
6334
+ for (const frame of this.outgoing.encode(data2)) {
5788
6335
  try {
5789
6336
  channel.send(toArrayBuffer(frame));
5790
6337
  } catch (error) {
@@ -5792,7 +6339,7 @@ var RtcDataChannelTransport = class {
5792
6339
  throw error;
5793
6340
  }
5794
6341
  }
5795
- this.bytesSent += data.byteLength;
6342
+ this.bytesSent += data2.byteLength;
5796
6343
  this.armWatchdog(channel);
5797
6344
  }
5798
6345
  onMessage(handler) {
@@ -5981,8 +6528,8 @@ var RtcDataChannelTransport = class {
5981
6528
  channel.onmessage = (event) => {
5982
6529
  if (this.closed || !this.opened)
5983
6530
  return;
5984
- const data = event.data;
5985
- 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);
5986
6533
  this.bytesReceived += frame.byteLength;
5987
6534
  try {
5988
6535
  const message = this.incoming.decode(frame);
@@ -6173,7 +6720,7 @@ function inspectSelectedPath(stats) {
6173
6720
  }
6174
6721
  }
6175
6722
  const pairs = [
6176
- ...[...selectedPairIds].map((id2) => candidatePairs.get(id2)).filter((pair) => pair !== void 0),
6723
+ ...[...selectedPairIds].map((id4) => candidatePairs.get(id4)).filter((pair) => pair !== void 0),
6177
6724
  ...selectedPairs
6178
6725
  ];
6179
6726
  for (const pair of pairs) {
@@ -6361,10 +6908,10 @@ function requireSdp(description) {
6361
6908
  }
6362
6909
  return description.sdp;
6363
6910
  }
6364
- function toArrayBuffer(data) {
6365
- if (data.byteOffset === 0 && data.byteLength === data.buffer.byteLength)
6366
- return data.buffer;
6367
- 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;
6368
6915
  }
6369
6916
  function asError(error) {
6370
6917
  return error instanceof Error ? error : new RtcConnectError("RTC_FAILED", "WebRTC negotiation failed.");
@@ -6436,26 +6983,27 @@ var AdaptiveTransport = class extends BaseTransport {
6436
6983
  };
6437
6984
  });
6438
6985
  }
6439
- async send(data) {
6986
+ async send(data2) {
6440
6987
  if (this.connectionId === void 0)
6441
6988
  throw new Error("adaptive transport has not been authorized");
6442
6989
  if (this.dataMode === "webrtc") {
6443
6990
  const rtc = this.rtc;
6444
6991
  if (rtc === void 0)
6445
6992
  throw new Error("webrtc data channel is not available");
6446
- this.bytesSent += data.byteLength;
6447
- await rtc.send(data);
6993
+ this.bytesSent += data2.byteLength;
6994
+ await rtc.send(data2);
6448
6995
  return;
6449
6996
  }
6450
6997
  if (this.socket?.readyState !== WebSocket.OPEN)
6451
6998
  throw new Error("adaptive transport is not connected");
6999
+ await waitForRelayCapacity(this.socket);
6452
7000
  this.sendControl("relay", {
6453
7001
  connectionId: this.connectionId,
6454
7002
  targetDeviceId: this.options.targetDeviceId,
6455
7003
  counter: this.relayCounter,
6456
- ciphertext: toBase64Url(data)
7004
+ ciphertext: toBase64Url(data2)
6457
7005
  });
6458
- this.bytesSent += data.byteLength;
7006
+ this.bytesSent += data2.byteLength;
6459
7007
  this.relayCounter += 1;
6460
7008
  }
6461
7009
  connectionInfo() {
@@ -6479,7 +7027,7 @@ var AdaptiveTransport = class extends BaseTransport {
6479
7027
  ...this.rtc === void 0 ? {} : { webRtc: await this.rtc.connectionDetails() }
6480
7028
  };
6481
7029
  }
6482
- async sendHandshake(step, data) {
7030
+ async sendHandshake(step, data2) {
6483
7031
  if (this.socket?.readyState !== WebSocket.OPEN || this.connectionId === void 0) {
6484
7032
  throw new Error("adaptive transport has not been authorized");
6485
7033
  }
@@ -6487,7 +7035,7 @@ var AdaptiveTransport = class extends BaseTransport {
6487
7035
  connectionId: this.connectionId,
6488
7036
  targetDeviceId: this.options.targetDeviceId,
6489
7037
  step,
6490
- data: toBase64Url(data)
7038
+ data: toBase64Url(data2)
6491
7039
  });
6492
7040
  }
6493
7041
  onHandshake(cb) {
@@ -6571,9 +7119,9 @@ var AdaptiveTransport = class extends BaseTransport {
6571
7119
  if (payload.connectionId !== this.connectionId || payload.targetDeviceId !== this.options.deviceId || !Number.isSafeInteger(payload.step) || typeof payload.data !== "string") {
6572
7120
  throw new Error("Received a secure handshake frame for an unknown connection");
6573
7121
  }
6574
- const data = fromBase64Url(payload.data);
7122
+ const data2 = fromBase64Url(payload.data);
6575
7123
  for (const handler of this.handshakeHandlers)
6576
- handler(payload.step, data);
7124
+ handler(payload.step, data2);
6577
7125
  return;
6578
7126
  }
6579
7127
  if (frame.type === "signal.answer") {
@@ -6602,9 +7150,9 @@ var AdaptiveTransport = class extends BaseTransport {
6602
7150
  if (payload.connectionId !== this.connectionId || payload.targetDeviceId !== this.options.deviceId || !Number.isSafeInteger(payload.counter) || typeof payload.ciphertext !== "string") {
6603
7151
  throw new Error("Received a relay frame for an unknown connection");
6604
7152
  }
6605
- const data = fromBase64Url(payload.ciphertext);
6606
- this.bytesReceived += data.byteLength;
6607
- this.emit(data);
7153
+ const data2 = fromBase64Url(payload.ciphertext);
7154
+ this.bytesReceived += data2.byteLength;
7155
+ this.emit(data2);
6608
7156
  }
6609
7157
  async negotiate() {
6610
7158
  if (this.connectionId === void 0)
@@ -6696,9 +7244,9 @@ var AdaptiveTransport = class extends BaseTransport {
6696
7244
  }
6697
7245
  });
6698
7246
  this.rtc = rtc;
6699
- rtc.onMessage((data) => {
6700
- this.bytesReceived += data.byteLength;
6701
- this.emit(data);
7247
+ rtc.onMessage((data2) => {
7248
+ this.bytesReceived += data2.byteLength;
7249
+ this.emit(data2);
6702
7250
  });
6703
7251
  rtc.onClose(() => {
6704
7252
  if (this.rtc === rtc && this.dataMode === "webrtc")
@@ -6793,6 +7341,27 @@ function negotiateTimeout(serverMs, localMs) {
6793
7341
  return Math.max(serverMs, localMs);
6794
7342
  }
6795
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
+
6796
7365
  // src/api-proxy-switch.ts
6797
7366
  var SWITCHED_DOMAINS = [
6798
7367
  "sessions",
@@ -6846,10 +7415,10 @@ var ApiProxySwitch = class {
6846
7415
  });
6847
7416
  this.installed = true;
6848
7417
  }
6849
- selectRemote(api, target) {
7418
+ selectRemote(api, target2) {
6850
7419
  if (!this.installed) throw new Error("The Harness API switch is not installed.");
6851
7420
  this.remote = api;
6852
- this.target = { ...target };
7421
+ this.target = { ...target2 };
6853
7422
  this.mode = "remote";
6854
7423
  }
6855
7424
  selectLocal() {
@@ -6895,7 +7464,7 @@ import * as nc from "node:crypto";
6895
7464
  var crypto = nc && typeof nc === "object" && "webcrypto" in nc ? nc.webcrypto : nc && typeof nc === "object" && "randomBytes" in nc ? nc : void 0;
6896
7465
 
6897
7466
  // ../../node_modules/.pnpm/@noble+ciphers@1.3.0/node_modules/@noble/ciphers/esm/webcrypto.js
6898
- function randomBytes(bytesLength = 32) {
7467
+ function randomBytes2(bytesLength = 32) {
6899
7468
  if (crypto && typeof crypto.getRandomValues === "function") {
6900
7469
  return crypto.getRandomValues(new Uint8Array(bytesLength));
6901
7470
  }
@@ -6995,11 +7564,11 @@ function utf8ToBytes(str) {
6995
7564
  throw new Error("string expected");
6996
7565
  return new Uint8Array(new TextEncoder().encode(str));
6997
7566
  }
6998
- function toBytes(data) {
6999
- if (typeof data === "string")
7000
- data = utf8ToBytes(data);
7001
- abytes(data);
7002
- return data;
7567
+ function toBytes(data2) {
7568
+ if (typeof data2 === "string")
7569
+ data2 = utf8ToBytes(data2);
7570
+ abytes(data2);
7571
+ return data2;
7003
7572
  }
7004
7573
  function concatBytes(...arrays) {
7005
7574
  let sum = 0;
@@ -7026,7 +7595,7 @@ function createHasher(hashCons) {
7026
7595
  hashC.create = () => hashCons();
7027
7596
  return hashC;
7028
7597
  }
7029
- function randomBytes2(bytesLength = 32) {
7598
+ function randomBytes3(bytesLength = 32) {
7030
7599
  if (crypto2 && typeof crypto2.getRandomValues === "function") {
7031
7600
  return crypto2.getRandomValues(new Uint8Array(bytesLength));
7032
7601
  }
@@ -7063,21 +7632,21 @@ var HashMD = class extends Hash {
7063
7632
  this.buffer = new Uint8Array(blockLen);
7064
7633
  this.view = createView(this.buffer);
7065
7634
  }
7066
- update(data) {
7635
+ update(data2) {
7067
7636
  aexists(this);
7068
- data = toBytes(data);
7069
- abytes(data);
7637
+ data2 = toBytes(data2);
7638
+ abytes(data2);
7070
7639
  const { view, buffer, blockLen } = this;
7071
- const len = data.length;
7640
+ const len = data2.length;
7072
7641
  for (let pos = 0; pos < len; ) {
7073
7642
  const take = Math.min(blockLen - this.pos, len - pos);
7074
7643
  if (take === blockLen) {
7075
- const dataView = createView(data);
7644
+ const dataView = createView(data2);
7076
7645
  for (; blockLen <= len - pos; pos += blockLen)
7077
7646
  this.process(dataView, pos);
7078
7647
  continue;
7079
7648
  }
7080
- buffer.set(data.subarray(pos, pos + take), this.pos);
7649
+ buffer.set(data2.subarray(pos, pos + take), this.pos);
7081
7650
  this.pos += take;
7082
7651
  pos += take;
7083
7652
  if (this.pos === blockLen) {
@@ -7085,7 +7654,7 @@ var HashMD = class extends Hash {
7085
7654
  this.pos = 0;
7086
7655
  }
7087
7656
  }
7088
- this.length += data.length;
7657
+ this.length += data2.length;
7089
7658
  this.roundClean();
7090
7659
  return this;
7091
7660
  }
@@ -8462,13 +9031,13 @@ function eddsa(Point, cHash, eddsaOpts = {}) {
8462
9031
  });
8463
9032
  const { prehash } = eddsaOpts;
8464
9033
  const { BASE, Fp: Fp2, Fn: Fn2 } = Point;
8465
- const randomBytes7 = eddsaOpts.randomBytes || randomBytes2;
9034
+ const randomBytes8 = eddsaOpts.randomBytes || randomBytes3;
8466
9035
  const adjustScalarBytes3 = eddsaOpts.adjustScalarBytes || ((bytes) => bytes);
8467
- const domain = eddsaOpts.domain || ((data, ctx, phflag) => {
9036
+ const domain = eddsaOpts.domain || ((data2, ctx, phflag) => {
8468
9037
  _abool2(phflag, "phflag");
8469
9038
  if (ctx.length || phflag)
8470
9039
  throw new Error("Contexts/pre-hash are not supported");
8471
- return data;
9040
+ return data2;
8472
9041
  });
8473
9042
  function modN_LE(hash) {
8474
9043
  return Fn2.create(bytesToNumberLE(hash));
@@ -8544,7 +9113,7 @@ function eddsa(Point, cHash, eddsaOpts = {}) {
8544
9113
  signature: 2 * _size,
8545
9114
  seed: _size
8546
9115
  };
8547
- function randomSecretKey(seed = randomBytes7(lengths.seed)) {
9116
+ function randomSecretKey(seed = randomBytes8(lengths.seed)) {
8548
9117
  return _abytes2(seed, lengths.seed, "seed");
8549
9118
  }
8550
9119
  function keygen(seed) {
@@ -8663,7 +9232,7 @@ function montgomery(curveDef) {
8663
9232
  const is25519 = type === "x25519";
8664
9233
  if (!is25519 && type !== "x448")
8665
9234
  throw new Error("invalid type");
8666
- const randomBytes_ = rand || randomBytes2;
9235
+ const randomBytes_ = rand || randomBytes3;
8667
9236
  const montgomeryBits = is25519 ? 255 : 448;
8668
9237
  const fieldLen = is25519 ? 32 : 56;
8669
9238
  const Gu = is25519 ? BigInt(9) : BigInt(5);
@@ -9312,8 +9881,8 @@ var SymmetricState = class {
9312
9881
  }
9313
9882
  this.ck.set(this.h);
9314
9883
  }
9315
- mixHash(data) {
9316
- 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));
9317
9886
  }
9318
9887
  mixKey(inputKeyMaterial) {
9319
9888
  const [ck, temp] = this.H.hkdf(this.ck, inputKeyMaterial);
@@ -9336,13 +9905,13 @@ var SymmetricState = class {
9336
9905
  }
9337
9906
  this.mixHash(out);
9338
9907
  }
9339
- decryptAndHash(data, out) {
9908
+ decryptAndHash(data2, out) {
9340
9909
  if (this.cipherstate) {
9341
- this.cipherstate.decryptWithAd(this.h, data, out);
9910
+ this.cipherstate.decryptWithAd(this.h, data2, out);
9342
9911
  } else {
9343
- out.set(data);
9912
+ out.set(data2);
9344
9913
  }
9345
- this.mixHash(data);
9914
+ this.mixHash(data2);
9346
9915
  }
9347
9916
  split() {
9348
9917
  if (!this.hasKey()) {
@@ -9742,7 +10311,7 @@ function createHasher2(hashCons, info = {}) {
9742
10311
  Object.assign(hashC, info);
9743
10312
  return Object.freeze(hashC);
9744
10313
  }
9745
- function randomBytes3(bytesLength = 32) {
10314
+ function randomBytes4(bytesLength = 32) {
9746
10315
  anumber2(bytesLength, "bytesLength");
9747
10316
  const cr = typeof globalThis === "object" ? globalThis.crypto : null;
9748
10317
  if (typeof cr?.getRandomValues !== "function")
@@ -9785,22 +10354,22 @@ var HashMD2 = class {
9785
10354
  this.buffer = new Uint8Array(blockLen);
9786
10355
  this.view = createView2(this.buffer);
9787
10356
  }
9788
- update(data) {
10357
+ update(data2) {
9789
10358
  aexists2(this);
9790
- abytes2(data);
10359
+ abytes2(data2);
9791
10360
  const { view, buffer, blockLen } = this;
9792
- const len = data.length;
10361
+ const len = data2.length;
9793
10362
  let processed = false;
9794
10363
  for (let pos = 0; pos < len; ) {
9795
10364
  const take = Math.min(blockLen - this.pos, len - pos);
9796
10365
  if (take === blockLen) {
9797
- const dataView = createView2(data);
10366
+ const dataView = createView2(data2);
9798
10367
  for (; blockLen <= len - pos; pos += blockLen)
9799
10368
  this.process(dataView, pos);
9800
10369
  processed = true;
9801
10370
  continue;
9802
10371
  }
9803
- 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);
9804
10373
  this.pos += take;
9805
10374
  pos += take;
9806
10375
  if (this.pos === blockLen) {
@@ -9809,7 +10378,7 @@ var HashMD2 = class {
9809
10378
  processed = true;
9810
10379
  }
9811
10380
  }
9812
- this.length += data.length;
10381
+ this.length += data2.length;
9813
10382
  if (processed)
9814
10383
  this.roundClean();
9815
10384
  return this;
@@ -10309,7 +10878,7 @@ function afunction(value, title) {
10309
10878
  var bytesToHex3 = bytesToHex2;
10310
10879
  var hexToBytes3 = (hex) => hexToBytes2(hex);
10311
10880
  var isBytes3 = isBytes2;
10312
- var randomBytes4 = (bytesLength) => randomBytes3(bytesLength);
10881
+ var randomBytes5 = (bytesLength) => randomBytes4(bytesLength);
10313
10882
  var _0n7 = /* @__PURE__ */ BigInt(0);
10314
10883
  var _1n7 = /* @__PURE__ */ BigInt(1);
10315
10884
  var atitle2 = (title) => title ? `"${title}" ` : "";
@@ -10868,18 +11437,18 @@ function validateTableBytes(numPoints, fpBytes) {
10868
11437
  if (bytes > TABLE_BYTES_MAX)
10869
11438
  throw new Error("invalid window size: table would need ~" + Math.ceil(bytes / 2 ** 20) + " MiB, max " + TABLE_BYTES_MAX / 2 ** 20 + " MiB");
10870
11439
  }
10871
- function probeRandomBytes(randomBytes7, length) {
10872
- if (randomBytes7 === void 0)
11440
+ function probeRandomBytes(randomBytes8, length) {
11441
+ if (randomBytes8 === void 0)
10873
11442
  return void 0;
10874
- afunction(randomBytes7, "randomBytes");
11443
+ afunction(randomBytes8, "randomBytes");
10875
11444
  try {
10876
- const probe = randomBytes7(length);
11445
+ const probe = randomBytes8(length);
10877
11446
  if (!isBytes3(probe) || probe.length !== length)
10878
11447
  return void 0;
10879
11448
  } catch {
10880
11449
  return void 0;
10881
11450
  }
10882
- return randomBytes7;
11451
+ return randomBytes8;
10883
11452
  }
10884
11453
  function validateMSMPoints2(points, c) {
10885
11454
  aarray(points, "points");
@@ -10972,9 +11541,9 @@ var ScalarMultiplier = class {
10972
11541
  baseCanBeBlinded;
10973
11542
  bits;
10974
11543
  // Parametrized with a given Point class (not individual point)
10975
- constructor(Point, randomBytes7) {
11544
+ constructor(Point, randomBytes8) {
10976
11545
  validatePointCons(Point);
10977
- this.randomBytes = probeRandomBytes(randomBytes7, BLIND_BYTES);
11546
+ this.randomBytes = probeRandomBytes(randomBytes8, BLIND_BYTES);
10978
11547
  this.Point = Point;
10979
11548
  this.BASE = Point.BASE;
10980
11549
  this.ZERO = Point.ZERO;
@@ -11245,7 +11814,7 @@ function edwards2(params, extraOpts = {}) {
11245
11814
  if (FpLegendre2(Fp2, CURVE.d) !== -1)
11246
11815
  throw new Error("edwards: CURVE.d must be a non-square in Fp for complete addition formulas");
11247
11816
  validateObject2(opts, {}, { uvRatio: "function", randomBytes: "function" });
11248
- const randomBytes7 = opts.randomBytes === void 0 ? randomBytes4 : opts.randomBytes;
11817
+ const randomBytes8 = opts.randomBytes === void 0 ? randomBytes5 : opts.randomBytes;
11249
11818
  const MASK = _2n6 << BigInt(Fp2.BYTES * 8) - _1n10;
11250
11819
  function isOdd(n) {
11251
11820
  if (!Fp2.isOdd)
@@ -11506,7 +12075,7 @@ function edwards2(params, extraOpts = {}) {
11506
12075
  }
11507
12076
  }
11508
12077
  const normalize = (points) => normalizeZ2(Point, points);
11509
- const wnaf = new ScalarMultiplier(Point, randomBytes7);
12078
+ const wnaf = new ScalarMultiplier(Point, randomBytes8);
11510
12079
  if (wnaf.bits >= 6)
11511
12080
  Point.BASE.precompute(6);
11512
12081
  Object.freeze(Point.prototype);
@@ -11549,7 +12118,7 @@ function montgomery2(curveDef) {
11549
12118
  const is25519 = type === "x25519";
11550
12119
  if (!is25519 && type !== "x448")
11551
12120
  throw new Error("invalid type");
11552
- const randomBytes_ = rand === void 0 ? randomBytes4 : rand;
12121
+ const randomBytes_ = rand === void 0 ? randomBytes5 : rand;
11553
12122
  const montgomeryBits = is25519 ? 255 : 448;
11554
12123
  const swap = cswap(P);
11555
12124
  const fieldLen = is25519 ? 32 : 56;
@@ -11838,11 +12407,11 @@ var NqHandshake = class {
11838
12407
  isFinished() {
11839
12408
  return this.status === 2;
11840
12409
  }
11841
- mixHash(data) {
11842
- this.symmetric.mixHash(data);
12410
+ mixHash(data2) {
12411
+ this.symmetric.mixHash(data2);
11843
12412
  }
11844
- mixKeyAndHash(data) {
11845
- this.symmetric.mixKeyAndHash(data);
12413
+ mixKeyAndHash(data2) {
12414
+ this.symmetric.mixKeyAndHash(data2);
11846
12415
  }
11847
12416
  pushPsk(psk) {
11848
12417
  if (psk.length !== PSK_LEN) {
@@ -12516,13 +13085,13 @@ var _BLAKE2 = class {
12516
13085
  this.buffer = new Uint8Array(blockLen);
12517
13086
  this.buffer32 = u32(this.buffer);
12518
13087
  }
12519
- update(data) {
13088
+ update(data2) {
12520
13089
  aexists2(this);
12521
- abytes2(data);
13090
+ abytes2(data2);
12522
13091
  const { blockLen, buffer, buffer32 } = this;
12523
- const len = data.length;
12524
- const offset = data.byteOffset;
12525
- const buf = data.buffer;
13092
+ const len = data2.length;
13093
+ const offset = data2.byteOffset;
13094
+ const buf = data2.buffer;
12526
13095
  for (let pos = 0; pos < len; ) {
12527
13096
  if (this.pos === blockLen) {
12528
13097
  swap32IfBE(buffer32);
@@ -12542,7 +13111,7 @@ var _BLAKE2 = class {
12542
13111
  swap32IfBE(data32);
12543
13112
  continue;
12544
13113
  }
12545
- 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);
12546
13115
  this.pos += take;
12547
13116
  this.length += take;
12548
13117
  pos += take;
@@ -12926,8 +13495,8 @@ var hmac = /* @__PURE__ */ (() => {
12926
13495
 
12927
13496
  // ../../node_modules/.pnpm/@lukeburns+clatterjs@1.0.0/node_modules/@lukeburns/clatterjs/dist/crypto/hash.js
12928
13497
  function makeHash(name2, hash, hashLen, blockLen) {
12929
- const doHash = (data) => hash.create().update(data).digest();
12930
- 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);
12931
13500
  const hmacMany = (key, parts) => hmac1(key, parts.length === 1 ? parts[0] : concatBytes2(...parts));
12932
13501
  return {
12933
13502
  name: name2,
@@ -13080,20 +13649,20 @@ var wrapCipher = /* @__NO_SIDE_EFFECTS__ */ (params, constructor) => {
13080
13649
  };
13081
13650
  let called = false;
13082
13651
  const wrCipher = {
13083
- encrypt(data, output) {
13652
+ encrypt(data2, output) {
13084
13653
  if (called)
13085
13654
  throw new Error("cannot encrypt() twice with same key + nonce");
13086
13655
  called = true;
13087
- abytes4(data, void 0, "data");
13656
+ abytes4(data2, void 0, "data");
13088
13657
  checkOutput(cipher.encrypt.length, output);
13089
- return cipher.encrypt(data, output);
13658
+ return cipher.encrypt(data2, output);
13090
13659
  },
13091
- decrypt(data, output) {
13092
- abytes4(data, void 0, "data");
13093
- if (tagl && data.length < tagl)
13660
+ decrypt(data2, output) {
13661
+ abytes4(data2, void 0, "data");
13662
+ if (tagl && data2.length < tagl)
13094
13663
  throw new Error('"ciphertext" expected length >= tagLength=' + tagl);
13095
13664
  checkOutput(cipher.decrypt.length, output);
13096
- return cipher.decrypt(data, output);
13665
+ return cipher.decrypt(data2, output);
13097
13666
  }
13098
13667
  };
13099
13668
  return wrCipher;
@@ -13137,12 +13706,12 @@ var BLOCK_LEN = 64;
13137
13706
  var BLOCK_LEN32 = 16;
13138
13707
  var MAX_COUNTER = /* @__PURE__ */ (() => 2 ** 32 - 1)();
13139
13708
  var U32_EMPTY = /* @__PURE__ */ Uint32Array.of();
13140
- function runCipher(core, sigma, key, nonce, data, output, counter, rounds) {
13141
- const len = data.length;
13709
+ function runCipher(core, sigma, key, nonce, data2, output, counter, rounds) {
13710
+ const len = data2.length;
13142
13711
  const block = new Uint8Array(BLOCK_LEN);
13143
13712
  const b32 = u322(block);
13144
- const isAligned = isLE2 && isAligned32(data) && isAligned32(output);
13145
- const d32 = isAligned ? u322(data) : U32_EMPTY;
13713
+ const isAligned = isLE2 && isAligned32(data2) && isAligned32(output);
13714
+ const d32 = isAligned ? u322(data2) : U32_EMPTY;
13146
13715
  const o32 = isAligned ? u322(output) : U32_EMPTY;
13147
13716
  if (!isLE2) {
13148
13717
  for (let pos = 0; pos < len; counter++) {
@@ -13153,7 +13722,7 @@ function runCipher(core, sigma, key, nonce, data, output, counter, rounds) {
13153
13722
  const take = Math.min(BLOCK_LEN, len - pos);
13154
13723
  for (let j = 0, posj; j < take; j++) {
13155
13724
  posj = pos + j;
13156
- output[posj] = data[posj] ^ block[j];
13725
+ output[posj] = data2[posj] ^ block[j];
13157
13726
  }
13158
13727
  pos += take;
13159
13728
  }
@@ -13177,7 +13746,7 @@ function runCipher(core, sigma, key, nonce, data, output, counter, rounds) {
13177
13746
  }
13178
13747
  for (let j = 0, posj; j < take; j++) {
13179
13748
  posj = pos + j;
13180
- output[posj] = data[posj] ^ block[j];
13749
+ output[posj] = data2[posj] ^ block[j];
13181
13750
  }
13182
13751
  pos += take;
13183
13752
  }
@@ -13190,11 +13759,11 @@ function createCipher(core, opts) {
13190
13759
  anumber4(rounds);
13191
13760
  abool2(counterRight);
13192
13761
  abool2(allowShortKeys);
13193
- return (key, nonce, data, output, counter = 0) => {
13762
+ return (key, nonce, data2, output, counter = 0) => {
13194
13763
  abytes4(key, void 0, "key");
13195
13764
  abytes4(nonce, void 0, "nonce");
13196
- abytes4(data, void 0, "data");
13197
- const len = data.length;
13765
+ abytes4(data2, void 0, "data");
13766
+ const len = data2.length;
13198
13767
  output = getOutput(len, output, false);
13199
13768
  anumber4(counter);
13200
13769
  if (counter < 0 || counter >= MAX_COUNTER)
@@ -13245,7 +13814,7 @@ function createCipher(core, opts) {
13245
13814
  }
13246
13815
  const n32 = swap32IfBE2(u322(nonce));
13247
13816
  try {
13248
- runCipher(core, sigma, k32, n32, data, output, counter, rounds);
13817
+ runCipher(core, sigma, k32, n32, data2, output, counter, rounds);
13249
13818
  return output;
13250
13819
  } finally {
13251
13820
  clean3(...toClean);
@@ -13292,7 +13861,7 @@ var Poly1305 = class {
13292
13861
  for (let i = 0; i < 8; i++)
13293
13862
  this.pad[i] = u8to16(key, 16 + 2 * i);
13294
13863
  }
13295
- process(data, offset, isLast = false) {
13864
+ process(data2, offset, isLast = false) {
13296
13865
  const hibit = isLast ? 0 : 1 << 11;
13297
13866
  const { h, r } = this;
13298
13867
  const r0 = r[0];
@@ -13305,14 +13874,14 @@ var Poly1305 = class {
13305
13874
  const r7 = r[7];
13306
13875
  const r8 = r[8];
13307
13876
  const r9 = r[9];
13308
- const t0 = u8to16(data, offset + 0);
13309
- const t1 = u8to16(data, offset + 2);
13310
- const t2 = u8to16(data, offset + 4);
13311
- const t3 = u8to16(data, offset + 6);
13312
- const t4 = u8to16(data, offset + 8);
13313
- const t5 = u8to16(data, offset + 10);
13314
- const t6 = u8to16(data, offset + 12);
13315
- 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);
13316
13885
  let h0 = h[0] + (t0 & 8191);
13317
13886
  let h1 = h[1] + ((t0 >>> 13 | t1 << 3) & 8191);
13318
13887
  let h2 = h[2] + ((t1 >>> 10 | t2 << 6) & 8191);
@@ -13448,20 +14017,20 @@ var Poly1305 = class {
13448
14017
  }
13449
14018
  clean3(g);
13450
14019
  }
13451
- update(data) {
14020
+ update(data2) {
13452
14021
  aexists3(this);
13453
- abytes4(data);
13454
- data = copyBytes4(data);
14022
+ abytes4(data2);
14023
+ data2 = copyBytes4(data2);
13455
14024
  const { buffer, blockLen } = this;
13456
- const len = data.length;
14025
+ const len = data2.length;
13457
14026
  for (let pos = 0; pos < len; ) {
13458
14027
  const take = Math.min(blockLen - this.pos, len - pos);
13459
14028
  if (take === blockLen) {
13460
14029
  for (; blockLen <= len - pos; pos += blockLen)
13461
- this.process(data, pos);
14030
+ this.process(data2, pos);
13462
14031
  continue;
13463
14032
  }
13464
- buffer.set(data.subarray(pos, pos + take), this.pos);
14033
+ buffer.set(data2.subarray(pos, pos + take), this.pos);
13465
14034
  this.pos += take;
13466
14035
  pos += take;
13467
14036
  if (this.pos === blockLen) {
@@ -13635,9 +14204,9 @@ var _poly1305_aead = (xorStream) => (key, nonce, AAD) => {
13635
14204
  },
13636
14205
  decrypt(ciphertext, output) {
13637
14206
  output = getOutput(ciphertext.length - tagLength, output, false);
13638
- const data = ciphertext.subarray(0, -tagLength);
14207
+ const data2 = ciphertext.subarray(0, -tagLength);
13639
14208
  const passedTag = ciphertext.subarray(-tagLength);
13640
- const tag = computeTag(xorStream, key, nonce, data, AAD);
14209
+ const tag = computeTag(xorStream, key, nonce, data2, AAD);
13641
14210
  if (!equalBytes2(passedTag, tag)) {
13642
14211
  clean3(tag);
13643
14212
  throw new Error("invalid tag");
@@ -13756,7 +14325,7 @@ var NoiseIkSession = class {
13756
14325
  ...initiator ? { rs: Uint8Array.from(this.expectedRemoteStatic) } : {},
13757
14326
  cipher: chachaPoly,
13758
14327
  hash: sha256H,
13759
- rng: options.random ?? randomBytes
14328
+ rng: options.random ?? randomBytes2
13760
14329
  });
13761
14330
  if (this.handshake.getName() !== NOISE_IK_PROTOCOL) {
13762
14331
  throw new NoiseSessionError("NOISE_SUITE_MISMATCH", "The Noise provider selected an unexpected cipher suite.");
@@ -13906,6 +14475,7 @@ var ClientSecureTransport = class {
13906
14475
  incoming = new SecureMessageCodec();
13907
14476
  outgoing = new SecureMessageCodec();
13908
14477
  closed = false;
14478
+ sends = new SerialSend();
13909
14479
  async connect() {
13910
14480
  this.closed = false;
13911
14481
  this.incoming.reset();
@@ -13933,12 +14503,14 @@ var ClientSecureTransport = class {
13933
14503
  throw error;
13934
14504
  }
13935
14505
  }
13936
- async send(data) {
13937
- 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();
13938
14509
  try {
13939
- for (const plaintext of plaintextFrames) {
13940
- await this.inner.send(this.requireNoise().encrypt(plaintext));
13941
- }
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
+ });
13942
14514
  } catch (error) {
13943
14515
  await this.close().catch(() => void 0);
13944
14516
  throw error;
@@ -13946,11 +14518,11 @@ var ClientSecureTransport = class {
13946
14518
  }
13947
14519
  onMessage(handler) {
13948
14520
  this.unsubscribeInner?.();
13949
- this.unsubscribeInner = this.inner.onMessage((data) => {
14521
+ this.unsubscribeInner = this.inner.onMessage((data2) => {
13950
14522
  const noise = this.noise;
13951
14523
  if (noise === void 0 || !noise.complete || this.closed) return;
13952
14524
  try {
13953
- const message = this.incoming.decode(noise.decrypt(data));
14525
+ const message = this.incoming.decode(noise.decrypt(data2));
13954
14526
  if (message !== void 0) handler(message);
13955
14527
  } catch {
13956
14528
  void this.close();
@@ -13989,11 +14561,11 @@ async function waitForResponder(inner, noise) {
13989
14561
  await new Promise((resolve3, reject) => {
13990
14562
  let settled = false;
13991
14563
  const timer = setTimeout(() => finish(new Error("Noise IK handshake timed out.")), 1e4);
13992
- const unsubscribe = inner.onHandshake((step, data) => {
14564
+ const unsubscribe = inner.onHandshake((step, data2) => {
13993
14565
  if (settled) return;
13994
14566
  try {
13995
14567
  if (step !== 2) throw new Error("Noise IK responder sent an out-of-order handshake message.");
13996
- noise.readHandshake(data);
14568
+ noise.readHandshake(data2);
13997
14569
  if (!noise.complete) throw new Error("Noise IK handshake did not complete.");
13998
14570
  finish();
13999
14571
  } catch (error) {
@@ -14016,16 +14588,19 @@ async function waitForResponder(inner, noise) {
14016
14588
 
14017
14589
  // src/control-route.ts
14018
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}`;
14019
14593
  var ENDPOINT_SEGMENT_PATTERN = /^[A-Za-z0-9_$.-]+$/;
14020
14594
  var INVALID_REQUEST_RPC_ID = "invalid-request";
14021
- function registerControlRoute(connection, handler, webServer) {
14595
+ function registerControlRoute(connection, handler, webServer, statusStream) {
14022
14596
  if (webServer !== void 0 && connection.requestRejection !== void 0) {
14023
14597
  const dispose = webServer.register({
14024
14598
  kind: "prefix",
14025
14599
  path: CONTROL_RPC_PREFIX,
14026
- handler: (req, res) => handleControlRequest(connection, handler, req, res)
14600
+ handler: (req, res) => handleControlRequest(connection, handler, req, res, statusStream)
14027
14601
  });
14028
14602
  return async () => {
14603
+ statusStream?.close();
14029
14604
  await dispose();
14030
14605
  };
14031
14606
  }
@@ -14033,7 +14608,7 @@ function registerControlRoute(connection, handler, webServer) {
14033
14608
  authority: "loopback"
14034
14609
  });
14035
14610
  }
14036
- async function handleControlRequest(connection, handler, req, res) {
14611
+ async function handleControlRequest(connection, handler, req, res, statusStream) {
14037
14612
  const rejection = connection.requestRejection?.(req);
14038
14613
  if (rejection !== void 0) {
14039
14614
  res.writeHead(rejection);
@@ -14041,6 +14616,10 @@ async function handleControlRequest(connection, handler, req, res) {
14041
14616
  return;
14042
14617
  }
14043
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
+ }
14044
14623
  if (req.method !== "POST" || endpoint === void 0) {
14045
14624
  writeText(res, 404, "not found");
14046
14625
  return;
@@ -14132,12 +14711,12 @@ function writeText(res, status2, body) {
14132
14711
  }
14133
14712
 
14134
14713
  // src/ids.ts
14135
- import { randomBytes as randomBytes6 } from "node:crypto";
14714
+ import { randomBytes as randomBytes7 } from "node:crypto";
14136
14715
  function uuidV7(now = Date.now()) {
14137
14716
  if (!Number.isSafeInteger(now) || now < 0 || now > 281474976710655) {
14138
14717
  throw new RangeError("UUIDv7 timestamp must be a non-negative 48-bit integer");
14139
14718
  }
14140
- const bytes = randomBytes6(16);
14719
+ const bytes = randomBytes7(16);
14141
14720
  let timestamp = BigInt(now);
14142
14721
  for (let index = 5; index >= 0; index -= 1) {
14143
14722
  bytes[index] = Number(timestamp & 0xffn);
@@ -14451,13 +15030,13 @@ var AsyncFrameQueue = class {
14451
15030
  };
14452
15031
  function routeStreamEvent(event, streamId, queue) {
14453
15032
  if (event.event === "harness.api.frame") {
14454
- const data = event.data;
14455
- if (data.streamId !== streamId || typeof data.frame !== "object" || data.frame === null || typeof data.frame.rpcId !== "string" || !("payload" in data.frame)) return;
14456
- 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);
14457
15036
  }
14458
15037
  if (event.event === "harness.api.stream.closed") {
14459
- const data = event.data;
14460
- if (data.streamId === streamId) queue.close();
15038
+ const data2 = event.data;
15039
+ if (data2.streamId === streamId) queue.close();
14461
15040
  }
14462
15041
  }
14463
15042
  function bytesToBase642(bytes) {
@@ -14566,8 +15145,8 @@ function normalizeEvent(value) {
14566
15145
  const { sourceEventSeqs: _sourceEventSeqs, ...rest } = next;
14567
15146
  next = rest;
14568
15147
  }
14569
- const data = normalizeEventData(type, next.data);
14570
- if (data !== next.data) next = { ...next, data };
15148
+ const data2 = normalizeEventData(type, next.data);
15149
+ if (data2 !== next.data) next = { ...next, data: data2 };
14571
15150
  const surfaceOp = normalizeSurfaceOp(next.surfaceOp);
14572
15151
  if (surfaceOp !== next.surfaceOp) next = { ...next, surfaceOp };
14573
15152
  return next;
@@ -14765,14 +15344,26 @@ var RemoteTypertGateway2 = class {
14765
15344
  };
14766
15345
  var AsyncValueQueue = class {
14767
15346
  values = [];
15347
+ bytes = 0;
14768
15348
  waiters = [];
14769
15349
  closed = false;
14770
15350
  error;
14771
15351
  push(value) {
14772
15352
  if (this.closed) return;
14773
15353
  const waiter = this.waiters.shift();
14774
- if (waiter === void 0) this.values.push(value);
14775
- 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 });
14776
15367
  }
14777
15368
  close() {
14778
15369
  if (this.closed) return;
@@ -14787,7 +15378,9 @@ var AsyncValueQueue = class {
14787
15378
  async *[Symbol.asyncIterator]() {
14788
15379
  while (true) {
14789
15380
  if (this.values.length > 0) {
14790
- yield this.values.shift();
15381
+ const entry = this.values.shift();
15382
+ this.bytes -= entry.bytes;
15383
+ yield entry.value;
14791
15384
  continue;
14792
15385
  }
14793
15386
  if (this.closed) {
@@ -14805,15 +15398,15 @@ var AsyncValueQueue = class {
14805
15398
  };
14806
15399
  function routeStreamEvent2(event, streamId, queue) {
14807
15400
  if (event.event === "harness.remote.frame") {
14808
- const data2 = event.data;
14809
- 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);
14810
15403
  return;
14811
15404
  }
14812
15405
  if (event.event !== "harness.remote.stream.closed") return;
14813
- const data = event.data;
14814
- if (data.streamId !== streamId) return;
14815
- if (data.reason === "failed") {
14816
- 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;
14817
15410
  queue.fail(remoteFailure({
14818
15411
  code: typeof failure2?.code === "string" ? failure2.code : "internal",
14819
15412
  message: typeof failure2?.message === "string" ? failure2.message : "The remote Harness stream failed.",
@@ -15580,7 +16173,7 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
15580
16173
  follow.liveToolResultSeq.delete(itemId);
15581
16174
  follow.liveToolResultSeq.delete(`${itemId}:call`);
15582
16175
  }
15583
- pushEvent(follow, type, data, view, placement) {
16176
+ pushEvent(follow, type, data2, view, placement) {
15584
16177
  const seq = follow.nextSeq++;
15585
16178
  const event = {
15586
16179
  type,
@@ -15588,7 +16181,7 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
15588
16181
  time: Date.now(),
15589
16182
  data: adaptEventData(
15590
16183
  type,
15591
- 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,
15592
16185
  this.sessionGeneration
15593
16186
  ),
15594
16187
  ...isSurfaceEvent(type) ? placement === void 0 ? { surfaceOp: "append" } : {
@@ -15616,9 +16209,9 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
15616
16209
  for (const block of collectImageBlocks(value)) {
15617
16210
  const attachment = record2(block.attachment);
15618
16211
  const attachmentId = string(attachment.attachmentId);
15619
- const data = string(block.data);
15620
- if (attachmentId === void 0 || data === void 0 || !attachmentId.startsWith(CODEX_IMAGE_ATTACHMENT_PREFIX)) continue;
15621
- 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 });
15622
16215
  }
15623
16216
  }
15624
16217
  ensureStreamBlock(follow, key, itemId, kind) {
@@ -16278,7 +16871,7 @@ async function loadCatalog(client, signal, pendingThreads) {
16278
16871
  if (cursor2 === void 0) break;
16279
16872
  }
16280
16873
  if (pendingThreads !== void 0) {
16281
- 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));
16282
16875
  for (const [threadId, thread] of pendingThreads) {
16283
16876
  if (listedIds.has(threadId)) pendingThreads.delete(threadId);
16284
16877
  else threads.unshift(thread);
@@ -16353,8 +16946,8 @@ async function loadModelDirectory(client, signal) {
16353
16946
  for (const value of array(result.data)) {
16354
16947
  const source = record2(value);
16355
16948
  if (source.hidden === true) continue;
16356
- const id2 = string(source.model) ?? string(source.id);
16357
- 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;
16358
16951
  const efforts = array(source.supportedReasoningEfforts).map((value2) => {
16359
16952
  const effort = record2(value2);
16360
16953
  const effortId = string(effort.reasoningEffort);
@@ -16369,8 +16962,8 @@ async function loadModelDirectory(client, signal) {
16369
16962
  const defaultEffort2 = string(source.defaultReasoningEffort);
16370
16963
  const description = string(source.description);
16371
16964
  const model = {
16372
- id: id2,
16373
- name: string(source.displayName) ?? id2,
16965
+ id: id4,
16966
+ name: string(source.displayName) ?? id4,
16374
16967
  ...description === void 0 ? {} : { description },
16375
16968
  ...efforts.length === 0 ? {} : {
16376
16969
  reasoning: {
@@ -16379,8 +16972,8 @@ async function loadModelDirectory(client, signal) {
16379
16972
  }
16380
16973
  }
16381
16974
  };
16382
- models.set(id2, model);
16383
- if (source.isDefault === true) defaultModelId = id2;
16975
+ models.set(id4, model);
16976
+ if (source.isDefault === true) defaultModelId = id4;
16384
16977
  }
16385
16978
  cursor2 = typeof result.nextCursor === "string" && result.nextCursor.length > 0 ? result.nextCursor : void 0;
16386
16979
  if (cursor2 === void 0) break;
@@ -16403,7 +16996,7 @@ function projectCodexNativeHistory(thread, sessionId, sessionGeneration = "legac
16403
16996
  let seq = 0;
16404
16997
  let turnNumber = 0;
16405
16998
  const active = activeTurnId(thread.turns);
16406
- const append = (type, data, time = Date.now(), view, sourceEventSeqs) => {
16999
+ const append = (type, data2, time = Date.now(), view, sourceEventSeqs) => {
16407
17000
  const eventSeq = seq++;
16408
17001
  entries.push({
16409
17002
  type: "event",
@@ -16411,7 +17004,7 @@ function projectCodexNativeHistory(thread, sessionId, sessionGeneration = "legac
16411
17004
  type,
16412
17005
  seq: eventSeq,
16413
17006
  time,
16414
- data: adaptEventData(type, data, sessionGeneration),
17007
+ data: adaptEventData(type, data2, sessionGeneration),
16415
17008
  ...sourceEventSeqs === void 0 ? {} : { sourceEventSeqs },
16416
17009
  ...isSurfaceEvent(type) ? { surfaceOp: "append" } : {}
16417
17010
  },
@@ -16501,9 +17094,9 @@ function paginateCodexNativeHistory(history, request) {
16501
17094
  ...history.activeTurnId === void 0 ? {} : { activeTurnId: history.activeTurnId }
16502
17095
  };
16503
17096
  }
16504
- function adaptEventData(type, data, sessionGeneration) {
16505
- if (sessionGeneration !== "v3" || type !== "assistant/message") return data;
16506
- const value = record2(data);
17097
+ function adaptEventData(type, data2, sessionGeneration) {
17098
+ if (sessionGeneration !== "v3" || type !== "assistant/message") return data2;
17099
+ const value = record2(data2);
16507
17100
  return Array.isArray(value.stream) ? value : { ...value, stream: [] };
16508
17101
  }
16509
17102
  function adaptCodexHistoryPage(value, sessionGeneration) {
@@ -16537,14 +17130,14 @@ function adaptCodexHistoryPage(value, sessionGeneration) {
16537
17130
  }
16538
17131
  function itemEvents(item, turn, step, requestId, selection = modelSelection(), sessionId = CODEX_SESSION_PREFIX, supplementalImages = []) {
16539
17132
  const type = string(item.type);
16540
- const id2 = string(item.id) ?? `${turn}:${step}:${hashString(JSON.stringify(item))}`;
17133
+ const id4 = string(item.id) ?? `${turn}:${step}:${hashString(JSON.stringify(item))}`;
16541
17134
  const text = itemText2(item);
16542
17135
  if (type === "userMessage") return [{
16543
17136
  type: "user/message",
16544
17137
  data: {
16545
- id: id2,
17138
+ id: id4,
16546
17139
  role: "user",
16547
- content: messageContent(item, `${sessionId}:${id2}`, "text", text),
17140
+ content: messageContent(item, `${sessionId}:${id4}`, "text", text),
16548
17141
  source: requestId === void 0 || requestId === "" ? { kind: "user" } : { kind: "user", rpcId: requestId }
16549
17142
  }
16550
17143
  }];
@@ -16554,11 +17147,11 @@ function itemEvents(item, turn, step, requestId, selection = modelSelection(), s
16554
17147
  turn,
16555
17148
  step,
16556
17149
  message: {
16557
- id: id2,
17150
+ id: id4,
16558
17151
  role: "assistant",
16559
17152
  content: messageContent(
16560
17153
  item,
16561
- `${sessionId}:${id2}`,
17154
+ `${sessionId}:${id4}`,
16562
17155
  type === "reasoning" ? "reasoning" : "text",
16563
17156
  text,
16564
17157
  type === "agentMessage" ? supplementalImages : []
@@ -16573,10 +17166,10 @@ function itemEvents(item, turn, step, requestId, selection = modelSelection(), s
16573
17166
  return [
16574
17167
  {
16575
17168
  type: "tool/call",
16576
- data: { turn, step, callId: id2, name: name2, arguments: JSON.stringify(args) },
17169
+ data: { turn, step, callId: id4, name: name2, arguments: JSON.stringify(args) },
16577
17170
  view: { for: "call", view: toolCallView(item, type, args) }
16578
17171
  },
16579
- toolResultEvent(item, turn, step, id2, toolResultText(item, type, text))
17172
+ toolResultEvent(item, turn, step, id4, toolResultText(item, type, text))
16580
17173
  ];
16581
17174
  }
16582
17175
  if (type === "error") return [{
@@ -16585,7 +17178,7 @@ function itemEvents(item, turn, step, requestId, selection = modelSelection(), s
16585
17178
  turn,
16586
17179
  step,
16587
17180
  message: {
16588
- id: id2,
17181
+ id: id4,
16589
17182
  role: "assistant",
16590
17183
  content: [{ type: "text", text: text ?? "CodeX reported an error." }],
16591
17184
  source: { kind: "model", provider: selection.provider, model: selection.model }
@@ -16672,19 +17265,19 @@ function parseImageBlock(block) {
16672
17265
  const url = string(block.url) ?? string(block.image_url) ?? string(record2(block.image_url).url);
16673
17266
  const parsed = url === void 0 ? void 0 : parseDataImageUrl2(url);
16674
17267
  if (parsed !== void 0) return parsed;
16675
- const data = string(block.data);
16676
- if (data === void 0 || !isCanonicalBase642(data)) return void 0;
16677
- if (data.length > MAX_CODEX_IMAGE_BASE64) return void 0;
16678
- const mediaType = string(block.mediaType) ?? string(block.mimeType) ?? sniffImageMediaType2(data);
16679
- 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;
16680
17273
  }
16681
17274
  function parseDataImageUrl2(value) {
16682
17275
  const match = DATA_IMAGE_URL2.exec(value);
16683
17276
  if (match === null) return void 0;
16684
17277
  const mediaType = match[1];
16685
- const data = match[2];
16686
- if (!CODEX_IMAGE_MEDIA_TYPES2.has(mediaType) || !isCanonicalBase642(data)) return void 0;
16687
- 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}` };
16688
17281
  }
16689
17282
  function collectImageBlocks(value, output = []) {
16690
17283
  if (Array.isArray(value)) {
@@ -16705,8 +17298,8 @@ function base64ByteLength(value) {
16705
17298
  const padding = value.endsWith("==") ? 2 : value.endsWith("=") ? 1 : 0;
16706
17299
  return Math.max(1, Math.floor(value.length * 3 / 4) - padding);
16707
17300
  }
16708
- function imageDimensions(mediaType, data) {
16709
- const bytes = base64PrefixBytes2(data, 256 * 1024);
17301
+ function imageDimensions(mediaType, data2) {
17302
+ const bytes = base64PrefixBytes2(data2, 256 * 1024);
16710
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;
16711
17304
  return parsed ?? { width: 1, height: 1 };
16712
17305
  }
@@ -16759,8 +17352,8 @@ function webpDimensions(bytes) {
16759
17352
  }
16760
17353
  return void 0;
16761
17354
  }
16762
- function sniffImageMediaType2(data) {
16763
- const bytes = base64PrefixBytes2(data, 32);
17355
+ function sniffImageMediaType2(data2) {
17356
+ const bytes = base64PrefixBytes2(data2, 32);
16764
17357
  if (pngDimensions(bytes) !== void 0) return "image/png";
16765
17358
  if (bytes.length >= 3 && bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255) return "image/jpeg";
16766
17359
  if (gifDimensions(bytes) !== void 0) return "image/gif";
@@ -16803,7 +17396,7 @@ function stringFromBytes(bytes, start, end) {
16803
17396
  for (let index = start; index < end; index += 1) output += String.fromCharCode(bytes[index]);
16804
17397
  return output;
16805
17398
  }
16806
- function toolResultEvent(item, turn, step, id2, resultText) {
17399
+ function toolResultEvent(item, turn, step, id4, resultText) {
16807
17400
  const type = string(item.type) ?? "unknown";
16808
17401
  return {
16809
17402
  type: "tool/result",
@@ -16811,23 +17404,23 @@ function toolResultEvent(item, turn, step, id2, resultText) {
16811
17404
  turn,
16812
17405
  step,
16813
17406
  message: {
16814
- id: `${id2}:result`,
17407
+ id: `${id4}:result`,
16815
17408
  role: "user",
16816
17409
  content: [{
16817
17410
  type: "tool-result",
16818
- toolCallId: id2,
17411
+ toolCallId: id4,
16819
17412
  content: [{ type: "text", text: resultText }],
16820
17413
  ...item.status === "failed" || item.success === false ? { isError: true } : {}
16821
17414
  }],
16822
- source: { kind: "tool", callId: id2 }
17415
+ source: { kind: "tool", callId: id4 }
16823
17416
  }
16824
17417
  },
16825
17418
  view: { for: "result", view: toolResultView(item, type, resultText) }
16826
17419
  };
16827
17420
  }
16828
17421
  function toolResultContentReplacement(event) {
16829
- const data = record2(event.data);
16830
- const message = record2(data.message);
17422
+ const data2 = record2(event.data);
17423
+ const message = record2(data2.message);
16831
17424
  const content = array(message.content);
16832
17425
  const result = record2(content[0]);
16833
17426
  if (result.isError === void 0) return event;
@@ -16835,7 +17428,7 @@ function toolResultContentReplacement(event) {
16835
17428
  return {
16836
17429
  ...event,
16837
17430
  data: {
16838
- ...data,
17431
+ ...data2,
16839
17432
  message: { ...message, content: [stableResult] }
16840
17433
  }
16841
17434
  };
@@ -16969,12 +17562,12 @@ function projectWorkspace(project) {
16969
17562
  }
16970
17563
  function projectCodexProject(value, fallbackPosition) {
16971
17564
  const source = record2(value);
16972
- const id2 = string(source.id);
16973
- if (id2 === void 0) return void 0;
17565
+ const id4 = string(source.id);
17566
+ if (id4 === void 0) return void 0;
16974
17567
  const roots = array(source.roots).map((root) => string(record2(root).path)).filter((path) => path !== void 0 && path.length > 0).map((path) => ({ path }));
16975
17568
  if (roots.length === 0) return void 0;
16976
17569
  return {
16977
- id: id2,
17570
+ id: id4,
16978
17571
  name: string(source.name) ?? basename(roots[0].path),
16979
17572
  roots,
16980
17573
  position: finiteNumber(source.position) ?? fallbackPosition,
@@ -17400,6 +17993,105 @@ var AsyncValueQueue2 = class {
17400
17993
  // src/server-api.ts
17401
17994
  import { platform } from "node:os";
17402
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
+
17403
18095
  // src/config.ts
17404
18096
  import { hostname } from "node:os";
17405
18097
  import s from "@deepseek-ai/schemastery";
@@ -17409,6 +18101,8 @@ var Config = s.object({
17409
18101
  role: s.union(["host", "client", "both"]),
17410
18102
  serverUrl: s.string(),
17411
18103
  deviceName: s.string(),
18104
+ terminal: s.object({ enabled: s.boolean() }),
18105
+ loopback: s.object({ ports: s.array(s.number()) }),
17412
18106
  forceRelay: s.boolean(),
17413
18107
  logLevel: s.union(["debug", "info", "warn", "error"]),
17414
18108
  reconnect: s.union([
@@ -17422,7 +18116,8 @@ var Config = s.object({
17422
18116
  codex: s.object({
17423
18117
  enabled: s.boolean(),
17424
18118
  binary: s.string()
17425
- })
18119
+ }),
18120
+ acp: s.object({ enabled: s.boolean(), backends: s.array(s.object({ id: s.string(), enabled: s.boolean(), command: s.string(), args: s.array(s.string()), cwd: s.string() })) })
17426
18121
  });
17427
18122
  var reconnectSchema = external_exports.union([
17428
18123
  external_exports.boolean(),
@@ -17437,13 +18132,16 @@ var configSchema = external_exports.object({
17437
18132
  role: external_exports.enum(["host", "client", "both"]).optional(),
17438
18133
  serverUrl: external_exports.string().url().optional(),
17439
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(),
17440
18137
  forceRelay: external_exports.boolean().optional(),
17441
18138
  logLevel: external_exports.enum(["debug", "info", "warn", "error"]).optional(),
17442
18139
  reconnect: reconnectSchema.optional(),
17443
18140
  codex: external_exports.object({
17444
18141
  enabled: external_exports.boolean().optional(),
17445
18142
  binary: external_exports.string().trim().min(1).max(4096).optional()
17446
- }).strict().optional()
18143
+ }).strict().optional(),
18144
+ acp: external_exports.object({ enabled: external_exports.boolean().optional(), backends: external_exports.array(external_exports.object({ id: external_exports.string().trim().regex(/^[a-z0-9][a-z0-9._-]{0,31}$/i), enabled: external_exports.boolean().optional(), command: external_exports.string().trim().min(1).max(4096).optional(), args: external_exports.array(external_exports.string().max(4096)).max(32).optional(), cwd: external_exports.string().max(4096).optional() }).strict()).max(12).optional(), backend: external_exports.string().trim().regex(/^[a-z0-9][a-z0-9._-]{0,31}$/i).optional(), command: external_exports.string().trim().min(1).max(4096).optional(), args: external_exports.array(external_exports.string().max(4096)).max(32).optional(), cwd: external_exports.string().max(4096).optional() }).strict().optional()
17447
18145
  }).strict();
17448
18146
  function resolveConfig(input2 = {}, env = process.env) {
17449
18147
  const parsed = configSchema.parse(input2);
@@ -17460,6 +18158,8 @@ function resolveConfig(input2 = {}, env = process.env) {
17460
18158
  role: parsed.role ?? "host",
17461
18159
  ...serverUrl === void 0 ? {} : { serverUrl },
17462
18160
  deviceName: parsed.deviceName ?? hostname(),
18161
+ terminal: { enabled: parsed.terminal?.enabled ?? false },
18162
+ loopback: { ports: [...new Set(parsed.loopback?.ports ?? [])] },
17463
18163
  forceRelay: parsed.forceRelay ?? false,
17464
18164
  logLevel: parsed.logLevel ?? "info",
17465
18165
  reconnect: {
@@ -17471,7 +18171,12 @@ function resolveConfig(input2 = {}, env = process.env) {
17471
18171
  codex: {
17472
18172
  enabled: parsed.codex?.enabled ?? true,
17473
18173
  binary: parsed.codex?.binary ?? "codex"
17474
- }
18174
+ },
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 } : {} };
18179
+ }) }
17475
18180
  };
17476
18181
  }
17477
18182
  function normalizeServerUrl(value) {
@@ -17493,7 +18198,7 @@ function normalizeServerUrl(value) {
17493
18198
  }
17494
18199
 
17495
18200
  // src/version.ts
17496
- var PLUGIN_VERSION = "0.4.13";
18201
+ var PLUGIN_VERSION = "0.4.15";
17497
18202
 
17498
18203
  // src/server-api.ts
17499
18204
  var TERMINAL_CONTROL_CHARACTERS = /[\u0000-\u001f\u007f-\u009f]/u;
@@ -17652,22 +18357,48 @@ var HostServerApi = class {
17652
18357
  this.credentials = await this.credentialsPromise;
17653
18358
  return this.credentials;
17654
18359
  }
17655
- async refreshCredentials() {
18360
+ async refreshCredentials(rejectedAccessToken = this.credentials?.accessToken) {
17656
18361
  const identity = this.requireIdentity();
17657
- const stored = await this.store.load(this.baseUrl, identity.deviceId);
17658
- if (stored === void 0 || stored.refreshTokenExpiresAt <= Date.now()) return this.register(identity);
17659
- const tokens = await this.publicRequest("/api/v1/auth/refresh", {
17660
- method: "POST",
17661
- 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);
17662
18369
  });
17663
- 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({
17664
18396
  serverUrl: this.baseUrl,
17665
18397
  deviceId: identity.deviceId,
17666
18398
  authorizationMethod: stored.authorizationMethod,
17667
18399
  ...stored.account === void 0 ? {} : { account: stored.account },
17668
18400
  ...validateTokens(tokens)
17669
18401
  });
17670
- return this.credentials;
17671
18402
  }
17672
18403
  async listDevices() {
17673
18404
  const result = await this.request("/api/v1/devices");
@@ -17693,21 +18424,13 @@ var HostServerApi = class {
17693
18424
  return { online: result.online, ...typeof result.lastSeenAt === "number" ? { lastSeenAt: result.lastSeenAt } : {} };
17694
18425
  }
17695
18426
  async loadOrIssue(identity) {
17696
- const stored = await this.store.load(this.baseUrl, identity.deviceId);
17697
- if (stored === void 0 || stored.refreshTokenExpiresAt <= Date.now() + 3e4) {
17698
- return this.register(identity);
17699
- }
17700
- if (stored.accessTokenExpiresAt > Date.now() + 3e4) return stored;
17701
- const tokens = await this.publicRequest("/api/v1/auth/refresh", {
17702
- method: "POST",
17703
- body: JSON.stringify({ deviceId: identity.deviceId, refreshToken: stored.refreshToken })
17704
- });
17705
- return this.store.save({
17706
- serverUrl: this.baseUrl,
17707
- deviceId: identity.deviceId,
17708
- authorizationMethod: stored.authorizationMethod,
17709
- ...stored.account === void 0 ? {} : { account: stored.account },
17710
- ...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);
17711
18434
  });
17712
18435
  }
17713
18436
  async register(identity, authorization) {
@@ -17800,11 +18523,12 @@ var ClientServerApi = class extends HostServerApi {
17800
18523
  }
17801
18524
  };
17802
18525
  var ServerApiError = class extends Error {
17803
- constructor(code, message, retryable, status2) {
18526
+ constructor(code, message, retryable, status2, phase) {
17804
18527
  super(message);
17805
18528
  this.code = code;
17806
18529
  this.retryable = retryable;
17807
18530
  this.status = status2;
18531
+ this.phase = phase;
17808
18532
  }
17809
18533
  };
17810
18534
  function validateTokens(value) {
@@ -17975,12 +18699,12 @@ var TypertGatewaySwitch = class {
17975
18699
  }
17976
18700
  this.installed = true;
17977
18701
  }
17978
- selectRemote(remote, support = ALL_REMOTE_COMMANDS, target) {
18702
+ selectRemote(remote, support = ALL_REMOTE_COMMANDS, target2) {
17979
18703
  if (!this.installed) throw new Error("The Typert gateway switch is not installed.");
17980
18704
  this.remoteInvoke = typeof remote === "function" ? remote : (request) => remote.invoke(request);
17981
18705
  this.remoteTarget = typeof remote === "function" ? void 0 : remote;
17982
18706
  this.remoteSupport = { ...support };
17983
- this.target = target === void 0 ? void 0 : { ...target };
18707
+ this.target = target2 === void 0 ? void 0 : { ...target2 };
17984
18708
  }
17985
18709
  selectLocal() {
17986
18710
  this.remoteInvoke = void 0;
@@ -18047,7 +18771,7 @@ import { networkInterfaces } from "node:os";
18047
18771
  // src/native-rtc-helper.ts
18048
18772
  import { spawn, spawnSync } from "node:child_process";
18049
18773
  import { existsSync, readdirSync, realpathSync, statSync } from "node:fs";
18050
- import { delimiter, dirname as dirname2, join as join2 } from "node:path";
18774
+ import { delimiter, dirname as dirname3, join as join3 } from "node:path";
18051
18775
  import { fileURLToPath } from "node:url";
18052
18776
  var cachedExternalFactory;
18053
18777
  var cachedExternalFactoryResolved = false;
@@ -18098,21 +18822,21 @@ function nodeBinaryCandidates() {
18098
18822
  add3(process.env.DSH_REMOTE_NODE);
18099
18823
  add3(process.env.NODE);
18100
18824
  add3(process.execPath);
18101
- 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"));
18102
18826
  add3("/opt/homebrew/bin/node");
18103
18827
  add3("/usr/local/bin/node");
18104
18828
  add3("/usr/bin/node");
18105
- add3(join2(process.env.HOME ?? "", ".volta", "bin", process.platform === "win32" ? "node.exe" : "node"));
18106
- add3(join2(process.env.HOME ?? "", ".asdf", "shims", process.platform === "win32" ? "node.exe" : "node"));
18107
- 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"));
18108
18832
  for (const nvmNode of nvmNodeCandidates()) add3(nvmNode);
18109
18833
  return candidates;
18110
18834
  }
18111
18835
  function nvmNodeCandidates() {
18112
- const root = join2(process.env.HOME ?? "", ".nvm", "versions", "node");
18836
+ const root = join3(process.env.HOME ?? "", ".nvm", "versions", "node");
18113
18837
  if (root === "" || !existsSync(root)) return [];
18114
18838
  try {
18115
- 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 }));
18116
18840
  } catch {
18117
18841
  return [];
18118
18842
  }
@@ -18134,7 +18858,7 @@ function isUsableExternalNode(candidate, requireFrom) {
18134
18858
  "console.log(`node:${process.versions.node}`);"
18135
18859
  ].join("")
18136
18860
  ], {
18137
- cwd: dirname2(requireFrom),
18861
+ cwd: dirname3(requireFrom),
18138
18862
  env,
18139
18863
  encoding: "utf8",
18140
18864
  timeout: 3e3
@@ -18156,7 +18880,7 @@ var ExternalNativePeerConnection = class {
18156
18880
  this.nodeBinary = nodeBinary;
18157
18881
  this.requireFrom = requireFrom;
18158
18882
  this.child = spawn(this.nodeBinary, ["--input-type=module", "--eval", HELPER_SOURCE], {
18159
- cwd: dirname2(this.requireFrom),
18883
+ cwd: dirname3(this.requireFrom),
18160
18884
  env: { ...process.env, DSH_REMOTE_RTC_HELPER_REQUIRE_FROM: this.requireFrom },
18161
18885
  stdio: ["pipe", "pipe", "pipe"]
18162
18886
  });
@@ -18225,8 +18949,8 @@ var ExternalNativePeerConnection = class {
18225
18949
  }, 1e3);
18226
18950
  timer.unref?.();
18227
18951
  }
18228
- sendChannelData(channelId, data) {
18229
- 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") };
18230
18954
  this.notify("channelSend", payload);
18231
18955
  }
18232
18956
  closeChannel(channelId) {
@@ -18234,11 +18958,11 @@ var ExternalNativePeerConnection = class {
18234
18958
  }
18235
18959
  request(method, payload) {
18236
18960
  if (this.closed) return Promise.reject(new Error("native rtc helper is closed"));
18237
- const id2 = this.nextRequestId++;
18961
+ const id4 = this.nextRequestId++;
18238
18962
  const promise = new Promise((resolve3, reject) => {
18239
- this.pending.set(id2, { resolve: resolve3, reject });
18963
+ this.pending.set(id4, { resolve: resolve3, reject });
18240
18964
  });
18241
- this.write({ id: id2, method, payload });
18965
+ this.write({ id: id4, method, payload });
18242
18966
  return promise;
18243
18967
  }
18244
18968
  notify(method, payload) {
@@ -18342,8 +19066,8 @@ var ExternalNativeDataChannel = class {
18342
19066
  onclose = null;
18343
19067
  onerror = null;
18344
19068
  onbufferedamountlow = null;
18345
- send(data) {
18346
- this.owner.sendChannelData(this.channelId, data);
19069
+ send(data2) {
19070
+ this.owner.sendChannelData(this.channelId, data2);
18347
19071
  }
18348
19072
  close() {
18349
19073
  this.owner.closeChannel(this.channelId);
@@ -18356,8 +19080,8 @@ var ExternalNativeDataChannel = class {
18356
19080
  if (previous !== "closed" && readyState === "closed") this.onclose?.();
18357
19081
  if (bufferedAmount === 0) this.onbufferedamountlow?.();
18358
19082
  }
18359
- emitMessage(data) {
18360
- this.onmessage?.({ data });
19083
+ emitMessage(data2) {
19084
+ this.onmessage?.({ data: data2 });
18361
19085
  }
18362
19086
  emitError() {
18363
19087
  this.onerror?.();
@@ -18834,10 +19558,10 @@ function adaptDataChannel2(raw) {
18834
19558
  return raw.onerror;
18835
19559
  },
18836
19560
  onbufferedamountlow: null,
18837
- send(data) {
18838
- const bytes = typeof data === "string" ? Buffer.byteLength(data) : data.byteLength;
19561
+ send(data2) {
19562
+ const bytes = typeof data2 === "string" ? Buffer.byteLength(data2) : data2.byteLength;
18839
19563
  try {
18840
- raw.send(typeof data === "string" ? data : Buffer.from(data));
19564
+ raw.send(typeof data2 === "string" ? data2 : Buffer.from(data2));
18841
19565
  } catch (error) {
18842
19566
  console.error("[werift-send-error] bytes=" + bytes, error instanceof Error ? error.message : error);
18843
19567
  throw error;
@@ -18900,17 +19624,17 @@ function adaptNativeDataChannel(raw) {
18900
19624
  get onbufferedamountlow() {
18901
19625
  return raw.onbufferedamountlow;
18902
19626
  },
18903
- send(data) {
18904
- raw.send(typeof data === "string" ? data : Buffer.from(data));
19627
+ send(data2) {
19628
+ raw.send(typeof data2 === "string" ? data2 : Buffer.from(data2));
18905
19629
  },
18906
19630
  close() {
18907
19631
  raw.close();
18908
19632
  }
18909
19633
  };
18910
19634
  }
18911
- function toArrayBuffer2(data) {
18912
- if (typeof data === "string") return data;
18913
- 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);
18914
19638
  }
18915
19639
  function normalizeNativeCandidate(candidate) {
18916
19640
  const json = typeof candidate.toJSON === "function" ? candidate.toJSON() : {
@@ -18924,11 +19648,11 @@ function normalizeNativeCandidate(candidate) {
18924
19648
  sdpMLineIndex: normalizeSdpMLineIndex(json.sdpMLineIndex)
18925
19649
  };
18926
19650
  }
18927
- function normalizeNativeMessageData(data) {
18928
- if (typeof data === "string") return data;
18929
- if (data instanceof ArrayBuffer) return data;
18930
- if (ArrayBuffer.isView(data)) {
18931
- 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);
18932
19656
  }
18933
19657
  return new Uint8Array().buffer;
18934
19658
  }
@@ -18958,8 +19682,8 @@ function detectHostIpv4Candidates(interfaces, preferredHostIpv4Candidates = [])
18958
19682
  return [...new Set(candidates.map((candidate) => candidate.ip))];
18959
19683
  }
18960
19684
  async function detectRouteHostIpv4Candidates(targets, timeoutMs = 500) {
18961
- const detected = await Promise.all(targets.map(async (target) => {
18962
- const routeTarget = parseRouteTarget(target);
19685
+ const detected = await Promise.all(targets.map(async (target2) => {
19686
+ const routeTarget = parseRouteTarget(target2);
18963
19687
  return routeTarget === void 0 ? void 0 : await detectRouteHostIpv4(routeTarget, timeoutMs).catch(() => void 0);
18964
19688
  }));
18965
19689
  return [...new Set(detected.filter((ip) => ip !== void 0 && isUsableIpv4(ip)))];
@@ -19027,7 +19751,7 @@ function parseRouteTarget(value) {
19027
19751
  return void 0;
19028
19752
  }
19029
19753
  }
19030
- async function detectRouteHostIpv4(target, timeoutMs) {
19754
+ async function detectRouteHostIpv4(target2, timeoutMs) {
19031
19755
  const socket = createSocket("udp4");
19032
19756
  try {
19033
19757
  return await new Promise((resolve3, reject) => {
@@ -19044,7 +19768,7 @@ async function detectRouteHostIpv4(target, timeoutMs) {
19044
19768
  reject(error);
19045
19769
  };
19046
19770
  socket.once("error", onError);
19047
- socket.connect(target.port, target.host, () => {
19771
+ socket.connect(target2.port, target2.host, () => {
19048
19772
  cleanup();
19049
19773
  const address = socket.address();
19050
19774
  resolve3(typeof address === "string" ? void 0 : address.address);
@@ -19119,6 +19843,7 @@ var ClientModeRuntime = class {
19119
19843
  this.proxySwitch = apiProxy === void 0 ? void 0 : new ApiProxySwitch(apiProxy);
19120
19844
  this.gatewaySwitch = new TypertGatewaySwitch(typertGateway);
19121
19845
  }
19846
+ preview;
19122
19847
  identity;
19123
19848
  connected;
19124
19849
  pendingWorkspaceSelection;
@@ -19170,6 +19895,11 @@ var ClientModeRuntime = class {
19170
19895
  ...this.host === void 0 ? {} : { host: this.host.hostStatus() }
19171
19896
  };
19172
19897
  }
19898
+ async closePreview() {
19899
+ const preview = this.preview;
19900
+ this.preview = void 0;
19901
+ await preview?.close();
19902
+ }
19173
19903
  async detailedStatus() {
19174
19904
  const connected = this.connected;
19175
19905
  if (connected === void 0 || this.identity === void 0) return this.status();
@@ -19235,6 +19965,7 @@ var ClientModeRuntime = class {
19235
19965
  this.pendingWorkspaceSelection = void 0;
19236
19966
  await this.closeCodexVirtual();
19237
19967
  this.proxySwitch?.selectLocal();
19968
+ await this.closePreview();
19238
19969
  this.gatewaySwitch.selectLocal();
19239
19970
  await this.closeCodexStreams(previous?.client);
19240
19971
  await previous?.client.close().catch(() => void 0);
@@ -19256,6 +19987,7 @@ var ClientModeRuntime = class {
19256
19987
  if (mode === "local") {
19257
19988
  await this.closeCodexVirtual();
19258
19989
  this.proxySwitch?.selectLocal();
19990
+ await this.closePreview();
19259
19991
  this.gatewaySwitch.selectLocal();
19260
19992
  const previous2 = this.connected;
19261
19993
  this.connected = void 0;
@@ -19278,6 +20010,7 @@ var ClientModeRuntime = class {
19278
20010
  throw error;
19279
20011
  }
19280
20012
  const previous = this.connected;
20013
+ await this.closePreview();
19281
20014
  this.connected = next;
19282
20015
  this.clearConnectionProgress(next.progressRunId);
19283
20016
  this.pendingWorkspaceSelection = void 0;
@@ -19413,9 +20146,11 @@ var ClientModeRuntime = class {
19413
20146
  return this.status();
19414
20147
  }
19415
20148
  async close() {
20149
+ await this.closePreview();
19416
20150
  if (this.closed) return;
19417
20151
  this.closed = true;
19418
20152
  this.proxySwitch?.selectLocal();
20153
+ await this.closePreview();
19419
20154
  this.gatewaySwitch.selectLocal();
19420
20155
  this.pendingWorkspaceSelection = void 0;
19421
20156
  await this.closeCodexVirtual();
@@ -19498,24 +20233,24 @@ var ClientModeRuntime = class {
19498
20233
  this.codexStreams.set(value.streamId, stream);
19499
20234
  return { opened: true, streamId: value.streamId, threadId: value.threadId };
19500
20235
  }
19501
- publishLocalCodexFrame = async (event, data) => {
19502
- const streamId = data.streamId;
20236
+ publishLocalCodexFrame = async (event, data2) => {
20237
+ const streamId = data2.streamId;
19503
20238
  const stream = this.codexStreams.get(streamId);
19504
20239
  if (stream === void 0 || stream.target.kind !== "local") return;
19505
20240
  if (event === "codex.app.frame") {
19506
- this.appendCodexFrame(stream, data);
20241
+ this.appendCodexFrame(stream, data2);
19507
20242
  return;
19508
20243
  }
19509
- const closed = data;
20244
+ const closed = data2;
19510
20245
  stream.closed = typeof closed.reason === "string" ? closed.reason : "closed";
19511
20246
  stream.wake();
19512
20247
  };
19513
- appendCodexFrame(stream, data) {
19514
- 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;
19515
20250
  if (stream.frames.length >= 256) {
19516
20251
  stream.closed = "overflow";
19517
20252
  } else {
19518
- stream.frames.push({ method: data.frame.method, params: data.frame.params });
20253
+ stream.frames.push({ method: data2.frame.method, params: data2.frame.params });
19519
20254
  }
19520
20255
  stream.wake();
19521
20256
  }
@@ -19569,16 +20304,16 @@ var ClientModeRuntime = class {
19569
20304
  }));
19570
20305
  }
19571
20306
  selectRemoteTarget(remote, transport = this.selectHarnessRemoteTransport(remote)) {
19572
- const target = { deviceId: remote.target.deviceId, name: remote.target.name };
20307
+ const target2 = { deviceId: remote.target.deviceId, name: remote.target.name };
19573
20308
  if (transport === "remoteGateway") {
19574
- this.gatewaySwitch.selectRemote(this.remoteTypertGateway(remote), void 0, target);
20309
+ this.gatewaySwitch.selectRemote(this.remoteTypertGateway(remote), void 0, target2);
19575
20310
  return;
19576
20311
  }
19577
- this.proxySwitch?.selectRemote(new RemoteHarnessApiProxy(remote.client).api, target);
20312
+ this.proxySwitch?.selectRemote(new RemoteHarnessApiProxy(remote.client).api, target2);
19578
20313
  this.gatewaySwitch.selectRemote((request) => invokeRemoteCommand(remote.client, request), {
19579
20314
  execute: true,
19580
20315
  list: remote.features.commandList
19581
- }, target);
20316
+ }, target2);
19582
20317
  }
19583
20318
  remoteTypertGateway(remote) {
19584
20319
  const localSessionGeneration = harnessSessionGeneration(this.host?.localHarnessVersion?.());
@@ -19588,13 +20323,13 @@ var ClientModeRuntime = class {
19588
20323
  );
19589
20324
  }
19590
20325
  selectCodexTarget(virtual, remote) {
19591
- const target = { deviceId: remote.target.deviceId, name: remote.target.name };
20326
+ const target2 = { deviceId: remote.target.deviceId, name: remote.target.name };
19592
20327
  if (this.gatewaySwitch.supportsCarrier()) {
19593
- this.gatewaySwitch.selectRemote(virtual, void 0, target);
20328
+ this.gatewaySwitch.selectRemote(virtual, void 0, target2);
19594
20329
  return;
19595
20330
  }
19596
- this.proxySwitch.selectRemote(virtual.api, target);
19597
- 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);
19598
20333
  }
19599
20334
  async closeCodexVirtual() {
19600
20335
  const virtual = this.codexVirtual;
@@ -19640,14 +20375,14 @@ var ClientModeRuntime = class {
19640
20375
  throw new ClientModeError("MEMBERSHIP_REQUIRED", "The selected Host is not authorized for this account.");
19641
20376
  }
19642
20377
  this.updateConnectionProgress(progressRunId, "authorizing-peer");
19643
- const target = await this.authorizeHostPeer(serverDevice);
20378
+ const target2 = await this.authorizeHostPeer(serverDevice);
19644
20379
  const presence = await this.server.presenceFor(targetDeviceId);
19645
20380
  if (!presence.online) throw new ClientModeError("HOST_OFFLINE", "The selected Host is offline.", true);
19646
20381
  const credentials = await this.server.authenticate(identity);
19647
20382
  const rtcFactory = this.config.forceRelay ? void 0 : await this.rtcFactoryProvider({ routeTargets: [this.server.baseUrl] }).catch(() => void 0);
19648
20383
  if (!this.config.forceRelay && rtcFactory === void 0) {
19649
20384
  this.logger.warn("remote Harness WebRTC backend unavailable; using relay", {
19650
- targetDeviceId: shortId(target.deviceId)
20385
+ targetDeviceId: shortId(target2.deviceId)
19651
20386
  });
19652
20387
  }
19653
20388
  let webRtcFallback = false;
@@ -19666,13 +20401,13 @@ var ClientModeRuntime = class {
19666
20401
  onWebRtcFallback: (error, diagnostics) => {
19667
20402
  webRtcFallback = true;
19668
20403
  this.logger.warn(attempt === "direct" ? "remote Harness direct WebRTC failed; trying TURN" : "remote Harness TURN WebRTC failed; using relay", {
19669
- targetDeviceId: shortId(target.deviceId),
20404
+ targetDeviceId: shortId(target2.deviceId),
19670
20405
  attempt,
19671
20406
  reason: diagnosticReason(error)
19672
20407
  });
19673
20408
  if (diagnostics !== void 0) {
19674
20409
  this.logger.debug("remote Harness WebRTC fallback diagnostics", {
19675
- targetDeviceId: shortId(target.deviceId),
20410
+ targetDeviceId: shortId(target2.deviceId),
19676
20411
  attempt,
19677
20412
  ...webrtcDiagnosticsLogFields(diagnostics)
19678
20413
  });
@@ -19687,7 +20422,7 @@ var ClientModeRuntime = class {
19687
20422
  const stopProgressTimer = this.beginAttemptProgress(progressRunId, attempt);
19688
20423
  try {
19689
20424
  transport = createTransport(attempt);
19690
- client = new RemoteClientCore(new ClientSecureTransport(transport, identity, target), 6e4);
20425
+ client = new RemoteClientCore(new ClientSecureTransport(transport, identity, target2), 6e4);
19691
20426
  await client.connect();
19692
20427
  signal?.throwIfAborted();
19693
20428
  } finally {
@@ -19699,7 +20434,7 @@ var ClientModeRuntime = class {
19699
20434
  transport = void 0;
19700
20435
  if (attempt === "turn") {
19701
20436
  this.logger.info("remote Harness relay fallback re-established", {
19702
- targetDeviceId: shortId(target.deviceId)
20437
+ targetDeviceId: shortId(target2.deviceId)
19703
20438
  });
19704
20439
  }
19705
20440
  }
@@ -19716,6 +20451,7 @@ var ClientModeRuntime = class {
19716
20451
  );
19717
20452
  connectedClient.onClose(() => {
19718
20453
  if (this.connected?.client !== connectedClient) return;
20454
+ void this.closePreview();
19719
20455
  this.connected = void 0;
19720
20456
  this.connectionProgress = void 0;
19721
20457
  this.pendingWorkspaceSelection = void 0;
@@ -19724,12 +20460,12 @@ var ClientModeRuntime = class {
19724
20460
  this.gatewaySwitch.selectLocal();
19725
20461
  void connectedClient.close().catch(() => void 0);
19726
20462
  this.logger.warn("remote Harness transport closed; falling back to local mode", {
19727
- targetDeviceId: shortId(target.deviceId)
20463
+ targetDeviceId: shortId(target2.deviceId)
19728
20464
  });
19729
20465
  });
19730
20466
  const connectionDetails = await connectedTransport.connectionDetails().catch(() => void 0);
19731
20467
  this.logger.info("remote Harness transport ready", {
19732
- targetDeviceId: shortId(target.deviceId),
20468
+ targetDeviceId: shortId(target2.deviceId),
19733
20469
  transport: connectedClient.getStats().mode,
19734
20470
  ...connectionDetails === void 0 ? {} : {
19735
20471
  preferredTransports: connectionDetails.preferredTransports,
@@ -19739,14 +20475,14 @@ var ClientModeRuntime = class {
19739
20475
  });
19740
20476
  if (connectionDetails?.webRtc?.diagnostics !== void 0) {
19741
20477
  this.logger.debug("remote Harness transport diagnostics", {
19742
- targetDeviceId: shortId(target.deviceId),
20478
+ targetDeviceId: shortId(target2.deviceId),
19743
20479
  ...webrtcDiagnosticsLogFields(connectionDetails.webRtc.diagnostics)
19744
20480
  });
19745
20481
  }
19746
20482
  const features = await probeRemoteHostFeatures(connectedClient, serverDevice.clientVersion);
19747
20483
  return {
19748
20484
  client: connectedClient,
19749
- target,
20485
+ target: target2,
19750
20486
  transport: connectedTransport,
19751
20487
  features,
19752
20488
  progressRunId,
@@ -19763,6 +20499,7 @@ var ClientModeRuntime = class {
19763
20499
  if (this.connected?.target.deviceId === targetDeviceId) return this.connected;
19764
20500
  const next = await this.connect(targetDeviceId, signal);
19765
20501
  const previous = this.connected;
20502
+ await this.closePreview();
19766
20503
  this.connected = next;
19767
20504
  this.clearConnectionProgress(next.progressRunId);
19768
20505
  await previous?.client.close().catch(() => void 0);
@@ -19819,6 +20556,12 @@ var ClientModeRuntime = class {
19819
20556
  }
19820
20557
  return ok2(await this.pollClientOAuthQrLogin(value.qrId));
19821
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
+ }
19822
20565
  if (endpoint === "directory.list") {
19823
20566
  const value = record3(payload);
19824
20567
  if (typeof value.targetDeviceId !== "string") throw new ClientModeError("INVALID_MESSAGE", "A Host is required.");
@@ -20083,6 +20826,7 @@ function remoteHostFeatures(clientVersion) {
20083
20826
  return {
20084
20827
  commandList: isVersionAtLeast(clientVersion, REMOTE_COMMAND_LIST_MIN_VERSION),
20085
20828
  fileViewer: isVersionAtLeast(clientVersion, REMOTE_FILE_VIEWER_MIN_VERSION),
20829
+ terminal: false,
20086
20830
  apiProxy: true,
20087
20831
  remoteGateway: false,
20088
20832
  codex: false
@@ -20104,6 +20848,7 @@ async function probeRemoteHostFeatures(client, clientVersion) {
20104
20848
  const apiProxy = capabilities.has("harness.api.v1");
20105
20849
  const remoteV1 = capabilities.has("harness.remote.v1");
20106
20850
  const remoteV3 = capabilities.has("harness.remote.v3");
20851
+ const terminal = capabilities.has("harness.terminal.v1");
20107
20852
  const codex = capabilities.has("codex.appserver.v1");
20108
20853
  if (remoteV1 && remoteV3) {
20109
20854
  throw new ClientModeError("INVALID_MESSAGE", "The remote Host advertised conflicting Harness Session formats.");
@@ -20116,6 +20861,7 @@ async function probeRemoteHostFeatures(client, clientVersion) {
20116
20861
  return {
20117
20862
  commandList: remoteGateway || apiProxy && fallback.commandList,
20118
20863
  fileViewer: capabilities.has("fileviewer.read.v1"),
20864
+ terminal,
20119
20865
  apiProxy,
20120
20866
  remoteGateway,
20121
20867
  ...sessionFormat === void 0 ? {} : { sessionFormat },
@@ -20182,12 +20928,13 @@ function iceServersForAttempt(attempt, iceServers) {
20182
20928
 
20183
20929
  // src/control-runtime.ts
20184
20930
  import { hostname as hostname2 } from "node:os";
20931
+ import { execFileSync } from "node:child_process";
20185
20932
 
20186
20933
  // src/identity-store.ts
20187
20934
  import { createHash } from "node:crypto";
20188
- 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";
20189
20936
  import { homedir } from "node:os";
20190
- import { dirname as dirname3, join as join3 } from "node:path";
20937
+ import { dirname as dirname4, join as join4 } from "node:path";
20191
20938
  var identitySchema = external_exports.object({
20192
20939
  schemaVersion: external_exports.literal(1),
20193
20940
  deviceId: external_exports.string().uuid(),
@@ -20216,15 +20963,15 @@ var IdentityStore = class {
20216
20963
  peers = /* @__PURE__ */ new Map();
20217
20964
  constructor(options = {}) {
20218
20965
  const env = options.env ?? process.env;
20219
- const dshHome = env.DSH_HOME || join3(options.homeDirectory ?? homedir(), ".dsh");
20220
- 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");
20221
20968
  }
20222
20969
  async loadOrCreate(deviceName) {
20223
- await mkdir(this.directory, { recursive: true, mode: 448 });
20224
- await chmod(this.directory, 448);
20225
- const devicePath = join3(this.directory, "device.json");
20226
- const keyPath = join3(this.directory, "device.key");
20227
- 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)]);
20228
20975
  if (hasDevice !== hasKey) {
20229
20976
  throw new IdentityInvalidError("device identity is incomplete; repair it explicitly before reconnecting");
20230
20977
  }
@@ -20235,10 +20982,10 @@ var IdentityStore = class {
20235
20982
  await atomicTextWrite(keyPath, `${keys.privateKey}
20236
20983
  `, 384);
20237
20984
  }
20238
- await assertPrivateMode(keyPath);
20985
+ await assertPrivateMode2(keyPath);
20239
20986
  try {
20240
- let record6 = identitySchema.parse(JSON.parse(await readFile2(devicePath, "utf8")));
20241
- 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();
20242
20989
  const regenerated = generateKeyPair(fromBase64Url2(privateKey));
20243
20990
  if (regenerated.publicKey !== record6.publicKey) {
20244
20991
  throw new IdentityInvalidError("device public and private keys do not match");
@@ -20260,7 +21007,7 @@ var IdentityStore = class {
20260
21007
  return this.identity;
20261
21008
  }
20262
21009
  async reset(deviceName) {
20263
- await rm(this.directory, { recursive: true, force: true });
21010
+ await rm2(this.directory, { recursive: true, force: true });
20264
21011
  this.identity = void 0;
20265
21012
  this.peers.clear();
20266
21013
  return this.loadOrCreate(deviceName);
@@ -20292,11 +21039,11 @@ var IdentityStore = class {
20292
21039
  return removed;
20293
21040
  }
20294
21041
  async loadPeers() {
20295
- const path = join3(this.directory, "trusted-peers.json");
20296
- if (!await exists(path)) {
21042
+ const path = join4(this.directory, "trusted-peers.json");
21043
+ if (!await exists2(path)) {
20297
21044
  await atomicJsonWrite(path, { schemaVersion: 1, peers: [] }, 384);
20298
21045
  }
20299
- const parsed = trustedPeersSchema.parse(JSON.parse(await readFile2(path, "utf8")));
21046
+ const parsed = trustedPeersSchema.parse(JSON.parse(await readFile3(path, "utf8")));
20300
21047
  const peers = /* @__PURE__ */ new Map();
20301
21048
  for (const peer of parsed.peers) {
20302
21049
  if (peer.fingerprint !== fingerprint(peer.publicKey)) {
@@ -20308,7 +21055,7 @@ var IdentityStore = class {
20308
21055
  this.peers = peers;
20309
21056
  }
20310
21057
  async savePeers() {
20311
- await atomicJsonWrite(join3(this.directory, "trusted-peers.json"), {
21058
+ await atomicJsonWrite(join4(this.directory, "trusted-peers.json"), {
20312
21059
  schemaVersion: 1,
20313
21060
  peers: [...this.peers.values()]
20314
21061
  }, 384);
@@ -20317,15 +21064,15 @@ var IdentityStore = class {
20317
21064
  function serverStorageDirectory(root, serverUrl, role) {
20318
21065
  const origin = new URL(serverUrl).origin;
20319
21066
  const scope = createHash("sha256").update(origin).digest("hex").slice(0, 24);
20320
- return join3(root, "servers", scope, role);
21067
+ return join4(root, "servers", scope, role);
20321
21068
  }
20322
21069
  function fingerprint(publicKey) {
20323
21070
  const compact = createHash("sha256").update(fromBase64Url2(publicKey)).digest("hex").slice(0, 12).toUpperCase();
20324
21071
  return compact.match(/.{1,4}/g).join(" ");
20325
21072
  }
20326
- async function assertPrivateMode(path) {
21073
+ async function assertPrivateMode2(path) {
20327
21074
  if (process.platform === "win32") return;
20328
- const mode = (await stat(path)).mode & 511;
21075
+ const mode = (await stat2(path)).mode & 511;
20329
21076
  if ((mode & 63) !== 0) {
20330
21077
  throw new IdentityInvalidError(`private key permissions must be 0600, got ${mode.toString(8).padStart(3, "0")}`);
20331
21078
  }
@@ -20335,16 +21082,16 @@ async function atomicJsonWrite(path, value, mode) {
20335
21082
  `, mode);
20336
21083
  }
20337
21084
  async function atomicTextWrite(path, value, mode) {
20338
- await mkdir(dirname3(path), { recursive: true, mode: 448 });
21085
+ await mkdir2(dirname4(path), { recursive: true, mode: 448 });
20339
21086
  const temporary = `${path}.${process.pid}.${uuidV7()}.tmp`;
20340
- await writeFile(temporary, value, { encoding: "utf8", mode, flag: "wx" });
20341
- await chmod(temporary, mode);
20342
- await rename(temporary, path);
20343
- 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);
20344
21091
  }
20345
- async function exists(path) {
21092
+ async function exists2(path) {
20346
21093
  try {
20347
- await stat(path);
21094
+ await stat2(path);
20348
21095
  return true;
20349
21096
  } catch (error) {
20350
21097
  if (isNodeError(error) && error.code === "ENOENT") return false;
@@ -20358,74 +21105,148 @@ function safeErrorMessage(error) {
20358
21105
  return error instanceof Error ? error.message : "invalid identity data";
20359
21106
  }
20360
21107
 
20361
- // src/server-credentials.ts
20362
- 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";
20363
- import { dirname as dirname4, join as join4 } from "node:path";
20364
- var credentialSchema = external_exports.object({
20365
- schemaVersion: external_exports.literal(1),
20366
- serverUrl: external_exports.string().url(),
20367
- deviceId: external_exports.string().min(1),
20368
- authorizationMethod: external_exports.enum(["account", "host_registration_code", "owned_device"]),
20369
- account: external_exports.string().min(1).max(254).optional(),
20370
- accessToken: external_exports.string().min(16),
20371
- accessTokenExpiresAt: external_exports.number().int().positive(),
20372
- refreshToken: external_exports.string().min(16),
20373
- refreshTokenExpiresAt: external_exports.number().int().positive()
20374
- }).strict();
20375
- var ServerCredentialStore = class {
20376
- path;
20377
- constructor(directory) {
20378
- 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);
20379
21165
  }
20380
- async load(serverUrl, deviceId) {
20381
- if (!await exists2(this.path)) return void 0;
20382
- await assertPrivateMode2(this.path);
20383
- 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;
20384
21198
  try {
20385
- parsed = credentialSchema.parse(JSON.parse(await readFile3(this.path, "utf8")));
20386
- } catch (error) {
20387
- 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;
20388
21203
  }
20389
- return parsed.serverUrl === serverUrl && parsed.deviceId === deviceId ? parsed : void 0;
20390
21204
  }
20391
- async save(credentials) {
20392
- const record6 = credentialSchema.parse({ schemaVersion: 1, ...credentials });
20393
- await atomicWrite(this.path, `${JSON.stringify(record6, null, 2)}
20394
- `);
20395
- 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
+ }
20396
21212
  }
20397
- async clear() {
20398
- 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;
20399
21238
  }
20400
- };
20401
- var ServerCredentialsInvalidError = class extends Error {
20402
- code = "SERVER_CREDENTIALS_INVALID";
20403
- };
20404
- async function atomicWrite(path, contents) {
20405
- await mkdir2(dirname4(path), { recursive: true, mode: 448 });
20406
- const temporary = `${path}.${process.pid}.${uuidV7()}.tmp`;
20407
- await writeFile2(temporary, contents, { encoding: "utf8", mode: 384, flag: "wx" });
20408
- await chmod2(temporary, 384);
20409
- await rename2(temporary, path);
20410
- await chmod2(path, 384);
20411
- }
20412
- async function assertPrivateMode2(path) {
20413
- if (process.platform === "win32") return;
20414
- const mode = (await stat2(path)).mode & 511;
20415
- if ((mode & 63) !== 0) throw new ServerCredentialsInvalidError("server credentials permissions must be 0600");
20416
- }
20417
- async function exists2(path) {
20418
- try {
20419
- await stat2(path);
20420
- return true;
20421
- } catch (error) {
20422
- if (error instanceof Error && "code" in error && error.code === "ENOENT") return false;
20423
- 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
+ }
20424
21248
  }
20425
- }
20426
- function safeMessage(error) {
20427
- return error instanceof Error ? error.message : "invalid credential data";
20428
- }
21249
+ };
20429
21250
 
20430
21251
  // src/control-runtime.ts
20431
21252
  var PluginControlRuntime = class {
@@ -20437,15 +21258,35 @@ var PluginControlRuntime = class {
20437
21258
  this.host = host;
20438
21259
  }
20439
21260
  register(connection, webServer) {
20440
- 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;
20441
21278
  }
20442
21279
  async handle(endpoint, payload, signal) {
20443
21280
  try {
21281
+ if (endpoint === "settings.development.set") return ok3(await this.setDevelopment(payload));
20444
21282
  if (endpoint === "settings.get") return ok3(await this.settingsView());
20445
21283
  if (endpoint === "settings.configure") return ok3(await this.configure(payload));
20446
21284
  if (endpoint === "settings.server.set") return ok3(await this.setServer(payload));
20447
21285
  if (endpoint === "settings.role.set") return ok3(await this.setRole(payload));
20448
21286
  if (endpoint === "settings.codex.set") return ok3(await this.setCodex(payload));
21287
+ if (endpoint === "settings.acp.set") return ok3(await this.setAcp(payload));
21288
+ if (endpoint === "settings.acp.add") return ok3(await this.addAcp(payload));
21289
+ if (endpoint === "settings.acp.remove") return ok3(await this.removeAcp(payload));
20449
21290
  if (endpoint === "settings.logout") return ok3(await this.logout());
20450
21291
  if (endpoint === "host.reconnect") {
20451
21292
  if (this.host === void 0) throw new ClientModeError("METHOD_NOT_ALLOWED", "This plugin is not running as a Host.");
@@ -20541,6 +21382,23 @@ var PluginControlRuntime = class {
20541
21382
  await this.settings.replace({ ...current, role });
20542
21383
  return this.settingsView();
20543
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
+ }
20544
21402
  async setCodex(payload) {
20545
21403
  if (this.settings === void 0) {
20546
21404
  throw new ClientModeError("SETTINGS_UNAVAILABLE", "DSH user settings are unavailable in this profile.");
@@ -20557,6 +21415,37 @@ var PluginControlRuntime = class {
20557
21415
  await this.settings.replace(editableConfig(next));
20558
21416
  return this.settingsView();
20559
21417
  }
21418
+ async setAcp(payload) {
21419
+ if (this.settings === void 0) throw new ClientModeError("SETTINGS_UNAVAILABLE", "DSH user settings are unavailable in this profile.");
21420
+ const value = record4(payload);
21421
+ if (typeof value.backend !== "string" || typeof value.enabled !== "boolean") throw new ClientModeError("INVALID_MESSAGE", "ACP backend and enabled are required.");
21422
+ const current = resolveConfig(this.settings.get());
21423
+ const backends = current.acp?.backends.map((item) => item.id === value.backend ? { ...item, enabled: value.enabled } : item) ?? [];
21424
+ await this.settings.replace({ ...editableConfig(current), acp: { enabled: current.acp?.enabled ?? true, backends } });
21425
+ return this.settingsView();
21426
+ }
21427
+ async addAcp(payload) {
21428
+ if (this.settings === void 0) throw new ClientModeError("SETTINGS_UNAVAILABLE", "DSH user settings are unavailable in this profile.");
21429
+ const value = record4(payload);
21430
+ if (typeof value.id !== "string" || typeof value.command !== "string" || !Array.isArray(value.args) || !value.args.every((item) => typeof item === "string")) {
21431
+ throw new ClientModeError("INVALID_MESSAGE", "ACP name, command, and arguments are required.");
21432
+ }
21433
+ const current = resolveConfig(this.settings.get());
21434
+ if (current.acp?.backends.some((item) => item.id === value.id)) throw new ClientModeError("INVALID_MESSAGE", "ACP backend already exists.");
21435
+ const backends = [...current.acp?.backends ?? [], { id: value.id, command: value.command, args: value.args, enabled: false }];
21436
+ const next = resolveConfig({ ...editableConfig(current), acp: { enabled: current.acp?.enabled ?? true, backends } });
21437
+ await this.settings.replace(editableConfig(next));
21438
+ return this.settingsView();
21439
+ }
21440
+ async removeAcp(payload) {
21441
+ if (this.settings === void 0) throw new ClientModeError("SETTINGS_UNAVAILABLE", "DSH user settings are unavailable in this profile.");
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.");
21444
+ const current = resolveConfig(this.settings.get());
21445
+ const backends = (current.acp?.backends ?? []).filter((item) => item.id !== id4);
21446
+ await this.settings.replace(editableConfig({ ...current, acp: { enabled: current.acp?.enabled ?? true, backends } }));
21447
+ return this.settingsView();
21448
+ }
20560
21449
  async authorizeOwnedRole(serverUrl, sourceRole, targetRole) {
20561
21450
  const sourceDirectory = serverStorageDirectory(this.identityDirectory, serverUrl, sourceRole);
20562
21451
  const sourceIdentity = await new IdentityStore({ directory: sourceDirectory }).loadOrCreate(hostname2());
@@ -20597,6 +21486,7 @@ var PluginControlRuntime = class {
20597
21486
  writable: this.settings !== void 0,
20598
21487
  applies: "restart",
20599
21488
  associations,
21489
+ acpAvailability: Object.fromEntries((config.acp?.backends ?? []).map((item) => [item.id, commandAvailable(item.command ?? "")])),
20600
21490
  ...association === void 0 ? {} : { association }
20601
21491
  };
20602
21492
  }
@@ -20633,11 +21523,21 @@ var PluginControlRuntime = class {
20633
21523
  };
20634
21524
  }
20635
21525
  };
21526
+ function commandAvailable(command) {
21527
+ try {
21528
+ execFileSync(process.platform === "win32" ? "where" : "which", [command], { stdio: "ignore" });
21529
+ return true;
21530
+ } catch {
21531
+ return false;
21532
+ }
21533
+ }
20636
21534
  function editableConfig(config) {
20637
21535
  return {
20638
21536
  enabled: config.enabled,
20639
21537
  role: config.role,
20640
21538
  ...config.serverUrl === void 0 ? {} : { serverUrl: config.serverUrl },
21539
+ terminal: config.terminal,
21540
+ loopback: config.loopback,
20641
21541
  forceRelay: config.forceRelay,
20642
21542
  logLevel: config.logLevel,
20643
21543
  reconnect: config.reconnect.enabled ? {
@@ -20648,7 +21548,8 @@ function editableConfig(config) {
20648
21548
  codex: {
20649
21549
  enabled: config.codex.enabled,
20650
21550
  binary: config.codex.binary
20651
- }
21551
+ },
21552
+ ...config.acp === void 0 ? {} : { acp: { enabled: config.acp.enabled, backends: config.acp.backends } }
20652
21553
  };
20653
21554
  }
20654
21555
  function isRecord8(value) {
@@ -20707,8 +21608,70 @@ function redact(value, key = "") {
20707
21608
  return Object.fromEntries(Object.entries(value).map(([childKey, child]) => [childKey, redact(child, childKey)]));
20708
21609
  }
20709
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
+
20710
21673
  // src/service.ts
20711
- import { randomUUID as randomUUID2 } from "node:crypto";
21674
+ import { randomUUID as randomUUID3 } from "node:crypto";
20712
21675
 
20713
21676
  // src/connection-controller.ts
20714
21677
  var ConnectionController = class {
@@ -20779,7 +21742,21 @@ var ConnectionController = class {
20779
21742
  return this.active.size;
20780
21743
  }
20781
21744
  peerDeviceIds() {
20782
- return [...new Set([...this.active.values()].map((connection) => connection.channel.peerDeviceId))];
21745
+ return this.connectedPeers().map((peer) => peer.deviceId);
21746
+ }
21747
+ connectedPeers() {
21748
+ const peers = [];
21749
+ const seen = /* @__PURE__ */ new Set();
21750
+ for (const connection of this.active.values()) {
21751
+ const deviceId = connection.channel.peerDeviceId;
21752
+ if (seen.has(deviceId)) continue;
21753
+ seen.add(deviceId);
21754
+ peers.push({
21755
+ deviceId,
21756
+ ...connection.channel.mode === void 0 ? {} : { mode: connection.channel.mode }
21757
+ });
21758
+ }
21759
+ return peers;
20783
21760
  }
20784
21761
  peerDeviceId() {
20785
21762
  const peers = this.peerDeviceIds();
@@ -20878,21 +21855,6 @@ function shortId2(value) {
20878
21855
  return value.length <= 12 ? value : `${value.slice(0, 8)}\u2026${value.slice(-4)}`;
20879
21856
  }
20880
21857
 
20881
- // src/safe-error.ts
20882
- var RpcError = class extends Error {
20883
- constructor(code, message, details, retryable = false) {
20884
- super(message);
20885
- this.code = code;
20886
- this.details = details;
20887
- this.retryable = retryable;
20888
- }
20889
- };
20890
- function safeErrorCode(error) {
20891
- if (error instanceof RpcError) return error.code;
20892
- if (error instanceof external_exports.ZodError) return "INVALID_MESSAGE";
20893
- return "INTERNAL_ERROR";
20894
- }
20895
-
20896
21858
  // src/rpc-router.ts
20897
21859
  var wireRequestSchema = external_exports.object({ method: external_exports.string().min(1), params: external_exports.unknown() }).strict();
20898
21860
  var emptyParamsSchema = external_exports.object({}).strict();
@@ -20915,6 +21877,7 @@ var apiMethods = /* @__PURE__ */ new Set([
20915
21877
  "harness.remote.transfer.close",
20916
21878
  "harness.remote.stream.open",
20917
21879
  "harness.remote.stream.close",
21880
+ "loopback.call",
20918
21881
  "fileviewer.call",
20919
21882
  "codex.app.call",
20920
21883
  "codex.app.respond",
@@ -20924,7 +21887,14 @@ var apiMethods = /* @__PURE__ */ new Set([
20924
21887
  "codex.app.transfer.chunk",
20925
21888
  "codex.app.transfer.commit",
20926
21889
  "codex.app.transfer.read",
20927
- "codex.app.transfer.close"
21890
+ "codex.app.transfer.close",
21891
+ "acp.initialize",
21892
+ "acp.session.new",
21893
+ "acp.session.load",
21894
+ "acp.session.prompt",
21895
+ "acp.session.respond_permission",
21896
+ "acp.session.cancel",
21897
+ "acp.session.set_mode"
20928
21898
  ]);
20929
21899
  var HOST_CAPABILITIES = [
20930
21900
  "harness.api.v1",
@@ -20933,10 +21903,11 @@ var HOST_CAPABILITIES = [
20933
21903
  "harness.remote.transfer.v1",
20934
21904
  "fileviewer.read.v1",
20935
21905
  "codex.appserver.v1",
20936
- "codex.appserver.transfer.v1"
21906
+ "codex.appserver.transfer.v1",
21907
+ "agent.acp.v1"
20937
21908
  ];
20938
21909
  var RpcRouter = class {
20939
- constructor(harnessApi, maxPending = 128, logger, fileViewer, harnessRemote, capabilities = () => HOST_CAPABILITIES, codex) {
21910
+ constructor(harnessApi, maxPending = 128, logger, fileViewer, harnessRemote, capabilities = () => HOST_CAPABILITIES, codex, acp, loopback) {
20940
21911
  this.harnessApi = harnessApi;
20941
21912
  this.maxPending = maxPending;
20942
21913
  this.logger = logger;
@@ -20944,9 +21915,12 @@ var RpcRouter = class {
20944
21915
  this.harnessRemote = harnessRemote;
20945
21916
  this.capabilities = capabilities;
20946
21917
  this.codex = codex;
21918
+ this.acp = acp;
21919
+ this.loopback = loopback;
20947
21920
  }
20948
21921
  active = 0;
20949
21922
  async closePeerStreams() {
21923
+ this.loopback?.closeAll();
20950
21924
  await Promise.all([
20951
21925
  this.harnessApi?.closeAll(),
20952
21926
  this.harnessRemote?.closeAll(),
@@ -21028,6 +22002,10 @@ var RpcRouter = class {
21028
22002
  return this.requireRemoteGateway().openStream(params);
21029
22003
  case "harness.remote.stream.close":
21030
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
+ }
21031
22009
  case "fileviewer.call": {
21032
22010
  if (this.fileViewer === void 0) {
21033
22011
  throw new RpcError("FILE_VIEWER_UNAVAILABLE", "The Remote Host does not have DSH File Viewer available.");
@@ -21052,6 +22030,20 @@ var RpcRouter = class {
21052
22030
  return this.requireCodex().readTransfer(params);
21053
22031
  case "codex.app.transfer.close":
21054
22032
  return this.requireCodex().closeTransfer(params);
22033
+ case "acp.initialize":
22034
+ return this.requireAcp().initialize(params);
22035
+ case "acp.session.new":
22036
+ return this.requireAcp().sessionNew(params);
22037
+ case "acp.session.load":
22038
+ return this.requireAcp().sessionLoad(params);
22039
+ case "acp.session.prompt":
22040
+ return this.requireAcp().prompt(params, async () => void 0);
22041
+ case "acp.session.respond_permission":
22042
+ return this.requireAcp().respondPermission(params);
22043
+ case "acp.session.cancel":
22044
+ return this.requireAcp().cancel(params);
22045
+ case "acp.session.set_mode":
22046
+ return this.requireAcp().setMode(params);
21055
22047
  default:
21056
22048
  throw new RpcError("METHOD_NOT_FOUND", "The requested method does not exist.");
21057
22049
  }
@@ -21068,6 +22060,10 @@ var RpcRouter = class {
21068
22060
  }
21069
22061
  return this.harnessRemote;
21070
22062
  }
22063
+ requireAcp() {
22064
+ if (!this.acp) throw new RpcError("CAPABILITY_NOT_SUPPORTED", "ACP is not configured on this Host.");
22065
+ return this.acp;
22066
+ }
21071
22067
  requireCodex() {
21072
22068
  if (this.codex === void 0) {
21073
22069
  throw new RpcError("FEATURE_NOT_SUPPORTED", "Codex Remote is disabled or unavailable on this Host.");
@@ -21230,6 +22226,7 @@ var HostServerConnection = class {
21230
22226
  lastActiveAt;
21231
22227
  reconnectRequested = false;
21232
22228
  resumeQueued = false;
22229
+ authRecoveryAttempted = false;
21233
22230
  rtcFactory;
21234
22231
  negotiatedCapabilities = ["transport.relay"];
21235
22232
  controlFrameLimits = {};
@@ -21293,14 +22290,23 @@ var HostServerConnection = class {
21293
22290
  }
21294
22291
  async run() {
21295
22292
  let delayMs = this.config.reconnect.initialDelayMs;
22293
+ this.authRecoveryAttempted = false;
21296
22294
  while (!this.stopped) {
21297
22295
  try {
21298
22296
  await this.connectOnce();
21299
22297
  delayMs = this.config.reconnect.initialDelayMs;
21300
22298
  } catch (error) {
21301
22299
  const code = errorCode2(error);
22300
+ if (code === "CREDENTIALS_REFRESHED") continue;
21302
22301
  this.terminalError = code;
21303
- 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
+ }
21304
22310
  if (TERMINAL_AUTH_ERRORS.has(code) || !this.config.reconnect.enabled) return;
21305
22311
  }
21306
22312
  if (this.stopped) return;
@@ -21356,6 +22362,7 @@ var HostServerConnection = class {
21356
22362
  this.lastActiveAt = Date.now();
21357
22363
  if (frame.type === "hello.ack") {
21358
22364
  const payload = requireHelloAck(frame.payload);
22365
+ this.authRecoveryAttempted = false;
21359
22366
  this.controlFrameLimits = {
21360
22367
  maxControlFrameBytes: payload.maxControlFrameBytes,
21361
22368
  maxRelayFrameBytes: payload.maxRelayFrameBytes
@@ -21392,13 +22399,28 @@ var HostServerConnection = class {
21392
22399
  socket.onclose = (event) => {
21393
22400
  const close = async () => {
21394
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
+ }
21395
22410
  if (event.code === 4002) {
22411
+ if (this.authRecoveryAttempted) {
22412
+ finish(new ControlConnectionError("AUTH_INVALID", "Server rejected refreshed credentials."));
22413
+ return;
22414
+ }
21396
22415
  try {
21397
- await this.api.refreshCredentials();
22416
+ await this.api.refreshCredentials(credentials.accessToken);
21398
22417
  } catch (error) {
21399
22418
  finish(asError2(error));
21400
22419
  return;
21401
22420
  }
22421
+ this.authRecoveryAttempted = true;
22422
+ finish(new ControlConnectionError("CREDENTIALS_REFRESHED", "Retry hello with refreshed credentials."));
22423
+ return;
21402
22424
  }
21403
22425
  if (event.code === 4004) {
21404
22426
  finish(new ControlConnectionError("DEVICE_REVOKED", "The Server revoked this Host device."));
@@ -21646,7 +22668,7 @@ var HostServerConnection = class {
21646
22668
  }
21647
22669
  });
21648
22670
  tunnel.rtc = rtc;
21649
- rtc.onMessage((data) => tunnel.channel?.receive(void 0, data));
22671
+ rtc.onMessage((data2) => tunnel.channel?.receive(void 0, data2));
21650
22672
  rtc.onClose(() => {
21651
22673
  void this.handleRtcFailed(tunnel, rtc, new Error("WebRTC data channel closed."));
21652
22674
  });
@@ -21803,6 +22825,10 @@ var HostServerConnection = class {
21803
22825
  async sendRelay(tunnel, ciphertext) {
21804
22826
  const counter = Number(tunnel.noise.sendingCounter() - 1n);
21805
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");
21806
22832
  this.sendControl("relay", {
21807
22833
  connectionId: tunnel.connectionId,
21808
22834
  targetDeviceId: tunnel.peer.deviceId,
@@ -21868,6 +22894,8 @@ var HostServerConnection = class {
21868
22894
  }
21869
22895
  };
21870
22896
  var TERMINAL_AUTH_ERRORS = /* @__PURE__ */ new Set([
22897
+ "CONNECTION_REPLACED",
22898
+ "SERVER_CREDENTIALS_BUSY",
21871
22899
  "ACCOUNT_AUTH_REQUIRED",
21872
22900
  "AUTH_INVALID",
21873
22901
  "DEVICE_OWNERSHIP_REQUIRED",
@@ -21888,6 +22916,7 @@ var ServerNoiseChannel = class {
21888
22916
  this.peerDeviceId = tunnel.peer.deviceId;
21889
22917
  this.peerIdentityKey = tunnel.peer.publicKey;
21890
22918
  }
22919
+ sends = new SerialSend();
21891
22920
  security;
21892
22921
  peerDeviceId;
21893
22922
  peerIdentityKey;
@@ -21898,11 +22927,12 @@ var ServerNoiseChannel = class {
21898
22927
  closed = false;
21899
22928
  async send(message) {
21900
22929
  if (this.closed) throw new Error("secure channel is closed");
21901
- const plaintextFrames = this.outgoing.encode(encodeMessage(message));
22930
+ const encoded = encodeMessage(message);
21902
22931
  try {
21903
- for (const plaintext of plaintextFrames) {
21904
- await this.transmit(this.tunnel.noise.encrypt(plaintext));
21905
- }
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
+ });
21906
22936
  } catch (error) {
21907
22937
  await this.close().catch(() => void 0);
21908
22938
  throw error;
@@ -21947,10 +22977,10 @@ function websocketUrl2(baseUrl) {
21947
22977
  url.pathname = `${url.pathname.replace(/\/$/, "")}/ws/v1/connect`;
21948
22978
  return url.toString();
21949
22979
  }
21950
- function decodeControl(data, limits) {
21951
- 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.");
21952
22982
  try {
21953
- return decodeControlFrame(data, limits);
22983
+ return decodeControlFrame(data2, limits);
21954
22984
  } catch {
21955
22985
  throw new ControlConnectionError("INVALID_MESSAGE", "Server sent an invalid control frame.");
21956
22986
  }
@@ -22056,10 +23086,11 @@ function errorCode2(error) {
22056
23086
  return error instanceof ServerApiError || error instanceof ControlConnectionError ? error.code : "CONNECTION_FAILED";
22057
23087
  }
22058
23088
  function isRetryable(error) {
22059
- return error instanceof ServerApiError ? error.retryable : errorCode2(error) !== "DEVICE_REVOKED";
23089
+ return !TERMINAL_AUTH_ERRORS.has(errorCode2(error)) && (!(error instanceof ServerApiError) || error.retryable);
22060
23090
  }
22061
23091
  function closeCode(code) {
22062
23092
  if (code === 4002) return "AUTH_INVALID";
23093
+ if (code === 4003) return "CONNECTION_REPLACED";
22063
23094
  if (code === 4004) return "DEVICE_REVOKED";
22064
23095
  if (code === 4007) return "RATE_LIMITED";
22065
23096
  if (code === 4011) return "UNSUPPORTED_VERSION";
@@ -22074,13 +23105,13 @@ var MAX_ENTRIES = 500;
22074
23105
  async function listRemoteDirectory(path, signal) {
22075
23106
  signal?.throwIfAborted();
22076
23107
  const home = resolve(homedir2());
22077
- const target = path === void 0 || path.trim() === "" ? home : resolve(path);
22078
- if (!isAbsolute2(target)) throw new Error("The remote directory path must be absolute.");
22079
- 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 });
22080
23111
  const directories = [];
22081
23112
  for (const row of rows) {
22082
23113
  signal?.throwIfAborted();
22083
- const child = resolve(target, row.name);
23114
+ const child = resolve(target2, row.name);
22084
23115
  let directory = row.isDirectory();
22085
23116
  if (!directory && row.isSymbolicLink()) directory = await stat3(child).then((value) => value.isDirectory()).catch(() => false);
22086
23117
  if (!directory) continue;
@@ -22088,9 +23119,9 @@ async function listRemoteDirectory(path, signal) {
22088
23119
  }
22089
23120
  directories.sort((left, right) => left.name.localeCompare(right.name, void 0, { sensitivity: "base" }));
22090
23121
  return {
22091
- path: target,
23122
+ path: target2,
22092
23123
  home,
22093
- crumbs: crumbs(target),
23124
+ crumbs: crumbs(target2),
22094
23125
  entries: directories.slice(0, MAX_ENTRIES),
22095
23126
  truncated: directories.length > MAX_ENTRIES
22096
23127
  };
@@ -22154,11 +23185,22 @@ var transferCloseSchema = external_exports.object({ transferId: transferIdSchema
22154
23185
  var commandExecuteSchema = external_exports.object({
22155
23186
  agentId: external_exports.string().min(1).max(128),
22156
23187
  line: external_exports.string().min(1).max(2048),
22157
- // The official Harness command endpoint requires the `images` wire field.
22158
- // Remote command execution keeps attachments out of scope, so only an empty
22159
- // list is accepted and legacy clients that omit it are normalized below.
22160
- images: external_exports.array(external_exports.never()).length(0).optional()
22161
- }).strict();
23188
+ // dsh-commands <= 0.1.2 calls this field `images`; dsh-commands 0.1.5
23189
+ // renamed it to `submittedAttachments` and expanded its element type to
23190
+ // include staged file receipts. Remote command execution keeps attachments
23191
+ // out of scope, so either compatibility field is restricted to an empty
23192
+ // list. The dispatcher below chooses the matching Host descriptor.
23193
+ images: external_exports.array(external_exports.never()).length(0).optional(),
23194
+ submittedAttachments: external_exports.array(external_exports.never()).length(0).optional()
23195
+ }).strict().superRefine((value, context) => {
23196
+ if (value.images !== void 0 && value.submittedAttachments !== void 0) {
23197
+ context.addIssue({
23198
+ code: external_exports.ZodIssueCode.custom,
23199
+ message: "Use either images or submittedAttachments, not both.",
23200
+ path: ["submittedAttachments"]
23201
+ });
23202
+ }
23203
+ });
22162
23204
  var commandListSchema = external_exports.object({
22163
23205
  agentId: external_exports.string().min(1).max(128)
22164
23206
  }).strict();
@@ -22279,7 +23321,7 @@ var HarnessApiBridge = class {
22279
23321
  this.maxStreams = maxStreams;
22280
23322
  this.logger = logger;
22281
23323
  this.harnessVersion = harnessVersion;
22282
- this.methods = createMethodMap(api, typertGateway);
23324
+ this.methods = createMethodMap(api, typertGateway, harnessVersion);
22283
23325
  this.mux = api.events.mux.bind(api.events);
22284
23326
  this.host = api.events.host.bind(api.events);
22285
23327
  this.answer = api.respond.bind(api);
@@ -22600,7 +23642,7 @@ function normalizeHostDescribe(response, harnessVersion, canListDirectory) {
22600
23642
  }
22601
23643
  };
22602
23644
  }
22603
- function createMethodMap(api, typertGateway) {
23645
+ function createMethodMap(api, typertGateway, harnessVersion) {
22604
23646
  const domains = api;
22605
23647
  const methods = /* @__PURE__ */ new Map();
22606
23648
  for (const method of HARNESS_API_ALLOWLIST) {
@@ -22610,7 +23652,17 @@ function createMethodMap(api, typertGateway) {
22610
23652
  const implementation2 = async (request, signal) => {
22611
23653
  if (commandMethod === "execute") {
22612
23654
  const payload = commandExecuteSchema.parse(request.payload);
22613
- const args2 = { ...payload, images: payload.images ?? [] };
23655
+ const legacyArgs = {
23656
+ agentId: payload.agentId,
23657
+ line: payload.line,
23658
+ images: payload.images ?? []
23659
+ };
23660
+ const currentArgs = {
23661
+ agentId: payload.agentId,
23662
+ line: payload.line,
23663
+ submittedAttachments: payload.submittedAttachments ?? []
23664
+ };
23665
+ const args2 = harnessSessionGeneration(harnessVersion) === "v3" ? currentArgs : legacyArgs;
22614
23666
  const value2 = await typertGateway.invoke({
22615
23667
  namespace,
22616
23668
  method: "execute",
@@ -22776,6 +23828,9 @@ function disableRemoteSettingsDocument(response) {
22776
23828
  }
22777
23829
  var EMPTY_SETTINGS_NAMESPACES = /* @__PURE__ */ new Set();
22778
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
+ }
22779
23834
  const allowed = await registeredSettingsNamespaces(api);
22780
23835
  if (!allowed.has(ns)) throw deniedSettingsNamespace(ns);
22781
23836
  }
@@ -22912,6 +23967,24 @@ var streamOpenSchema2 = external_exports.object({
22912
23967
  payload: external_exports.unknown()
22913
23968
  }).strict();
22914
23969
  var streamCloseSchema2 = external_exports.object({ streamId: external_exports.string().min(1).max(128) }).strict();
23970
+ var commandExecuteArgsSchema = external_exports.object({
23971
+ agentId: external_exports.string().min(1).max(128),
23972
+ line: external_exports.string().min(1).max(2048),
23973
+ // dsh-commands <= 0.1.2 calls this field `images`; dsh-commands 0.1.5
23974
+ // renamed it to `submittedAttachments`. Only empty arrays are exposed by
23975
+ // the Remote bridge; command attachments are deliberately out of scope.
23976
+ images: external_exports.array(external_exports.never()).length(0).optional(),
23977
+ submittedAttachments: external_exports.array(external_exports.never()).length(0).optional()
23978
+ }).strict().superRefine((value, context) => {
23979
+ if (value.images !== void 0 && value.submittedAttachments !== void 0) {
23980
+ context.addIssue({
23981
+ code: external_exports.ZodIssueCode.custom,
23982
+ message: "Use either images or submittedAttachments, not both.",
23983
+ path: ["submittedAttachments"]
23984
+ });
23985
+ }
23986
+ });
23987
+ var commandExecutePayloadSchema = external_exports.object({ args: commandExecuteArgsSchema }).strict();
22915
23988
  var transferOpenSchema2 = external_exports.object({
22916
23989
  transferId: external_exports.string().uuid(),
22917
23990
  totalBytes: external_exports.number().int().positive().max(MAX_HARNESS_API_TRANSFER_BYTES),
@@ -22983,6 +24056,15 @@ var HARNESS_REMOTE_ALLOWLIST = [
22983
24056
  "subagents/interruptByParent",
22984
24057
  "subagents/list",
22985
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",
22986
24068
  "workspace/archiveSession",
22987
24069
  "workspace/create",
22988
24070
  "workspace/delete",
@@ -22993,10 +24075,12 @@ var HARNESS_REMOTE_ALLOWLIST = [
22993
24075
  ];
22994
24076
  var allowedEndpoints = new Set(HARNESS_REMOTE_ALLOWLIST);
22995
24077
  var HarnessRemoteBridge = class {
22996
- constructor(gateway, publish, logger) {
24078
+ constructor(gateway, publish, logger, harnessVersion, terminal = new TerminalPolicy(() => false, "", /* @__PURE__ */ new Map())) {
22997
24079
  this.gateway = gateway;
22998
24080
  this.publish = publish;
22999
24081
  this.logger = logger;
24082
+ this.harnessVersion = harnessVersion;
24083
+ this.terminal = terminal;
23000
24084
  }
23001
24085
  streams = /* @__PURE__ */ new Map();
23002
24086
  incomingTransfers = /* @__PURE__ */ new Map();
@@ -23004,20 +24088,29 @@ var HarnessRemoteBridge = class {
23004
24088
  async call(input2) {
23005
24089
  const params = callSchema3.parse(input2);
23006
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;
23007
24099
  if (params.endpoint === "session/canOpenWorkspacePath") {
23008
24100
  return { ok: true, value: true };
23009
24101
  }
23010
24102
  const startedAt = performance.now();
23011
24103
  const signal = AbortSignal.timeout(6e4);
23012
24104
  try {
23013
- const nativeResult = await this.gateway.dispatch(params.endpoint, params.payload, signal);
24105
+ const nativeResult = params.endpoint === "commands/execute" ? await dispatchCommandForHost(this.gateway, params.payload, signal, this.harnessVersion) : await this.gateway.dispatch(params.endpoint, params.payload, signal);
23014
24106
  const result = params.endpoint === "directoryPicker/list" && needsDirectoryFallback(nativeResult) ? await this.directoryList(params.payload, signal) : nativeResult;
23015
24107
  this.logger?.debug("harness remote call ok", {
23016
24108
  endpoint: params.endpoint,
23017
24109
  durationMs: Math.round(performance.now() - startedAt)
23018
24110
  });
23019
- return result;
24111
+ return reservation === void 0 ? result : this.terminal.result(params.endpoint, params.payload, result, reservation);
23020
24112
  } catch (error) {
24113
+ if (reservation !== void 0) this.terminal.result(params.endpoint, params.payload, { ok: false, error: { code: "FAILED", message: "", details: {} } }, reservation);
23021
24114
  if (params.endpoint === "directoryPicker/list") {
23022
24115
  const result = await this.directoryList(params.payload, signal);
23023
24116
  this.logger?.debug("harness remote call ok", {
@@ -23144,13 +24237,24 @@ var HarnessRemoteBridge = class {
23144
24237
  async openStream(input2) {
23145
24238
  const params = streamOpenSchema2.parse(input2);
23146
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);
23147
24243
  if (this.streams.has(params.streamId)) throw new RpcError("REQUEST_CONFLICT", "The Harness Remote stream is already open.");
23148
24244
  if (this.streams.size >= MAX_ACTIVE_STREAMS) {
23149
24245
  throw new RpcError("RATE_LIMITED", "Too many Harness Remote streams are open.", void 0, true);
23150
24246
  }
23151
24247
  const controller = new AbortController();
23152
- const source = await this.gateway.open(params.endpoint, params.payload, controller.signal);
23153
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
+ }
23154
24258
  void this.pump(params.streamId, source, controller.signal);
23155
24259
  return { opened: true, streamId: params.streamId };
23156
24260
  }
@@ -23171,7 +24275,7 @@ var HarnessRemoteBridge = class {
23171
24275
  for (const [, stream] of streams) stream.controller.abort(reason);
23172
24276
  }
23173
24277
  assertAllowed(endpoint) {
23174
- if (!allowedEndpoints.has(endpoint)) {
24278
+ if (!allowedEndpoints.has(endpoint) && !TERMINAL_CALLS.has(endpoint) && !TERMINAL_STREAMS.has(endpoint)) {
23175
24279
  throw new RpcError("METHOD_NOT_ALLOWED", "The requested Harness Remote endpoint is not allowed.");
23176
24280
  }
23177
24281
  if (endpoint === "$events" && !this.gateway.supportsCarrier) {
@@ -23205,10 +24309,33 @@ var HarnessRemoteBridge = class {
23205
24309
  }
23206
24310
  pruneTransfers() {
23207
24311
  const cutoff = Date.now() - TRANSFER_IDLE_MS;
23208
- for (const [id2, transfer] of this.incomingTransfers) if (transfer.touchedAt < cutoff) this.incomingTransfers.delete(id2);
23209
- 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);
23210
24314
  }
23211
24315
  };
24316
+ async function dispatchCommandForHost(gateway, payload, signal, harnessVersion) {
24317
+ const parsed = commandExecutePayloadSchema.parse(payload);
24318
+ const legacyPayload = {
24319
+ args: {
24320
+ agentId: parsed.args.agentId,
24321
+ line: parsed.args.line,
24322
+ images: parsed.args.images ?? []
24323
+ }
24324
+ };
24325
+ const currentPayload = {
24326
+ args: {
24327
+ agentId: parsed.args.agentId,
24328
+ line: parsed.args.line,
24329
+ submittedAttachments: parsed.args.submittedAttachments ?? []
24330
+ }
24331
+ };
24332
+ const args = harnessSessionGeneration(harnessVersion) === "v3" ? currentPayload : legacyPayload;
24333
+ return gateway.dispatch(
24334
+ "commands/execute",
24335
+ args,
24336
+ signal
24337
+ );
24338
+ }
23212
24339
  function decodeCanonicalBase642(value) {
23213
24340
  const bytes = Buffer.from(value, "base64");
23214
24341
  if (bytes.toString("base64") !== value) throw new RpcError("INVALID_MESSAGE", "The Harness Remote transfer chunk is invalid.");
@@ -23242,7 +24369,7 @@ function isRecord9(value) {
23242
24369
  }
23243
24370
 
23244
24371
  // src/codex/domain.ts
23245
- import { randomUUID } from "node:crypto";
24372
+ import { randomUUID as randomUUID2 } from "node:crypto";
23246
24373
  import { readdir as readdir2, realpath, stat as stat4 } from "node:fs/promises";
23247
24374
  import { homedir as homedir3 } from "node:os";
23248
24375
  import { basename as basename3, isAbsolute as isAbsolute3, join as join5, relative, resolve as resolve2 } from "node:path";
@@ -23296,11 +24423,11 @@ var CodexAppServerClient = class {
23296
24423
  if (!this.ready) throw new CodexAppServerError("CODEX_UNAVAILABLE", "Codex App Server is not ready.");
23297
24424
  return this.request(method, params, timeoutMs);
23298
24425
  }
23299
- async respond(id2, result) {
23300
- this.write({ id: id2, result });
24426
+ async respond(id4, result) {
24427
+ this.write({ id: id4, result });
23301
24428
  }
23302
- async respondError(id2, code, message) {
23303
- this.write({ id: id2, error: { code, message } });
24429
+ async respondError(id4, code, message) {
24430
+ this.write({ id: id4, error: { code, message } });
23304
24431
  }
23305
24432
  onInbound(handler) {
23306
24433
  this.inboundHandlers.add(handler);
@@ -23381,19 +24508,19 @@ var CodexAppServerClient = class {
23381
24508
  }
23382
24509
  }
23383
24510
  request(method, params, timeoutMs) {
23384
- const id2 = this.nextId++;
24511
+ const id4 = this.nextId++;
23385
24512
  const result = new Promise((resolve3, reject) => {
23386
24513
  const timer = setTimeout(() => {
23387
- this.pending.delete(id2);
24514
+ this.pending.delete(id4);
23388
24515
  reject(new CodexAppServerError("CODEX_REQUEST_TIMEOUT", "Codex App Server request timed out."));
23389
24516
  }, timeoutMs);
23390
24517
  timer.unref?.();
23391
- this.pending.set(id2, { resolve: resolve3, reject, timer });
24518
+ this.pending.set(id4, { resolve: resolve3, reject, timer });
23392
24519
  });
23393
24520
  try {
23394
- this.write({ id: id2, method, params });
24521
+ this.write({ id: id4, method, params });
23395
24522
  } catch (error) {
23396
- const pending = this.takePending(id2);
24523
+ const pending = this.takePending(id4);
23397
24524
  pending?.reject(error instanceof Error ? error : new Error("Codex App Server write failed."));
23398
24525
  }
23399
24526
  return result;
@@ -23464,15 +24591,15 @@ var CodexAppServerClient = class {
23464
24591
  this.failureNotified = true;
23465
24592
  for (const handler of this.unavailableHandlers) handler(code);
23466
24593
  }
23467
- takePending(id2) {
23468
- const pending = this.pending.get(id2);
24594
+ takePending(id4) {
24595
+ const pending = this.pending.get(id4);
23469
24596
  if (pending === void 0) return void 0;
23470
- this.pending.delete(id2);
24597
+ this.pending.delete(id4);
23471
24598
  clearTimeout(pending.timer);
23472
24599
  return pending;
23473
24600
  }
23474
24601
  failPending(error) {
23475
- 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);
23476
24603
  }
23477
24604
  };
23478
24605
  function safeUpstreamError(value) {
@@ -23486,7 +24613,7 @@ function isRecord10(value) {
23486
24613
  }
23487
24614
 
23488
24615
  // src/codex/method-policy.ts
23489
- var id = external_exports.string().min(1).max(256);
24616
+ var id3 = external_exports.string().min(1).max(256);
23490
24617
  var cursor = external_exports.string().min(1).max(4096).nullable().optional();
23491
24618
  var textInput = external_exports.object({
23492
24619
  type: external_exports.literal("text"),
@@ -23535,9 +24662,9 @@ var schemas = {
23535
24662
  useStateDbOnly: external_exports.boolean().optional(),
23536
24663
  searchTerm: external_exports.string().max(1024).optional()
23537
24664
  }).strict(),
23538
- "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(),
23539
24666
  "dsh/sessionHistory": external_exports.object({
23540
- threadId: id,
24667
+ threadId: id3,
23541
24668
  beforeSeq: external_exports.number().int().nonnegative().optional(),
23542
24669
  throughSeq: external_exports.number().int().min(-1).optional(),
23543
24670
  maxMessages: external_exports.number().int().min(1).max(200).optional()
@@ -23552,21 +24679,21 @@ var schemas = {
23552
24679
  permissionPreset: permissionPreset.optional()
23553
24680
  }).strict(),
23554
24681
  "thread/resume": external_exports.object({
23555
- threadId: id,
24682
+ threadId: id3,
23556
24683
  model: external_exports.string().min(1).max(128).optional(),
23557
24684
  permissionPreset: permissionPreset.optional()
23558
24685
  }).strict(),
23559
24686
  "thread/fork": external_exports.object({
23560
- threadId: id,
23561
- lastTurnId: id.optional(),
24687
+ threadId: id3,
24688
+ lastTurnId: id3.optional(),
23562
24689
  permissionPreset: permissionPreset.optional()
23563
24690
  }).strict(),
23564
- "thread/name/set": external_exports.object({ threadId: id, name: external_exports.string().trim().min(1).max(256) }).strict(),
23565
- "thread/archive": external_exports.object({ threadId: id }).strict(),
23566
- "thread/unarchive": external_exports.object({ threadId: id }).strict(),
23567
- "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(),
23568
24695
  "turn/start": external_exports.object({
23569
- threadId: id,
24696
+ threadId: id3,
23570
24697
  input,
23571
24698
  model: external_exports.string().min(1).max(128).optional(),
23572
24699
  effort: external_exports.enum(["none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra"]).optional(),
@@ -23574,8 +24701,8 @@ var schemas = {
23574
24701
  personality: external_exports.string().min(1).max(64).optional(),
23575
24702
  permissionPreset: permissionPreset.optional()
23576
24703
  }).strict(),
23577
- "turn/steer": external_exports.object({ threadId: id, input, expectedTurnId: id }).strict(),
23578
- "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()
23579
24706
  };
23580
24707
  var CODEX_APP_ALLOWLIST = Object.freeze(Object.keys(schemas));
23581
24708
  function parseCodexCall(method, params) {
@@ -23793,14 +24920,14 @@ var CodexPeerBridge = class {
23793
24920
  if (this.closed) return;
23794
24921
  const streamIds = [...this.streams.entries()].filter(([, targetThreadId]) => targetThreadId === threadId).map(([streamId]) => streamId);
23795
24922
  for (const streamId of streamIds) {
23796
- const data = { streamId, frame };
23797
- 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) {
23798
24925
  this.streams.delete(streamId);
23799
24926
  await this.publish("codex.app.stream.closed", { streamId, reason: "failed" });
23800
24927
  this.logger?.warn("Codex stream closed after oversized frame", { streamId });
23801
24928
  continue;
23802
24929
  }
23803
- await this.publish("codex.app.frame", data);
24930
+ await this.publish("codex.app.frame", data2);
23804
24931
  }
23805
24932
  }
23806
24933
  async failStreams(reason = "failed") {
@@ -23829,11 +24956,11 @@ var CodexPeerBridge = class {
23829
24956
  }
23830
24957
  pruneTransfers() {
23831
24958
  const staleBefore = Date.now() - TRANSFER_IDLE_MS;
23832
- for (const [id2, transfer] of this.incomingTransfers) {
23833
- 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);
23834
24961
  }
23835
- for (const [id2, transfer] of this.outgoingTransfers) {
23836
- 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);
23837
24964
  }
23838
24965
  }
23839
24966
  requireOpen() {
@@ -24300,7 +25427,7 @@ var CodexRemoteDomain = class {
24300
25427
  await appServer.respond(message.id, { decision: "decline" });
24301
25428
  return;
24302
25429
  }
24303
- const requestHandle = randomUUID();
25430
+ const requestHandle = randomUUID2();
24304
25431
  this.approvals.set(requestHandle, {
24305
25432
  upstreamId: message.id,
24306
25433
  connectionId: owner.connectionId,
@@ -24608,14 +25735,14 @@ var CodexRemoteDomain = class {
24608
25735
  }
24609
25736
  }
24610
25737
  async listCodexDirectory(path) {
24611
- const target = await this.resolveCodexDirectory(path);
24612
- 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);
24613
25740
  if (rows === void 0) {
24614
25741
  throw new RpcError("CODEX_PATH_NOT_ALLOWED", "The CodeX working directory is not available as a Workspace.");
24615
25742
  }
24616
25743
  const entries = [];
24617
25744
  for (const row of rows) {
24618
- const child = resolve2(target.path, row.name);
25745
+ const child = resolve2(target2.path, row.name);
24619
25746
  let directory = row.isDirectory();
24620
25747
  if (!directory && row.isSymbolicLink()) {
24621
25748
  directory = await stat4(child).then((value) => value.isDirectory()).catch(() => false);
@@ -24624,7 +25751,7 @@ var CodexRemoteDomain = class {
24624
25751
  try {
24625
25752
  const canonicalChild = await realpath(child);
24626
25753
  if (!(await stat4(canonicalChild)).isDirectory()) continue;
24627
- if (!containsCodexPath(target.canonicalRoot, canonicalChild)) continue;
25754
+ if (!containsCodexPath(target2.canonicalRoot, canonicalChild)) continue;
24628
25755
  } catch {
24629
25756
  continue;
24630
25757
  }
@@ -24636,9 +25763,9 @@ var CodexRemoteDomain = class {
24636
25763
  }
24637
25764
  entries.sort((left, right) => left.name.localeCompare(right.name, void 0, { sensitivity: "base" }));
24638
25765
  return {
24639
- path: target.path,
24640
- home: target.root,
24641
- crumbs: codexDirectoryCrumbs(target.root, target.path),
25766
+ path: target2.path,
25767
+ home: target2.root,
25768
+ crumbs: codexDirectoryCrumbs(target2.root, target2.path),
24642
25769
  entries: entries.slice(0, CODEX_DIRECTORY_ENTRY_LIMIT),
24643
25770
  truncated: entries.length > CODEX_DIRECTORY_ENTRY_LIMIT
24644
25771
  };
@@ -24718,7 +25845,7 @@ function sanitizeThreadList(result) {
24718
25845
  if (!isRecord12(result) || !Array.isArray(result.data)) {
24719
25846
  throw new RpcError("CODEX_INVALID_RESPONSE", "Codex App Server returned an invalid thread list.");
24720
25847
  }
24721
- const data = result.data.flatMap((value) => {
25848
+ const data2 = result.data.flatMap((value) => {
24722
25849
  if (!isRecord12(value) || typeof value.id !== "string") return [];
24723
25850
  return [{
24724
25851
  id: value.id,
@@ -24735,7 +25862,7 @@ function sanitizeThreadList(result) {
24735
25862
  }];
24736
25863
  });
24737
25864
  return {
24738
- data,
25865
+ data: data2,
24739
25866
  ...typeof result.nextCursor === "string" && result.nextCursor.length > 0 ? { nextCursor: result.nextCursor } : { nextCursor: null },
24740
25867
  ...typeof result.backwardsCursor === "string" && result.backwardsCursor.length > 0 ? { backwardsCursor: result.backwardsCursor } : {}
24741
25868
  };
@@ -24769,9 +25896,9 @@ function sanitizeProjectList(result) {
24769
25896
  if (!isRecord12(result) || !Array.isArray(result.data)) {
24770
25897
  throw new RpcError("CODEX_INVALID_RESPONSE", "Codex App Server returned an invalid project list.");
24771
25898
  }
24772
- const data = result.data.flatMap((value) => sanitizeProject(value) ?? []);
25899
+ const data2 = result.data.flatMap((value) => sanitizeProject(value) ?? []);
24773
25900
  return {
24774
- data,
25901
+ data: data2,
24775
25902
  ...typeof result.nextCursor === "string" && result.nextCursor.length > 0 ? { nextCursor: result.nextCursor } : { nextCursor: null }
24776
25903
  };
24777
25904
  }
@@ -24912,7 +26039,119 @@ function isActiveWriterMessage(message) {
24912
26039
  return message.toLowerCase().includes("active writer");
24913
26040
  }
24914
26041
 
26042
+ // src/acp.ts
26043
+ import { spawn as spawn3 } from "node:child_process";
26044
+ var AcpGateway = class {
26045
+ constructor(adapters) {
26046
+ this.adapters = adapters;
26047
+ }
26048
+ get(p) {
26049
+ const list = this.adapters instanceof Object && "backend" in this.adapters ? [this.adapters] : [...this.adapters];
26050
+ const a = list.find((x) => !p.backend || x.backend === p.backend);
26051
+ if (!a) throw new Error("CAPABILITY_NOT_SUPPORTED");
26052
+ return a;
26053
+ }
26054
+ initialize(p) {
26055
+ return this.get(p).initialize(p);
26056
+ }
26057
+ sessionNew(p) {
26058
+ return this.get(p).sessionNew(p);
26059
+ }
26060
+ sessionLoad(p) {
26061
+ const a = this.get(p);
26062
+ if (!a.sessionLoad) throw new Error("CAPABILITY_NOT_SUPPORTED");
26063
+ return a.sessionLoad(p);
26064
+ }
26065
+ prompt(p, emit) {
26066
+ return this.get(p).prompt(p, emit);
26067
+ }
26068
+ respondPermission(p) {
26069
+ const a = this.get(p);
26070
+ if (!a.respondPermission) throw new Error("CAPABILITY_NOT_SUPPORTED");
26071
+ return a.respondPermission(p);
26072
+ }
26073
+ cancel(p) {
26074
+ const a = this.get(p);
26075
+ if (!a.cancel) throw new Error("CAPABILITY_NOT_SUPPORTED");
26076
+ return a.cancel(p);
26077
+ }
26078
+ setMode(p) {
26079
+ const a = this.get(p);
26080
+ if (!a.setMode) throw new Error("CAPABILITY_NOT_SUPPORTED");
26081
+ return a.setMode(p);
26082
+ }
26083
+ };
26084
+ var StdioAcpAdapter = class {
26085
+ constructor(config) {
26086
+ this.config = config;
26087
+ this.backend = config.id;
26088
+ }
26089
+ backend;
26090
+ child;
26091
+ nextId = 1;
26092
+ ensure() {
26093
+ if (!this.child) this.child = spawn3(this.config.command, this.config.args ?? [], { cwd: this.config.cwd, stdio: "pipe" });
26094
+ return this.child;
26095
+ }
26096
+ async initialize(params) {
26097
+ await this.call("initialize", params);
26098
+ return { protocolVersion: 1, capability: "agent.acp.v1", backend: this.backend, capabilities: ["session.new", "session.load", "session.prompt", "session.cancel"] };
26099
+ }
26100
+ async sessionNew(params) {
26101
+ const r = await this.call("session/new", params);
26102
+ if (!r.sessionId) throw new Error("INVALID_MESSAGE");
26103
+ return { sessionId: r.sessionId };
26104
+ }
26105
+ async sessionLoad(params) {
26106
+ const r = await this.call("session/load", params);
26107
+ if (!r.sessionId) throw new Error("INVALID_MESSAGE");
26108
+ return { sessionId: r.sessionId };
26109
+ }
26110
+ async prompt(params, emit) {
26111
+ await this.call("session/prompt", params, async (n) => emit({ sessionId: params.sessionId, update: n, seq: this.nextId++ }));
26112
+ }
26113
+ async cancel(p) {
26114
+ await this.call("session/cancel", p);
26115
+ }
26116
+ async setMode(p) {
26117
+ await this.call("session/set_mode", p);
26118
+ }
26119
+ async respondPermission(p) {
26120
+ await this.call("session/request_permission", p);
26121
+ }
26122
+ async close() {
26123
+ this.child?.kill();
26124
+ this.child = void 0;
26125
+ }
26126
+ call(method, params, onNotification) {
26127
+ const c = this.ensure();
26128
+ const id4 = this.nextId++;
26129
+ c.stdin.write(JSON.stringify({ jsonrpc: "2.0", id: id4, method, params }) + "\n");
26130
+ return new Promise((resolve3, reject) => {
26131
+ let buf = "";
26132
+ const onData = async (d) => {
26133
+ buf += d;
26134
+ const lines = buf.split("\n");
26135
+ buf = lines.pop() ?? "";
26136
+ for (const l of lines) {
26137
+ try {
26138
+ const m = JSON.parse(l);
26139
+ if (m.id === id4) {
26140
+ c.stdout.off("data", onData);
26141
+ m.error ? reject(new Error(m.error.message ?? "ACP error")) : resolve3(m.result);
26142
+ } else if (m.method && onNotification) await onNotification(m.params);
26143
+ } catch {
26144
+ }
26145
+ }
26146
+ };
26147
+ c.stdout.on("data", onData);
26148
+ c.once("error", reject);
26149
+ });
26150
+ }
26151
+ };
26152
+
24915
26153
  // src/service.ts
26154
+ import { execFileSync as execFileSync2 } from "node:child_process";
24916
26155
  var HostPluginRuntime = class {
24917
26156
  constructor(config, identities, apiProxy, logger, localGateway, fileViewerHost) {
24918
26157
  this.config = config;
@@ -24921,11 +26160,13 @@ var HostPluginRuntime = class {
24921
26160
  this.logger = logger;
24922
26161
  this.localGateway = localGateway;
24923
26162
  this.fileViewerHost = fileViewerHost;
26163
+ this.terminalEnabled = config.terminal.enabled;
26164
+ this.loopback = new LoopbackHost(config.loopback.ports);
24924
26165
  this.codex = new CodexRemoteDomain(config.codex, logger);
24925
26166
  this.connections = new ConnectionController(this.identities, (context, send) => {
24926
26167
  const harnessApi = this.apiProxy === void 0 ? void 0 : new HarnessApiBridge(
24927
26168
  this.apiProxy,
24928
- (event, data) => send(createEvent(event, data)),
26169
+ (event, data2) => send(createEvent(event, data2)),
24929
26170
  void 0,
24930
26171
  this.logger,
24931
26172
  this.localGateway,
@@ -24933,8 +26174,10 @@ var HostPluginRuntime = class {
24933
26174
  );
24934
26175
  const harnessRemote = this.localGateway?.supportsCarrier === true ? new HarnessRemoteBridge(
24935
26176
  this.localGateway,
24936
- (event, data) => send(createEvent(event, data)),
24937
- this.logger
26177
+ (event, data2) => send(createEvent(event, data2)),
26178
+ this.logger,
26179
+ this.harnessVersion,
26180
+ new TerminalPolicy(() => this.terminalEnabled, context.peerDeviceId, this.terminalOwners)
24938
26181
  ) : void 0;
24939
26182
  const fileViewer = new RemoteFileViewerBridge(
24940
26183
  () => this.fileViewerHost?.(),
@@ -24942,8 +26185,10 @@ var HostPluginRuntime = class {
24942
26185
  );
24943
26186
  const codex = this.codex.createPeer(
24944
26187
  context,
24945
- (event, data) => send(createEvent(event, data))
26188
+ (event, data2) => send(createEvent(event, data2))
24946
26189
  );
26190
+ const adapters = (config.acp?.backends ?? []).filter((item) => item.enabled && this.acpAvailable(item.command)).map((item) => new StdioAcpAdapter(item));
26191
+ const acp = config.acp?.enabled && adapters.length > 0 ? new AcpGateway(adapters) : void 0;
24947
26192
  return new RpcRouter(
24948
26193
  harnessApi,
24949
26194
  void 0,
@@ -24951,7 +26196,9 @@ var HostPluginRuntime = class {
24951
26196
  fileViewer,
24952
26197
  harnessRemote,
24953
26198
  () => this.hostCapabilities(),
24954
- codex
26199
+ codex,
26200
+ acp,
26201
+ this.loopback
24955
26202
  );
24956
26203
  }, this.logger);
24957
26204
  if (config.serverUrl !== void 0) {
@@ -24959,6 +26206,9 @@ var HostPluginRuntime = class {
24959
26206
  }
24960
26207
  }
24961
26208
  connections;
26209
+ terminalOwners = /* @__PURE__ */ new Map();
26210
+ terminalEnabled;
26211
+ loopback;
24962
26212
  identity;
24963
26213
  serverApi;
24964
26214
  serverConnection;
@@ -24967,6 +26217,12 @@ var HostPluginRuntime = class {
24967
26217
  codex;
24968
26218
  localCodexPeer;
24969
26219
  localCodexPublish = async () => void 0;
26220
+ setTerminalEnabled(enabled) {
26221
+ this.terminalEnabled = enabled;
26222
+ }
26223
+ setLoopbackPorts(ports) {
26224
+ this.loopback.setPorts(ports);
26225
+ }
24970
26226
  async start() {
24971
26227
  if (this.closed) throw new Error("remote runtime is closed");
24972
26228
  this.identity = await this.identities.loadOrCreate(this.config.deviceName);
@@ -25004,9 +26260,21 @@ var HostPluginRuntime = class {
25004
26260
  ...error === void 0 ? {} : { error },
25005
26261
  ...authorization?.account === void 0 ? {} : { account: authorization.account },
25006
26262
  authorized: authorization !== void 0,
25007
- accountRequired: error === "ACCOUNT_AUTH_REQUIRED" || error === "AUTH_INVALID" || error === "TOKEN_EXPIRED"
26263
+ accountRequired: error === "ACCOUNT_AUTH_REQUIRED" || error === "AUTH_INVALID" || error === "TOKEN_EXPIRED",
26264
+ connectedClients: this.listConnectedClients()
25008
26265
  };
25009
26266
  }
26267
+ listConnectedClients() {
26268
+ return this.connections.connectedPeers().map((peer) => {
26269
+ const trusted = this.identities.trustedPeer(peer.deviceId);
26270
+ return {
26271
+ deviceId: peer.deviceId,
26272
+ name: trusted?.name.trim() ?? "",
26273
+ ...trusted === void 0 ? {} : { platform: trusted.platform },
26274
+ ...peer.mode === void 0 ? {} : { mode: peer.mode }
26275
+ };
26276
+ });
26277
+ }
25010
26278
  localHarnessVersion() {
25011
26279
  return this.harnessVersion;
25012
26280
  }
@@ -25162,7 +26430,7 @@ var HostPluginRuntime = class {
25162
26430
  let reportedVersion;
25163
26431
  let errorCode5;
25164
26432
  try {
25165
- const response = await this.apiProxy?.host.describe({ rpcId: randomUUID2(), payload: {} });
26433
+ const response = await this.apiProxy?.host.describe({ rpcId: randomUUID3(), payload: {} });
25166
26434
  if (response === void 0) throw new Error("ApiProxy is unavailable");
25167
26435
  if (!response.result.ok) {
25168
26436
  errorCode5 = response.result.error.code;
@@ -25179,19 +26447,32 @@ var HostPluginRuntime = class {
25179
26447
  }
25180
26448
  hostCapabilities() {
25181
26449
  const capabilities = [];
26450
+ if (this.loopback.hasPorts()) capabilities.push("loopback.http-ws.v1");
25182
26451
  if (this.localGateway?.supportsCarrier === true) {
25183
26452
  capabilities.push(
25184
26453
  harnessSessionGeneration(this.harnessVersion) === "v3" ? "harness.remote.v3" : "harness.remote.v1",
25185
26454
  "harness.remote.transfer.v1"
25186
26455
  );
25187
26456
  }
26457
+ if (this.localGateway?.supportsCarrier && this.terminalEnabled) capabilities.push("harness.terminal.v1");
25188
26458
  if (this.apiProxy !== void 0) {
25189
26459
  capabilities.push("harness.api.v1", "harness.api.transfer.v1");
25190
26460
  }
25191
26461
  if (this.fileViewerHost?.() !== void 0) capabilities.push("fileviewer.read.v1");
25192
26462
  if (this.codex.isAvailable()) capabilities.push("codex.appserver.v1", "codex.appserver.transfer.v1");
26463
+ if (this.config.acp?.enabled) {
26464
+ for (const item of this.config.acp.backends) if (item.enabled && this.acpAvailable(item.command)) capabilities.push(`agent.acp.v1.${item.id}`);
26465
+ }
25193
26466
  return capabilities;
25194
26467
  }
26468
+ acpAvailable(command) {
26469
+ try {
26470
+ execFileSync2(process.platform === "win32" ? "where" : "which", [command], { stdio: "ignore" });
26471
+ return true;
26472
+ } catch {
26473
+ return false;
26474
+ }
26475
+ }
25195
26476
  requireLocalCodexPeer() {
25196
26477
  if (!this.codex.isAvailable()) {
25197
26478
  throw new RpcError("CODEX_UNAVAILABLE", "Local CodeX is disabled or unavailable on this Host.");
@@ -25201,7 +26482,7 @@ var HostPluginRuntime = class {
25201
26482
  const peer = this.codex.createPeer({
25202
26483
  connectionId: `loopback:${identity.deviceId}`,
25203
26484
  peerDeviceId: identity.deviceId
25204
- }, (event, data) => this.localCodexPublish(event, data));
26485
+ }, (event, data2) => this.localCodexPublish(event, data2));
25205
26486
  if (peer === void 0) {
25206
26487
  throw new RpcError("CODEX_UNAVAILABLE", "Local CodeX is disabled or unavailable on this Host.");
25207
26488
  }
@@ -25459,11 +26740,11 @@ function helpText() {
25459
26740
  ""
25460
26741
  ].join("\n");
25461
26742
  }
25462
- function write(target, value) {
25463
- target.write(value);
26743
+ function write(target2, value) {
26744
+ target2.write(value);
25464
26745
  }
25465
- function terminalLink(url, target) {
25466
- if (target.isTTY !== true) return url;
26746
+ function terminalLink(url, target2) {
26747
+ if (target2.isTTY !== true) return url;
25467
26748
  return `\x1B]8;;${url}\x07${url}\x1B]8;;\x07`;
25468
26749
  }
25469
26750
  async function exists3(path) {
@@ -25524,10 +26805,10 @@ function installTuiRemoteCommand(ctx, resolveTarget) {
25524
26805
  return { kind: "success" };
25525
26806
  }
25526
26807
  if (command === "logout" && args.length === 1) {
25527
- const target = resolveTarget();
25528
- if (target === void 0) return remoteNotReady();
26808
+ const target2 = resolveTarget();
26809
+ if (target2 === void 0) return remoteNotReady();
25529
26810
  try {
25530
- await target.runtime.clearHostAuthorization();
26811
+ await target2.runtime.clearHostAuthorization();
25531
26812
  return {
25532
26813
  kind: "success",
25533
26814
  text: "Remote Host logged out and its local device identity was rotated."
@@ -25624,11 +26905,11 @@ function remoteCommandChildren(canonicalPath) {
25624
26905
  }
25625
26906
  return [];
25626
26907
  }
25627
- function remoteStatusLines(target) {
25628
- if (target === void 0) {
26908
+ function remoteStatusLines(target2) {
26909
+ if (target2 === void 0) {
25629
26910
  return ["Remote Host is disabled or still starting. Check the ds-harness-remote settings and retry."];
25630
26911
  }
25631
- const { runtime, config } = target;
26912
+ const { runtime, config } = target2;
25632
26913
  const status2 = runtime.hostStatus();
25633
26914
  const diagnostics = runtime.diagnostics();
25634
26915
  const codex = runtime.codexStatus();
@@ -25640,6 +26921,8 @@ function remoteStatusLines(target) {
25640
26921
  `Device: ${status2.deviceId ?? "not initialized"}`,
25641
26922
  `Authorization: ${status2.authorized ? status2.account === void 0 ? "logged in" : `logged in (${status2.account})` : "logged out"}`,
25642
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]."] : [],
25643
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"}`,
25644
26927
  `Remote clients: ${diagnostics.activeConnections}`,
25645
26928
  `Codex Remote: ${codex.enabled ? codex.state : "disabled"}`,
@@ -25647,8 +26930,8 @@ function remoteStatusLines(target) {
25647
26930
  "Commands: /remote login [github|zhihu] \xB7 /remote status \xB7 /remote logout"
25648
26931
  ];
25649
26932
  }
25650
- function formatRemoteStatusInline(target) {
25651
- return remoteStatusLines(target).filter(Boolean).join(" \xB7 ");
26933
+ function formatRemoteStatusInline(target2) {
26934
+ return remoteStatusLines(target2).filter(Boolean).join(" \xB7 ");
25652
26935
  }
25653
26936
  function remoteCommandUsage() {
25654
26937
  return [
@@ -25689,16 +26972,16 @@ var RemoteLoginController = class {
25689
26972
  }
25690
26973
  async run(attempt, provider) {
25691
26974
  try {
25692
- const target = this.resolveTarget();
25693
- if (target === void 0) throw new Error("REMOTE_NOT_READY");
25694
- 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);
25695
26978
  const [qr, compactQr] = await Promise.all([
25696
26979
  renderTerminalQr(session.scanUrl),
25697
26980
  renderCompactTerminalQr(session.scanUrl)
25698
26981
  ]);
25699
26982
  if (attempt !== this.attempt) return;
25700
26983
  this.update({ phase: "waiting", provider, qr, compactQr, scanUrl: session.scanUrl });
25701
- await this.poll(attempt, provider, session, target.runtime);
26984
+ await this.poll(attempt, provider, session, target2.runtime);
25702
26985
  } catch (error) {
25703
26986
  if (attempt !== this.attempt) return;
25704
26987
  this.update({ phase: "error", provider, error: errorCode4(error) });
@@ -26108,6 +27391,7 @@ function isPlainRecord2(value) {
26108
27391
  return typeof value === "object" && value !== null && !Array.isArray(value);
26109
27392
  }
26110
27393
  export {
27394
+ AcpGateway,
26111
27395
  ApiProxySwitch,
26112
27396
  CODEX_APP_ALLOWLIST,
26113
27397
  ClientModeError,