ds-harness-remote 0.4.3 → 0.4.5

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 (54) hide show
  1. package/README.md +57 -9
  2. package/bin/ds-harness-remote.js +11 -0
  3. package/cordis.patch.yml +22 -0
  4. package/dist/cli.d.ts +31 -0
  5. package/dist/cli.d.ts.map +1 -0
  6. package/dist/cli.js +283 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/client.github.js +31 -15
  9. package/dist/client.js +31 -15
  10. package/dist/client.js.map +1 -1
  11. package/dist/codex/domain.d.ts +6 -2
  12. package/dist/codex/domain.d.ts.map +1 -1
  13. package/dist/codex/domain.js +279 -68
  14. package/dist/codex/domain.js.map +1 -1
  15. package/dist/codex/method-policy.d.ts +14 -7
  16. package/dist/codex/method-policy.d.ts.map +1 -1
  17. package/dist/codex/method-policy.js +3 -0
  18. package/dist/codex/method-policy.js.map +1 -1
  19. package/dist/codex/virtual-harness.d.ts +6 -0
  20. package/dist/codex/virtual-harness.d.ts.map +1 -1
  21. package/dist/codex/virtual-harness.js +161 -23
  22. package/dist/codex/virtual-harness.js.map +1 -1
  23. package/dist/config.d.ts +1 -0
  24. package/dist/config.d.ts.map +1 -1
  25. package/dist/config.js +1 -0
  26. package/dist/config.js.map +1 -1
  27. package/dist/harness-api-bridge.d.ts.map +1 -1
  28. package/dist/harness-api-bridge.js +14 -6
  29. package/dist/harness-api-bridge.js.map +1 -1
  30. package/dist/harness-remote-bridge.d.ts +1 -1
  31. package/dist/harness-remote-bridge.d.ts.map +1 -1
  32. package/dist/harness-remote-bridge.js +1 -0
  33. package/dist/harness-remote-bridge.js.map +1 -1
  34. package/dist/index.d.ts +2 -0
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +1342 -270
  37. package/dist/index.js.map +4 -4
  38. package/dist/server-api.d.ts.map +1 -1
  39. package/dist/server-api.js +15 -2
  40. package/dist/server-api.js.map +1 -1
  41. package/dist/server-credentials.d.ts +2 -2
  42. package/dist/service.d.ts +4 -1
  43. package/dist/service.d.ts.map +1 -1
  44. package/dist/service.js +34 -2
  45. package/dist/service.js.map +1 -1
  46. package/dist/tui-command.d.ts +13 -0
  47. package/dist/tui-command.d.ts.map +1 -0
  48. package/dist/tui-command.js +378 -0
  49. package/dist/tui-command.js.map +1 -0
  50. package/dist/version.d.ts +1 -1
  51. package/dist/version.js +1 -1
  52. package/dsh-plugin.json +41 -0
  53. package/package.json +6 -1
  54. package/public.d.ts +1 -0
package/dist/index.js CHANGED
@@ -538,18 +538,18 @@ var ParseStatus = class _ParseStatus {
538
538
  if (this.value !== "aborted")
539
539
  this.value = "aborted";
540
540
  }
541
- static mergeArray(status, results) {
541
+ static mergeArray(status2, results) {
542
542
  const arrayValue = [];
543
543
  for (const s2 of results) {
544
544
  if (s2.status === "aborted")
545
545
  return INVALID;
546
546
  if (s2.status === "dirty")
547
- status.dirty();
547
+ status2.dirty();
548
548
  arrayValue.push(s2.value);
549
549
  }
550
- return { status: status.value, value: arrayValue };
550
+ return { status: status2.value, value: arrayValue };
551
551
  }
552
- static async mergeObjectAsync(status, pairs) {
552
+ static async mergeObjectAsync(status2, pairs) {
553
553
  const syncPairs = [];
554
554
  for (const pair of pairs) {
555
555
  const key = await pair.key;
@@ -559,9 +559,9 @@ var ParseStatus = class _ParseStatus {
559
559
  value
560
560
  });
561
561
  }
562
- return _ParseStatus.mergeObjectSync(status, syncPairs);
562
+ return _ParseStatus.mergeObjectSync(status2, syncPairs);
563
563
  }
564
- static mergeObjectSync(status, pairs) {
564
+ static mergeObjectSync(status2, pairs) {
565
565
  const finalObject = {};
566
566
  for (const pair of pairs) {
567
567
  const { key, value } = pair;
@@ -570,14 +570,14 @@ var ParseStatus = class _ParseStatus {
570
570
  if (value.status === "aborted")
571
571
  return INVALID;
572
572
  if (key.status === "dirty")
573
- status.dirty();
573
+ status2.dirty();
574
574
  if (value.status === "dirty")
575
- status.dirty();
575
+ status2.dirty();
576
576
  if (key.value !== "__proto__" && (typeof value.value !== "undefined" || pair.alwaysSet)) {
577
577
  finalObject[key.value] = value.value;
578
578
  }
579
579
  }
580
- return { status: status.value, value: finalObject };
580
+ return { status: status2.value, value: finalObject };
581
581
  }
582
582
  };
583
583
  var INVALID = Object.freeze({
@@ -1037,7 +1037,7 @@ var ZodString = class _ZodString extends ZodType {
1037
1037
  });
1038
1038
  return INVALID;
1039
1039
  }
1040
- const status = new ParseStatus();
1040
+ const status2 = new ParseStatus();
1041
1041
  let ctx = void 0;
1042
1042
  for (const check of this._def.checks) {
1043
1043
  if (check.kind === "min") {
@@ -1051,7 +1051,7 @@ var ZodString = class _ZodString extends ZodType {
1051
1051
  exact: false,
1052
1052
  message: check.message
1053
1053
  });
1054
- status.dirty();
1054
+ status2.dirty();
1055
1055
  }
1056
1056
  } else if (check.kind === "max") {
1057
1057
  if (input2.data.length > check.value) {
@@ -1064,7 +1064,7 @@ var ZodString = class _ZodString extends ZodType {
1064
1064
  exact: false,
1065
1065
  message: check.message
1066
1066
  });
1067
- status.dirty();
1067
+ status2.dirty();
1068
1068
  }
1069
1069
  } else if (check.kind === "length") {
1070
1070
  const tooBig = input2.data.length > check.value;
@@ -1090,7 +1090,7 @@ var ZodString = class _ZodString extends ZodType {
1090
1090
  message: check.message
1091
1091
  });
1092
1092
  }
1093
- status.dirty();
1093
+ status2.dirty();
1094
1094
  }
1095
1095
  } else if (check.kind === "email") {
1096
1096
  if (!emailRegex.test(input2.data)) {
@@ -1100,7 +1100,7 @@ var ZodString = class _ZodString extends ZodType {
1100
1100
  code: ZodIssueCode.invalid_string,
1101
1101
  message: check.message
1102
1102
  });
1103
- status.dirty();
1103
+ status2.dirty();
1104
1104
  }
1105
1105
  } else if (check.kind === "emoji") {
1106
1106
  if (!emojiRegex) {
@@ -1113,7 +1113,7 @@ var ZodString = class _ZodString extends ZodType {
1113
1113
  code: ZodIssueCode.invalid_string,
1114
1114
  message: check.message
1115
1115
  });
1116
- status.dirty();
1116
+ status2.dirty();
1117
1117
  }
1118
1118
  } else if (check.kind === "uuid") {
1119
1119
  if (!uuidRegex.test(input2.data)) {
@@ -1123,7 +1123,7 @@ var ZodString = class _ZodString extends ZodType {
1123
1123
  code: ZodIssueCode.invalid_string,
1124
1124
  message: check.message
1125
1125
  });
1126
- status.dirty();
1126
+ status2.dirty();
1127
1127
  }
1128
1128
  } else if (check.kind === "nanoid") {
1129
1129
  if (!nanoidRegex.test(input2.data)) {
@@ -1133,7 +1133,7 @@ var ZodString = class _ZodString extends ZodType {
1133
1133
  code: ZodIssueCode.invalid_string,
1134
1134
  message: check.message
1135
1135
  });
1136
- status.dirty();
1136
+ status2.dirty();
1137
1137
  }
1138
1138
  } else if (check.kind === "cuid") {
1139
1139
  if (!cuidRegex.test(input2.data)) {
@@ -1143,7 +1143,7 @@ var ZodString = class _ZodString extends ZodType {
1143
1143
  code: ZodIssueCode.invalid_string,
1144
1144
  message: check.message
1145
1145
  });
1146
- status.dirty();
1146
+ status2.dirty();
1147
1147
  }
1148
1148
  } else if (check.kind === "cuid2") {
1149
1149
  if (!cuid2Regex.test(input2.data)) {
@@ -1153,7 +1153,7 @@ var ZodString = class _ZodString extends ZodType {
1153
1153
  code: ZodIssueCode.invalid_string,
1154
1154
  message: check.message
1155
1155
  });
1156
- status.dirty();
1156
+ status2.dirty();
1157
1157
  }
1158
1158
  } else if (check.kind === "ulid") {
1159
1159
  if (!ulidRegex.test(input2.data)) {
@@ -1163,7 +1163,7 @@ var ZodString = class _ZodString extends ZodType {
1163
1163
  code: ZodIssueCode.invalid_string,
1164
1164
  message: check.message
1165
1165
  });
1166
- status.dirty();
1166
+ status2.dirty();
1167
1167
  }
1168
1168
  } else if (check.kind === "url") {
1169
1169
  try {
@@ -1175,7 +1175,7 @@ var ZodString = class _ZodString extends ZodType {
1175
1175
  code: ZodIssueCode.invalid_string,
1176
1176
  message: check.message
1177
1177
  });
1178
- status.dirty();
1178
+ status2.dirty();
1179
1179
  }
1180
1180
  } else if (check.kind === "regex") {
1181
1181
  check.regex.lastIndex = 0;
@@ -1187,7 +1187,7 @@ var ZodString = class _ZodString extends ZodType {
1187
1187
  code: ZodIssueCode.invalid_string,
1188
1188
  message: check.message
1189
1189
  });
1190
- status.dirty();
1190
+ status2.dirty();
1191
1191
  }
1192
1192
  } else if (check.kind === "trim") {
1193
1193
  input2.data = input2.data.trim();
@@ -1199,7 +1199,7 @@ var ZodString = class _ZodString extends ZodType {
1199
1199
  validation: { includes: check.value, position: check.position },
1200
1200
  message: check.message
1201
1201
  });
1202
- status.dirty();
1202
+ status2.dirty();
1203
1203
  }
1204
1204
  } else if (check.kind === "toLowerCase") {
1205
1205
  input2.data = input2.data.toLowerCase();
@@ -1213,7 +1213,7 @@ var ZodString = class _ZodString extends ZodType {
1213
1213
  validation: { startsWith: check.value },
1214
1214
  message: check.message
1215
1215
  });
1216
- status.dirty();
1216
+ status2.dirty();
1217
1217
  }
1218
1218
  } else if (check.kind === "endsWith") {
1219
1219
  if (!input2.data.endsWith(check.value)) {
@@ -1223,7 +1223,7 @@ var ZodString = class _ZodString extends ZodType {
1223
1223
  validation: { endsWith: check.value },
1224
1224
  message: check.message
1225
1225
  });
1226
- status.dirty();
1226
+ status2.dirty();
1227
1227
  }
1228
1228
  } else if (check.kind === "datetime") {
1229
1229
  const regex = datetimeRegex(check);
@@ -1234,7 +1234,7 @@ var ZodString = class _ZodString extends ZodType {
1234
1234
  validation: "datetime",
1235
1235
  message: check.message
1236
1236
  });
1237
- status.dirty();
1237
+ status2.dirty();
1238
1238
  }
1239
1239
  } else if (check.kind === "date") {
1240
1240
  const regex = dateRegex;
@@ -1245,7 +1245,7 @@ var ZodString = class _ZodString extends ZodType {
1245
1245
  validation: "date",
1246
1246
  message: check.message
1247
1247
  });
1248
- status.dirty();
1248
+ status2.dirty();
1249
1249
  }
1250
1250
  } else if (check.kind === "time") {
1251
1251
  const regex = timeRegex(check);
@@ -1256,7 +1256,7 @@ var ZodString = class _ZodString extends ZodType {
1256
1256
  validation: "time",
1257
1257
  message: check.message
1258
1258
  });
1259
- status.dirty();
1259
+ status2.dirty();
1260
1260
  }
1261
1261
  } else if (check.kind === "duration") {
1262
1262
  if (!durationRegex.test(input2.data)) {
@@ -1266,7 +1266,7 @@ var ZodString = class _ZodString extends ZodType {
1266
1266
  code: ZodIssueCode.invalid_string,
1267
1267
  message: check.message
1268
1268
  });
1269
- status.dirty();
1269
+ status2.dirty();
1270
1270
  }
1271
1271
  } else if (check.kind === "ip") {
1272
1272
  if (!isValidIP(input2.data, check.version)) {
@@ -1276,7 +1276,7 @@ var ZodString = class _ZodString extends ZodType {
1276
1276
  code: ZodIssueCode.invalid_string,
1277
1277
  message: check.message
1278
1278
  });
1279
- status.dirty();
1279
+ status2.dirty();
1280
1280
  }
1281
1281
  } else if (check.kind === "jwt") {
1282
1282
  if (!isValidJWT(input2.data, check.alg)) {
@@ -1286,7 +1286,7 @@ var ZodString = class _ZodString extends ZodType {
1286
1286
  code: ZodIssueCode.invalid_string,
1287
1287
  message: check.message
1288
1288
  });
1289
- status.dirty();
1289
+ status2.dirty();
1290
1290
  }
1291
1291
  } else if (check.kind === "cidr") {
1292
1292
  if (!isValidCidr(input2.data, check.version)) {
@@ -1296,7 +1296,7 @@ var ZodString = class _ZodString extends ZodType {
1296
1296
  code: ZodIssueCode.invalid_string,
1297
1297
  message: check.message
1298
1298
  });
1299
- status.dirty();
1299
+ status2.dirty();
1300
1300
  }
1301
1301
  } else if (check.kind === "base64") {
1302
1302
  if (!base64Regex.test(input2.data)) {
@@ -1306,7 +1306,7 @@ var ZodString = class _ZodString extends ZodType {
1306
1306
  code: ZodIssueCode.invalid_string,
1307
1307
  message: check.message
1308
1308
  });
1309
- status.dirty();
1309
+ status2.dirty();
1310
1310
  }
1311
1311
  } else if (check.kind === "base64url") {
1312
1312
  if (!base64urlRegex.test(input2.data)) {
@@ -1316,13 +1316,13 @@ var ZodString = class _ZodString extends ZodType {
1316
1316
  code: ZodIssueCode.invalid_string,
1317
1317
  message: check.message
1318
1318
  });
1319
- status.dirty();
1319
+ status2.dirty();
1320
1320
  }
1321
1321
  } else {
1322
1322
  util.assertNever(check);
1323
1323
  }
1324
1324
  }
1325
- return { status: status.value, value: input2.data };
1325
+ return { status: status2.value, value: input2.data };
1326
1326
  }
1327
1327
  _regex(regex, validation, message) {
1328
1328
  return this.refinement((data) => regex.test(data), {
@@ -1598,7 +1598,7 @@ var ZodNumber = class _ZodNumber extends ZodType {
1598
1598
  return INVALID;
1599
1599
  }
1600
1600
  let ctx = void 0;
1601
- const status = new ParseStatus();
1601
+ const status2 = new ParseStatus();
1602
1602
  for (const check of this._def.checks) {
1603
1603
  if (check.kind === "int") {
1604
1604
  if (!util.isInteger(input2.data)) {
@@ -1609,7 +1609,7 @@ var ZodNumber = class _ZodNumber extends ZodType {
1609
1609
  received: "float",
1610
1610
  message: check.message
1611
1611
  });
1612
- status.dirty();
1612
+ status2.dirty();
1613
1613
  }
1614
1614
  } else if (check.kind === "min") {
1615
1615
  const tooSmall = check.inclusive ? input2.data < check.value : input2.data <= check.value;
@@ -1623,7 +1623,7 @@ var ZodNumber = class _ZodNumber extends ZodType {
1623
1623
  exact: false,
1624
1624
  message: check.message
1625
1625
  });
1626
- status.dirty();
1626
+ status2.dirty();
1627
1627
  }
1628
1628
  } else if (check.kind === "max") {
1629
1629
  const tooBig = check.inclusive ? input2.data > check.value : input2.data >= check.value;
@@ -1637,7 +1637,7 @@ var ZodNumber = class _ZodNumber extends ZodType {
1637
1637
  exact: false,
1638
1638
  message: check.message
1639
1639
  });
1640
- status.dirty();
1640
+ status2.dirty();
1641
1641
  }
1642
1642
  } else if (check.kind === "multipleOf") {
1643
1643
  if (floatSafeRemainder(input2.data, check.value) !== 0) {
@@ -1647,7 +1647,7 @@ var ZodNumber = class _ZodNumber extends ZodType {
1647
1647
  multipleOf: check.value,
1648
1648
  message: check.message
1649
1649
  });
1650
- status.dirty();
1650
+ status2.dirty();
1651
1651
  }
1652
1652
  } else if (check.kind === "finite") {
1653
1653
  if (!Number.isFinite(input2.data)) {
@@ -1656,13 +1656,13 @@ var ZodNumber = class _ZodNumber extends ZodType {
1656
1656
  code: ZodIssueCode.not_finite,
1657
1657
  message: check.message
1658
1658
  });
1659
- status.dirty();
1659
+ status2.dirty();
1660
1660
  }
1661
1661
  } else {
1662
1662
  util.assertNever(check);
1663
1663
  }
1664
1664
  }
1665
- return { status: status.value, value: input2.data };
1665
+ return { status: status2.value, value: input2.data };
1666
1666
  }
1667
1667
  gte(value, message) {
1668
1668
  return this.setLimit("min", value, true, errorUtil.toString(message));
@@ -1827,7 +1827,7 @@ var ZodBigInt = class _ZodBigInt extends ZodType {
1827
1827
  return this._getInvalidInput(input2);
1828
1828
  }
1829
1829
  let ctx = void 0;
1830
- const status = new ParseStatus();
1830
+ const status2 = new ParseStatus();
1831
1831
  for (const check of this._def.checks) {
1832
1832
  if (check.kind === "min") {
1833
1833
  const tooSmall = check.inclusive ? input2.data < check.value : input2.data <= check.value;
@@ -1840,7 +1840,7 @@ var ZodBigInt = class _ZodBigInt extends ZodType {
1840
1840
  inclusive: check.inclusive,
1841
1841
  message: check.message
1842
1842
  });
1843
- status.dirty();
1843
+ status2.dirty();
1844
1844
  }
1845
1845
  } else if (check.kind === "max") {
1846
1846
  const tooBig = check.inclusive ? input2.data > check.value : input2.data >= check.value;
@@ -1853,7 +1853,7 @@ var ZodBigInt = class _ZodBigInt extends ZodType {
1853
1853
  inclusive: check.inclusive,
1854
1854
  message: check.message
1855
1855
  });
1856
- status.dirty();
1856
+ status2.dirty();
1857
1857
  }
1858
1858
  } else if (check.kind === "multipleOf") {
1859
1859
  if (input2.data % check.value !== BigInt(0)) {
@@ -1863,13 +1863,13 @@ var ZodBigInt = class _ZodBigInt extends ZodType {
1863
1863
  multipleOf: check.value,
1864
1864
  message: check.message
1865
1865
  });
1866
- status.dirty();
1866
+ status2.dirty();
1867
1867
  }
1868
1868
  } else {
1869
1869
  util.assertNever(check);
1870
1870
  }
1871
1871
  }
1872
- return { status: status.value, value: input2.data };
1872
+ return { status: status2.value, value: input2.data };
1873
1873
  }
1874
1874
  _getInvalidInput(input2) {
1875
1875
  const ctx = this._getOrReturnCtx(input2);
@@ -2027,7 +2027,7 @@ var ZodDate = class _ZodDate extends ZodType {
2027
2027
  });
2028
2028
  return INVALID;
2029
2029
  }
2030
- const status = new ParseStatus();
2030
+ const status2 = new ParseStatus();
2031
2031
  let ctx = void 0;
2032
2032
  for (const check of this._def.checks) {
2033
2033
  if (check.kind === "min") {
@@ -2041,7 +2041,7 @@ var ZodDate = class _ZodDate extends ZodType {
2041
2041
  minimum: check.value,
2042
2042
  type: "date"
2043
2043
  });
2044
- status.dirty();
2044
+ status2.dirty();
2045
2045
  }
2046
2046
  } else if (check.kind === "max") {
2047
2047
  if (input2.data.getTime() > check.value) {
@@ -2054,14 +2054,14 @@ var ZodDate = class _ZodDate extends ZodType {
2054
2054
  maximum: check.value,
2055
2055
  type: "date"
2056
2056
  });
2057
- status.dirty();
2057
+ status2.dirty();
2058
2058
  }
2059
2059
  } else {
2060
2060
  util.assertNever(check);
2061
2061
  }
2062
2062
  }
2063
2063
  return {
2064
- status: status.value,
2064
+ status: status2.value,
2065
2065
  value: new Date(input2.data.getTime())
2066
2066
  };
2067
2067
  }
@@ -2247,7 +2247,7 @@ ZodVoid.create = (params) => {
2247
2247
  };
2248
2248
  var ZodArray = class _ZodArray extends ZodType {
2249
2249
  _parse(input2) {
2250
- const { ctx, status } = this._processInputParams(input2);
2250
+ const { ctx, status: status2 } = this._processInputParams(input2);
2251
2251
  const def = this._def;
2252
2252
  if (ctx.parsedType !== ZodParsedType.array) {
2253
2253
  addIssueToContext(ctx, {
@@ -2270,7 +2270,7 @@ var ZodArray = class _ZodArray extends ZodType {
2270
2270
  exact: true,
2271
2271
  message: def.exactLength.message
2272
2272
  });
2273
- status.dirty();
2273
+ status2.dirty();
2274
2274
  }
2275
2275
  }
2276
2276
  if (def.minLength !== null) {
@@ -2283,7 +2283,7 @@ var ZodArray = class _ZodArray extends ZodType {
2283
2283
  exact: false,
2284
2284
  message: def.minLength.message
2285
2285
  });
2286
- status.dirty();
2286
+ status2.dirty();
2287
2287
  }
2288
2288
  }
2289
2289
  if (def.maxLength !== null) {
@@ -2296,20 +2296,20 @@ var ZodArray = class _ZodArray extends ZodType {
2296
2296
  exact: false,
2297
2297
  message: def.maxLength.message
2298
2298
  });
2299
- status.dirty();
2299
+ status2.dirty();
2300
2300
  }
2301
2301
  }
2302
2302
  if (ctx.common.async) {
2303
2303
  return Promise.all([...ctx.data].map((item, i) => {
2304
2304
  return def.type._parseAsync(new ParseInputLazyPath(ctx, item, ctx.path, i));
2305
2305
  })).then((result2) => {
2306
- return ParseStatus.mergeArray(status, result2);
2306
+ return ParseStatus.mergeArray(status2, result2);
2307
2307
  });
2308
2308
  }
2309
2309
  const result = [...ctx.data].map((item, i) => {
2310
2310
  return def.type._parseSync(new ParseInputLazyPath(ctx, item, ctx.path, i));
2311
2311
  });
2312
- return ParseStatus.mergeArray(status, result);
2312
+ return ParseStatus.mergeArray(status2, result);
2313
2313
  }
2314
2314
  get element() {
2315
2315
  return this._def.type;
@@ -2398,7 +2398,7 @@ var ZodObject = class _ZodObject extends ZodType {
2398
2398
  });
2399
2399
  return INVALID;
2400
2400
  }
2401
- const { status, ctx } = this._processInputParams(input2);
2401
+ const { status: status2, ctx } = this._processInputParams(input2);
2402
2402
  const { shape, keys: shapeKeys } = this._getCached();
2403
2403
  const extraKeys = [];
2404
2404
  if (!(this._def.catchall instanceof ZodNever && this._def.unknownKeys === "strip")) {
@@ -2433,7 +2433,7 @@ var ZodObject = class _ZodObject extends ZodType {
2433
2433
  code: ZodIssueCode.unrecognized_keys,
2434
2434
  keys: extraKeys
2435
2435
  });
2436
- status.dirty();
2436
+ status2.dirty();
2437
2437
  }
2438
2438
  } else if (unknownKeys === "strip") {
2439
2439
  } else {
@@ -2467,10 +2467,10 @@ var ZodObject = class _ZodObject extends ZodType {
2467
2467
  }
2468
2468
  return syncPairs;
2469
2469
  }).then((syncPairs) => {
2470
- return ParseStatus.mergeObjectSync(status, syncPairs);
2470
+ return ParseStatus.mergeObjectSync(status2, syncPairs);
2471
2471
  });
2472
2472
  } else {
2473
- return ParseStatus.mergeObjectSync(status, pairs);
2473
+ return ParseStatus.mergeObjectSync(status2, pairs);
2474
2474
  }
2475
2475
  }
2476
2476
  get shape() {
@@ -2948,7 +2948,7 @@ function mergeValues(a, b) {
2948
2948
  }
2949
2949
  var ZodIntersection = class extends ZodType {
2950
2950
  _parse(input2) {
2951
- const { status, ctx } = this._processInputParams(input2);
2951
+ const { status: status2, ctx } = this._processInputParams(input2);
2952
2952
  const handleParsed = (parsedLeft, parsedRight) => {
2953
2953
  if (isAborted(parsedLeft) || isAborted(parsedRight)) {
2954
2954
  return INVALID;
@@ -2961,9 +2961,9 @@ var ZodIntersection = class extends ZodType {
2961
2961
  return INVALID;
2962
2962
  }
2963
2963
  if (isDirty(parsedLeft) || isDirty(parsedRight)) {
2964
- status.dirty();
2964
+ status2.dirty();
2965
2965
  }
2966
- return { status: status.value, value: merged.data };
2966
+ return { status: status2.value, value: merged.data };
2967
2967
  };
2968
2968
  if (ctx.common.async) {
2969
2969
  return Promise.all([
@@ -3001,7 +3001,7 @@ ZodIntersection.create = (left, right, params) => {
3001
3001
  };
3002
3002
  var ZodTuple = class _ZodTuple extends ZodType {
3003
3003
  _parse(input2) {
3004
- const { status, ctx } = this._processInputParams(input2);
3004
+ const { status: status2, ctx } = this._processInputParams(input2);
3005
3005
  if (ctx.parsedType !== ZodParsedType.array) {
3006
3006
  addIssueToContext(ctx, {
3007
3007
  code: ZodIssueCode.invalid_type,
@@ -3029,7 +3029,7 @@ var ZodTuple = class _ZodTuple extends ZodType {
3029
3029
  exact: false,
3030
3030
  type: "array"
3031
3031
  });
3032
- status.dirty();
3032
+ status2.dirty();
3033
3033
  }
3034
3034
  const items = [...ctx.data].map((item, itemIndex) => {
3035
3035
  const schema = this._def.items[itemIndex] || this._def.rest;
@@ -3039,10 +3039,10 @@ var ZodTuple = class _ZodTuple extends ZodType {
3039
3039
  }).filter((x) => !!x);
3040
3040
  if (ctx.common.async) {
3041
3041
  return Promise.all(items).then((results) => {
3042
- return ParseStatus.mergeArray(status, results);
3042
+ return ParseStatus.mergeArray(status2, results);
3043
3043
  });
3044
3044
  } else {
3045
- return ParseStatus.mergeArray(status, items);
3045
+ return ParseStatus.mergeArray(status2, items);
3046
3046
  }
3047
3047
  }
3048
3048
  get items() {
@@ -3074,7 +3074,7 @@ var ZodRecord = class _ZodRecord extends ZodType {
3074
3074
  return this._def.valueType;
3075
3075
  }
3076
3076
  _parse(input2) {
3077
- const { status, ctx } = this._processInputParams(input2);
3077
+ const { status: status2, ctx } = this._processInputParams(input2);
3078
3078
  if (ctx.parsedType !== ZodParsedType.object) {
3079
3079
  addIssueToContext(ctx, {
3080
3080
  code: ZodIssueCode.invalid_type,
@@ -3094,9 +3094,9 @@ var ZodRecord = class _ZodRecord extends ZodType {
3094
3094
  });
3095
3095
  }
3096
3096
  if (ctx.common.async) {
3097
- return ParseStatus.mergeObjectAsync(status, pairs);
3097
+ return ParseStatus.mergeObjectAsync(status2, pairs);
3098
3098
  } else {
3099
- return ParseStatus.mergeObjectSync(status, pairs);
3099
+ return ParseStatus.mergeObjectSync(status2, pairs);
3100
3100
  }
3101
3101
  }
3102
3102
  get element() {
@@ -3127,7 +3127,7 @@ var ZodMap = class extends ZodType {
3127
3127
  return this._def.valueType;
3128
3128
  }
3129
3129
  _parse(input2) {
3130
- const { status, ctx } = this._processInputParams(input2);
3130
+ const { status: status2, ctx } = this._processInputParams(input2);
3131
3131
  if (ctx.parsedType !== ZodParsedType.map) {
3132
3132
  addIssueToContext(ctx, {
3133
3133
  code: ZodIssueCode.invalid_type,
@@ -3154,11 +3154,11 @@ var ZodMap = class extends ZodType {
3154
3154
  return INVALID;
3155
3155
  }
3156
3156
  if (key.status === "dirty" || value.status === "dirty") {
3157
- status.dirty();
3157
+ status2.dirty();
3158
3158
  }
3159
3159
  finalMap.set(key.value, value.value);
3160
3160
  }
3161
- return { status: status.value, value: finalMap };
3161
+ return { status: status2.value, value: finalMap };
3162
3162
  });
3163
3163
  } else {
3164
3164
  const finalMap = /* @__PURE__ */ new Map();
@@ -3169,11 +3169,11 @@ var ZodMap = class extends ZodType {
3169
3169
  return INVALID;
3170
3170
  }
3171
3171
  if (key.status === "dirty" || value.status === "dirty") {
3172
- status.dirty();
3172
+ status2.dirty();
3173
3173
  }
3174
3174
  finalMap.set(key.value, value.value);
3175
3175
  }
3176
- return { status: status.value, value: finalMap };
3176
+ return { status: status2.value, value: finalMap };
3177
3177
  }
3178
3178
  }
3179
3179
  };
@@ -3187,7 +3187,7 @@ ZodMap.create = (keyType, valueType, params) => {
3187
3187
  };
3188
3188
  var ZodSet = class _ZodSet extends ZodType {
3189
3189
  _parse(input2) {
3190
- const { status, ctx } = this._processInputParams(input2);
3190
+ const { status: status2, ctx } = this._processInputParams(input2);
3191
3191
  if (ctx.parsedType !== ZodParsedType.set) {
3192
3192
  addIssueToContext(ctx, {
3193
3193
  code: ZodIssueCode.invalid_type,
@@ -3207,7 +3207,7 @@ var ZodSet = class _ZodSet extends ZodType {
3207
3207
  exact: false,
3208
3208
  message: def.minSize.message
3209
3209
  });
3210
- status.dirty();
3210
+ status2.dirty();
3211
3211
  }
3212
3212
  }
3213
3213
  if (def.maxSize !== null) {
@@ -3220,7 +3220,7 @@ var ZodSet = class _ZodSet extends ZodType {
3220
3220
  exact: false,
3221
3221
  message: def.maxSize.message
3222
3222
  });
3223
- status.dirty();
3223
+ status2.dirty();
3224
3224
  }
3225
3225
  }
3226
3226
  const valueType = this._def.valueType;
@@ -3230,10 +3230,10 @@ var ZodSet = class _ZodSet extends ZodType {
3230
3230
  if (element.status === "aborted")
3231
3231
  return INVALID;
3232
3232
  if (element.status === "dirty")
3233
- status.dirty();
3233
+ status2.dirty();
3234
3234
  parsedSet.add(element.value);
3235
3235
  }
3236
- return { status: status.value, value: parsedSet };
3236
+ return { status: status2.value, value: parsedSet };
3237
3237
  }
3238
3238
  const elements = [...ctx.data.values()].map((item, i) => valueType._parse(new ParseInputLazyPath(ctx, item, ctx.path, i)));
3239
3239
  if (ctx.common.async) {
@@ -3564,15 +3564,15 @@ var ZodEffects = class extends ZodType {
3564
3564
  return this._def.schema._def.typeName === ZodFirstPartyTypeKind.ZodEffects ? this._def.schema.sourceType() : this._def.schema;
3565
3565
  }
3566
3566
  _parse(input2) {
3567
- const { status, ctx } = this._processInputParams(input2);
3567
+ const { status: status2, ctx } = this._processInputParams(input2);
3568
3568
  const effect = this._def.effect || null;
3569
3569
  const checkCtx = {
3570
3570
  addIssue: (arg) => {
3571
3571
  addIssueToContext(ctx, arg);
3572
3572
  if (arg.fatal) {
3573
- status.abort();
3573
+ status2.abort();
3574
3574
  } else {
3575
- status.dirty();
3575
+ status2.dirty();
3576
3576
  }
3577
3577
  },
3578
3578
  get path() {
@@ -3584,7 +3584,7 @@ var ZodEffects = class extends ZodType {
3584
3584
  const processed = effect.transform(ctx.data, checkCtx);
3585
3585
  if (ctx.common.async) {
3586
3586
  return Promise.resolve(processed).then(async (processed2) => {
3587
- if (status.value === "aborted")
3587
+ if (status2.value === "aborted")
3588
3588
  return INVALID;
3589
3589
  const result = await this._def.schema._parseAsync({
3590
3590
  data: processed2,
@@ -3595,12 +3595,12 @@ var ZodEffects = class extends ZodType {
3595
3595
  return INVALID;
3596
3596
  if (result.status === "dirty")
3597
3597
  return DIRTY(result.value);
3598
- if (status.value === "dirty")
3598
+ if (status2.value === "dirty")
3599
3599
  return DIRTY(result.value);
3600
3600
  return result;
3601
3601
  });
3602
3602
  } else {
3603
- if (status.value === "aborted")
3603
+ if (status2.value === "aborted")
3604
3604
  return INVALID;
3605
3605
  const result = this._def.schema._parseSync({
3606
3606
  data: processed,
@@ -3611,7 +3611,7 @@ var ZodEffects = class extends ZodType {
3611
3611
  return INVALID;
3612
3612
  if (result.status === "dirty")
3613
3613
  return DIRTY(result.value);
3614
- if (status.value === "dirty")
3614
+ if (status2.value === "dirty")
3615
3615
  return DIRTY(result.value);
3616
3616
  return result;
3617
3617
  }
@@ -3636,17 +3636,17 @@ var ZodEffects = class extends ZodType {
3636
3636
  if (inner.status === "aborted")
3637
3637
  return INVALID;
3638
3638
  if (inner.status === "dirty")
3639
- status.dirty();
3639
+ status2.dirty();
3640
3640
  executeRefinement(inner.value);
3641
- return { status: status.value, value: inner.value };
3641
+ return { status: status2.value, value: inner.value };
3642
3642
  } else {
3643
3643
  return this._def.schema._parseAsync({ data: ctx.data, path: ctx.path, parent: ctx }).then((inner) => {
3644
3644
  if (inner.status === "aborted")
3645
3645
  return INVALID;
3646
3646
  if (inner.status === "dirty")
3647
- status.dirty();
3647
+ status2.dirty();
3648
3648
  return executeRefinement(inner.value).then(() => {
3649
- return { status: status.value, value: inner.value };
3649
+ return { status: status2.value, value: inner.value };
3650
3650
  });
3651
3651
  });
3652
3652
  }
@@ -3664,13 +3664,13 @@ var ZodEffects = class extends ZodType {
3664
3664
  if (result instanceof Promise) {
3665
3665
  throw new Error(`Asynchronous transform encountered during synchronous parse operation. Use .parseAsync instead.`);
3666
3666
  }
3667
- return { status: status.value, value: result };
3667
+ return { status: status2.value, value: result };
3668
3668
  } else {
3669
3669
  return this._def.schema._parseAsync({ data: ctx.data, path: ctx.path, parent: ctx }).then((base) => {
3670
3670
  if (!isValid(base))
3671
3671
  return INVALID;
3672
3672
  return Promise.resolve(effect.transform(base.value, checkCtx)).then((result) => ({
3673
- status: status.value,
3673
+ status: status2.value,
3674
3674
  value: result
3675
3675
  }));
3676
3676
  });
@@ -3849,7 +3849,7 @@ var ZodBranded = class extends ZodType {
3849
3849
  };
3850
3850
  var ZodPipeline = class _ZodPipeline extends ZodType {
3851
3851
  _parse(input2) {
3852
- const { status, ctx } = this._processInputParams(input2);
3852
+ const { status: status2, ctx } = this._processInputParams(input2);
3853
3853
  if (ctx.common.async) {
3854
3854
  const handleAsync = async () => {
3855
3855
  const inResult = await this._def.in._parseAsync({
@@ -3860,7 +3860,7 @@ var ZodPipeline = class _ZodPipeline extends ZodType {
3860
3860
  if (inResult.status === "aborted")
3861
3861
  return INVALID;
3862
3862
  if (inResult.status === "dirty") {
3863
- status.dirty();
3863
+ status2.dirty();
3864
3864
  return DIRTY(inResult.value);
3865
3865
  } else {
3866
3866
  return this._def.out._parseAsync({
@@ -3880,7 +3880,7 @@ var ZodPipeline = class _ZodPipeline extends ZodType {
3880
3880
  if (inResult.status === "aborted")
3881
3881
  return INVALID;
3882
3882
  if (inResult.status === "dirty") {
3883
- status.dirty();
3883
+ status2.dirty();
3884
3884
  return {
3885
3885
  status: "dirty",
3886
3886
  value: inResult.value
@@ -4631,6 +4631,39 @@ function projectCodexThread(value) {
4631
4631
  ...typeof value.isPinned === "boolean" ? { pinned: value.isPinned } : {}
4632
4632
  };
4633
4633
  }
4634
+ function deriveCodexCwdWorkspaces(values) {
4635
+ const byPath = /* @__PURE__ */ new Map();
4636
+ const usedIds = /* @__PURE__ */ new Set();
4637
+ for (const value of values) {
4638
+ const session = projectCodexThread(value);
4639
+ if (session === void 0 || session.cwd === void 0 || session.cwd.length === 0)
4640
+ continue;
4641
+ const path = session.cwd;
4642
+ if (!isAbsoluteWorkspacePath(path))
4643
+ continue;
4644
+ const key = normalizeWorkspacePath(path);
4645
+ const existing = byPath.get(key);
4646
+ if (existing !== void 0) {
4647
+ existing.createdAt = earliestTimestamp(existing.createdAt, session.createdAt);
4648
+ existing.updatedAt = Math.max(existing.updatedAt, session.updatedAt);
4649
+ continue;
4650
+ }
4651
+ const baseId = `cwd-${hashWorkspacePath(key)}`;
4652
+ let id2 = baseId;
4653
+ for (let suffix = 2; usedIds.has(id2); suffix += 1)
4654
+ id2 = `${baseId}-${suffix}`;
4655
+ usedIds.add(id2);
4656
+ byPath.set(key, {
4657
+ id: id2,
4658
+ name: workspaceBasename(path),
4659
+ path,
4660
+ position: byPath.size,
4661
+ createdAt: session.createdAt,
4662
+ updatedAt: session.updatedAt
4663
+ });
4664
+ }
4665
+ return [...byPath.values()];
4666
+ }
4634
4667
  function projectCodexHistory(thread) {
4635
4668
  if (!isRecord(thread) || typeof thread.id !== "string" || !Array.isArray(thread.turns))
4636
4669
  return [];
@@ -4884,6 +4917,31 @@ function boundedText(value) {
4884
4917
  return value.length <= MAX_DISPLAY_ITEM_TEXT ? value : `${value.slice(0, MAX_DISPLAY_ITEM_TEXT)}
4885
4918
  \u2026`;
4886
4919
  }
4920
+ function normalizeWorkspacePath(value) {
4921
+ return value.replace(/[\\/]+$/u, "") || value;
4922
+ }
4923
+ function isAbsoluteWorkspacePath(value) {
4924
+ return value.startsWith("/") || /^\\\\[^\\]+\\[^\\]+/u.test(value) || /^[A-Za-z]:[\\/]/u.test(value);
4925
+ }
4926
+ function workspaceBasename(value) {
4927
+ const normalized = normalizeWorkspacePath(value);
4928
+ return normalized.split(/[\\/]/u).at(-1) || value;
4929
+ }
4930
+ function earliestTimestamp(left, right) {
4931
+ if (left <= 0)
4932
+ return right;
4933
+ if (right <= 0)
4934
+ return left;
4935
+ return Math.min(left, right);
4936
+ }
4937
+ function hashWorkspacePath(value) {
4938
+ let hash = 2166136261;
4939
+ for (let index = 0; index < value.length; index += 1) {
4940
+ hash ^= value.charCodeAt(index);
4941
+ hash = Math.imul(hash, 16777619);
4942
+ }
4943
+ return (hash >>> 0).toString(36);
4944
+ }
4887
4945
  function normalizeTimestamp(value) {
4888
4946
  if (typeof value !== "number" || !Number.isFinite(value) || value <= 0)
4889
4947
  return 0;
@@ -4976,13 +5034,13 @@ var RemoteClientCore = class {
4976
5034
  if (signal?.aborted)
4977
5035
  throw rpcAbortedError(method, signal.reason);
4978
5036
  const request = createRpcRequest(method, params);
4979
- const result = new Promise((resolve2, reject) => {
5037
+ const result = new Promise((resolve3, reject) => {
4980
5038
  const timer = setTimeout(() => {
4981
5039
  this.rejectPending(request.id, new RemoteClientError("RPC_TIMEOUT", `RPC ${method} timed out after ${this.timeoutMs}ms`));
4982
5040
  }, this.timeoutMs);
4983
5041
  const pending = {
4984
5042
  method,
4985
- resolve: resolve2,
5043
+ resolve: resolve3,
4986
5044
  reject,
4987
5045
  timer
4988
5046
  };
@@ -5546,8 +5604,8 @@ var RtcDataChannelTransport = class {
5546
5604
  if (this.connectPromise !== void 0)
5547
5605
  return this.connectPromise;
5548
5606
  this.armAbort(signal);
5549
- this.connectPromise = new Promise((resolve2, reject) => {
5550
- this.openResolve = resolve2;
5607
+ this.connectPromise = new Promise((resolve3, reject) => {
5608
+ this.openResolve = resolve3;
5551
5609
  this.openReject = reject;
5552
5610
  this.negotiateTimer = setTimeout(() => {
5553
5611
  void this.failOpenAfterStats(new RtcConnectError("RTC_CONNECT_TIMEOUT", `WebRTC negotiation timed out after ${this.negotiateTimeoutMs}ms.`));
@@ -5762,9 +5820,9 @@ var RtcDataChannelTransport = class {
5762
5820
  if (this.closed || this.opened)
5763
5821
  return;
5764
5822
  this.opened = true;
5765
- const resolve2 = this.openResolve;
5823
+ const resolve3 = this.openResolve;
5766
5824
  this.clearNegotiation();
5767
- void this.resolveSelectedTransport().then(() => resolve2?.());
5825
+ void this.resolveSelectedTransport().then(() => resolve3?.());
5768
5826
  };
5769
5827
  channel.onmessage = (event) => {
5770
5828
  if (this.closed || !this.opened)
@@ -6158,7 +6216,7 @@ function asError(error) {
6158
6216
  return error instanceof Error ? error : new RtcConnectError("RTC_FAILED", "WebRTC negotiation failed.");
6159
6217
  }
6160
6218
  function sleep(ms) {
6161
- return new Promise((resolve2) => setTimeout(resolve2, ms));
6219
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
6162
6220
  }
6163
6221
 
6164
6222
  // ../webrtc/dist/adaptive-transport.js
@@ -6196,8 +6254,8 @@ var AdaptiveTransport = class extends BaseTransport {
6196
6254
  this.socket = new WebSocket(this.url);
6197
6255
  this.controlFrameLimits = {};
6198
6256
  this.socket.binaryType = "arraybuffer";
6199
- await new Promise((resolve2, reject) => {
6200
- this.readyResolve = resolve2;
6257
+ await new Promise((resolve3, reject) => {
6258
+ this.readyResolve = resolve3;
6201
6259
  this.readyReject = reject;
6202
6260
  this.handshakeTimer = setTimeout(() => this.failConnection(new Error("Adaptive control handshake timed out")), this.options.handshakeTimeoutMs ?? 15e3);
6203
6261
  const socket = this.socket;
@@ -13774,7 +13832,7 @@ var ClientSecureTransport = class {
13774
13832
  }
13775
13833
  };
13776
13834
  async function waitForResponder(inner, noise) {
13777
- await new Promise((resolve2, reject) => {
13835
+ await new Promise((resolve3, reject) => {
13778
13836
  let settled = false;
13779
13837
  const timer = setTimeout(() => finish(new Error("Noise IK handshake timed out.")), 1e4);
13780
13838
  const unsubscribe = inner.onHandshake((step, data) => {
@@ -13793,7 +13851,7 @@ async function waitForResponder(inner, noise) {
13793
13851
  settled = true;
13794
13852
  clearTimeout(timer);
13795
13853
  unsubscribe();
13796
- if (error === void 0) resolve2();
13854
+ if (error === void 0) resolve3();
13797
13855
  else reject(error);
13798
13856
  };
13799
13857
  void inner.sendHandshake(1, noise.writeHandshake()).catch((error) => {
@@ -14051,7 +14109,7 @@ var AsyncFrameQueue = class {
14051
14109
  continue;
14052
14110
  }
14053
14111
  if (this.closed) return;
14054
- const next = await new Promise((resolve2) => this.waiters.push(resolve2));
14112
+ const next = await new Promise((resolve3) => this.waiters.push(resolve3));
14055
14113
  if (next.done) return;
14056
14114
  yield next.value;
14057
14115
  }
@@ -14239,7 +14297,7 @@ var AsyncValueQueue = class {
14239
14297
  if (this.error !== void 0) throw this.error;
14240
14298
  return;
14241
14299
  }
14242
- const next = await new Promise((resolve2) => this.waiters.push(resolve2));
14300
+ const next = await new Promise((resolve3) => this.waiters.push(resolve3));
14243
14301
  if (next.done) {
14244
14302
  if (this.error !== void 0) throw this.error;
14245
14303
  return;
@@ -14471,15 +14529,21 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
14471
14529
  case "session/selectModel":
14472
14530
  return business(await this.selectModel(requestArg(args), signal));
14473
14531
  case "session/canOpenWorkspacePath":
14474
- return business(false);
14532
+ return business(this.selectedWorkspaceId !== void 0);
14475
14533
  case "session/openWorkspacePath":
14476
14534
  return business(failure("bad-request", "Opening Host paths is unavailable in CodeX mode."));
14535
+ case "host/describe":
14536
+ return business(success(await this.describeHost(signal)));
14537
+ case "host/listDirectory":
14538
+ return business(await this.listDirectory(requestArg(args), signal));
14539
+ case "directoryPicker/list":
14540
+ return business(await this.listDirectory(requestArg(args), signal));
14477
14541
  case "skills/list":
14478
14542
  return business(success({ items: [] }));
14479
14543
  case "commands/list":
14480
14544
  return ok(this.commandList(args));
14481
14545
  case "commands/execute":
14482
- return ok(this.executeCommand(args));
14546
+ return ok(await this.executeCommand(args, signal));
14483
14547
  default:
14484
14548
  return fail("method-not-found", `CodeX virtual Harness does not implement ${endpoint}.`);
14485
14549
  }
@@ -14553,6 +14617,10 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
14553
14617
  permissionSelection(sessionId) {
14554
14618
  return this.selectedPermissions.get(sessionId) ?? CODEX_DEFAULT_PERMISSION;
14555
14619
  }
14620
+ setPermissionSelection(sessionId, preset) {
14621
+ this.selectedPermissions.set(sessionId, preset);
14622
+ this.publishProjection(sessionId, "permissions", codexPermissionsProjection(preset), this.nextProjectionSeq());
14623
+ }
14556
14624
  commandList(args) {
14557
14625
  nativeThreadId(requiredString(args.agentId, "agentId"));
14558
14626
  return [{
@@ -14561,9 +14629,9 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
14561
14629
  input: { hint: "<preset>" }
14562
14630
  }];
14563
14631
  }
14564
- executeCommand(args) {
14632
+ async executeCommand(args, signal) {
14565
14633
  const sessionId = requiredString(args.agentId, "agentId");
14566
- nativeThreadId(sessionId);
14634
+ const threadId = nativeThreadId(sessionId);
14567
14635
  const line = requiredString(args.line, "command line").trim();
14568
14636
  if (!Array.isArray(args.images)) throw new Error("The CodeX command image list is required.");
14569
14637
  const match = /^\/permission(?:\s+([\s\S]*))?$/u.exec(line);
@@ -14582,9 +14650,10 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
14582
14650
  commandId,
14583
14651
  result: { kind: "error", text: `unknown preset "${requested}" (available: workspace-write, danger-full-access)` }
14584
14652
  };
14585
- this.selectedPermissions.set(sessionId, requested);
14586
- this.publishProjection(sessionId, "permissions", codexPermissionsProjection(requested), this.nextProjectionSeq());
14587
- return { commandId, result: { kind: "success", text: `preset ${requested}` } };
14653
+ const result = await this.client.request("thread/resume", { threadId, permissionPreset: requested }, signal);
14654
+ const effective = codexPermissionPresetFromResponse(result) ?? requested;
14655
+ this.setPermissionSelection(sessionId, effective);
14656
+ return { commandId, result: { kind: "success", text: `preset ${effective}` } };
14588
14657
  }
14589
14658
  sessionTitle(sessionId) {
14590
14659
  const session = this.catalog?.sessions.find((item) => item.id === sessionId);
@@ -14728,7 +14797,7 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
14728
14797
  queue,
14729
14798
  nextSeq: history.cursor + 1,
14730
14799
  turn: history.nextTurn,
14731
- stepOpen: false,
14800
+ stepOpen: history.activeTurnId !== void 0,
14732
14801
  startedItems: /* @__PURE__ */ new Set(),
14733
14802
  completedItems: /* @__PURE__ */ new Set(),
14734
14803
  streamedBlocks: /* @__PURE__ */ new Map(),
@@ -14737,7 +14806,8 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
14737
14806
  liveItems: /* @__PURE__ */ new Map(),
14738
14807
  liveToolOutput: /* @__PURE__ */ new Map(),
14739
14808
  liveToolResultSeq: /* @__PURE__ */ new Map(),
14740
- requestId: this.pendingRequestIds.get(sessionId)
14809
+ requestId: this.pendingRequestIds.get(sessionId),
14810
+ ...history.activeTurnId === void 0 ? {} : { activeTurnId: history.activeTurnId }
14741
14811
  };
14742
14812
  this.follows.add(follow);
14743
14813
  queue.push({
@@ -14786,6 +14856,11 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
14786
14856
  this.publishProjection(follow.sessionId, "title", title, seq);
14787
14857
  return;
14788
14858
  }
14859
+ if (frame.method === "thread/settings/updated") {
14860
+ const preset = codexPermissionPresetFromResponse(params);
14861
+ if (preset !== void 0) this.setPermissionSelection(follow.sessionId, preset);
14862
+ return;
14863
+ }
14789
14864
  if (frame.method === "turn/started") {
14790
14865
  const turn = record(params.turn);
14791
14866
  this.resetLiveTurn(follow);
@@ -14840,8 +14915,8 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
14840
14915
  const item = record(value);
14841
14916
  const content = string(item.step);
14842
14917
  if (content === void 0) return [];
14843
- const status = item.status === "inProgress" ? "in_progress" : item.status === "completed" ? "completed" : "pending";
14844
- return [{ content, status }];
14918
+ const status2 = item.status === "inProgress" ? "in_progress" : item.status === "completed" ? "completed" : "pending";
14919
+ return [{ content, status: status2 }];
14845
14920
  });
14846
14921
  this.pushEvent(follow, "todo/write", { todos });
14847
14922
  return;
@@ -14871,10 +14946,10 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
14871
14946
  return;
14872
14947
  }
14873
14948
  if (frame.method === "thread/status/changed") {
14874
- const status = record(params.status);
14875
- const running = status.type === "active";
14949
+ const status2 = record(params.status);
14950
+ const running = status2.type === "active";
14876
14951
  this.emitRemoteEvent("api-session/status", [follow.sessionId, running]);
14877
- if (status.type === "systemError") {
14952
+ if (status2.type === "systemError") {
14878
14953
  this.broadcastRcHost({
14879
14954
  type: "host/agent-error",
14880
14955
  sessionId: follow.sessionId,
@@ -15154,6 +15229,46 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
15154
15229
  const workspace = catalog.workspaces.find((item) => sessionId !== void 0 && item.sessionIds.includes(sessionId));
15155
15230
  return workspace === void 0 ? failure("workspace-not-found", "The CodeX virtual Workspace was not found.") : success({ workspace: nativeWorkspace(workspace) });
15156
15231
  }
15232
+ selectedWorkspace(catalog) {
15233
+ return this.selectedWorkspaceId === void 0 ? void 0 : catalog.workspaces.find((item) => item.workspaceId === this.selectedWorkspaceId);
15234
+ }
15235
+ async describeHost(signal) {
15236
+ const catalog = await this.currentCatalog(signal);
15237
+ const workspace = this.selectedWorkspace(catalog);
15238
+ const models = await this.models(signal).catch(() => void 0);
15239
+ return {
15240
+ version: "CodeX Remote",
15241
+ cwd: workspace?.path ?? "",
15242
+ home: workspace?.path ?? "",
15243
+ provider: "CodeX",
15244
+ ...models === void 0 ? {} : { model: models.default.model },
15245
+ attachedSessions: catalog.sessions.length,
15246
+ canOpenPath: workspace !== void 0
15247
+ };
15248
+ }
15249
+ async listDirectory(request, signal) {
15250
+ const catalog = await this.currentCatalog(signal);
15251
+ const workspace = this.selectedWorkspace(catalog);
15252
+ if (workspace === void 0) return failure("workspace-not-found", "The CodeX virtual Workspace was not found.");
15253
+ const path = string(request.path) ?? workspace.path;
15254
+ if (!containsPath(workspace.path, path)) {
15255
+ return failure("workspace-not-found", "The selected directory is outside the current CodeX virtual Workspace.");
15256
+ }
15257
+ const listing = record(await this.client.request("dsh/directoryList", { path }, signal));
15258
+ if (!isCodexDirectoryListing(listing)) {
15259
+ return failure("internal", "CodeX Remote returned an invalid directory listing.");
15260
+ }
15261
+ const listedPaths = [
15262
+ listing.path,
15263
+ listing.home,
15264
+ ...listing.crumbs.map((item) => item.path),
15265
+ ...listing.entries.map((item) => item.path)
15266
+ ];
15267
+ if (listedPaths.some((path2) => !containsPath(workspace.path, path2))) {
15268
+ return failure("internal", "CodeX Remote returned a directory outside the current Workspace.");
15269
+ }
15270
+ return success(listing);
15271
+ }
15157
15272
  async archiveSession(request, signal) {
15158
15273
  const sessionId = requiredString(request.sessionId, "sessionId");
15159
15274
  await this.client.request("thread/archive", { threadId: nativeThreadId(sessionId) }, signal);
@@ -15164,16 +15279,21 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
15164
15279
  async createSession(request, signal) {
15165
15280
  const catalog = await this.currentCatalog(signal);
15166
15281
  const workspaceId = string(request.workspaceId) ?? this.selectedWorkspaceId;
15167
- const cwd = string(request.cwd) ?? (workspaceId === void 0 ? void 0 : catalog.workspaces.find((item) => item.workspaceId === workspaceId)?.path);
15282
+ const workspace = workspaceId === void 0 ? void 0 : catalog.workspaces.find((item) => item.workspaceId === workspaceId);
15283
+ const cwd = string(request.cwd) ?? workspace?.path;
15168
15284
  if (cwd === void 0) return failure("workspace-not-found", "The CodeX virtual Workspace was not found.");
15285
+ if (workspace !== void 0 && !containsPath(workspace.path, cwd)) {
15286
+ return failure("workspace-not-found", "The selected directory is outside the current CodeX virtual Workspace.");
15287
+ }
15169
15288
  const directory = await this.models(signal);
15170
15289
  const selection = directory.default;
15171
- const permissionPreset2 = CODEX_DEFAULT_PERMISSION;
15290
+ const requestedPreset = CODEX_DEFAULT_PERMISSION;
15172
15291
  const result = record(await this.client.request("thread/start", {
15173
15292
  cwd,
15174
15293
  model: selection.model,
15175
- permissionPreset: permissionPreset2
15294
+ permissionPreset: requestedPreset
15176
15295
  }, signal));
15296
+ const permissionPreset2 = codexPermissionPresetFromResponse(result) ?? requestedPreset;
15177
15297
  const thread = { ...record(result.thread), cwd, turns: array(record(result.thread).turns) };
15178
15298
  const projected = projectCodexThread(thread);
15179
15299
  if (projected === void 0) return failure("internal", "CodeX returned an invalid Thread.");
@@ -15194,11 +15314,12 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
15194
15314
  }
15195
15315
  async forkSession(request, signal) {
15196
15316
  const sessionId = requiredString(request.sessionId, "sessionId");
15197
- const permissionPreset2 = this.permissionSelection(sessionId);
15317
+ let permissionPreset2 = this.selectedPermissions.get(sessionId);
15198
15318
  const result = record(await this.client.request("thread/fork", {
15199
15319
  threadId: nativeThreadId(sessionId),
15200
- permissionPreset: permissionPreset2
15320
+ ...permissionPreset2 === void 0 ? {} : { permissionPreset: permissionPreset2 }
15201
15321
  }, signal));
15322
+ permissionPreset2 = codexPermissionPresetFromResponse(result) ?? permissionPreset2 ?? CODEX_DEFAULT_PERMISSION;
15202
15323
  const projected = projectCodexThread(record(result.thread));
15203
15324
  if (projected === void 0) return failure("internal", "CodeX returned an invalid forked Thread.");
15204
15325
  this.selectedModels.set(projected.id, this.modelSelection(sessionId, await this.models(signal)));
@@ -15215,14 +15336,20 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
15215
15336
  if (input2 === void 0) return failure("attachment-error", "CodeX virtual Sessions accept text and pasted PNG, JPEG, WebP, or GIF images only.");
15216
15337
  const threadId = nativeThreadId(sessionId);
15217
15338
  const selection = this.modelSelection(sessionId, await this.models(signal));
15218
- const permissionPreset2 = this.permissionSelection(sessionId);
15339
+ let permissionPreset2 = this.selectedPermissions.get(sessionId);
15219
15340
  const isFreshBlankThread = this.blankThreads.has(threadId) || this.pendingThreads.has(threadId);
15220
15341
  await this.ensureRcFollow(sessionId);
15221
- if (!isFreshBlankThread) await this.client.request("thread/resume", {
15222
- threadId,
15223
- model: selection.model,
15224
- permissionPreset: permissionPreset2
15225
- }, signal);
15342
+ if (!isFreshBlankThread) {
15343
+ const resumeResult = await this.client.request("thread/resume", {
15344
+ threadId,
15345
+ model: selection.model
15346
+ }, signal);
15347
+ const serverPreset = codexPermissionPresetFromResponse(resumeResult);
15348
+ if (permissionPreset2 === void 0 && serverPreset !== void 0) {
15349
+ permissionPreset2 = serverPreset;
15350
+ this.setPermissionSelection(sessionId, serverPreset);
15351
+ }
15352
+ }
15226
15353
  this.pendingRequestIds.set(sessionId, string(request.requestId) ?? "");
15227
15354
  const mode = request.mode === "steer" ? "turn/steer" : "turn/start";
15228
15355
  if (mode === "turn/steer") {
@@ -15234,7 +15361,7 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
15234
15361
  threadId,
15235
15362
  input: input2,
15236
15363
  ...codexModelParams(selection),
15237
- permissionPreset: permissionPreset2
15364
+ ...permissionPreset2 === void 0 ? {} : { permissionPreset: permissionPreset2 }
15238
15365
  }, signal);
15239
15366
  }
15240
15367
  this.blankThreads.delete(threadId);
@@ -15376,7 +15503,7 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
15376
15503
  queue: new AsyncValueQueue2(controller.signal),
15377
15504
  nextSeq: history.cursor + 1,
15378
15505
  turn: history.nextTurn,
15379
- stepOpen: false,
15506
+ stepOpen: history.activeTurnId !== void 0,
15380
15507
  startedItems: /* @__PURE__ */ new Set(),
15381
15508
  completedItems: /* @__PURE__ */ new Set(),
15382
15509
  streamedBlocks: /* @__PURE__ */ new Map(),
@@ -15386,7 +15513,8 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
15386
15513
  liveToolOutput: /* @__PURE__ */ new Map(),
15387
15514
  liveToolResultSeq: /* @__PURE__ */ new Map(),
15388
15515
  requestId: this.pendingRequestIds.get(sessionId),
15389
- rcOnly: true
15516
+ rcOnly: true,
15517
+ ...history.activeTurnId === void 0 ? {} : { activeTurnId: history.activeTurnId }
15390
15518
  };
15391
15519
  this.follows.add(follow);
15392
15520
  try {
@@ -15462,7 +15590,10 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
15462
15590
  archiveSession: call("workspace.archiveSession")
15463
15591
  },
15464
15592
  subagents: {},
15465
- host: {},
15593
+ host: {
15594
+ describe: call("host.describe"),
15595
+ listDirectory: call("host.listDirectory")
15596
+ },
15466
15597
  skills: { list: call("skills.list") },
15467
15598
  agentPresets: {},
15468
15599
  goals: {},
@@ -15520,7 +15651,7 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
15520
15651
  }
15521
15652
  };
15522
15653
  async function loadCatalog(client, signal, pendingThreads) {
15523
- const projects = await loadCodexProjects(client, signal);
15654
+ let projects = await loadCodexProjects(client, signal);
15524
15655
  const threads = [];
15525
15656
  let cursor2;
15526
15657
  for (let page = 0; page < MAX_CODEX_PAGES; page += 1) {
@@ -15545,6 +15676,16 @@ async function loadCatalog(client, signal, pendingThreads) {
15545
15676
  else threads.unshift(thread);
15546
15677
  }
15547
15678
  }
15679
+ if (projects.length === 0) {
15680
+ projects = deriveCodexCwdWorkspaces(threads).map((workspace) => ({
15681
+ id: workspace.id,
15682
+ name: workspace.name,
15683
+ roots: [{ path: workspace.path }],
15684
+ position: workspace.position,
15685
+ createdAt: workspace.createdAt,
15686
+ updatedAt: workspace.updatedAt
15687
+ }));
15688
+ }
15548
15689
  const projected = threads.map((thread) => {
15549
15690
  const session = projectCodexThread(thread);
15550
15691
  return session === void 0 ? void 0 : { thread, session };
@@ -15653,6 +15794,7 @@ function projectCodexNativeHistory(thread, sessionId) {
15653
15794
  const entries = [];
15654
15795
  let seq = 0;
15655
15796
  let turnNumber = 0;
15797
+ const active = activeTurnId(thread.turns);
15656
15798
  const append = (type, data, time = Date.now(), view, sourceEventSeqs) => {
15657
15799
  const eventSeq = seq++;
15658
15800
  entries.push({
@@ -15672,6 +15814,7 @@ function projectCodexNativeHistory(thread, sessionId) {
15672
15814
  for (const rawTurn of array(thread.turns)) {
15673
15815
  const turn = record(rawTurn);
15674
15816
  turnNumber += 1;
15817
+ const turnActive = isActiveCodexTurn(turn);
15675
15818
  const time = normalizeTime(turn.createdAt) || normalizeTime(turn.startedAt) || normalizeTime(thread.createdAt) || Date.now();
15676
15819
  append("turn/start", { turn: turnNumber }, time);
15677
15820
  append("step/start", { turn: turnNumber, step: 1 }, time);
@@ -15689,6 +15832,7 @@ function projectCodexNativeHistory(thread, sessionId) {
15689
15832
  if (event.type === "tool/call") toolCallSeq = eventSeq;
15690
15833
  }
15691
15834
  }
15835
+ if (turnActive) continue;
15692
15836
  append("step/end", { turn: turnNumber, step: 1 }, normalizeTime(turn.updatedAt) || normalizeTime(turn.completedAt) || time);
15693
15837
  append("turn/end", {
15694
15838
  turn: turnNumber,
@@ -15705,7 +15849,8 @@ function projectCodexNativeHistory(thread, sessionId) {
15705
15849
  },
15706
15850
  entries,
15707
15851
  lastSeq: seq - 1,
15708
- nextTurn: turnNumber
15852
+ nextTurn: turnNumber,
15853
+ ...active === void 0 ? {} : { activeTurnId: active }
15709
15854
  };
15710
15855
  }
15711
15856
  function paginateCodexNativeHistory(history, request) {
@@ -15729,7 +15874,8 @@ function paginateCodexNativeHistory(history, request) {
15729
15874
  cursor: history.lastSeq,
15730
15875
  nextTurn: history.nextTurn,
15731
15876
  records: window.filter((entry) => entry.event.seq >= cut),
15732
- hasMore: window.some((entry) => entry.event.seq < cut)
15877
+ hasMore: window.some((entry) => entry.event.seq < cut),
15878
+ ...history.activeTurnId === void 0 ? {} : { activeTurnId: history.activeTurnId }
15733
15879
  };
15734
15880
  }
15735
15881
  function itemEvents(item, turn, step, requestId, selection = modelSelection(), sessionId = CODEX_SESSION_PREFIX) {
@@ -16242,6 +16388,22 @@ function codexImageLimitsProjection() {
16242
16388
  function isCodexPermissionPreset(value) {
16243
16389
  return value === "workspace-write" || value === "danger-full-access";
16244
16390
  }
16391
+ function codexPermissionPresetFromResponse(value) {
16392
+ const root = record(value);
16393
+ return codexPermissionPresetFromPolicyRecord(root) ?? codexPermissionPresetFromPolicyRecord(record(root.threadSettings));
16394
+ }
16395
+ function codexPermissionPresetFromPolicyRecord(value) {
16396
+ const sandbox = value.sandbox ?? value.sandboxPolicy;
16397
+ if (value.approvalPolicy === "never" && isDangerFullAccessSandbox(sandbox)) return "danger-full-access";
16398
+ if (value.approvalPolicy === "on-request" && isWorkspaceWriteSandbox(sandbox)) return "workspace-write";
16399
+ return void 0;
16400
+ }
16401
+ function isDangerFullAccessSandbox(value) {
16402
+ return value === "danger-full-access" || record(value).type === "dangerFullAccess";
16403
+ }
16404
+ function isWorkspaceWriteSandbox(value) {
16405
+ return value === "workspace-write" || record(value).type === "workspaceWrite";
16406
+ }
16245
16407
  function modelCatalog(directory) {
16246
16408
  return {
16247
16409
  default: directory.default,
@@ -16382,6 +16544,17 @@ function nativeThreadId(sessionId) {
16382
16544
  }
16383
16545
  return sessionId.slice(CODEX_SESSION_PREFIX.length);
16384
16546
  }
16547
+ function activeTurnId(value) {
16548
+ if (!Array.isArray(value)) return void 0;
16549
+ for (let index = value.length - 1; index >= 0; index -= 1) {
16550
+ const turn = record(value[index]);
16551
+ if (isActiveCodexTurn(turn) && typeof turn.id === "string" && turn.id.length > 0) return turn.id;
16552
+ }
16553
+ return void 0;
16554
+ }
16555
+ function isActiveCodexTurn(turn) {
16556
+ return turn.status === "inProgress" || turn.status === "running";
16557
+ }
16385
16558
  function carrierArgs(payload) {
16386
16559
  return record(record(payload).args);
16387
16560
  }
@@ -16410,6 +16583,14 @@ function business(value) {
16410
16583
  }
16411
16584
  return ok(value);
16412
16585
  }
16586
+ function isCodexDirectoryListing(value) {
16587
+ const listing = record(value);
16588
+ return typeof listing.path === "string" && typeof listing.home === "string" && Array.isArray(listing.crumbs) && listing.crumbs.every(isCodexDirectoryEntry) && Array.isArray(listing.entries) && listing.entries.every(isCodexDirectoryEntry) && typeof listing.truncated === "boolean";
16589
+ }
16590
+ function isCodexDirectoryEntry(value) {
16591
+ const entry = record(value);
16592
+ return typeof entry.name === "string" && typeof entry.path === "string" && typeof entry.hidden === "boolean";
16593
+ }
16413
16594
  function fail(code, message, details = {}) {
16414
16595
  return { ok: false, error: { code, message, details } };
16415
16596
  }
@@ -16464,7 +16645,7 @@ function optionalPositiveInteger(value) {
16464
16645
  function isCodexNativeHistoryPage(value, sessionId) {
16465
16646
  const page = record(value);
16466
16647
  const header = record(page.header);
16467
- return header.id === sessionId && integer(page.cursor) !== void 0 && integer(page.nextTurn) !== void 0 && Array.isArray(page.records) && typeof page.hasMore === "boolean";
16648
+ return header.id === sessionId && integer(page.cursor) !== void 0 && integer(page.nextTurn) !== void 0 && Array.isArray(page.records) && typeof page.hasMore === "boolean" && (page.activeTurnId === void 0 || typeof page.activeTurnId === "string");
16468
16649
  }
16469
16650
  function truncateCodePoints(value, maximum) {
16470
16651
  return [...value].slice(0, maximum).join("");
@@ -16541,7 +16722,7 @@ var AsyncValueQueue2 = class {
16541
16722
  continue;
16542
16723
  }
16543
16724
  if (this.closed) return;
16544
- const next = await new Promise((resolve2) => this.waiters.push(resolve2));
16725
+ const next = await new Promise((resolve3) => this.waiters.push(resolve3));
16545
16726
  if (next.done) return;
16546
16727
  yield next.value;
16547
16728
  }
@@ -16554,6 +16735,7 @@ import { platform } from "node:os";
16554
16735
  // src/config.ts
16555
16736
  import { hostname } from "node:os";
16556
16737
  import s from "@deepseek-ai/schemastery";
16738
+ var DEFAULT_REMOTE_SERVER_URL = "https://dsh.r2049.cn";
16557
16739
  var Config = s.object({
16558
16740
  enabled: s.boolean(),
16559
16741
  role: s.union(["host", "client", "both"]),
@@ -16643,9 +16825,10 @@ function normalizeServerUrl(value) {
16643
16825
  }
16644
16826
 
16645
16827
  // src/version.ts
16646
- var PLUGIN_VERSION = "0.4.3";
16828
+ var PLUGIN_VERSION = "0.4.5";
16647
16829
 
16648
16830
  // src/server-api.ts
16831
+ var TERMINAL_CONTROL_CHARACTERS = /[\u0000-\u001f\u007f-\u009f]/u;
16649
16832
  var HostServerApi = class {
16650
16833
  constructor(serverUrl, store, fetchImplementation = fetch, role = "host") {
16651
16834
  this.store = store;
@@ -16694,20 +16877,20 @@ var HostServerApi = class {
16694
16877
  if (account.length === 0 || password.length === 0) {
16695
16878
  throw new ServerApiError("INVALID_MESSAGE", "Email and password are required.", false);
16696
16879
  }
16697
- const login = validateWebLogin(await this.publicRequest("/api/v1/auth/login", {
16880
+ const login2 = validateWebLogin(await this.publicRequest("/api/v1/auth/login", {
16698
16881
  method: "POST",
16699
16882
  body: JSON.stringify({ email: account, password })
16700
16883
  }));
16701
16884
  await this.register(identity, {
16702
- accountToken: login.token,
16703
- account: login.account,
16885
+ accountToken: login2.token,
16886
+ account: login2.account,
16704
16887
  authorizationMethod: "account"
16705
16888
  });
16706
16889
  return {
16707
16890
  method: "account",
16708
- account: login.account,
16709
- expiresAt: login.expiresAt,
16710
- isAdmin: login.isAdmin
16891
+ account: login2.account,
16892
+ expiresAt: login2.expiresAt,
16893
+ isAdmin: login2.isAdmin
16711
16894
  };
16712
16895
  }
16713
16896
  async startOAuthQrLogin(provider = "zhihu") {
@@ -16715,10 +16898,11 @@ var HostServerApi = class {
16715
16898
  method: "POST",
16716
16899
  body: "{}"
16717
16900
  }), "QR login");
16718
- if (typeof value.qrId !== "string" || value.qrId.length < 20 || typeof value.scanUrl !== "string" || !value.scanUrl.startsWith(`${this.baseUrl}/`) || !Number.isSafeInteger(value.expiresIn) || provider === "github" && value.provider !== "github" || value.provider !== void 0 && value.provider !== provider) {
16901
+ const scanUrl = normalizeOAuthScanUrl(value.scanUrl, this.baseUrl);
16902
+ if (typeof value.qrId !== "string" || value.qrId.length < 20 || scanUrl === void 0 || !Number.isSafeInteger(value.expiresIn) || provider === "github" && value.provider !== "github" || value.provider !== void 0 && value.provider !== provider) {
16719
16903
  throw new ServerApiError("INVALID_MESSAGE", "The Server returned an invalid QR login session.", false);
16720
16904
  }
16721
- return { qrId: value.qrId, scanUrl: value.scanUrl, expiresIn: value.expiresIn };
16905
+ return { qrId: value.qrId, scanUrl, expiresIn: value.expiresIn };
16722
16906
  }
16723
16907
  async pollOAuthQrLogin(identity, qrId, recoverIdentity) {
16724
16908
  this.bindIdentity(identity);
@@ -16933,17 +17117,26 @@ var HostServerApi = class {
16933
17117
  return this.identity;
16934
17118
  }
16935
17119
  };
17120
+ function normalizeOAuthScanUrl(value, baseUrl) {
17121
+ if (typeof value !== "string" || TERMINAL_CONTROL_CHARACTERS.test(value)) return void 0;
17122
+ try {
17123
+ const normalized = new URL(value).href;
17124
+ return normalized.startsWith(`${baseUrl}/`) ? normalized : void 0;
17125
+ } catch {
17126
+ return void 0;
17127
+ }
17128
+ }
16936
17129
  var ClientServerApi = class extends HostServerApi {
16937
17130
  constructor(serverUrl, store, fetchImplementation = fetch) {
16938
17131
  super(serverUrl, store, fetchImplementation, "client");
16939
17132
  }
16940
17133
  };
16941
17134
  var ServerApiError = class extends Error {
16942
- constructor(code, message, retryable, status) {
17135
+ constructor(code, message, retryable, status2) {
16943
17136
  super(message);
16944
17137
  this.code = code;
16945
17138
  this.retryable = retryable;
16946
- this.status = status;
17139
+ this.status = status2;
16947
17140
  }
16948
17141
  };
16949
17142
  function validateTokens(value) {
@@ -16974,12 +17167,12 @@ async function parseBody(response) {
16974
17167
  throw new ServerApiError("INVALID_MESSAGE", "The Server returned invalid JSON.", false, response.status);
16975
17168
  }
16976
17169
  }
16977
- function mapStatus(status) {
16978
- if (status === 401) return "AUTH_INVALID";
16979
- if (status === 403) return "AUTH_REQUIRED";
16980
- if (status === 404) return "DEVICE_NOT_FOUND";
16981
- if (status === 429) return "RATE_LIMITED";
16982
- return status >= 500 ? "CONNECTION_FAILED" : "INVALID_MESSAGE";
17170
+ function mapStatus(status2) {
17171
+ if (status2 === 401) return "AUTH_INVALID";
17172
+ if (status2 === 403) return "AUTH_REQUIRED";
17173
+ if (status2 === 404) return "DEVICE_NOT_FOUND";
17174
+ if (status2 === 429) return "RATE_LIMITED";
17175
+ return status2 >= 500 ? "CONNECTION_FAILED" : "INVALID_MESSAGE";
16983
17176
  }
16984
17177
  function parseHostDevice(value) {
16985
17178
  const item = requireRecord(value, "host device");
@@ -17281,10 +17474,10 @@ function isUsableExternalNode(candidate, requireFrom) {
17281
17474
  }
17282
17475
  function isExecutableFile(candidate) {
17283
17476
  try {
17284
- const stat4 = statSync(candidate);
17285
- if (!stat4.isFile()) return false;
17477
+ const stat6 = statSync(candidate);
17478
+ if (!stat6.isFile()) return false;
17286
17479
  if (process.platform === "win32") return true;
17287
- return (stat4.mode & 73) !== 0;
17480
+ return (stat6.mode & 73) !== 0;
17288
17481
  } catch {
17289
17482
  return false;
17290
17483
  }
@@ -17373,8 +17566,8 @@ var ExternalNativePeerConnection = class {
17373
17566
  request(method, payload) {
17374
17567
  if (this.closed) return Promise.reject(new Error("native rtc helper is closed"));
17375
17568
  const id2 = this.nextRequestId++;
17376
- const promise = new Promise((resolve2, reject) => {
17377
- this.pending.set(id2, { resolve: resolve2, reject });
17569
+ const promise = new Promise((resolve3, reject) => {
17570
+ this.pending.set(id2, { resolve: resolve3, reject });
17378
17571
  });
17379
17572
  this.write({ id: id2, method, payload });
17380
17573
  return promise;
@@ -18168,10 +18361,10 @@ function parseRouteTarget(value) {
18168
18361
  async function detectRouteHostIpv4(target, timeoutMs) {
18169
18362
  const socket = createSocket("udp4");
18170
18363
  try {
18171
- return await new Promise((resolve2, reject) => {
18364
+ return await new Promise((resolve3, reject) => {
18172
18365
  const timer = setTimeout(() => {
18173
18366
  cleanup();
18174
- resolve2(void 0);
18367
+ resolve3(void 0);
18175
18368
  }, timeoutMs);
18176
18369
  const cleanup = () => {
18177
18370
  clearTimeout(timer);
@@ -18185,7 +18378,7 @@ async function detectRouteHostIpv4(target, timeoutMs) {
18185
18378
  socket.connect(target.port, target.host, () => {
18186
18379
  cleanup();
18187
18380
  const address = socket.address();
18188
- resolve2(typeof address === "string" ? void 0 : address.address);
18381
+ resolve3(typeof address === "string" ? void 0 : address.address);
18189
18382
  });
18190
18383
  });
18191
18384
  } finally {
@@ -19139,7 +19332,7 @@ async function probeRemoteHostFeatures(client, clientVersion) {
19139
19332
  }
19140
19333
  async function waitForCodexFrames(stream, signal) {
19141
19334
  if (signal?.aborted) throw new ClientModeError("RPC_ABORTED", "The Codex event poll was cancelled.");
19142
- await new Promise((resolve2, reject) => {
19335
+ await new Promise((resolve3, reject) => {
19143
19336
  const previousWake = stream.wake;
19144
19337
  const timer = setTimeout(done, 25e3);
19145
19338
  const onAbort = () => {
@@ -19153,7 +19346,7 @@ async function waitForCodexFrames(stream, signal) {
19153
19346
  }
19154
19347
  function done() {
19155
19348
  cleanup();
19156
- resolve2();
19349
+ resolve3();
19157
19350
  }
19158
19351
  stream.wake = () => {
19159
19352
  previousWake();
@@ -20368,7 +20561,7 @@ var HostServerConnection = class {
20368
20561
  this.controlFrameLimits = {};
20369
20562
  let acknowledged = false;
20370
20563
  let messageQueue = Promise.resolve();
20371
- await new Promise((resolve2, reject) => {
20564
+ await new Promise((resolve3, reject) => {
20372
20565
  let settled = false;
20373
20566
  const helloTimer = setTimeout(() => socket.close(4001, "hello timeout"), 1e4);
20374
20567
  const finish = (error) => {
@@ -20377,7 +20570,7 @@ var HostServerConnection = class {
20377
20570
  clearTimeout(helloTimer);
20378
20571
  this.online = false;
20379
20572
  if (this.socket === socket) this.socket = void 0;
20380
- void this.dropTunnels().finally(() => error === void 0 ? resolve2() : reject(error));
20573
+ void this.dropTunnels().finally(() => error === void 0 ? resolve3() : reject(error));
20381
20574
  };
20382
20575
  socket.onopen = () => {
20383
20576
  this.sendControl("hello", {
@@ -20895,14 +21088,14 @@ var HostServerConnection = class {
20895
21088
  waitBeforeRetry(baseDelay) {
20896
21089
  const spread = baseDelay * this.config.reconnect.jitter;
20897
21090
  const delay = Math.max(0, Math.round(baseDelay - spread + Math.random() * spread * 2));
20898
- return new Promise((resolve2) => {
21091
+ return new Promise((resolve3) => {
20899
21092
  const timer = setTimeout(() => {
20900
21093
  this.retryWake = void 0;
20901
- resolve2();
21094
+ resolve3();
20902
21095
  }, delay);
20903
21096
  this.retryWake = () => {
20904
21097
  clearTimeout(timer);
20905
- resolve2();
21098
+ resolve3();
20906
21099
  };
20907
21100
  });
20908
21101
  }
@@ -21353,7 +21546,9 @@ var HarnessApiBridge = class {
21353
21546
  const value = await listRemoteDirectory(typeof payload.path === "string" ? payload.path : void 0, signal);
21354
21547
  response = { rpcId: params.rpcId, result: { ok: true, value } };
21355
21548
  }
21356
- if (params.method === "host.describe") response = normalizeHostDescribeVersion(response, this.harnessVersion);
21549
+ if (params.method === "host.describe") {
21550
+ response = normalizeHostDescribe(response, this.harnessVersion, this.methods.has("host.listDirectory"));
21551
+ }
21357
21552
  this.logger?.debug("harness api call ok", {
21358
21553
  method: params.method,
21359
21554
  durationMs: Math.round(performance.now() - startedAt)
@@ -21584,8 +21779,8 @@ function needsRemoteDirectoryFallback(response) {
21584
21779
  const result = response.result;
21585
21780
  return typeof result === "object" && result !== null && "ok" in result && result.ok === false && "error" in result && typeof result.error === "object" && result.error !== null && "code" in result.error && result.error.code === "directory-picker-unavailable";
21586
21781
  }
21587
- function normalizeHostDescribeVersion(response, harnessVersion) {
21588
- if (!response.result.ok || harnessVersion === void 0) return response;
21782
+ function normalizeHostDescribe(response, harnessVersion, canListDirectory) {
21783
+ if (!response.result.ok) return response;
21589
21784
  const value = response.result.value;
21590
21785
  if (typeof value !== "object" || value === null || Array.isArray(value)) return response;
21591
21786
  const description = value;
@@ -21593,12 +21788,19 @@ function normalizeHostDescribeVersion(response, harnessVersion) {
21593
21788
  normalizeHarnessVersion(description.version),
21594
21789
  harnessVersion
21595
21790
  );
21596
- if (selectedVersion === void 0 || description.version === selectedVersion) return response;
21791
+ const versionChanged = selectedVersion !== void 0 && description.version !== selectedVersion;
21792
+ const canOpenPathChanged = canListDirectory && description.canOpenPath !== true;
21793
+ if (!versionChanged && !canOpenPathChanged) return response;
21794
+ const normalized = {
21795
+ ...description,
21796
+ ...versionChanged ? { version: selectedVersion } : {},
21797
+ ...canOpenPathChanged ? { canOpenPath: true } : {}
21798
+ };
21597
21799
  return {
21598
21800
  ...response,
21599
21801
  result: {
21600
21802
  ...response.result,
21601
- value: { ...description, version: selectedVersion }
21803
+ value: normalized
21602
21804
  }
21603
21805
  };
21604
21806
  }
@@ -21868,7 +22070,7 @@ function historyRequestPayload(payload, maxMessages) {
21868
22070
  return { ...payload, maxMessages };
21869
22071
  }
21870
22072
  function withTimeout(promise, ms, message) {
21871
- return new Promise((resolve2, reject) => {
22073
+ return new Promise((resolve3, reject) => {
21872
22074
  const timer = setTimeout(() => {
21873
22075
  reject(new RpcError("TIMEOUT", message, void 0, true));
21874
22076
  }, ms);
@@ -21876,7 +22078,7 @@ function withTimeout(promise, ms, message) {
21876
22078
  promise.then(
21877
22079
  (value) => {
21878
22080
  clearTimeout(timer);
21879
- resolve2(value);
22081
+ resolve3(value);
21880
22082
  },
21881
22083
  (error) => {
21882
22084
  clearTimeout(timer);
@@ -21961,6 +22163,7 @@ var HARNESS_REMOTE_ALLOWLIST = [
21961
22163
  "pluginInventory/list",
21962
22164
  "session/attachment",
21963
22165
  "session/cancel",
22166
+ "session/canOpenWorkspacePath",
21964
22167
  "session/control",
21965
22168
  "session/create",
21966
22169
  "session/follow",
@@ -22206,8 +22409,9 @@ function concatChunks2(chunks, totalBytes) {
22206
22409
 
22207
22410
  // src/codex/domain.ts
22208
22411
  import { randomUUID } from "node:crypto";
22412
+ import { readdir as readdir2, realpath, stat as stat4 } from "node:fs/promises";
22209
22413
  import { homedir as homedir3 } from "node:os";
22210
- import { isAbsolute as isAbsolute3, join as join5 } from "node:path";
22414
+ import { basename as basename3, isAbsolute as isAbsolute3, join as join5, relative, resolve as resolve2 } from "node:path";
22211
22415
 
22212
22416
  // src/codex/app-server.ts
22213
22417
  import { spawn as spawn2 } from "node:child_process";
@@ -22280,15 +22484,15 @@ var CodexAppServerClient = class {
22280
22484
  const child = this.process;
22281
22485
  this.process = void 0;
22282
22486
  if (child === void 0 || child.exitCode !== null || child.killed) return;
22283
- await new Promise((resolve2) => {
22487
+ await new Promise((resolve3) => {
22284
22488
  const timer = setTimeout(() => {
22285
22489
  child.kill("SIGKILL");
22286
- resolve2();
22490
+ resolve3();
22287
22491
  }, 2e3);
22288
22492
  timer.unref?.();
22289
22493
  child.once("exit", () => {
22290
22494
  clearTimeout(timer);
22291
- resolve2();
22495
+ resolve3();
22292
22496
  });
22293
22497
  child.kill("SIGTERM");
22294
22498
  });
@@ -22344,13 +22548,13 @@ var CodexAppServerClient = class {
22344
22548
  }
22345
22549
  request(method, params, timeoutMs) {
22346
22550
  const id2 = this.nextId++;
22347
- const result = new Promise((resolve2, reject) => {
22551
+ const result = new Promise((resolve3, reject) => {
22348
22552
  const timer = setTimeout(() => {
22349
22553
  this.pending.delete(id2);
22350
22554
  reject(new CodexAppServerError("CODEX_REQUEST_TIMEOUT", "Codex App Server request timed out."));
22351
22555
  }, timeoutMs);
22352
22556
  timer.unref?.();
22353
- this.pending.set(id2, { resolve: resolve2, reject, timer });
22557
+ this.pending.set(id2, { resolve: resolve3, reject, timer });
22354
22558
  });
22355
22559
  try {
22356
22560
  this.write({ id: id2, method, params });
@@ -22496,6 +22700,9 @@ var schemas = {
22496
22700
  throughSeq: external_exports.number().int().min(-1).optional(),
22497
22701
  maxMessages: external_exports.number().int().min(1).max(200).optional()
22498
22702
  }).strict(),
22703
+ "dsh/directoryList": external_exports.object({
22704
+ path: external_exports.string().min(1).max(4096)
22705
+ }).strict(),
22499
22706
  "thread/start": external_exports.object({
22500
22707
  cwd: external_exports.string().min(1).max(4096),
22501
22708
  model: external_exports.string().min(1).max(128).optional(),
@@ -22825,8 +23032,11 @@ function concatChunks3(chunks, totalBytes) {
22825
23032
  // src/codex/domain.ts
22826
23033
  var APPROVAL_TTL_MS = 5 * 6e4;
22827
23034
  var DEFAULT_RESTART_DELAYS_MS = [1e3, 2e3, 4e3, 8e3, 15e3];
23035
+ var CODEX_PAGE_LIMIT2 = 100;
23036
+ var MAX_CODEX_PAGES2 = 32;
22828
23037
  var CODEX_HISTORY_PAGE_LIMIT = 25;
22829
23038
  var MAX_CODEX_HISTORY_PAGES = 64;
23039
+ var CODEX_DIRECTORY_ENTRY_LIMIT = 500;
22830
23040
  var CodexRemoteDomain = class {
22831
23041
  constructor(config, logger, createAppServer = (binary, targetLogger) => new CodexAppServerClient(binary, targetLogger), restartDelaysMs = DEFAULT_RESTART_DELAYS_MS) {
22832
23042
  this.config = config;
@@ -22838,6 +23048,7 @@ var CodexRemoteDomain = class {
22838
23048
  unsubscribeInbound;
22839
23049
  unsubscribeUnavailable;
22840
23050
  peers = /* @__PURE__ */ new Map();
23051
+ peerDeviceIds = /* @__PURE__ */ new Map();
22841
23052
  turnOwners = /* @__PURE__ */ new Map();
22842
23053
  approvals = /* @__PURE__ */ new Map();
22843
23054
  approvalExpiryTimer;
@@ -22877,6 +23088,7 @@ var CodexRemoteDomain = class {
22877
23088
  if (!this.config.enabled) return void 0;
22878
23089
  const bridge = new CodexPeerBridge(this, context, publish, this.logger);
22879
23090
  this.peers.set(context.connectionId, bridge);
23091
+ this.peerDeviceIds.set(context.connectionId, context.peerDeviceId);
22880
23092
  return bridge;
22881
23093
  }
22882
23094
  async call(connectionId, input2) {
@@ -22890,19 +23102,19 @@ var CodexRemoteDomain = class {
22890
23102
  return sanitizeProjectList(await this.callUpstream(call.method, call.params));
22891
23103
  }
22892
23104
  if (call.method === "thread/list") {
22893
- return filterThreadListByProjectAuthority(
22894
- sanitizeThreadList(await this.callUpstream(call.method, call.params)),
22895
- await this.readProjectAuthority()
23105
+ const result = sanitizeThreadList(await this.callUpstream(call.method, call.params));
23106
+ return filterThreadListByWorkspaceAuthority(
23107
+ result,
23108
+ await this.readWorkspaceAuthority(result.data)
22896
23109
  );
22897
23110
  }
22898
23111
  if (call.method === "thread/start") {
22899
23112
  const cwd = await this.requireCodexWorkspacePath(call.params.cwd);
22900
- const permission = codexPermission(call.params);
23113
+ const permission = codexPermission(call.params, "workspace-write");
22901
23114
  const result = await this.callUpstream(call.method, {
22902
23115
  ...permission.params,
22903
23116
  cwd,
22904
- approvalPolicy: permission.approvalPolicy,
22905
- sandbox: permission.sandbox,
23117
+ ...codexThreadPermissionParams(permission),
22906
23118
  serviceName: "deepseek_harness_remote"
22907
23119
  });
22908
23120
  if (extractThread(result)?.id === void 0) {
@@ -22913,7 +23125,7 @@ var CodexRemoteDomain = class {
22913
23125
  const threadId = threadIdFromParams(call.params);
22914
23126
  if (call.method === "dsh/sessionHistory") {
22915
23127
  const thread = await this.readThreadForHistory(connectionId, threadId);
22916
- return paginateCodexNativeHistory(
23128
+ const page = paginateCodexNativeHistory(
22917
23129
  projectCodexNativeHistory(thread, `codex:${threadId}`),
22918
23130
  {
22919
23131
  beforeSeq: optionalInteger2(call.params.beforeSeq),
@@ -22921,6 +23133,11 @@ var CodexRemoteDomain = class {
22921
23133
  maxMessages: optionalInteger2(call.params.maxMessages)
22922
23134
  }
22923
23135
  );
23136
+ const activeTurnId2 = typeof page.activeTurnId === "string" ? page.activeTurnId : this.turnOwners.get(threadId)?.turnId;
23137
+ return activeTurnId2 === void 0 ? page : { ...page, activeTurnId: activeTurnId2 };
23138
+ }
23139
+ if (call.method === "dsh/directoryList") {
23140
+ return this.listCodexDirectory(call.params.path);
22924
23141
  }
22925
23142
  const allowedThread = threadId === void 0 ? void 0 : await this.readKnownThread(threadId);
22926
23143
  if (call.method === "thread/read") {
@@ -22933,39 +23150,38 @@ var CodexRemoteDomain = class {
22933
23150
  return this.callUpstream(call.method, call.params);
22934
23151
  }
22935
23152
  let claimed = false;
23153
+ let previousOwner;
22936
23154
  if (isThreadMutation(call.method) && threadId !== void 0) {
22937
- claimed = this.claimTurn(threadId, connectionId, call.method);
23155
+ const claim = this.claimTurn(
23156
+ threadId,
23157
+ connectionId,
23158
+ call.method,
23159
+ typeof call.params.turnId === "string" ? call.params.turnId : void 0
23160
+ );
23161
+ claimed = claim.claimed;
23162
+ previousOwner = claim.previous;
22938
23163
  }
22939
23164
  try {
22940
23165
  const permission = codexPermission(call.params);
22941
23166
  const upstreamParams = call.method === "thread/resume" && allowedThread !== void 0 ? {
22942
23167
  ...permission.params,
22943
23168
  ...allowedThread.cwd === void 0 ? {} : { cwd: allowedThread.cwd },
22944
- approvalPolicy: permission.approvalPolicy,
22945
- sandbox: permission.sandbox,
23169
+ ...codexThreadPermissionParams(permission),
22946
23170
  excludeTurns: true
22947
23171
  } : call.method === "thread/fork" && allowedThread !== void 0 ? {
22948
23172
  ...permission.params,
22949
23173
  ...allowedThread.cwd === void 0 ? {} : { cwd: allowedThread.cwd },
22950
- approvalPolicy: permission.approvalPolicy,
22951
- sandbox: permission.sandbox
23174
+ ...codexThreadPermissionParams(permission)
22952
23175
  } : call.method === "turn/start" ? {
22953
23176
  ...permission.params,
22954
23177
  ...allowedThread?.cwd === void 0 ? {} : { cwd: allowedThread.cwd },
22955
- approvalPolicy: permission.approvalPolicy,
22956
- sandboxPolicy: permission.sandbox === "danger-full-access" ? { type: "dangerFullAccess" } : {
22957
- type: "workspaceWrite",
22958
- writableRoots: allowedThread?.cwd === void 0 ? [] : [allowedThread.cwd],
22959
- networkAccess: false,
22960
- excludeTmpdirEnvVar: false,
22961
- excludeSlashTmp: false
22962
- }
23178
+ ...codexTurnPermissionParams(permission, allowedThread?.cwd)
22963
23179
  } : permission.params;
22964
23180
  let result;
22965
23181
  try {
22966
23182
  result = await this.callUpstream(call.method, upstreamParams);
22967
23183
  } catch (error) {
22968
- if (call.method === "thread/resume" && allowedThread !== void 0 && error instanceof RpcError && error.code === "CODEX_UPSTREAM_ERROR") {
23184
+ if (call.method === "thread/resume" && allowedThread !== void 0 && error instanceof RpcError && error.code === "CODEX_UPSTREAM_ERROR" && call.params.permissionPreset === void 0) {
22969
23185
  return { thread: allowedThread.thread };
22970
23186
  }
22971
23187
  throw error;
@@ -22973,9 +23189,15 @@ var CodexRemoteDomain = class {
22973
23189
  if (call.method === "thread/resume" || call.method === "thread/fork" || call.method === "thread/unarchive") {
22974
23190
  await this.assertResultThreadAllowed(result);
22975
23191
  }
23192
+ if (call.method === "turn/start" && threadId !== void 0) {
23193
+ this.rememberTurnId(threadId, connectionId, extractTurnId(result));
23194
+ }
22976
23195
  return result;
22977
23196
  } catch (error) {
22978
- if (claimed && threadId !== void 0) this.turnOwners.delete(threadId);
23197
+ if (claimed && threadId !== void 0) {
23198
+ if (previousOwner === void 0) this.turnOwners.delete(threadId);
23199
+ else this.turnOwners.set(threadId, previousOwner);
23200
+ }
22979
23201
  throw mapAppServerError(error);
22980
23202
  }
22981
23203
  }
@@ -22994,8 +23216,9 @@ var CodexRemoteDomain = class {
22994
23216
  async detachPeer(connectionId) {
22995
23217
  const bridge = this.peers.get(connectionId);
22996
23218
  if (bridge !== void 0) this.peers.delete(connectionId);
23219
+ this.peerDeviceIds.delete(connectionId);
22997
23220
  for (const [threadId, owner] of this.turnOwners) {
22998
- if (owner === connectionId) this.turnOwners.delete(threadId);
23221
+ if (owner.connectionId === connectionId) this.turnOwners.delete(threadId);
22999
23222
  }
23000
23223
  const appServer = this.appServer;
23001
23224
  const pending = [...this.approvals.entries()].filter(([, approval]) => approval.connectionId === connectionId);
@@ -23014,6 +23237,7 @@ var CodexRemoteDomain = class {
23014
23237
  this.restartTimer = void 0;
23015
23238
  for (const bridge of [...this.peers.values()]) await bridge.closeAll();
23016
23239
  this.peers.clear();
23240
+ this.peerDeviceIds.clear();
23017
23241
  this.turnOwners.clear();
23018
23242
  if (this.approvalExpiryTimer !== void 0) clearTimeout(this.approvalExpiryTimer);
23019
23243
  this.approvalExpiryTimer = void 0;
@@ -23133,6 +23357,13 @@ var CodexRemoteDomain = class {
23133
23357
  }
23134
23358
  const threadId = extractThreadId(message.params);
23135
23359
  if (message.method === "turn/completed" && threadId !== void 0) this.turnOwners.delete(threadId);
23360
+ if (message.method === "turn/started" && threadId !== void 0) {
23361
+ const owner = this.turnOwners.get(threadId);
23362
+ const turnId = extractTurnId(message.params);
23363
+ if (owner !== void 0) {
23364
+ this.turnOwners.set(threadId, { ...owner, ...turnId === void 0 ? {} : { turnId } });
23365
+ }
23366
+ }
23136
23367
  if (message.method === "serverRequest/resolved") this.resolveUpstreamApproval(message.params);
23137
23368
  if (threadId === void 0) return;
23138
23369
  await Promise.all([...this.peers.values()].map((peer) => peer.publishInbound(threadId, {
@@ -23149,7 +23380,7 @@ var CodexRemoteDomain = class {
23149
23380
  }
23150
23381
  const threadId = extractThreadId(message.params);
23151
23382
  const owner = threadId === void 0 ? void 0 : this.turnOwners.get(threadId);
23152
- const peer = owner === void 0 ? void 0 : this.peers.get(owner);
23383
+ const peer = owner === void 0 ? void 0 : this.peers.get(owner.connectionId);
23153
23384
  if (threadId === void 0 || owner === void 0 || peer === void 0 || !peer.hasThreadSubscription(threadId)) {
23154
23385
  await appServer.respond(message.id, { decision: "decline" });
23155
23386
  return;
@@ -23157,7 +23388,7 @@ var CodexRemoteDomain = class {
23157
23388
  const requestHandle = randomUUID();
23158
23389
  this.approvals.set(requestHandle, {
23159
23390
  upstreamId: message.id,
23160
- connectionId: owner,
23391
+ connectionId: owner.connectionId,
23161
23392
  threadId,
23162
23393
  method: message.method,
23163
23394
  expiresAt: Date.now() + APPROVAL_TTL_MS
@@ -23176,7 +23407,7 @@ var CodexRemoteDomain = class {
23176
23407
  }
23177
23408
  }
23178
23409
  async readKnownThread(threadId) {
23179
- const authority = await this.readProjectAuthority();
23410
+ const authority = await this.readWorkspaceAuthority();
23180
23411
  let result;
23181
23412
  try {
23182
23413
  result = await this.callUpstream("thread/read", { threadId, includeTurns: false });
@@ -23189,7 +23420,7 @@ var CodexRemoteDomain = class {
23189
23420
  return { thread: listed, ...typeof listed.cwd === "string" && listed.cwd.length > 0 ? { cwd: listed.cwd } : {} };
23190
23421
  }
23191
23422
  const thread = extractThread(result);
23192
- if (thread === void 0 || thread.id !== threadId || !isThreadAllowedByProjectAuthority(thread, authority)) {
23423
+ if (thread === void 0 || thread.id !== threadId || !isThreadAllowedByWorkspaceAuthority(thread, authority)) {
23193
23424
  throw new RpcError("CODEX_THREAD_NOT_ALLOWED", "The Codex thread is not available through this Remote Host.");
23194
23425
  }
23195
23426
  return { thread, ...typeof thread.cwd === "string" && thread.cwd.length > 0 ? { cwd: thread.cwd } : {} };
@@ -23282,26 +23513,41 @@ var CodexRemoteDomain = class {
23282
23513
  if (thread === void 0 || typeof thread.id !== "string") {
23283
23514
  throw new RpcError("CODEX_INVALID_RESPONSE", "Codex App Server returned an invalid thread.");
23284
23515
  }
23285
- if (!isThreadAllowedByProjectAuthority(thread, await this.readProjectAuthority())) {
23516
+ if (!isThreadAllowedByWorkspaceAuthority(thread, await this.readWorkspaceAuthority())) {
23286
23517
  throw new RpcError("CODEX_THREAD_NOT_ALLOWED", "The Codex thread is not available through this Remote Host.");
23287
23518
  }
23288
23519
  }
23289
- claimTurn(threadId, connectionId, method) {
23520
+ claimTurn(threadId, connectionId, method, turnId) {
23290
23521
  const owner = this.turnOwners.get(threadId);
23522
+ const peerDeviceId = this.peerDeviceIds.get(connectionId) ?? connectionId;
23523
+ const nextOwner = { connectionId, peerDeviceId, ...turnId === void 0 ? {} : { turnId } };
23291
23524
  if (method === "turn/interrupt") {
23292
23525
  if (owner === void 0) {
23293
- this.turnOwners.set(threadId, connectionId);
23294
- return true;
23526
+ this.turnOwners.set(threadId, nextOwner);
23527
+ return { claimed: true };
23295
23528
  }
23296
- if (owner !== connectionId) throw new RpcError("CODEX_TURN_OWNED", "Only the connection that started this Codex turn can interrupt it.");
23297
- return false;
23529
+ if (owner.connectionId !== connectionId && owner.peerDeviceId !== peerDeviceId) {
23530
+ throw new RpcError("CODEX_TURN_OWNED", "Only the connection that started this Codex turn can interrupt it.");
23531
+ }
23532
+ if (owner.connectionId !== connectionId) {
23533
+ this.turnOwners.set(threadId, { ...owner, connectionId, peerDeviceId, ...turnId === void 0 ? {} : { turnId } });
23534
+ return { claimed: true, previous: owner };
23535
+ }
23536
+ if (turnId !== void 0 && owner.turnId === void 0) this.turnOwners.set(threadId, { ...owner, turnId });
23537
+ return { claimed: false };
23298
23538
  }
23299
- if (owner !== void 0 && owner !== connectionId) {
23539
+ if (owner !== void 0 && owner.connectionId !== connectionId && owner.peerDeviceId !== peerDeviceId) {
23300
23540
  throw new RpcError("CODEX_TURN_OWNED", "Another Remote connection owns the active Codex turn.");
23301
23541
  }
23302
- if (owner === connectionId) return false;
23303
- this.turnOwners.set(threadId, connectionId);
23304
- return true;
23542
+ if (owner?.connectionId === connectionId) return { claimed: false };
23543
+ this.turnOwners.set(threadId, owner === void 0 ? nextOwner : { ...owner, connectionId, peerDeviceId });
23544
+ return { claimed: true, previous: owner };
23545
+ }
23546
+ rememberTurnId(threadId, connectionId, turnId) {
23547
+ if (turnId === void 0) return;
23548
+ const owner = this.turnOwners.get(threadId);
23549
+ if (owner === void 0 || owner.connectionId !== connectionId) return;
23550
+ this.turnOwners.set(threadId, { ...owner, turnId });
23305
23551
  }
23306
23552
  hasSubscriber(threadId) {
23307
23553
  return [...this.peers.values()].some((peer) => peer.hasThreadSubscription(threadId));
@@ -23348,29 +23594,53 @@ var CodexRemoteDomain = class {
23348
23594
  throw mapAppServerError(error);
23349
23595
  }
23350
23596
  }
23351
- async readProjectAuthority() {
23597
+ async readWorkspaceAuthority(listedThreads) {
23352
23598
  const projectIds = /* @__PURE__ */ new Set();
23353
23599
  const roots = [];
23354
23600
  let cursor2;
23355
- for (let page = 0; page < 32; page += 1) {
23356
- const result = sanitizeProjectList(await this.callUpstream("project/list", {
23357
- limit: 100,
23358
- ...cursor2 === void 0 ? {} : { cursor: cursor2 }
23359
- }));
23360
- for (const project of result.data) {
23361
- const projectRoots = project.roots.map((root) => root.path).filter((path) => isAbsolute3(path));
23362
- if (projectRoots.length === 0) continue;
23363
- projectIds.add(project.id);
23364
- roots.push(...projectRoots);
23601
+ try {
23602
+ for (let page = 0; page < MAX_CODEX_PAGES2; page += 1) {
23603
+ const result = sanitizeProjectList(await this.callUpstream("project/list", {
23604
+ limit: CODEX_PAGE_LIMIT2,
23605
+ ...cursor2 === void 0 ? {} : { cursor: cursor2 }
23606
+ }));
23607
+ for (const project of result.data) {
23608
+ const projectRoots = project.roots.map((root) => root.path).filter((path) => isAbsolute3(path));
23609
+ if (projectRoots.length === 0) continue;
23610
+ projectIds.add(project.id);
23611
+ roots.push(...projectRoots);
23612
+ }
23613
+ cursor2 = typeof result.nextCursor === "string" && result.nextCursor.length > 0 ? result.nextCursor : void 0;
23614
+ if (cursor2 === void 0) break;
23365
23615
  }
23366
- cursor2 = typeof result.nextCursor === "string" && result.nextCursor.length > 0 ? result.nextCursor : void 0;
23367
- if (cursor2 === void 0) break;
23616
+ } catch (error) {
23617
+ if (!isProjectListFallbackError(error)) throw error;
23618
+ }
23619
+ if (roots.length > 0) return { projectIds, roots };
23620
+ const threads = listedThreads === void 0 ? [] : [...listedThreads];
23621
+ if (listedThreads === void 0) {
23622
+ cursor2 = void 0;
23623
+ for (let page = 0; page < MAX_CODEX_PAGES2; page += 1) {
23624
+ const result = sanitizeThreadList(await this.callUpstream("thread/list", {
23625
+ limit: CODEX_PAGE_LIMIT2,
23626
+ sortKey: "updated_at",
23627
+ sortDirection: "desc",
23628
+ archived: false,
23629
+ ...cursor2 === void 0 ? {} : { cursor: cursor2 }
23630
+ }));
23631
+ threads.push(...result.data);
23632
+ cursor2 = typeof result.nextCursor === "string" && result.nextCursor.length > 0 ? result.nextCursor : void 0;
23633
+ if (cursor2 === void 0) break;
23634
+ }
23635
+ }
23636
+ for (const workspace of deriveCodexCwdWorkspaces(threads)) {
23637
+ if (isAbsolute3(workspace.path)) roots.push(workspace.path);
23368
23638
  }
23369
23639
  return { projectIds, roots };
23370
23640
  }
23371
23641
  async findKnownThreadInList(threadId, authority) {
23372
23642
  let cursor2;
23373
- for (let page = 0; page < 32; page += 1) {
23643
+ for (let page = 0; page < MAX_CODEX_PAGES2; page += 1) {
23374
23644
  const result = sanitizeThreadList(await this.callUpstream("thread/list", {
23375
23645
  limit: 100,
23376
23646
  sortKey: "updated_at",
@@ -23380,7 +23650,7 @@ var CodexRemoteDomain = class {
23380
23650
  }));
23381
23651
  const thread = result.data.find((item) => item.id === threadId);
23382
23652
  if (thread !== void 0) {
23383
- return isThreadAllowedByProjectAuthority(thread, authority) ? thread : void 0;
23653
+ return isThreadAllowedByWorkspaceAuthority(thread, authority) ? thread : void 0;
23384
23654
  }
23385
23655
  cursor2 = typeof result.nextCursor === "string" && result.nextCursor.length > 0 ? result.nextCursor : void 0;
23386
23656
  if (cursor2 === void 0) break;
@@ -23391,16 +23661,90 @@ var CodexRemoteDomain = class {
23391
23661
  if (!isAbsolute3(path)) {
23392
23662
  throw new RpcError("CODEX_PATH_NOT_ALLOWED", "The CodeX working directory is not available as a Workspace.");
23393
23663
  }
23394
- const paths = await this.listCodexProjectWorkspacePaths();
23395
- const canonical = paths.get(normalizeCodexPathForCompare(path));
23396
- if (canonical === void 0) {
23664
+ const paths = await this.listCodexWorkspacePaths();
23665
+ const lexicalCandidate = resolve2(path);
23666
+ let candidate;
23667
+ try {
23668
+ candidate = await realpath(path);
23669
+ if (!(await stat4(candidate)).isDirectory()) throw new Error("not a directory");
23670
+ } catch {
23671
+ throw new RpcError("CODEX_PATH_NOT_ALLOWED", "The CodeX working directory is not available as a Workspace.");
23672
+ }
23673
+ for (const root of paths.values()) {
23674
+ try {
23675
+ if (!containsCodexPath(resolve2(root), lexicalCandidate)) continue;
23676
+ const canonicalRoot = await realpath(root);
23677
+ if (containsCodexPath(canonicalRoot, candidate)) return lexicalCandidate;
23678
+ } catch {
23679
+ }
23680
+ }
23681
+ throw new RpcError("CODEX_PATH_NOT_ALLOWED", "The CodeX working directory is not available as a Workspace.");
23682
+ }
23683
+ async listCodexDirectory(path) {
23684
+ const target = await this.resolveCodexDirectory(path);
23685
+ const rows = await readdir2(target.path, { withFileTypes: true }).catch(() => void 0);
23686
+ if (rows === void 0) {
23687
+ throw new RpcError("CODEX_PATH_NOT_ALLOWED", "The CodeX working directory is not available as a Workspace.");
23688
+ }
23689
+ const entries = [];
23690
+ for (const row of rows) {
23691
+ const child = resolve2(target.path, row.name);
23692
+ let directory = row.isDirectory();
23693
+ if (!directory && row.isSymbolicLink()) {
23694
+ directory = await stat4(child).then((value) => value.isDirectory()).catch(() => false);
23695
+ }
23696
+ if (!directory) continue;
23697
+ try {
23698
+ const canonicalChild = await realpath(child);
23699
+ if (!(await stat4(canonicalChild)).isDirectory()) continue;
23700
+ if (!containsCodexPath(target.canonicalRoot, canonicalChild)) continue;
23701
+ } catch {
23702
+ continue;
23703
+ }
23704
+ entries.push({
23705
+ name: row.name,
23706
+ path: child,
23707
+ hidden: process.platform !== "win32" && row.name.startsWith(".")
23708
+ });
23709
+ }
23710
+ entries.sort((left, right) => left.name.localeCompare(right.name, void 0, { sensitivity: "base" }));
23711
+ return {
23712
+ path: target.path,
23713
+ home: target.root,
23714
+ crumbs: codexDirectoryCrumbs(target.root, target.path),
23715
+ entries: entries.slice(0, CODEX_DIRECTORY_ENTRY_LIMIT),
23716
+ truncated: entries.length > CODEX_DIRECTORY_ENTRY_LIMIT
23717
+ };
23718
+ }
23719
+ async resolveCodexDirectory(path) {
23720
+ if (!isAbsolute3(path)) {
23721
+ throw new RpcError("CODEX_PATH_NOT_ALLOWED", "The CodeX working directory is not available as a Workspace.");
23722
+ }
23723
+ const authority = await this.readWorkspaceAuthority();
23724
+ const lexicalCandidate = resolve2(path);
23725
+ let canonicalCandidate;
23726
+ try {
23727
+ canonicalCandidate = await realpath(path);
23728
+ if (!(await stat4(canonicalCandidate)).isDirectory()) throw new Error("not a directory");
23729
+ } catch {
23397
23730
  throw new RpcError("CODEX_PATH_NOT_ALLOWED", "The CodeX working directory is not available as a Workspace.");
23398
23731
  }
23399
- return canonical;
23732
+ for (const root of authority.roots) {
23733
+ try {
23734
+ const lexicalRoot = resolve2(root);
23735
+ if (!containsCodexPath(lexicalRoot, lexicalCandidate)) continue;
23736
+ const canonicalRoot = await realpath(root);
23737
+ if (containsCodexPath(canonicalRoot, canonicalCandidate)) {
23738
+ return { path: lexicalCandidate, root: lexicalRoot, canonicalRoot };
23739
+ }
23740
+ } catch {
23741
+ }
23742
+ }
23743
+ throw new RpcError("CODEX_PATH_NOT_ALLOWED", "The CodeX working directory is not available as a Workspace.");
23400
23744
  }
23401
- async listCodexProjectWorkspacePaths() {
23745
+ async listCodexWorkspacePaths() {
23402
23746
  const paths = /* @__PURE__ */ new Map();
23403
- const authority = await this.readProjectAuthority();
23747
+ const authority = await this.readWorkspaceAuthority();
23404
23748
  for (const root of authority.roots) paths.set(normalizeCodexPathForCompare(root), root);
23405
23749
  return paths;
23406
23750
  }
@@ -23469,13 +23813,13 @@ function sanitizeThreadList(result) {
23469
23813
  ...typeof result.backwardsCursor === "string" && result.backwardsCursor.length > 0 ? { backwardsCursor: result.backwardsCursor } : {}
23470
23814
  };
23471
23815
  }
23472
- function filterThreadListByProjectAuthority(result, authority) {
23816
+ function filterThreadListByWorkspaceAuthority(result, authority) {
23473
23817
  if (!isRecord9(result) || !Array.isArray(result.data)) {
23474
23818
  throw new RpcError("CODEX_INVALID_RESPONSE", "Codex App Server returned an invalid thread list.");
23475
23819
  }
23476
23820
  return {
23477
23821
  ...result,
23478
- data: result.data.map((record4) => isRecord9(record4) ? record4 : void 0).filter((thread) => thread !== void 0 && isThreadAllowedByProjectAuthority(thread, authority))
23822
+ data: result.data.map((record4) => isRecord9(record4) ? record4 : void 0).filter((thread) => thread !== void 0 && isThreadAllowedByWorkspaceAuthority(thread, authority))
23479
23823
  };
23480
23824
  }
23481
23825
  function sanitizeProjectList(result) {
@@ -23503,7 +23847,7 @@ function sanitizeProjectList(result) {
23503
23847
  ...typeof result.nextCursor === "string" && result.nextCursor.length > 0 ? { nextCursor: result.nextCursor } : { nextCursor: null }
23504
23848
  };
23505
23849
  }
23506
- function isThreadAllowedByProjectAuthority(thread, authority) {
23850
+ function isThreadAllowedByWorkspaceAuthority(thread, authority) {
23507
23851
  const projectId = typeof thread.projectId === "string" ? thread.projectId : void 0;
23508
23852
  if (projectId !== void 0 && authority.projectIds.has(projectId)) return true;
23509
23853
  const cwd = typeof thread.cwd === "string" ? thread.cwd : void 0;
@@ -23517,9 +23861,26 @@ function containsCodexPath(root, candidate) {
23517
23861
  function normalizeCodexPathForCompare(path) {
23518
23862
  return path.replace(/[\\/]+$/u, "") || path;
23519
23863
  }
23864
+ function codexDirectoryCrumbs(root, path) {
23865
+ const crumbs2 = [{ name: basename3(root) || root, path: root, hidden: false }];
23866
+ const remainder = relative(root, path);
23867
+ if (remainder === "") return crumbs2;
23868
+ let current = root;
23869
+ for (const segment of remainder.split(/[\\/]+/u).filter(Boolean)) {
23870
+ current = resolve2(current, segment);
23871
+ crumbs2.push({ name: segment, path: current, hidden: false });
23872
+ }
23873
+ return crumbs2;
23874
+ }
23520
23875
  function extractThread(result) {
23521
23876
  return isRecord9(result) && isRecord9(result.thread) ? result.thread : void 0;
23522
23877
  }
23878
+ function extractTurnId(result) {
23879
+ if (!isRecord9(result)) return void 0;
23880
+ if (typeof result.turnId === "string" && result.turnId.length > 0) return result.turnId;
23881
+ if (isRecord9(result.turn) && typeof result.turn.id === "string" && result.turn.id.length > 0) return result.turn.id;
23882
+ return void 0;
23883
+ }
23523
23884
  function extractThreadId(params) {
23524
23885
  if (!isRecord9(params)) return void 0;
23525
23886
  if (typeof params.threadId === "string") return params.threadId;
@@ -23530,15 +23891,37 @@ function extractThreadId(params) {
23530
23891
  function optionalInteger2(value) {
23531
23892
  return typeof value === "number" && Number.isSafeInteger(value) ? value : void 0;
23532
23893
  }
23533
- function codexPermission(params) {
23894
+ function codexPermission(params, fallbackPreset) {
23534
23895
  const { permissionPreset: permissionPreset2, ...rest } = params;
23535
- const fullAccess = permissionPreset2 === "danger-full-access";
23896
+ const preset = permissionPreset2 === "workspace-write" || permissionPreset2 === "danger-full-access" ? permissionPreset2 : fallbackPreset;
23897
+ if (preset === void 0) return { params: mapCodexImageInputs(rest) };
23898
+ const fullAccess = preset === "danger-full-access";
23536
23899
  return {
23537
23900
  params: mapCodexImageInputs(rest),
23538
23901
  approvalPolicy: fullAccess ? "never" : "on-request",
23539
23902
  sandbox: fullAccess ? "danger-full-access" : "workspace-write"
23540
23903
  };
23541
23904
  }
23905
+ function codexThreadPermissionParams(permission) {
23906
+ if (permission.approvalPolicy === void 0 || permission.sandbox === void 0) return {};
23907
+ return {
23908
+ approvalPolicy: permission.approvalPolicy,
23909
+ sandbox: permission.sandbox
23910
+ };
23911
+ }
23912
+ function codexTurnPermissionParams(permission, cwd) {
23913
+ if (permission.approvalPolicy === void 0 || permission.sandbox === void 0) return {};
23914
+ return {
23915
+ approvalPolicy: permission.approvalPolicy,
23916
+ sandboxPolicy: permission.sandbox === "danger-full-access" ? { type: "dangerFullAccess" } : {
23917
+ type: "workspaceWrite",
23918
+ writableRoots: cwd === void 0 ? [] : [cwd],
23919
+ networkAccess: false,
23920
+ excludeTmpdirEnvVar: false,
23921
+ excludeSlashTmp: false
23922
+ }
23923
+ };
23924
+ }
23542
23925
  function mapCodexImageInputs(params) {
23543
23926
  if (!Array.isArray(params.input)) return params;
23544
23927
  return {
@@ -23575,6 +23958,9 @@ function errorCode3(error) {
23575
23958
  function isHistoryReadRecoverable(error) {
23576
23959
  return error instanceof RpcError && ["METHOD_NOT_ALLOWED", "METHOD_NOT_FOUND", "CODEX_UPSTREAM_ERROR", "CODEX_REQUEST_TIMEOUT", "CODEX_THREAD_BUSY"].includes(error.code);
23577
23960
  }
23961
+ function isProjectListFallbackError(error) {
23962
+ return error instanceof RpcError && ["METHOD_NOT_ALLOWED", "METHOD_NOT_FOUND", "CODEX_UPSTREAM_ERROR"].includes(error.code);
23963
+ }
23578
23964
  function canTryNextBinary(error) {
23579
23965
  return !(error instanceof RpcError) || !["CODEX_AUTH_REQUIRED", "CODEX_CLOSED"].includes(error.code);
23580
23966
  }
@@ -23693,13 +24079,42 @@ var HostPluginRuntime = class {
23693
24079
  }
23694
24080
  this.serverConnection.reconnect();
23695
24081
  }
24082
+ async startHostOAuthQrLogin(provider) {
24083
+ if (this.serverApi === void 0) {
24084
+ throw new ServerApiError("SERVER_NOT_CONFIGURED", "Remote Host Server is unavailable.", false);
24085
+ }
24086
+ return this.serverApi.startOAuthQrLogin(provider);
24087
+ }
24088
+ async pollHostOAuthQrLogin(qrId) {
24089
+ if (this.serverApi === void 0) {
24090
+ throw new ServerApiError("SERVER_NOT_CONFIGURED", "Remote Host Server is unavailable.", false);
24091
+ }
24092
+ const result = await this.serverApi.pollOAuthQrLogin(this.currentIdentity(), qrId, async () => {
24093
+ await this.serverConnection?.stop();
24094
+ this.identity = await this.identities.reset(this.config.deviceName);
24095
+ this.serverApi.bindIdentity(this.identity);
24096
+ this.serverConnection = this.createServerConnection(this.identity);
24097
+ return this.identity;
24098
+ });
24099
+ if (result.status === "complete") {
24100
+ this.serverConnection?.reconnect();
24101
+ this.logger.info("Host account authorized through OAuth QR login");
24102
+ }
24103
+ return result;
24104
+ }
23696
24105
  async clearHostAuthorization() {
23697
24106
  await this.serverConnection?.stop();
23698
- await this.serverApi?.revokeCurrentDevice();
24107
+ let revokeFailure;
24108
+ try {
24109
+ await this.serverApi?.revokeCurrentDevice();
24110
+ } catch (error) {
24111
+ revokeFailure = error;
24112
+ }
23699
24113
  this.identity = await this.identities.reset(this.config.deviceName);
23700
24114
  this.serverApi?.bindIdentity(this.identity);
23701
24115
  if (this.serverApi !== void 0) this.serverConnection = this.createServerConnection(this.identity);
23702
24116
  this.logger.info("Host authorization cleared");
24117
+ if (revokeFailure !== void 0) throw revokeFailure;
23703
24118
  }
23704
24119
  async authorizeHostAsOwned(accessToken, account) {
23705
24120
  if (this.serverApi === void 0) {
@@ -23787,6 +24202,7 @@ var HostPluginRuntime = class {
23787
24202
  peerDeviceId: this.connections.peerDeviceId() === void 0 ? void 0 : shortId5(this.connections.peerDeviceId()),
23788
24203
  peerDeviceIds: this.connections.peerDeviceIds().map(shortId5),
23789
24204
  trustedPeers: this.identities.listTrustedPeers().length,
24205
+ capabilities: this.hostCapabilities(),
23790
24206
  codex: this.codex.status()
23791
24207
  };
23792
24208
  }
@@ -23806,12 +24222,12 @@ var HostPluginRuntime = class {
23806
24222
  }
23807
24223
  async readHarnessVersion() {
23808
24224
  let reportedVersion;
23809
- let errorCode4;
24225
+ let errorCode5;
23810
24226
  try {
23811
24227
  const response = await this.apiProxy?.host.describe({ rpcId: randomUUID2(), payload: {} });
23812
24228
  if (response === void 0) throw new Error("ApiProxy is unavailable");
23813
24229
  if (!response.result.ok) {
23814
- errorCode4 = response.result.error.code;
24230
+ errorCode5 = response.result.error.code;
23815
24231
  } else {
23816
24232
  reportedVersion = normalizeHarnessVersion(response.result.value.version);
23817
24233
  }
@@ -23820,7 +24236,7 @@ var HostPluginRuntime = class {
23820
24236
  const distributionVersion = reportedVersion === void 0 || reportedVersion === "0.0.1" ? await readHarnessDistributionVersion() : void 0;
23821
24237
  const version = selectHarnessVersion(reportedVersion, distributionVersion);
23822
24238
  if (version !== void 0) return version;
23823
- this.logger.warn("Harness version is unavailable", errorCode4 === void 0 ? void 0 : { code: errorCode4 });
24239
+ this.logger.warn("Harness version is unavailable", errorCode5 === void 0 ? void 0 : { code: errorCode5 });
23824
24240
  return void 0;
23825
24241
  }
23826
24242
  hostCapabilities() {
@@ -23857,6 +24273,618 @@ function isPlainRecord(value) {
23857
24273
  return typeof value === "object" && value !== null && !Array.isArray(value);
23858
24274
  }
23859
24275
 
24276
+ // src/cli.ts
24277
+ import { stat as stat5 } from "node:fs/promises";
24278
+ import { hostname as hostname3 } from "node:os";
24279
+ import { join as join6 } from "node:path";
24280
+ var QR_POLL_INTERVAL_MS = 2e3;
24281
+ var TERMINAL_QR_MARGIN = 4;
24282
+ async function runCli(args = process.argv.slice(2), dependencies = {}) {
24283
+ const runtime = resolveDependencies(dependencies);
24284
+ const [command, ...rest] = args;
24285
+ try {
24286
+ if (command === "login") return await login(rest, runtime);
24287
+ if (command === "status") return await status(rest, runtime);
24288
+ if (command === "logout") return await logout(rest, runtime);
24289
+ if (command === "help" || command === "--help" || command === "-h" || command === void 0) {
24290
+ write(runtime.stdout, helpText());
24291
+ return 0;
24292
+ }
24293
+ throw new CliUsageError(`Unknown command: ${command}`);
24294
+ } catch (error) {
24295
+ write(runtime.stderr, `${cliErrorMessage(error)}
24296
+ `);
24297
+ if (error instanceof CliUsageError) write(runtime.stderr, `
24298
+ ${helpText()}`);
24299
+ return error instanceof CliUsageError ? 2 : 1;
24300
+ }
24301
+ }
24302
+ async function login(args, runtime) {
24303
+ if (args.length > 1) throw new CliUsageError("Usage: ds-harness-remote login [github|zhihu]");
24304
+ const provider = args[0] ?? "zhihu";
24305
+ if (provider !== "github" && provider !== "zhihu") {
24306
+ throw new CliUsageError("Login provider must be github or zhihu.");
24307
+ }
24308
+ const context = await hostContext(runtime);
24309
+ const session = await context.api.startOAuthQrLogin(provider);
24310
+ const qr = await runtime.renderQr(session.scanUrl);
24311
+ const providerName = provider === "github" ? "GitHub" : "Zhihu";
24312
+ write(runtime.stdout, `Using ${providerName} QR login. ${alternativeProviderHint(provider)}
24313
+ `);
24314
+ write(runtime.stdout, "Scan this QR code to authorize this Host:\n\n");
24315
+ write(runtime.stdout, `${qr.trimEnd()}
24316
+ `);
24317
+ write(runtime.stdout, `Authorization URL: ${terminalLink(session.scanUrl, runtime.stdout)}
24318
+
24319
+ Waiting for authorization...
24320
+ `);
24321
+ const deadline = runtime.now() + session.expiresIn * 1e3;
24322
+ let identity = context.identity;
24323
+ while (runtime.now() < deadline) {
24324
+ let result;
24325
+ try {
24326
+ result = await context.api.pollOAuthQrLogin(identity, session.qrId, async () => {
24327
+ identity = await context.identities.reset(context.deviceName);
24328
+ return identity;
24329
+ });
24330
+ } catch (error) {
24331
+ if (!(error instanceof ServerApiError) || !error.retryable) throw error;
24332
+ await runtime.wait(Math.min(QR_POLL_INTERVAL_MS, Math.max(1, deadline - runtime.now())));
24333
+ continue;
24334
+ }
24335
+ if (result.status === "complete") {
24336
+ write(runtime.stdout, `${authorizedMessage(result.authorization)} Restart dsh-tui to bring the Remote Host online.
24337
+ `);
24338
+ return 0;
24339
+ }
24340
+ if (result.status === "expired") break;
24341
+ await runtime.wait(Math.min(QR_POLL_INTERVAL_MS, Math.max(1, deadline - runtime.now())));
24342
+ }
24343
+ write(runtime.stderr, "The QR login expired. Run the login command again to generate a new code.\n");
24344
+ return 1;
24345
+ }
24346
+ async function status(args, runtime) {
24347
+ if (args.length !== 0) throw new CliUsageError("Usage: ds-harness-remote status");
24348
+ const serverUrl = selectedServer();
24349
+ const root = new IdentityStore({ env: runtime.env }).directory;
24350
+ const directory = serverStorageDirectory(root, serverUrl, "host");
24351
+ const lines = [
24352
+ "Remote Host status",
24353
+ `Server: ${serverUrl}`,
24354
+ "Host control: enabled (dsh-TUI default)"
24355
+ ];
24356
+ if (!await exists3(join6(directory, "device.json"))) {
24357
+ lines.push("Device: not initialized", "Authorization: logged out", "Credential: unavailable");
24358
+ write(runtime.stdout, `${lines.join("\n")}
24359
+
24360
+ Run "ds-harness-remote login" or use "/remote login" in dsh-TUI.
24361
+ `);
24362
+ return 0;
24363
+ }
24364
+ const identities = runtime.createIdentityStore({ directory, env: runtime.env });
24365
+ const identity = await identities.loadOrCreate(hostname3());
24366
+ const store = new ServerCredentialStore(directory);
24367
+ const stored = await store.load(serverUrl, identity.deviceId);
24368
+ lines.push(`Device: ${identity.name} (${identity.deviceId})`);
24369
+ if (stored === void 0) {
24370
+ lines.push("Authorization: logged out", "Credential: unavailable");
24371
+ write(runtime.stdout, `${lines.join("\n")}
24372
+
24373
+ Run "ds-harness-remote login" or use "/remote login" in dsh-TUI.
24374
+ `);
24375
+ return 0;
24376
+ }
24377
+ lines.push(`Authorization: logged in (${authorizationLabel(stored.authorizationMethod)})`);
24378
+ if (stored.account !== void 0) lines.push(`Account: ${stored.account}`);
24379
+ const api = runtime.createHostApi(serverUrl, store);
24380
+ api.bindIdentity(identity);
24381
+ try {
24382
+ await api.authenticate(identity);
24383
+ lines.push("Credential: ready");
24384
+ lines.push("Presence: published while dsh-tui is running");
24385
+ write(runtime.stdout, `${lines.join("\n")}
24386
+ `);
24387
+ return 0;
24388
+ } catch (error) {
24389
+ lines.push(`Credential: unavailable (${statusErrorCode(error)})`);
24390
+ write(runtime.stdout, `${lines.join("\n")}
24391
+ `);
24392
+ return 1;
24393
+ }
24394
+ }
24395
+ async function logout(args, runtime) {
24396
+ if (args.length !== 0) throw new CliUsageError("Usage: ds-harness-remote logout");
24397
+ const serverUrl = selectedServer();
24398
+ const root = new IdentityStore({ env: runtime.env }).directory;
24399
+ const directory = serverStorageDirectory(root, serverUrl, "host");
24400
+ if (!await exists3(join6(directory, "device.json"))) {
24401
+ await new ServerCredentialStore(directory).clear();
24402
+ write(runtime.stdout, "This Host is already logged out.\n");
24403
+ return 0;
24404
+ }
24405
+ const deviceName = hostname3();
24406
+ const identities = runtime.createIdentityStore({ directory, env: runtime.env });
24407
+ const identity = await identities.loadOrCreate(deviceName);
24408
+ const api = runtime.createHostApi(serverUrl, new ServerCredentialStore(directory));
24409
+ api.bindIdentity(identity);
24410
+ let revokeFailure;
24411
+ try {
24412
+ await api.revokeCurrentDevice();
24413
+ } catch (error) {
24414
+ revokeFailure = error;
24415
+ }
24416
+ await identities.reset(deviceName);
24417
+ if (revokeFailure !== void 0) {
24418
+ throw new Error(`Local Host credentials were cleared, but Server revocation failed: ${cliErrorMessage(revokeFailure)}`);
24419
+ }
24420
+ write(runtime.stdout, "Remote Host logged out and its local device identity was rotated. Restart dsh-tui.\n");
24421
+ return 0;
24422
+ }
24423
+ async function hostContext(runtime) {
24424
+ const serverUrl = selectedServer();
24425
+ const root = new IdentityStore({ env: runtime.env }).directory;
24426
+ const directory = serverStorageDirectory(root, serverUrl, "host");
24427
+ const deviceName = hostname3();
24428
+ const identities = runtime.createIdentityStore({ directory, env: runtime.env });
24429
+ const identity = await identities.loadOrCreate(deviceName);
24430
+ const api = runtime.createHostApi(serverUrl, new ServerCredentialStore(directory));
24431
+ return { api, identities, identity, deviceName };
24432
+ }
24433
+ function selectedServer() {
24434
+ return normalizeServerUrl(DEFAULT_REMOTE_SERVER_URL);
24435
+ }
24436
+ function resolveDependencies(input2) {
24437
+ return {
24438
+ env: input2.env ?? process.env,
24439
+ stdout: input2.stdout ?? process.stdout,
24440
+ stderr: input2.stderr ?? process.stderr,
24441
+ now: input2.now ?? Date.now,
24442
+ wait: input2.wait ?? ((milliseconds) => new Promise((resolve3) => setTimeout(resolve3, milliseconds))),
24443
+ renderQr: input2.renderQr ?? renderTerminalQr,
24444
+ createIdentityStore: input2.createIdentityStore ?? ((options) => new IdentityStore(options)),
24445
+ createHostApi: input2.createHostApi ?? ((serverUrl, store) => new HostServerApi(serverUrl, store))
24446
+ };
24447
+ }
24448
+ async function renderTerminalQr(url) {
24449
+ const { default: QRCode } = await import("qrcode");
24450
+ const modules = QRCode.create(url, { errorCorrectionLevel: "M" }).modules;
24451
+ const width = modules.size + TERMINAL_QR_MARGIN * 2;
24452
+ const quietLine = `\x1B[47m${" ".repeat(width * 2)}\x1B[0m`;
24453
+ const lines = Array.from({ length: TERMINAL_QR_MARGIN }, () => quietLine);
24454
+ for (let row = 0; row < modules.size; row += 1) {
24455
+ let line = `\x1B[47m${" ".repeat(TERMINAL_QR_MARGIN * 2)}`;
24456
+ for (let column = 0; column < modules.size; column += 1) {
24457
+ line += modules.get(row, column) === 1 ? "\x1B[40m " : "\x1B[47m ";
24458
+ }
24459
+ line += `\x1B[47m${" ".repeat(TERMINAL_QR_MARGIN * 2)}\x1B[0m`;
24460
+ lines.push(line);
24461
+ }
24462
+ lines.push(...Array.from({ length: TERMINAL_QR_MARGIN }, () => quietLine));
24463
+ return lines.join("\n");
24464
+ }
24465
+ async function renderCompactTerminalQr(url) {
24466
+ const { default: QRCode } = await import("qrcode");
24467
+ const modules = QRCode.create(url, { errorCorrectionLevel: "M" }).modules;
24468
+ const size = modules.size + TERMINAL_QR_MARGIN * 2;
24469
+ const lines = [];
24470
+ const isBlack = (row, column) => {
24471
+ const qrRow = row - TERMINAL_QR_MARGIN;
24472
+ const qrColumn = column - TERMINAL_QR_MARGIN;
24473
+ return qrRow >= 0 && qrRow < modules.size && qrColumn >= 0 && qrColumn < modules.size && modules.get(qrRow, qrColumn) === 1;
24474
+ };
24475
+ for (let row = 0; row < size; row += 2) {
24476
+ let line = "\x1B[30;47m";
24477
+ for (let column = 0; column < size; column += 1) {
24478
+ const top = isBlack(row, column);
24479
+ const bottom = isBlack(row + 1, column);
24480
+ line += top ? bottom ? "\u2588" : "\u2580" : bottom ? "\u2584" : " ";
24481
+ }
24482
+ lines.push(`${line}\x1B[0m`);
24483
+ }
24484
+ return lines.join("\n");
24485
+ }
24486
+ function authorizedMessage(authorization) {
24487
+ return authorization.account === void 0 ? "Remote Host login complete." : `Remote Host login complete for ${authorization.account}.`;
24488
+ }
24489
+ function alternativeProviderHint(provider) {
24490
+ return provider === "zhihu" ? "GitHub is also supported: ds-harness-remote login github" : "Zhihu is also supported: ds-harness-remote login zhihu";
24491
+ }
24492
+ function authorizationLabel(method) {
24493
+ if (method === "host_registration_code") return "Host registration code";
24494
+ if (method === "owned_device") return "owned device";
24495
+ return "account";
24496
+ }
24497
+ function statusErrorCode(error) {
24498
+ return error instanceof ServerApiError ? error.code : "CONNECTION_FAILED";
24499
+ }
24500
+ function cliErrorMessage(error) {
24501
+ const message = error instanceof Error ? error.message : "The Remote command failed.";
24502
+ const code = error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : void 0;
24503
+ return code === void 0 ? message : `${message} (${code})`;
24504
+ }
24505
+ function helpText() {
24506
+ return [
24507
+ "Usage:",
24508
+ " ds-harness-remote login [github|zhihu]",
24509
+ " ds-harness-remote status",
24510
+ " ds-harness-remote logout",
24511
+ "",
24512
+ "Inside dsh-TUI, use /remote login, /remote status, or /remote logout.",
24513
+ "login defaults to Zhihu and authorizes this computer as a Remote Host with a terminal QR code.",
24514
+ `The Server is ${DEFAULT_REMOTE_SERVER_URL}.`,
24515
+ "Host configuration is not exposed by this CLI yet. Restart dsh-tui after login or logout.",
24516
+ ""
24517
+ ].join("\n");
24518
+ }
24519
+ function write(target, value) {
24520
+ target.write(value);
24521
+ }
24522
+ function terminalLink(url, target) {
24523
+ if (target.isTTY !== true) return url;
24524
+ return `\x1B]8;;${url}\x07${url}\x1B]8;;\x07`;
24525
+ }
24526
+ async function exists3(path) {
24527
+ try {
24528
+ await stat5(path);
24529
+ return true;
24530
+ } catch (error) {
24531
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return false;
24532
+ throw error;
24533
+ }
24534
+ }
24535
+ var CliUsageError = class extends Error {
24536
+ };
24537
+
24538
+ // src/tui-command.ts
24539
+ var QR_POLL_INTERVAL_MS2 = 2e3;
24540
+ var REMOTE_LOGIN_SCENE_ID = "remote-login";
24541
+ var REMOTE_STATUS_SCENE_ID = "remote-status";
24542
+ function installTuiRemoteCommand(ctx, resolveTarget) {
24543
+ const tuiContext = ctx;
24544
+ const commands = tuiContext.get("commands", false);
24545
+ if (commands === void 0) return;
24546
+ const pluginHost = tuiContext.get("tuiPluginHost", false);
24547
+ const trees = tuiContext.get("tuiCommandTrees", false);
24548
+ const scenes = tuiContext.get("tuiScenes", false);
24549
+ const login2 = new RemoteLoginController(resolveTarget);
24550
+ const LoginScene = createRemoteLoginScene(login2);
24551
+ const StatusScene = createRemoteStatusScene(resolveTarget);
24552
+ tuiContext.effect(() => {
24553
+ const definition = {
24554
+ name: "remote",
24555
+ description: "Manage Remote Host login and connection status",
24556
+ input: { hint: "[status | login [github|zhihu] | logout]" },
24557
+ recordInput: false,
24558
+ handler: async ({ rawInput }) => {
24559
+ const args = rawInput.trim().split(/\s+/u).filter(Boolean);
24560
+ const command = args[0] ?? "status";
24561
+ if (command === "status" && args.length <= 1) {
24562
+ if (scenes?.open(REMOTE_STATUS_SCENE_ID) === true) return { kind: "success" };
24563
+ return { kind: "success", text: formatRemoteStatusInline(resolveTarget()) };
24564
+ }
24565
+ if (command === "login" && args.length <= 2) {
24566
+ const provider = args[1] ?? "zhihu";
24567
+ if (provider !== "github" && provider !== "zhihu") {
24568
+ return { kind: "error", text: "Usage: /remote login [github|zhihu]" };
24569
+ }
24570
+ if (scenes === void 0) {
24571
+ return {
24572
+ kind: "error",
24573
+ text: `The Remote login screen is unavailable. Run "ds-harness-remote login ${provider}" outside dsh-TUI, then restart it.`
24574
+ };
24575
+ }
24576
+ login2.start(provider);
24577
+ if (!scenes.open(REMOTE_LOGIN_SCENE_ID)) {
24578
+ login2.cancel();
24579
+ return { kind: "error", text: "The Remote login screen is unavailable." };
24580
+ }
24581
+ return { kind: "success" };
24582
+ }
24583
+ if (command === "logout" && args.length === 1) {
24584
+ const target = resolveTarget();
24585
+ if (target === void 0) return remoteNotReady();
24586
+ try {
24587
+ await target.runtime.clearHostAuthorization();
24588
+ return {
24589
+ kind: "success",
24590
+ text: "Remote Host logged out and its local device identity was rotated."
24591
+ };
24592
+ } catch (error) {
24593
+ return {
24594
+ kind: "error",
24595
+ text: `Local Remote Host credentials were cleared, but Server revocation failed (${errorCode4(error)}).`
24596
+ };
24597
+ }
24598
+ }
24599
+ if (command === "config") {
24600
+ return { kind: "error", text: "Host configuration is not supported yet." };
24601
+ }
24602
+ return { kind: "error", text: remoteCommandUsage() };
24603
+ }
24604
+ };
24605
+ const disposeCommand = registerRemoteCommand(tuiContext, commands, pluginHost, definition);
24606
+ const disposeTree = registerOptional(tuiContext, "command completion", () => trees?.register({
24607
+ root: "remote",
24608
+ descriptions: {
24609
+ en: "Manage Remote Host login and connection status",
24610
+ zh: "\u7BA1\u7406 Remote Host \u767B\u5F55\u548C\u8FDE\u63A5\u72B6\u6001"
24611
+ },
24612
+ children: remoteCommandChildren
24613
+ }));
24614
+ const disposeScene = registerOptional(tuiContext, "login scene", () => scenes?.register({
24615
+ id: REMOTE_LOGIN_SCENE_ID,
24616
+ title: "Remote Host login",
24617
+ component: LoginScene
24618
+ }, tuiContext));
24619
+ const disposeStatusScene = registerOptional(tuiContext, "status scene", () => scenes?.register({
24620
+ id: REMOTE_STATUS_SCENE_ID,
24621
+ title: "Remote Host status",
24622
+ component: StatusScene
24623
+ }, tuiContext));
24624
+ return () => {
24625
+ login2.cancel();
24626
+ disposeCommand();
24627
+ disposeTree();
24628
+ disposeScene();
24629
+ disposeStatusScene();
24630
+ };
24631
+ }, "ds-harness-remote: dsh-tui /remote command");
24632
+ }
24633
+ function registerRemoteCommand(ctx, commands, pluginHost, definition) {
24634
+ if (pluginHost === void 0) return commands.register(definition);
24635
+ try {
24636
+ return pluginHost.registerCommand(ctx, definition);
24637
+ } catch (error) {
24638
+ if (!hasErrorCode2(error, "COMPONENT_NOT_ADMITTED")) throw error;
24639
+ ctx.logger.debug("dsh-TUI Remote command is using the legacy command registry because Component admission is unavailable");
24640
+ return commands.register(definition);
24641
+ }
24642
+ }
24643
+ function hasErrorCode2(error, code) {
24644
+ return typeof error === "object" && error !== null && "code" in error && error.code === code;
24645
+ }
24646
+ function registerOptional(ctx, feature, register) {
24647
+ try {
24648
+ return register() ?? (() => {
24649
+ });
24650
+ } catch (error) {
24651
+ ctx.logger.warn(`dsh-TUI Remote ${feature} is unavailable`, { code: errorCode4(error) });
24652
+ return () => {
24653
+ };
24654
+ }
24655
+ }
24656
+ function remoteCommandChildren(canonicalPath) {
24657
+ if (canonicalPath.length === 1 && canonicalPath[0] === "remote") {
24658
+ return [
24659
+ {
24660
+ name: "status",
24661
+ description: "Show Remote Host authorization and connection status",
24662
+ descriptions: { zh: "\u67E5\u770B Remote Host \u6388\u6743\u548C\u8FDE\u63A5\u72B6\u6001" }
24663
+ },
24664
+ {
24665
+ name: "login",
24666
+ description: "Authorize this Host with a GitHub or Zhihu QR code",
24667
+ descriptions: { zh: "\u4F7F\u7528 GitHub \u6216\u77E5\u4E4E\u4E8C\u7EF4\u7801\u6388\u6743\u5F53\u524D Host" }
24668
+ },
24669
+ {
24670
+ name: "logout",
24671
+ description: "Revoke this Host and rotate its local identity",
24672
+ descriptions: { zh: "\u64A4\u9500\u5F53\u524D Host \u5E76\u8F6E\u6362\u672C\u5730\u8EAB\u4EFD" }
24673
+ }
24674
+ ];
24675
+ }
24676
+ if (canonicalPath.length === 2 && canonicalPath[0] === "remote" && canonicalPath[1] === "login") {
24677
+ return [
24678
+ { name: "zhihu", description: "Sign in with Zhihu (default)", descriptions: { zh: "\u4F7F\u7528\u77E5\u4E4E\u767B\u5F55\uFF08\u9ED8\u8BA4\uFF09" } },
24679
+ { name: "github", description: "Sign in with GitHub", descriptions: { zh: "\u4F7F\u7528 GitHub \u767B\u5F55" } }
24680
+ ];
24681
+ }
24682
+ return [];
24683
+ }
24684
+ function remoteStatusLines(target) {
24685
+ if (target === void 0) {
24686
+ return ["Remote Host is disabled or still starting. Check the ds-harness-remote settings and retry."];
24687
+ }
24688
+ const { runtime, config } = target;
24689
+ const status2 = runtime.hostStatus();
24690
+ const diagnostics = runtime.diagnostics();
24691
+ const codex = runtime.codexStatus();
24692
+ const capabilities = new Set(diagnostics.capabilities);
24693
+ const connection = status2.online ? "online" : status2.reconnecting ? "reconnecting" : status2.accountRequired ? "authorization required" : "offline";
24694
+ return [
24695
+ `Server: ${config.serverUrl ?? "not configured"}`,
24696
+ "Host control: enabled",
24697
+ `Device: ${status2.deviceId ?? "not initialized"}`,
24698
+ `Authorization: ${status2.authorized ? status2.account === void 0 ? "logged in" : `logged in (${status2.account})` : "logged out"}`,
24699
+ `Server connection: ${connection}`,
24700
+ `Harness Remote API: ${capabilities.has("harness.api.v1") ? "available (ApiProxy)" : capabilities.has("harness.remote.v1") ? "available (Typert Remote)" : "unavailable"}`,
24701
+ `Remote clients: ${diagnostics.activeConnections}`,
24702
+ `Codex Remote: ${codex.enabled ? codex.state : "disabled"}`,
24703
+ "",
24704
+ "Commands: /remote login [github|zhihu] \xB7 /remote status \xB7 /remote logout"
24705
+ ];
24706
+ }
24707
+ function formatRemoteStatusInline(target) {
24708
+ return remoteStatusLines(target).filter(Boolean).join(" \xB7 ");
24709
+ }
24710
+ function remoteCommandUsage() {
24711
+ return [
24712
+ "Usage:",
24713
+ " /remote",
24714
+ " /remote status",
24715
+ " /remote login [github|zhihu]",
24716
+ " /remote logout"
24717
+ ].join("\n");
24718
+ }
24719
+ function remoteNotReady() {
24720
+ return {
24721
+ kind: "error",
24722
+ text: "Remote Host is disabled or still starting. Check the ds-harness-remote settings and retry."
24723
+ };
24724
+ }
24725
+ var RemoteLoginController = class {
24726
+ constructor(resolveTarget) {
24727
+ this.resolveTarget = resolveTarget;
24728
+ }
24729
+ current = { phase: "idle", provider: "zhihu" };
24730
+ listeners = /* @__PURE__ */ new Set();
24731
+ attempt = 0;
24732
+ snapshot = () => this.current;
24733
+ subscribe = (listener) => {
24734
+ this.listeners.add(listener);
24735
+ return () => {
24736
+ this.listeners.delete(listener);
24737
+ };
24738
+ };
24739
+ start(provider) {
24740
+ const attempt = ++this.attempt;
24741
+ this.update({ phase: "loading", provider });
24742
+ void this.run(attempt, provider);
24743
+ }
24744
+ cancel() {
24745
+ this.attempt += 1;
24746
+ }
24747
+ async run(attempt, provider) {
24748
+ try {
24749
+ const target = this.resolveTarget();
24750
+ if (target === void 0) throw new Error("REMOTE_NOT_READY");
24751
+ const session = await target.runtime.startHostOAuthQrLogin(provider);
24752
+ const [qr, compactQr] = await Promise.all([
24753
+ renderTerminalQr(session.scanUrl),
24754
+ renderCompactTerminalQr(session.scanUrl)
24755
+ ]);
24756
+ if (attempt !== this.attempt) return;
24757
+ this.update({ phase: "waiting", provider, qr, compactQr, scanUrl: session.scanUrl });
24758
+ await this.poll(attempt, provider, session, target.runtime);
24759
+ } catch (error) {
24760
+ if (attempt !== this.attempt) return;
24761
+ this.update({ phase: "error", provider, error: errorCode4(error) });
24762
+ }
24763
+ }
24764
+ async poll(attempt, provider, session, runtime) {
24765
+ const deadline = Date.now() + session.expiresIn * 1e3;
24766
+ while (attempt === this.attempt && Date.now() < deadline) {
24767
+ try {
24768
+ const result = await runtime.pollHostOAuthQrLogin(session.qrId);
24769
+ if (attempt !== this.attempt) return;
24770
+ if (result.status === "complete") {
24771
+ this.update({ phase: "complete", provider, account: result.authorization.account });
24772
+ return;
24773
+ }
24774
+ if (result.status === "expired") break;
24775
+ } catch (error) {
24776
+ if (!(error instanceof ServerApiError) || !error.retryable) throw error;
24777
+ }
24778
+ await wait(Math.min(QR_POLL_INTERVAL_MS2, Math.max(1, deadline - Date.now())));
24779
+ }
24780
+ if (attempt === this.attempt) this.update({ phase: "expired", provider });
24781
+ }
24782
+ update(snapshot) {
24783
+ this.current = snapshot;
24784
+ for (const listener of this.listeners) listener(snapshot);
24785
+ }
24786
+ };
24787
+ function createRemoteLoginScene(controller) {
24788
+ return function RemoteLoginScene({ React, ui, close }) {
24789
+ const [snapshot, setSnapshot] = React.useState(controller.snapshot());
24790
+ const terminal = ui.useTerminalSize();
24791
+ React.useEffect(() => {
24792
+ const unsubscribe = controller.subscribe(setSnapshot);
24793
+ return () => {
24794
+ unsubscribe();
24795
+ controller.cancel();
24796
+ };
24797
+ }, []);
24798
+ ui.useInput((input2, key) => {
24799
+ if (key.escape === true || input2.toLowerCase() === "q") {
24800
+ controller.cancel();
24801
+ close();
24802
+ }
24803
+ });
24804
+ const provider = snapshot.provider === "github" ? "GitHub" : "Zhihu";
24805
+ const other = snapshot.provider === "github" ? "/remote login zhihu" : "/remote login github";
24806
+ const children = [
24807
+ React.createElement(ui.Text, { key: "title", bold: true, color: "accent" }, "Remote Host login"),
24808
+ React.createElement(ui.Text, { key: "provider" }, `Provider: ${provider} \xB7 You can also use ${other}`)
24809
+ ];
24810
+ if (snapshot.phase === "loading") {
24811
+ children.push(React.createElement(ui.Text, { key: "loading", color: "subtle" }, "Creating authorization QR code\u2026"));
24812
+ } else if (snapshot.phase === "waiting" && snapshot.qr !== void 0 && snapshot.scanUrl !== void 0) {
24813
+ const fullRows = snapshot.qr.split("\n").length;
24814
+ const fullColumns = printableWidth(snapshot.qr.split("\n")[0] ?? "");
24815
+ const useFullQr = terminal.rows >= fullRows + 7 && terminal.columns >= fullColumns + 4;
24816
+ const qr = useFullQr || snapshot.compactQr === void 0 ? snapshot.qr : snapshot.compactQr;
24817
+ const qrRows = qr.split("\n").length;
24818
+ const qrColumns = printableWidth(qr.split("\n")[0] ?? "");
24819
+ if (terminal.rows < qrRows + 7 || terminal.columns < qrColumns + 4) {
24820
+ children.push(React.createElement(
24821
+ ui.Text,
24822
+ { key: "size-warning", color: "warning" },
24823
+ `Resize the terminal to at least ${qrColumns + 4} \xD7 ${qrRows + 7} if the QR code is clipped.`
24824
+ ));
24825
+ }
24826
+ children.push(
24827
+ React.createElement(ui.Text, { key: "scan" }, "Scan this QR code to authorize this Host:"),
24828
+ React.createElement(ui.Text, { key: "qr", wrap: "truncate" }, qr),
24829
+ React.createElement(ui.Text, { key: "url", color: "link", underline: true }, terminalLink2(snapshot.scanUrl)),
24830
+ React.createElement(ui.Text, { key: "waiting", color: "subtle" }, "Waiting for authorization\u2026")
24831
+ );
24832
+ } else if (snapshot.phase === "complete") {
24833
+ children.push(React.createElement(
24834
+ ui.Text,
24835
+ { key: "complete", color: "success" },
24836
+ snapshot.account === void 0 ? "Remote Host login complete. The Host is reconnecting now." : `Remote Host login complete for ${snapshot.account}. The Host is reconnecting now.`
24837
+ ));
24838
+ } else if (snapshot.phase === "expired") {
24839
+ children.push(React.createElement(ui.Text, { key: "expired", color: "warning" }, "The QR code expired. Close this screen and run /remote login again."));
24840
+ } else if (snapshot.phase === "error") {
24841
+ children.push(React.createElement(ui.Text, { key: "error", color: "error" }, `Remote Host login failed (${snapshot.error ?? "UNKNOWN"}).`));
24842
+ }
24843
+ children.push(React.createElement(ui.Text, { key: "close", color: "subtle" }, "Press Esc or q to return."));
24844
+ return React.createElement(ui.Box, {
24845
+ flexDirection: "column",
24846
+ gap: 1,
24847
+ paddingX: Math.max(0, Math.min(2, Math.floor((terminal.columns - 1) / 2)))
24848
+ }, ...children);
24849
+ };
24850
+ }
24851
+ function createRemoteStatusScene(resolveTarget) {
24852
+ return function RemoteStatusScene({ React, ui, close }) {
24853
+ const terminal = ui.useTerminalSize();
24854
+ ui.useInput((input2, key) => {
24855
+ if (key.escape === true || input2.toLowerCase() === "q") close();
24856
+ });
24857
+ const children = [
24858
+ React.createElement(ui.Text, { key: "title", bold: true, color: "accent" }, "Remote Host status"),
24859
+ ...remoteStatusLines(resolveTarget()).map((line, index) => React.createElement(
24860
+ ui.Text,
24861
+ { key: `status-${index}`, color: line === "" ? "subtle" : void 0 },
24862
+ line === "" ? " " : line
24863
+ )),
24864
+ React.createElement(ui.Text, { key: "close", color: "subtle" }, "Press Esc or q to return.")
24865
+ ];
24866
+ return React.createElement(ui.Box, {
24867
+ flexDirection: "column",
24868
+ gap: 1,
24869
+ paddingX: Math.max(0, Math.min(2, Math.floor((terminal.columns - 1) / 2)))
24870
+ }, ...children);
24871
+ };
24872
+ }
24873
+ function terminalLink2(url) {
24874
+ return `\x1B]8;;${url}\x07${url}\x1B]8;;\x07`;
24875
+ }
24876
+ function printableWidth(value) {
24877
+ return value.replace(/\u001B\[[0-9;]*m/gu, "").length;
24878
+ }
24879
+ function errorCode4(error) {
24880
+ if (error instanceof ServerApiError) return error.code;
24881
+ if (error instanceof Error && "code" in error && typeof error.code === "string") return error.code;
24882
+ return "CONNECTION_FAILED";
24883
+ }
24884
+ function wait(milliseconds) {
24885
+ return new Promise((resolve3) => setTimeout(resolve3, milliseconds));
24886
+ }
24887
+
23860
24888
  // src/remote-file-content-provider.ts
23861
24889
  var REMOTE_FILE_SAVE_AS_MAX_BYTES = 100 * 1024 * 1024;
23862
24890
  var REMOTE_FILE_FAST_SAVE_AS_MAX_BYTES = 1024 * 1024 * 1024;
@@ -23936,15 +24964,44 @@ var legacyLoaderModuleNames = /* @__PURE__ */ new Set(["dsh-remote", "@dsh-remot
23936
24964
  var pluginSettingsNamespace = "ds-harness-remote";
23937
24965
  var legacySettingsNamespace = "dsh-remote";
23938
24966
  function apply(ctx, input2 = {}) {
23939
- ctx.inject(["settings", "connection", "typertGateway"], (runtimeContext) => {
24967
+ const tuiCommandsAvailable = ctx.get("commands", false) !== void 0 && ctx.get("tuiScenes", false) !== void 0;
24968
+ const tuiBinding = tuiCommandsAvailable ? {} : void 0;
24969
+ if (tuiBinding !== void 0) installTuiRemoteCommand(ctx, () => tuiBinding.target);
24970
+ const activateWhenCarrierReady = (runtimeContext) => {
23940
24971
  const gateway = runtimeContext.get("typertGateway");
23941
- if (runtimeContext.get("apiProxy") !== void 0 || new TypertGatewaySwitch(gateway).supportsCarrier()) {
23942
- return activate(runtimeContext, input2);
24972
+ const connection = runtimeContext.get("connection");
24973
+ let disposePendingControl;
24974
+ const installPendingControl = () => {
24975
+ if (connection === void 0 || disposePendingControl !== void 0) return;
24976
+ disposePendingControl = runtimeContext.effect(() => {
24977
+ const control = new PluginControlRuntime(
24978
+ resolveConfig(input2),
24979
+ new IdentityStore().directory,
24980
+ void 0,
24981
+ void 0,
24982
+ void 0
24983
+ );
24984
+ return control.register(connection);
24985
+ }, "dsh-remote pending control");
24986
+ };
24987
+ const startActiveRuntime = async (activeContext) => {
24988
+ await disposePendingControl?.();
24989
+ disposePendingControl = void 0;
24990
+ await activate(activeContext, input2, tuiBinding);
24991
+ };
24992
+ if (tuiCommandsAvailable || runtimeContext.get("apiProxy") !== void 0 || new TypertGatewaySwitch(gateway).supportsCarrier()) {
24993
+ return startActiveRuntime(runtimeContext);
23943
24994
  }
23944
- runtimeContext.inject(["apiProxy"], (legacyContext) => activate(legacyContext, input2));
23945
- });
24995
+ installPendingControl();
24996
+ runtimeContext.inject(["apiProxy"], (legacyContext) => startActiveRuntime(legacyContext));
24997
+ };
24998
+ if (tuiCommandsAvailable) {
24999
+ ctx.inject(["settings", "typertGateway"], activateWhenCarrierReady);
25000
+ } else {
25001
+ ctx.inject(["settings", "connection", "typertGateway"], activateWhenCarrierReady);
25002
+ }
23946
25003
  }
23947
- async function activate(ctx, input2) {
25004
+ async function activate(ctx, input2, tuiBinding) {
23948
25005
  const settings = ctx.get("settings");
23949
25006
  const settingsScope = settings?.register(pluginSettingsNamespace, Config, {
23950
25007
  base: input2,
@@ -23954,12 +25011,20 @@ async function activate(ctx, input2) {
23954
25011
  }
23955
25012
  });
23956
25013
  if (settings !== void 0 && settingsScope !== void 0) {
23957
- const migration = await migrateLegacySettings(settings, settingsScope);
23958
- if (migration === "migrated") ctx.logger.info("migrated legacy Remote settings namespace");
23959
- if (migration === "failed") ctx.logger.warn("failed to migrate legacy Remote settings namespace");
25014
+ const migration = migrateLegacySettings(settings, settingsScope);
25015
+ reportSettingsMigration(ctx, await migration);
25016
+ }
25017
+ const connection = ctx.get("connection");
25018
+ const resolvedConfig = resolveConfig(settingsScope?.get() ?? input2);
25019
+ const config = connection === void 0 && resolvedConfig.serverUrl === void 0 ? { ...resolvedConfig, serverUrl: DEFAULT_REMOTE_SERVER_URL } : resolvedConfig;
25020
+ const defaultIdentityDirectory = new IdentityStore().directory;
25021
+ if (!config.enabled) {
25022
+ if (connection !== void 0) {
25023
+ const controlRuntime2 = new PluginControlRuntime(config, defaultIdentityDirectory, settingsScope, void 0, void 0);
25024
+ ctx.effect(() => controlRuntime2.register(connection), "dsh-remote disabled control");
25025
+ }
25026
+ return;
23960
25027
  }
23961
- const config = resolveConfig(settingsScope?.get() ?? input2);
23962
- if (!config.enabled) return;
23963
25028
  const logger = new SafeLogger({
23964
25029
  debug: (message) => {
23965
25030
  ctx.logger.debug(message);
@@ -23978,13 +25043,10 @@ async function activate(ctx, input2) {
23978
25043
  console.error(message);
23979
25044
  }
23980
25045
  }, config.logLevel);
23981
- await disableLegacyLoaderEntries(ctx, logger);
23982
- const defaultIdentityDirectory = new IdentityStore().directory;
23983
25046
  const hostIdentities = new IdentityStore({
23984
25047
  directory: config.serverUrl === void 0 ? defaultIdentityDirectory : serverStorageDirectory(defaultIdentityDirectory, config.serverUrl, "host")
23985
25048
  });
23986
25049
  const apiProxy = ctx.get("apiProxy");
23987
- const connection = ctx.get("connection");
23988
25050
  const nativeTypertGateway = ctx.get("typertGateway");
23989
25051
  const localTypertGateway = new TypertGatewaySwitch(nativeTypertGateway).local();
23990
25052
  const runtime = new HostPluginRuntime(
@@ -24014,6 +25076,9 @@ async function activate(ctx, input2) {
24014
25076
  const controlRuntime = connection === void 0 ? void 0 : new PluginControlRuntime(config, defaultIdentityDirectory, settingsScope, clientRuntime, hostControl);
24015
25077
  ctx.provide("dshRemote", runtime);
24016
25078
  if (clientRuntime !== void 0) ctx.provide("dshRemoteClient", clientRuntime);
25079
+ const tuiTarget = { runtime, config };
25080
+ if (tuiBinding !== void 0) tuiBinding.target = tuiTarget;
25081
+ await disableLegacyLoaderEntries(ctx, logger);
24017
25082
  await ctx.effect(async () => {
24018
25083
  const disposeControl = controlRuntime?.register(connection);
24019
25084
  try {
@@ -24029,18 +25094,24 @@ async function activate(ctx, input2) {
24029
25094
  });
24030
25095
  }
24031
25096
  } catch (error) {
25097
+ if (tuiBinding?.target === tuiTarget) tuiBinding.target = void 0;
24032
25098
  await disposeControl?.();
24033
25099
  await clientRuntime?.close();
24034
25100
  await runtime.close();
24035
25101
  throw error;
24036
25102
  }
24037
25103
  return async () => {
25104
+ if (tuiBinding?.target === tuiTarget) tuiBinding.target = void 0;
24038
25105
  await disposeControl?.();
24039
25106
  await clientRuntime?.close();
24040
25107
  await runtime.close();
24041
25108
  };
24042
25109
  }, "dsh-remote lifecycle");
24043
25110
  }
25111
+ function reportSettingsMigration(ctx, migration) {
25112
+ if (migration === "migrated") ctx.logger.info("migrated legacy Remote settings namespace");
25113
+ if (migration === "failed") ctx.logger.warn("failed to migrate legacy Remote settings namespace");
25114
+ }
24044
25115
  async function migrateLegacySettings(settings, currentScope) {
24045
25116
  if (typeof settings.describe !== "function") return "skipped";
24046
25117
  try {
@@ -24131,6 +25202,7 @@ export {
24131
25202
  migrateLegacySettings,
24132
25203
  name,
24133
25204
  resolveConfig,
25205
+ runCli,
24134
25206
  serverStorageDirectory
24135
25207
  };
24136
25208
  /*! Bundled license information: