@blue-soda/dsh-remote 0.4.28 → 0.4.30

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 (51) hide show
  1. package/README.md +9 -9
  2. package/dist/atomic-file.d.ts +16 -0
  3. package/dist/atomic-file.d.ts.map +1 -0
  4. package/dist/atomic-file.js +81 -0
  5. package/dist/atomic-file.js.map +1 -0
  6. package/dist/client-runtime.d.ts +137 -1
  7. package/dist/client-runtime.d.ts.map +1 -1
  8. package/dist/client-runtime.js +399 -22
  9. package/dist/client-runtime.js.map +1 -1
  10. package/dist/client-secure-transport.d.ts.map +1 -1
  11. package/dist/client-secure-transport.js +2 -1
  12. package/dist/client-secure-transport.js.map +1 -1
  13. package/dist/client-target-store.d.ts +40 -0
  14. package/dist/client-target-store.d.ts.map +1 -0
  15. package/dist/client-target-store.js +79 -0
  16. package/dist/client-target-store.js.map +1 -0
  17. package/dist/client.github.js +63 -15
  18. package/dist/client.js +63 -15
  19. package/dist/codex/domain.d.ts +9 -0
  20. package/dist/codex/domain.d.ts.map +1 -1
  21. package/dist/codex/domain.js +48 -7
  22. package/dist/codex/domain.js.map +1 -1
  23. package/dist/identity-store.d.ts.map +1 -1
  24. package/dist/identity-store.js +6 -3
  25. package/dist/identity-store.js.map +1 -1
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +779 -220
  28. package/dist/index.js.map +4 -4
  29. package/dist/remote-typert-gateway.d.ts.map +1 -1
  30. package/dist/remote-typert-gateway.js +5 -3
  31. package/dist/remote-typert-gateway.js.map +1 -1
  32. package/dist/rpc-binary-attachments.d.ts +17 -0
  33. package/dist/rpc-binary-attachments.d.ts.map +1 -0
  34. package/dist/rpc-binary-attachments.js +89 -0
  35. package/dist/rpc-binary-attachments.js.map +1 -0
  36. package/dist/server-credentials.d.ts.map +1 -1
  37. package/dist/server-credentials.js +6 -3
  38. package/dist/server-credentials.js.map +1 -1
  39. package/dist/service.d.ts +10 -0
  40. package/dist/service.d.ts.map +1 -1
  41. package/dist/service.js +17 -1
  42. package/dist/service.js.map +1 -1
  43. package/dist/tui-command.js +7 -5
  44. package/dist/tui-command.js.map +1 -1
  45. package/dist/typert-gateway-switch.d.ts.map +1 -1
  46. package/dist/typert-gateway-switch.js +56 -4
  47. package/dist/typert-gateway-switch.js.map +1 -1
  48. package/dist/version.d.ts +1 -1
  49. package/dist/version.js +1 -1
  50. package/dsh-plugin.json +1 -1
  51. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -14579,7 +14579,7 @@ var ClientSecureTransport = class {
14579
14579
  }
14580
14580
  requireNoise() {
14581
14581
  if (this.noise === void 0 || !this.noise.complete || this.closed) {
14582
- throw new Error("The authenticated Noise channel is not connected.");
14582
+ throw Object.assign(new Error("The authenticated Noise channel is not connected."), { code: "TRANSPORT_CLOSED" });
14583
14583
  }
14584
14584
  return this.noise;
14585
14585
  }
@@ -15162,6 +15162,68 @@ function base64ToBytes2(value) {
15162
15162
  return bytes;
15163
15163
  }
15164
15164
 
15165
+ // src/rpc-binary-attachments.ts
15166
+ function hydrateRpcAttachments(result) {
15167
+ if (!isRecord3(result)) return result;
15168
+ const envelope = result;
15169
+ if (!Array.isArray(envelope.attachments) || envelope.attachments.length === 0) return result;
15170
+ const { attachments, ...rest } = result;
15171
+ const base = Object.hasOwn(rest, "value") ? rest.value : void 0;
15172
+ for (const attachment of attachments) {
15173
+ const entry = attachment;
15174
+ const path = Array.isArray(entry.path) ? entry.path : void 0;
15175
+ if (path === void 0 || path.length === 0) {
15176
+ throw new Error("The remote Host returned a byte attachment without a path.");
15177
+ }
15178
+ assignAtPath(base, path, decodeAttachmentBytes(entry.bytes));
15179
+ }
15180
+ return rest;
15181
+ }
15182
+ function assignAtPath(base, path, bytes) {
15183
+ if (base === null || typeof base !== "object") {
15184
+ throw new Error("The remote Host returned a byte attachment that its result cannot hold.");
15185
+ }
15186
+ let cursor2 = base;
15187
+ for (let index = 0; index < path.length - 1; index += 1) {
15188
+ const key = pathKey(path[index]);
15189
+ const next = cursor2[key];
15190
+ if (next === null || typeof next !== "object") {
15191
+ throw new Error("The remote Host returned a byte attachment at an unknown result path.");
15192
+ }
15193
+ cursor2 = next;
15194
+ }
15195
+ cursor2[pathKey(path[path.length - 1])] = bytes;
15196
+ }
15197
+ function pathKey(segment) {
15198
+ if (typeof segment === "string") return segment;
15199
+ if (typeof segment === "number" && Number.isInteger(segment) && segment >= 0) return String(segment);
15200
+ throw new Error("The remote Host returned a byte attachment with an invalid path segment.");
15201
+ }
15202
+ function decodeAttachmentBytes(value) {
15203
+ if (value instanceof Uint8Array) return value;
15204
+ if (typeof value === "string") return decodeBase64(value);
15205
+ if (Array.isArray(value)) return Uint8Array.from(value);
15206
+ if (!isRecord3(value)) throw new Error("The remote Host returned invalid byte attachment data.");
15207
+ if (Array.isArray(value.data)) {
15208
+ return Uint8Array.from(value.data);
15209
+ }
15210
+ const keys = Object.keys(value);
15211
+ if (keys.every((key) => /^\d+$/u.test(key))) {
15212
+ const ordered = keys.map(Number).sort((left, right) => left - right);
15213
+ return Uint8Array.from(ordered.map((key) => value[String(key)]));
15214
+ }
15215
+ throw new Error("The remote Host returned invalid byte attachment data.");
15216
+ }
15217
+ function decodeBase64(value) {
15218
+ const binary = atob(value);
15219
+ const bytes = new Uint8Array(binary.length);
15220
+ for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);
15221
+ return bytes;
15222
+ }
15223
+ function isRecord3(value) {
15224
+ return typeof value === "object" && value !== null && !Array.isArray(value);
15225
+ }
15226
+
15165
15227
  // src/session-format-compat.ts
15166
15228
  function normalizeLegacySessionGatewayValue(endpoint, value) {
15167
15229
  if (endpoint === "session/page") return normalizePage(value);
@@ -15169,7 +15231,7 @@ function normalizeLegacySessionGatewayValue(endpoint, value) {
15169
15231
  return value;
15170
15232
  }
15171
15233
  function normalizeFollowFrame(value) {
15172
- if (!isRecord3(value)) return value;
15234
+ if (!isRecord4(value)) return value;
15173
15235
  if (value.type === "snapshot") {
15174
15236
  return {
15175
15237
  ...value,
@@ -15181,7 +15243,7 @@ function normalizeFollowFrame(value) {
15181
15243
  return normalizeEntry(value);
15182
15244
  }
15183
15245
  function normalizePage(value) {
15184
- if (!isRecord3(value)) return value;
15246
+ if (!isRecord4(value)) return value;
15185
15247
  return { ...value, records: normalizeRecords(value.records) };
15186
15248
  }
15187
15249
  function normalizeRecords(value) {
@@ -15226,10 +15288,10 @@ function eventTime(value) {
15226
15288
  return typeof value.event.time === "number" && Number.isSafeInteger(value.event.time) ? value.event.time : 0;
15227
15289
  }
15228
15290
  function isEventEntry(value) {
15229
- return isRecord3(value) && value.type === "event" && isRecord3(value.event);
15291
+ return isRecord4(value) && value.type === "event" && isRecord4(value.event);
15230
15292
  }
15231
15293
  function normalizeHeader(value) {
15232
- if (!isRecord3(value)) return value;
15294
+ if (!isRecord4(value)) return value;
15233
15295
  const next = {
15234
15296
  ...value,
15235
15297
  version: 3,
@@ -15238,7 +15300,7 @@ function normalizeHeader(value) {
15238
15300
  return next.agentPreset === "code" ? { ...next, agentPreset: "ptc" } : next;
15239
15301
  }
15240
15302
  function normalizeEvent(value) {
15241
- if (!isRecord3(value)) return value;
15303
+ if (!isRecord4(value)) return value;
15242
15304
  let next = value;
15243
15305
  const type = normalizeEventType(value.type);
15244
15306
  if (type !== value.type) next = { ...next, type };
@@ -15258,7 +15320,7 @@ function normalizeEventType(value) {
15258
15320
  return value;
15259
15321
  }
15260
15322
  function normalizeEventData(type, value) {
15261
- if (!isRecord3(value)) return value;
15323
+ if (!isRecord4(value)) return value;
15262
15324
  if (type === "request/header") return normalizeRequestHeaderData(value);
15263
15325
  if (type === "agent-preset/selected" && value.agentPreset === "code") return { ...value, agentPreset: "ptc" };
15264
15326
  if (type === "user/message") return normalizeMessage(value);
@@ -15271,7 +15333,7 @@ function normalizeEventData(type, value) {
15271
15333
  return value;
15272
15334
  }
15273
15335
  function normalizeRequestHeaderData(value) {
15274
- if (!isRecord3(value.header)) return value;
15336
+ if (!isRecord4(value.header)) return value;
15275
15337
  const header = normalizeRequestHeader(value.header);
15276
15338
  return header === value.header ? value : { ...value, header };
15277
15339
  }
@@ -15287,7 +15349,7 @@ function normalizeRequestHeader(value) {
15287
15349
  changed = true;
15288
15350
  continue;
15289
15351
  }
15290
- if (key === "adapterDefaults" && isRecord3(field) && Object.keys(field).length === 0) {
15352
+ if (key === "adapterDefaults" && isRecord4(field) && Object.keys(field).length === 0) {
15291
15353
  changed = true;
15292
15354
  continue;
15293
15355
  }
@@ -15296,12 +15358,12 @@ function normalizeRequestHeader(value) {
15296
15358
  return changed ? next : value;
15297
15359
  }
15298
15360
  function normalizeMessage(value) {
15299
- if (!isRecord3(value) || !isRecord3(value.source)) return value;
15361
+ if (!isRecord4(value) || !isRecord4(value.source)) return value;
15300
15362
  if (value.source.kind !== "plugin" || value.source.plugin !== "tools-code-mode") return value;
15301
15363
  return { ...value, source: { ...value.source, plugin: "tools-ptc" } };
15302
15364
  }
15303
15365
  function normalizeSurfaceOp(value) {
15304
- if (!isRecord3(value) || value.op !== "replace") return value;
15366
+ if (!isRecord4(value) || value.op !== "replace") return value;
15305
15367
  const { start, end, ...rest } = value;
15306
15368
  if (start === void 0 && end === void 0) return value;
15307
15369
  return {
@@ -15311,7 +15373,7 @@ function normalizeSurfaceOp(value) {
15311
15373
  ...end === void 0 ? {} : { endSeq: end }
15312
15374
  };
15313
15375
  }
15314
- function isRecord3(value) {
15376
+ function isRecord4(value) {
15315
15377
  return typeof value === "object" && value !== null && !Array.isArray(value);
15316
15378
  }
15317
15379
  function isEventSeq(value) {
@@ -15352,7 +15414,7 @@ var RemoteTypertGateway2 = class {
15352
15414
  response = await this.callTransferred(encoded, signal);
15353
15415
  }
15354
15416
  }
15355
- const result = parseRpcResult(response);
15417
+ const result = parseRpcResult(hydrateRpcAttachments(response));
15356
15418
  const settingsResult = this.normalizeLegacyWelcomeSettings(endpoint, payload, result);
15357
15419
  if (settingsResult !== void 0) return settingsResult;
15358
15420
  if (result.ok && this.compatibility === "legacy-to-v3") {
@@ -15365,11 +15427,11 @@ var RemoteTypertGateway2 = class {
15365
15427
  /** DSH <=0.1.6 cannot persist the 0.1.7 welcome acknowledgement remotely. */
15366
15428
  normalizeLegacyWelcomeSettings(endpoint, payload, result) {
15367
15429
  if (!isLegacyRemoteHost2(this.harnessVersion)) return void 0;
15368
- if (endpoint === "settings/describe" && result.ok && isRecord4(result.value)) {
15430
+ if (endpoint === "settings/describe" && result.ok && isRecord5(result.value)) {
15369
15431
  this.settingsDescribeValue = result.value;
15370
15432
  return this.legacyWelcomeAcknowledged ? { ...result, value: patchWelcomeDescribe2(result.value) } : void 0;
15371
15433
  }
15372
- if (endpoint !== "settings/mutate" || !isRecord4(payload) || !isRecord4(payload.args)) return void 0;
15434
+ if (endpoint !== "settings/mutate" || !isRecord5(payload) || !isRecord5(payload.args)) return void 0;
15373
15435
  const args = payload.args;
15374
15436
  if (args.ns !== WELCOME_NOTICE_NAMESPACE2 || !isWelcomeNoticeOperation(args.ops)) return void 0;
15375
15437
  const value = patchWelcomeDescribe2(this.settingsDescribeValue ?? { namespaces: [] });
@@ -15398,7 +15460,8 @@ var RemoteTypertGateway2 = class {
15398
15460
  async *iterate(streamId, endpoint, queue, unsubscribe, unsubscribeClose, signal, onAbort) {
15399
15461
  try {
15400
15462
  for await (const value of queue) {
15401
- yield this.compatibility === "legacy-to-v3" ? normalizeLegacySessionGatewayValue(endpoint, value) : value;
15463
+ const hydrated = hydrateRpcAttachments(value);
15464
+ yield this.compatibility === "legacy-to-v3" ? normalizeLegacySessionGatewayValue(endpoint, hydrated) : hydrated;
15402
15465
  }
15403
15466
  } finally {
15404
15467
  signal.removeEventListener("abort", onAbort);
@@ -15533,16 +15596,16 @@ function routeStreamEvent2(event, streamId, queue) {
15533
15596
  queue.fail(remoteFailure({
15534
15597
  code: typeof failure2?.code === "string" ? failure2.code : "internal",
15535
15598
  message: typeof failure2?.message === "string" ? failure2.message : "The remote Harness stream failed.",
15536
- details: isRecord4(failure2?.details) ? failure2.details : {}
15599
+ details: isRecord5(failure2?.details) ? failure2.details : {}
15537
15600
  }));
15538
15601
  } else {
15539
15602
  queue.close();
15540
15603
  }
15541
15604
  }
15542
15605
  function parseRpcResult(value) {
15543
- if (!isRecord4(value)) throw new Error("The remote Host returned an invalid Gateway result.");
15606
+ if (!isRecord5(value)) throw new Error("The remote Host returned an invalid Gateway result.");
15544
15607
  if (value.ok === true) return Object.hasOwn(value, "value") ? { ok: true, value: value.value } : { ok: true };
15545
- if (value.ok !== false || !isRecord4(value.error) || typeof value.error.code !== "string" || typeof value.error.message !== "string" || !isRecord4(value.error.details)) {
15608
+ if (value.ok !== false || !isRecord5(value.error) || typeof value.error.code !== "string" || typeof value.error.message !== "string" || !isRecord5(value.error.details)) {
15546
15609
  throw new Error("The remote Host returned an invalid Gateway failure.");
15547
15610
  }
15548
15611
  return {
@@ -15576,7 +15639,7 @@ function base64ToBytes3(value) {
15576
15639
  if (bytesToBase643(bytes) !== value) throw new Error("The remote Host returned non-canonical Harness Remote transfer data.");
15577
15640
  return bytes;
15578
15641
  }
15579
- function isRecord4(value) {
15642
+ function isRecord5(value) {
15580
15643
  return typeof value === "object" && value !== null && !Array.isArray(value);
15581
15644
  }
15582
15645
  function hasErrorCode(error, code) {
@@ -15589,13 +15652,13 @@ function isLegacyRemoteHost2(version) {
15589
15652
  return Number(match[1]) === 0 && Number(match[2]) === 1 && Number(match[3]) < 7;
15590
15653
  }
15591
15654
  function isWelcomeNoticeOperation(value) {
15592
- return Array.isArray(value) && value.some((operation) => isRecord4(operation) && operation.op === "set" && Array.isArray(operation.path) && operation.path.length === 1 && operation.path[0] === WELCOME_NOTICE_FIELD2 && operation.value === WELCOME_NOTICE_VERSION2);
15655
+ return Array.isArray(value) && value.some((operation) => isRecord5(operation) && operation.op === "set" && Array.isArray(operation.path) && operation.path.length === 1 && operation.path[0] === WELCOME_NOTICE_FIELD2 && operation.value === WELCOME_NOTICE_VERSION2);
15593
15656
  }
15594
15657
  function patchWelcomeDescribe2(value) {
15595
15658
  if (!Array.isArray(value.namespaces)) return value;
15596
15659
  const namespaces = value.namespaces.map((namespace) => {
15597
- if (!isRecord4(namespace) || namespace.ns !== WELCOME_NOTICE_NAMESPACE2) return namespace;
15598
- const current = isRecord4(namespace.value) ? namespace.value : {};
15660
+ if (!isRecord5(namespace) || namespace.ns !== WELCOME_NOTICE_NAMESPACE2) return namespace;
15661
+ const current = isRecord5(namespace.value) ? namespace.value : {};
15599
15662
  return { ...namespace, value: { ...current, [WELCOME_NOTICE_FIELD2]: WELCOME_NOTICE_VERSION2 } };
15600
15663
  });
15601
15664
  return { ...value, namespaces };
@@ -15757,11 +15820,11 @@ var DATA_IMAGE_URL2 = /^data:(image\/(?:png|jpeg|webp|gif));base64,([A-Za-z0-9+/
15757
15820
  function string(value) {
15758
15821
  return typeof value === "string" ? value : void 0;
15759
15822
  }
15760
- function isRecord5(value) {
15823
+ function isRecord6(value) {
15761
15824
  return typeof value === "object" && value !== null && !Array.isArray(value);
15762
15825
  }
15763
15826
  function record2(value) {
15764
- return isRecord5(value) ? value : {};
15827
+ return isRecord6(value) ? value : {};
15765
15828
  }
15766
15829
  function toolOutputImageBlocks(item) {
15767
15830
  const candidates = [
@@ -15851,7 +15914,7 @@ function collectImageBlocks(value, output = []) {
15851
15914
  for (const item of value) collectImageBlocks(item, output);
15852
15915
  return output;
15853
15916
  }
15854
- if (!isRecord5(value)) return output;
15917
+ if (!isRecord6(value)) return output;
15855
15918
  if (value.type === "image") output.push(value);
15856
15919
  for (const [key, child] of Object.entries(value)) {
15857
15920
  if (key === "attachment") continue;
@@ -16932,7 +16995,7 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
16932
16995
  this.broadcastRcHost({ type: "host/session-status", sessionId, running: args[1] });
16933
16996
  } else if (event === "api-session/removed" && sessionId !== void 0) {
16934
16997
  this.broadcastRcHost({ type: "host/session-removed", sessionId });
16935
- } else if (event === "api-session/added" && isRecord6(args[0])) {
16998
+ } else if (event === "api-session/added" && isRecord7(args[0])) {
16936
16999
  const summary = args[0];
16937
17000
  this.broadcastRcHost({
16938
17001
  type: "host/session-added",
@@ -17322,7 +17385,7 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
17322
17385
  }
17323
17386
  createApiProxy() {
17324
17387
  const call = (endpoint) => async (request, signal) => {
17325
- const payload = endpoint === "session.prompt" && isRecord6(request.payload) ? { ...request.payload, requestId: String(request.rpcId) } : request.payload;
17388
+ const payload = endpoint === "session.prompt" && isRecord7(request.payload) ? { ...request.payload, requestId: String(request.rpcId) } : request.payload;
17326
17389
  const result = await this.dispatch(rcEndpoint(endpoint), { args: { request: payload } }, signal ?? new AbortController().signal);
17327
17390
  return {
17328
17391
  rpcId: request.rpcId,
@@ -17977,7 +18040,7 @@ function codexPromptInput(content) {
17977
18040
  if (content.length === 0 || content.length > MAX_CODEX_PROMPT_PARTS) return void 0;
17978
18041
  const input2 = [];
17979
18042
  for (const value of content) {
17980
- if (!isRecord6(value)) return void 0;
18043
+ if (!isRecord7(value)) return void 0;
17981
18044
  if (value.type === "text") {
17982
18045
  if (typeof value.text !== "string" || value.text.length === 0 || value.text.length > MAX_CODEX_PROMPT_TEXT) return void 0;
17983
18046
  input2.push({ type: "text", text: value.text });
@@ -18227,12 +18290,12 @@ function errorCode(error) {
18227
18290
  return "code" in error && typeof error.code === "string" ? error.code : void 0;
18228
18291
  }
18229
18292
  function errorDetails(error) {
18230
- return "details" in error && isRecord6(error.details) ? error.details : {};
18293
+ return "details" in error && isRecord7(error.details) ? error.details : {};
18231
18294
  }
18232
18295
  function record3(value) {
18233
- return isRecord6(value) ? value : {};
18296
+ return isRecord7(value) ? value : {};
18234
18297
  }
18235
- function isRecord6(value) {
18298
+ function isRecord7(value) {
18236
18299
  return typeof value === "object" && value !== null && !Array.isArray(value);
18237
18300
  }
18238
18301
  function array(value) {
@@ -18354,8 +18417,63 @@ var AsyncValueQueue2 = class {
18354
18417
  import { platform } from "node:os";
18355
18418
 
18356
18419
  // src/server-credentials.ts
18357
- import { chmod, mkdir, readFile as readFile2, rename, rm, stat, writeFile } from "node:fs/promises";
18358
- import { dirname as dirname2, join as join2 } from "node:path";
18420
+ import { chmod, mkdir, readFile as readFile2, rm as rm2, stat as stat2, writeFile } from "node:fs/promises";
18421
+ import { dirname as dirname2, join as join3 } from "node:path";
18422
+
18423
+ // src/atomic-file.ts
18424
+ import { readdir, rename, rm, stat } from "node:fs/promises";
18425
+ import { join as join2 } from "node:path";
18426
+ var TRANSIENT_REPLACE_CODES = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES"]);
18427
+ var REPLACE_RETRY_DELAYS_MS = [20, 40, 80, 160, 320];
18428
+ var TEMPORARY_NAME = /\.\d+\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.tmp$/iu;
18429
+ var STALE_TEMPORARY_AGE_MS = 10 * 60 * 1e3;
18430
+ async function replaceFile(temporary, target2, options = {}) {
18431
+ const delays = options.delaysMs ?? REPLACE_RETRY_DELAYS_MS;
18432
+ for (let attempt = 0; ; attempt += 1) {
18433
+ try {
18434
+ await rename(temporary, target2);
18435
+ return;
18436
+ } catch (error) {
18437
+ const delay = delays[attempt];
18438
+ if (delay === void 0 || !TRANSIENT_REPLACE_CODES.has(errorCode2(error) ?? "")) throw error;
18439
+ await sleep2(delay);
18440
+ }
18441
+ }
18442
+ }
18443
+ async function sweepStaleTemporaries(directory, options = {}) {
18444
+ const maxAgeMs = options.maxAgeMs ?? STALE_TEMPORARY_AGE_MS;
18445
+ const removed = [];
18446
+ let entries;
18447
+ try {
18448
+ entries = await readdir(directory);
18449
+ } catch {
18450
+ return removed;
18451
+ }
18452
+ for (const entry of entries) {
18453
+ if (!TEMPORARY_NAME.test(entry)) continue;
18454
+ const path = join2(directory, entry);
18455
+ try {
18456
+ const info = await stat(path);
18457
+ if (!info.isFile() || Date.now() - info.mtimeMs < maxAgeMs) continue;
18458
+ await rm(path, { force: true });
18459
+ removed.push(path);
18460
+ } catch {
18461
+ }
18462
+ }
18463
+ return removed;
18464
+ }
18465
+ function errorCode2(error) {
18466
+ if (typeof error !== "object" || error === null || !("code" in error)) return void 0;
18467
+ const code = error.code;
18468
+ return typeof code === "string" ? code : void 0;
18469
+ }
18470
+ function sleep2(ms) {
18471
+ return new Promise((resolve4) => {
18472
+ setTimeout(resolve4, ms);
18473
+ });
18474
+ }
18475
+
18476
+ // src/server-credentials.ts
18359
18477
  var credentialSchema = external_exports.object({
18360
18478
  schemaVersion: external_exports.literal(1),
18361
18479
  serverUrl: external_exports.string().url(),
@@ -18370,7 +18488,7 @@ var credentialSchema = external_exports.object({
18370
18488
  var ServerCredentialStore = class {
18371
18489
  path;
18372
18490
  constructor(directory) {
18373
- this.path = join2(directory, "server-credentials.json");
18491
+ this.path = join3(directory, "server-credentials.json");
18374
18492
  }
18375
18493
  /** Serialize the complete read/refresh/write transaction across processes.
18376
18494
  * Never steal an old lock: a suspended owner may still consume a one-use token.
@@ -18393,7 +18511,7 @@ var ServerCredentialStore = class {
18393
18511
  try {
18394
18512
  return await operation();
18395
18513
  } finally {
18396
- await rm(lock, { recursive: true });
18514
+ await rm2(lock, { recursive: true });
18397
18515
  }
18398
18516
  }
18399
18517
  async load(serverUrl, deviceId) {
@@ -18414,7 +18532,7 @@ var ServerCredentialStore = class {
18414
18532
  return record7;
18415
18533
  }
18416
18534
  async clear() {
18417
- await rm(this.path, { force: true });
18535
+ await rm2(this.path, { force: true });
18418
18536
  }
18419
18537
  };
18420
18538
  var ServerCredentialsInvalidError = class extends Error {
@@ -18427,21 +18545,23 @@ var ServerCredentialsBusyError = class extends Error {
18427
18545
  }
18428
18546
  };
18429
18547
  async function atomicWrite(path, contents) {
18430
- await mkdir(dirname2(path), { recursive: true, mode: 448 });
18548
+ const directory = dirname2(path);
18549
+ await mkdir(directory, { recursive: true, mode: 448 });
18550
+ await sweepStaleTemporaries(directory);
18431
18551
  const temporary = `${path}.${process.pid}.${uuidV7()}.tmp`;
18432
18552
  await writeFile(temporary, contents, { encoding: "utf8", mode: 384, flag: "wx" });
18433
18553
  await chmod(temporary, 384);
18434
- await rename(temporary, path);
18554
+ await replaceFile(temporary, path);
18435
18555
  await chmod(path, 384);
18436
18556
  }
18437
18557
  async function assertPrivateMode(path) {
18438
18558
  if (process.platform === "win32") return;
18439
- const mode = (await stat(path)).mode & 511;
18559
+ const mode = (await stat2(path)).mode & 511;
18440
18560
  if ((mode & 63) !== 0) throw new ServerCredentialsInvalidError("server credentials permissions must be 0600");
18441
18561
  }
18442
18562
  async function exists(path) {
18443
18563
  try {
18444
- await stat(path);
18564
+ await stat2(path);
18445
18565
  return true;
18446
18566
  } catch (error) {
18447
18567
  if (error instanceof Error && "code" in error && error.code === "ENOENT") return false;
@@ -18572,7 +18692,7 @@ function normalizeServerUrl(value) {
18572
18692
  }
18573
18693
 
18574
18694
  // src/version.ts
18575
- var PLUGIN_VERSION = "0.4.28";
18695
+ var PLUGIN_VERSION = "0.4.30";
18576
18696
 
18577
18697
  // src/server-api.ts
18578
18698
  var ENABLED_QR_PROVIDERS = ["github"];
@@ -19047,6 +19167,17 @@ function requireRecord(value, name2) {
19047
19167
  // src/typert-gateway-switch.ts
19048
19168
  var REMOTE_COMMAND_METHODS = ["execute", "list"];
19049
19169
  var LOCAL_ONLY_NAMESPACES = /* @__PURE__ */ new Set(["dynamicCordisRunner"]);
19170
+ var LOCAL_FALLBACK_NAMESPACES = /* @__PURE__ */ new Set(["$events", "settings", "credentials", "dynamicCordisRunner"]);
19171
+ var PEER_GONE_CODES = /* @__PURE__ */ new Set([
19172
+ "TRANSPORT_CLOSED",
19173
+ "CLIENT_CLOSED",
19174
+ "RPC_TIMEOUT",
19175
+ "CONNECTION_FAILED",
19176
+ "CONNECTION_REPLACED",
19177
+ "NOT_CONNECTED",
19178
+ "UNAVAILABLE",
19179
+ "internal"
19180
+ ]);
19050
19181
  var ALL_REMOTE_COMMANDS = { execute: true, list: true };
19051
19182
  var TypertGatewaySwitch = class {
19052
19183
  runtime;
@@ -19198,7 +19329,7 @@ var TypertGatewaySwitch = class {
19198
19329
  if (isLocalOnlyEndpoint(endpointOf(request))) return this.localInvoke(request);
19199
19330
  if (this.remoteTarget !== void 0 && this.remoteAvailability()) {
19200
19331
  return this.remoteTarget.invoke(request).catch((error) => {
19201
- if (!isUnansweredByPeer(error)) throw error;
19332
+ if (!localFallbackAllowed(endpointOf(request), error)) throw error;
19202
19333
  console.warn(`[dsh-remote] serving ${endpointOf(request)} locally: the peer did not answer it`, error);
19203
19334
  return this.localInvoke(request);
19204
19335
  });
@@ -19225,7 +19356,7 @@ var TypertGatewaySwitch = class {
19225
19356
  */
19226
19357
  withLocalFallback(endpoint, remote, local) {
19227
19358
  return remote().catch((error) => {
19228
- if (!isUnansweredByPeer(error)) throw error;
19359
+ if (!localFallbackAllowed(endpoint, error)) throw error;
19229
19360
  console.warn(`[dsh-remote] serving ${endpoint} locally: the peer did not answer it`, error);
19230
19361
  return local();
19231
19362
  });
@@ -19235,7 +19366,7 @@ var TypertGatewaySwitch = class {
19235
19366
  if (normalized !== void 0) return normalized;
19236
19367
  const source = error instanceof Error ? error : new Error("The Harness Gateway rejected the request.");
19237
19368
  const code = "code" in source && typeof source.code === "string" ? source.code : "internal";
19238
- const details = "details" in source && isRecord7(source.details) ? source.details : {};
19369
+ const details = "details" in source && isRecord8(source.details) ? source.details : {};
19239
19370
  return { code, message: source.message, details };
19240
19371
  }
19241
19372
  };
@@ -19275,7 +19406,7 @@ function requestFromCarrier(endpoint, payload, signal) {
19275
19406
  if (segments.length !== 2 || segments.some((segment) => segment.length === 0)) {
19276
19407
  throw new Error("The Harness Gateway endpoint is invalid.");
19277
19408
  }
19278
- if (!isRecord7(payload) || !isRecord7(payload.args)) {
19409
+ if (!isRecord8(payload) || !isRecord8(payload.args)) {
19279
19410
  throw new Error("The Harness Gateway payload is invalid.");
19280
19411
  }
19281
19412
  return { namespace: segments[0], method: segments[1], args: payload.args, signal };
@@ -19283,15 +19414,27 @@ function requestFromCarrier(endpoint, payload, signal) {
19283
19414
  function endpointOf(request) {
19284
19415
  return `${request.namespace}/${request.method}`;
19285
19416
  }
19286
- function isLocalOnlyEndpoint(endpoint) {
19417
+ function namespaceOf(endpoint) {
19287
19418
  const separator = endpoint.indexOf("/");
19288
- return separator > 0 && LOCAL_ONLY_NAMESPACES.has(endpoint.slice(0, separator));
19419
+ return separator > 0 ? endpoint.slice(0, separator) : void 0;
19420
+ }
19421
+ function isLocalOnlyEndpoint(endpoint) {
19422
+ const namespace = namespaceOf(endpoint);
19423
+ return namespace !== void 0 && LOCAL_ONLY_NAMESPACES.has(namespace);
19424
+ }
19425
+ function localFallbackAllowed(endpoint, error) {
19426
+ if (!isUnansweredByPeer(error)) return false;
19427
+ if (!isRecord8(error)) return true;
19428
+ const code = typeof error.code === "string" ? error.code : "";
19429
+ if (!PEER_GONE_CODES.has(code)) return true;
19430
+ const namespace = namespaceOf(endpoint);
19431
+ return namespace !== void 0 && LOCAL_FALLBACK_NAMESPACES.has(namespace);
19289
19432
  }
19290
19433
  function isRemoteCommandMethod(method) {
19291
19434
  return REMOTE_COMMAND_METHODS.includes(method);
19292
19435
  }
19293
19436
  function isUnansweredByPeer(error) {
19294
- if (!isRecord7(error)) return error instanceof Error;
19437
+ if (!isRecord8(error)) return error instanceof Error;
19295
19438
  const code = typeof error.code === "string" ? error.code : "";
19296
19439
  return code === "" || UNANSWERED_BY_PEER_CODES.has(code);
19297
19440
  }
@@ -19304,9 +19447,15 @@ var UNANSWERED_BY_PEER_CODES = /* @__PURE__ */ new Set([
19304
19447
  "CONNECTION_REPLACED",
19305
19448
  "NOT_CONNECTED",
19306
19449
  "UNAVAILABLE",
19450
+ // The client's own transport codes: the peer cannot answer a call it never received, so these are
19451
+ // "unanswered" rather than a refusal - and PEER_GONE_CODES above is what decides whether the local
19452
+ // shell may answer for it.
19453
+ "TRANSPORT_CLOSED",
19454
+ "CLIENT_CLOSED",
19455
+ "RPC_TIMEOUT",
19307
19456
  "internal"
19308
19457
  ]);
19309
- function isRecord7(value) {
19458
+ function isRecord8(value) {
19310
19459
  return typeof value === "object" && value !== null && !Array.isArray(value);
19311
19460
  }
19312
19461
 
@@ -19317,7 +19466,7 @@ import { networkInterfaces } from "node:os";
19317
19466
  // src/native-rtc-helper.ts
19318
19467
  import { spawn, spawnSync } from "node:child_process";
19319
19468
  import { existsSync, readdirSync, realpathSync, statSync } from "node:fs";
19320
- import { delimiter, dirname as dirname3, join as join3 } from "node:path";
19469
+ import { delimiter, dirname as dirname3, join as join4 } from "node:path";
19321
19470
  import { fileURLToPath } from "node:url";
19322
19471
  var cachedExternalFactory;
19323
19472
  var cachedExternalFactoryResolved = false;
@@ -19368,21 +19517,21 @@ function nodeBinaryCandidates() {
19368
19517
  add3(process.env.DSH_REMOTE_NODE);
19369
19518
  add3(process.env.NODE);
19370
19519
  if (process.versions.electron === void 0) add3(process.execPath);
19371
- for (const part of (process.env.PATH ?? "").split(delimiter)) add3(join3(part, process.platform === "win32" ? "node.exe" : "node"));
19520
+ for (const part of (process.env.PATH ?? "").split(delimiter)) add3(join4(part, process.platform === "win32" ? "node.exe" : "node"));
19372
19521
  add3("/opt/homebrew/bin/node");
19373
19522
  add3("/usr/local/bin/node");
19374
19523
  add3("/usr/bin/node");
19375
- add3(join3(process.env.HOME ?? "", ".volta", "bin", process.platform === "win32" ? "node.exe" : "node"));
19376
- add3(join3(process.env.HOME ?? "", ".asdf", "shims", process.platform === "win32" ? "node.exe" : "node"));
19377
- add3(join3(process.env.HOME ?? "", ".local", "bin", process.platform === "win32" ? "node.exe" : "node"));
19524
+ add3(join4(process.env.HOME ?? "", ".volta", "bin", process.platform === "win32" ? "node.exe" : "node"));
19525
+ add3(join4(process.env.HOME ?? "", ".asdf", "shims", process.platform === "win32" ? "node.exe" : "node"));
19526
+ add3(join4(process.env.HOME ?? "", ".local", "bin", process.platform === "win32" ? "node.exe" : "node"));
19378
19527
  for (const nvmNode of nvmNodeCandidates()) add3(nvmNode);
19379
19528
  return candidates;
19380
19529
  }
19381
19530
  function nvmNodeCandidates() {
19382
- const root = join3(process.env.HOME ?? "", ".nvm", "versions", "node");
19531
+ const root = join4(process.env.HOME ?? "", ".nvm", "versions", "node");
19383
19532
  if (root === "" || !existsSync(root)) return [];
19384
19533
  try {
19385
- return readdirSync(root).map((version) => join3(root, version, "bin", process.platform === "win32" ? "node.exe" : "node")).sort((left, right) => right.localeCompare(left, "en", { numeric: true }));
19534
+ return readdirSync(root).map((version) => join4(root, version, "bin", process.platform === "win32" ? "node.exe" : "node")).sort((left, right) => right.localeCompare(left, "en", { numeric: true }));
19386
19535
  } catch {
19387
19536
  return [];
19388
19537
  }
@@ -19413,10 +19562,10 @@ function isUsableExternalNode(candidate, requireFrom) {
19413
19562
  }
19414
19563
  function isExecutableFile(candidate) {
19415
19564
  try {
19416
- const stat7 = statSync(candidate);
19417
- if (!stat7.isFile()) return false;
19565
+ const stat8 = statSync(candidate);
19566
+ if (!stat8.isFile()) return false;
19418
19567
  if (process.platform === "win32") return true;
19419
- return (stat7.mode & 73) !== 0;
19568
+ return (stat8.mode & 73) !== 0;
19420
19569
  } catch {
19421
19570
  return false;
19422
19571
  }
@@ -20385,14 +20534,34 @@ var HOST_AUTHORIZATION_ERRORS = /* @__PURE__ */ new Set([
20385
20534
  "DEVICE_REVOKED",
20386
20535
  "TOKEN_EXPIRED"
20387
20536
  ]);
20537
+ var CREDENTIAL_FAILURE_CODES = /* @__PURE__ */ new Set([
20538
+ "AUTH_REQUIRED",
20539
+ "ACCOUNT_AUTH_REQUIRED",
20540
+ "AUTH_INVALID",
20541
+ "TOKEN_EXPIRED",
20542
+ "DEVICE_NOT_FOUND",
20543
+ "DEVICE_REVOKED",
20544
+ "MEMBERSHIP_REQUIRED"
20545
+ ]);
20546
+ var LIVENESS_INTERVAL_MS = 3e4;
20547
+ var FAST_RECONNECT_INTERVAL_MS = 5e3;
20548
+ var LIVENESS_TIMEOUT_MS = 4e3;
20549
+ var LIVENESS_TOLERATED_FAILURES = 2;
20550
+ var NO_ANSWER_CODES = /* @__PURE__ */ new Set(["RPC_TIMEOUT", "CLIENT_CLOSED", "TRANSPORT_CLOSED", "RPC_ABORTED"]);
20551
+ function livenessProbeLost(error) {
20552
+ const code = typeof error === "object" && error !== null && "code" in error ? error.code : void 0;
20553
+ if (typeof code !== "string") return true;
20554
+ return NO_ANSWER_CODES.has(code);
20555
+ }
20388
20556
  var ClientModeRuntime = class {
20389
- constructor(config, identities, server, apiProxy, typertGateway, logger, host, rtcFactoryProvider = loadNodeRtcFactory) {
20557
+ constructor(config, identities, server, apiProxy, typertGateway, logger, host, rtcFactoryProvider = loadNodeRtcFactory, targetStore) {
20390
20558
  this.config = config;
20391
20559
  this.identities = identities;
20392
20560
  this.server = server;
20393
20561
  this.logger = logger;
20394
20562
  this.host = host;
20395
20563
  this.rtcFactoryProvider = rtcFactoryProvider;
20564
+ this.targetStore = targetStore;
20396
20565
  this.proxySwitch = apiProxy === void 0 ? void 0 : new ApiProxySwitch(apiProxy);
20397
20566
  this.gatewaySwitch = new TypertGatewaySwitch(typertGateway);
20398
20567
  this.gatewaySwitch.setRemoteAvailability(() => this.connected !== void 0);
@@ -20417,12 +20586,56 @@ var ClientModeRuntime = class {
20417
20586
  */
20418
20587
  remoteReconnectRun = 0;
20419
20588
  pendingWorkspaceSelection;
20589
+ /**
20590
+ * Transport a running fast reconnect is replacing.
20591
+ *
20592
+ * The rebuild opens its own control connection and the Server closes the older one for the same
20593
+ * device, so that close belongs to our own replacement. Reading it as a lost peer is what made the
20594
+ * fast level escalate itself into the fallback before it could ever succeed.
20595
+ */
20596
+ supersededClient;
20597
+ /**
20598
+ * True while a fast reconnect is rebuilding the link.
20599
+ *
20600
+ * The probe must not judge the link in the middle of its own replacement: the Server closes the
20601
+ * older connection for the device, so a check that ran then would read our replacement's side effect
20602
+ * as a second miss and escalate the level that is busy recovering. The rebuild's own outcome decides
20603
+ * first; only once it has settled does the cadence resume judging.
20604
+ */
20605
+ fastRebuildInFlight = false;
20606
+ /**
20607
+ * The last workspace the user opened for a Host.
20608
+ *
20609
+ * Kept so a reconnect can republish it: re-selecting the workspace is what makes the native UI
20610
+ * re-read its session list, and without it a list poisoned by the outage stays wrong even after
20611
+ * the link is back.
20612
+ */
20613
+ lastWorkspaceSelection;
20420
20614
  codexVirtual;
20421
20615
  proxySwitch;
20422
20616
  gatewaySwitch;
20423
20617
  codexStreams = /* @__PURE__ */ new Map();
20424
20618
  connectionProgress;
20425
20619
  connectionProgressRun = 0;
20620
+ /**
20621
+ * Host a boot is trying to restore, reported to the UI while the retry loop runs.
20622
+ *
20623
+ * Nothing is connected yet, so without this the window would show the local shell while the
20624
+ * user is still looking at the remote workspace they left.
20625
+ */
20626
+ /**
20627
+ * Recovery in progress, and which kind.
20628
+ *
20629
+ * `restore` is a start that is reconnecting to the recorded target, `fast` keeps the session on
20630
+ * screen while its link is rebuilt, and `fallback` is the last resort that returns the user to
20631
+ * the local shell. The UI shows all three, so a reconnect is never invisible, and the retry loop
20632
+ * stops as soon as the user asks for local.
20633
+ */
20634
+ reconnecting;
20635
+ /** Periodic proof of life for the live remote session; absent while nothing is connected. */
20636
+ livenessTimer;
20637
+ livenessInFlight = false;
20638
+ livenessFailures = 0;
20426
20639
  closed = false;
20427
20640
  async start() {
20428
20641
  if (this.closed) throw new Error("client remote-mode runtime is closed");
@@ -20483,6 +20696,7 @@ var ClientModeRuntime = class {
20483
20696
  ...targetStatus,
20484
20697
  connected: this.connected !== void 0,
20485
20698
  fellBackToLocal: this.fellBackToLocal,
20699
+ ...this.reconnecting === void 0 ? {} : { reconnecting: { ...this.reconnecting } },
20486
20700
  transport: this.connected?.client.getStats().mode ?? "Disconnected",
20487
20701
  connectedTargetDeviceId: this.connected?.target.deviceId,
20488
20702
  preferredTransports: this.config.forceRelay ? ["relay"] : ["lan", "p2p", "turn", "relay"],
@@ -20629,10 +20843,14 @@ var ClientModeRuntime = class {
20629
20843
  this.connected = void 0;
20630
20844
  this.connectionProgress = void 0;
20631
20845
  this.pendingWorkspaceSelection = void 0;
20846
+ this.lastWorkspaceSelection = void 0;
20632
20847
  await this.closeCodexStreams(previous2?.client);
20633
20848
  await previous2?.client.close().catch(() => void 0);
20634
20849
  this.fellBackToLocal = false;
20850
+ this.reconnecting = void 0;
20851
+ this.stopLivenessWatch();
20635
20852
  this.remoteReconnectRun += 1;
20853
+ await this.rememberTarget({ mode: "local" });
20636
20854
  this.logger.info("Harness target switched", { mode: "local" });
20637
20855
  return this.status();
20638
20856
  }
@@ -20655,11 +20873,191 @@ var ClientModeRuntime = class {
20655
20873
  await this.closeCodexVirtual();
20656
20874
  this.selectRemoteTarget(next);
20657
20875
  this.fellBackToLocal = false;
20876
+ this.reconnecting = void 0;
20877
+ await this.rememberTarget({ mode: "remote", hostDeviceId: next.target.deviceId, hostName: next.target.name });
20658
20878
  await this.closeCodexStreams(previous?.client);
20659
20879
  await previous?.client.close().catch(() => void 0);
20660
20880
  this.logger.info("Harness target switched", { mode: "remote", targetDeviceId: shortId(next.target.deviceId) });
20661
20881
  return this.status();
20662
20882
  }
20883
+ /**
20884
+ * Prove the live remote link still answers, and act on the result.
20885
+ *
20886
+ * Only a missing answer counts as loss: the peer's own error (a refusal, an unknown method) came
20887
+ * back over the same link and therefore proves it is there.
20888
+ *
20889
+ * The two recovery levels differ in what the user keeps. The first miss rebuilds the link in
20890
+ * place, so the session, its workspace selection and the remote carriers all stay; the second
20891
+ * gives up and returns to the local shell.
20892
+ */
20893
+ async verifyRemoteLiveness() {
20894
+ const connected = this.connected;
20895
+ if (connected === void 0 || this.livenessInFlight || this.fastRebuildInFlight) return;
20896
+ this.livenessInFlight = true;
20897
+ try {
20898
+ await connected.client.rpc("harness.transport.describe", {}, void 0, { timeoutMs: LIVENESS_TIMEOUT_MS });
20899
+ this.livenessFailures = 0;
20900
+ if (this.reconnecting?.phase === "fast") this.finishReconnect("the link answered again");
20901
+ } catch (error) {
20902
+ if (!livenessProbeLost(error)) {
20903
+ this.livenessFailures = 0;
20904
+ return;
20905
+ }
20906
+ this.livenessFailures += 1;
20907
+ this.logger.warn("remote Harness liveness check found no answer", {
20908
+ targetDeviceId: shortId(connected.target.deviceId),
20909
+ attempt: this.livenessFailures
20910
+ });
20911
+ if (this.livenessFailures === 1) {
20912
+ await this.enterFastReconnect(connected.target.deviceId, connected.target.name);
20913
+ return;
20914
+ }
20915
+ if (this.livenessFailures >= LIVENESS_TOLERATED_FAILURES) {
20916
+ this.handleRemoteTransportLost(connected.client, connected.target.deviceId, "unanswered-twice");
20917
+ }
20918
+ } finally {
20919
+ this.livenessInFlight = false;
20920
+ }
20921
+ }
20922
+ /**
20923
+ * Rebuild the link while the session stays on screen.
20924
+ *
20925
+ * This is what separates the two recovery levels: nothing is handed back to the local shell, so a
20926
+ * link that recovers does not cost the user the view they were working in.
20927
+ * @param targetDeviceId - the Host to rebuild the link to.
20928
+ */
20929
+ async enterFastReconnect(targetDeviceId, targetName) {
20930
+ if (this.reconnecting !== void 0) return;
20931
+ this.reconnecting = { targetDeviceId, ...targetName === void 0 ? {} : { targetName }, phase: "fast" };
20932
+ this.logger.warn("remote Harness link stopped answering; reconnecting in place", {
20933
+ targetDeviceId: shortId(targetDeviceId)
20934
+ });
20935
+ this.armLivenessWatch(FAST_RECONNECT_INTERVAL_MS);
20936
+ await this.reestablish(targetDeviceId);
20937
+ }
20938
+ /**
20939
+ * Build a new transport for the session already on screen.
20940
+ *
20941
+ * The carriers hold the previous client, so they are rebound to the new one before the old client
20942
+ * is closed; leaving it open would keep pointing remote calls at a dead transport.
20943
+ * @param targetDeviceId - the Host to reconnect to.
20944
+ * @returns true when a new session is in place.
20945
+ */
20946
+ async reestablish(targetDeviceId) {
20947
+ const previous = this.connected;
20948
+ this.supersededClient = previous?.client;
20949
+ this.fastRebuildInFlight = true;
20950
+ try {
20951
+ const next = await this.connect(targetDeviceId);
20952
+ if (previous === void 0) {
20953
+ this.supersededClient = void 0;
20954
+ await next.client.close().catch(() => void 0);
20955
+ return false;
20956
+ }
20957
+ await this.closePreview();
20958
+ this.connected = next;
20959
+ this.clearConnectionProgress(next.progressRunId);
20960
+ this.selectRemoteTarget(next);
20961
+ await this.closeCodexStreams(previous.client);
20962
+ await previous.client.close().catch(() => void 0);
20963
+ this.supersededClient = void 0;
20964
+ this.finishReconnect("link re-established");
20965
+ return true;
20966
+ } catch (error) {
20967
+ this.supersededClient = void 0;
20968
+ this.logger.warn("fast reconnect attempt failed", {
20969
+ targetDeviceId: shortId(targetDeviceId),
20970
+ code: safeErrorCode(error)
20971
+ });
20972
+ return false;
20973
+ } finally {
20974
+ this.fastRebuildInFlight = false;
20975
+ }
20976
+ }
20977
+ rememberWorkspaceSelection(selection) {
20978
+ this.pendingWorkspaceSelection = selection;
20979
+ this.lastWorkspaceSelection = { ...selection };
20980
+ }
20981
+ /**
20982
+ * Republish the workspace selection so the native UI re-reads its remote session list.
20983
+ *
20984
+ * The client half consumes status.workspaceSelection and reconnects that workspace; that refresh is
20985
+ * what replaces a list the outage had filled with local answers.
20986
+ * @param targetDeviceId - the Host the reconnect finished against.
20987
+ */
20988
+ restoreWorkspaceSelection(targetDeviceId) {
20989
+ const selection = this.lastWorkspaceSelection;
20990
+ if (selection === void 0 || selection.targetDeviceId !== targetDeviceId) return;
20991
+ this.pendingWorkspaceSelection = { ...selection };
20992
+ this.logger.info("republishing the workspace selection to re-read the remote session list", {
20993
+ targetDeviceId: shortId(targetDeviceId),
20994
+ workspaceId: selection.workspaceId
20995
+ });
20996
+ }
20997
+ finishReconnect(reason) {
20998
+ const target2 = this.reconnecting?.targetDeviceId;
20999
+ if (target2 === void 0) return;
21000
+ this.reconnecting = void 0;
21001
+ this.livenessFailures = 0;
21002
+ this.armLivenessWatch(LIVENESS_INTERVAL_MS);
21003
+ this.restoreWorkspaceSelection(target2);
21004
+ this.logger.info("remote Harness reconnect finished", { targetDeviceId: shortId(target2), reason });
21005
+ }
21006
+ /**
21007
+ * Check the live session now, outside the cadence.
21008
+ *
21009
+ * Used when the page becomes visible again, which is when a suspended client is most likely to be
21010
+ * holding a link that already ended.
21011
+ * @returns the status after the check.
21012
+ */
21013
+ async verifyRemoteConnection() {
21014
+ await this.verifyRemoteLiveness();
21015
+ return this.status();
21016
+ }
21017
+ armLivenessWatch(intervalMs) {
21018
+ if (this.livenessTimer !== void 0) clearInterval(this.livenessTimer);
21019
+ const timer = setInterval(() => {
21020
+ void this.verifyRemoteLiveness();
21021
+ }, intervalMs);
21022
+ timer.unref?.();
21023
+ this.livenessTimer = timer;
21024
+ }
21025
+ stopLivenessWatch() {
21026
+ if (this.livenessTimer !== void 0) clearInterval(this.livenessTimer);
21027
+ this.livenessTimer = void 0;
21028
+ this.livenessFailures = 0;
21029
+ this.livenessInFlight = false;
21030
+ }
21031
+ /**
21032
+ * Shared cleanup for a session whose transport is gone.
21033
+ *
21034
+ * A close event and an exhausted liveness check must leave exactly the same state behind, so both
21035
+ * paths run this. The session is gone for good here, which is why the phase becomes 'fallback' and
21036
+ * the retry loop keeps the UI saying that it is reconnecting.
21037
+ * @param client - the client that was connected.
21038
+ * @param targetDeviceId - the Host it was bound to.
21039
+ */
21040
+ handleRemoteTransportLost(client, targetDeviceId, reason) {
21041
+ if (this.connected?.client !== client) return;
21042
+ this.supersededClient = void 0;
21043
+ const targetName = this.connected.target.name;
21044
+ this.stopLivenessWatch();
21045
+ void this.closePreview();
21046
+ this.connected = void 0;
21047
+ this.connectionProgress = void 0;
21048
+ this.pendingWorkspaceSelection = void 0;
21049
+ void this.closeCodexVirtual();
21050
+ this.proxySwitch?.selectLocal();
21051
+ this.gatewaySwitch.selectLocal();
21052
+ this.fellBackToLocal = true;
21053
+ this.reconnecting = { targetDeviceId, ...targetName === void 0 ? {} : { targetName }, phase: "fallback" };
21054
+ void client.close().catch(() => void 0);
21055
+ this.logger.warn("remote Harness transport lost; reconnecting", {
21056
+ targetDeviceId: shortId(targetDeviceId),
21057
+ reason
21058
+ });
21059
+ void this.reconnectRemoteSession(targetDeviceId);
21060
+ }
20663
21061
  /**
20664
21062
  * Re-establish a remote session whose transport closed.
20665
21063
  *
@@ -20683,20 +21081,79 @@ var ClientModeRuntime = class {
20683
21081
  if (this.connected !== void 0) return;
20684
21082
  try {
20685
21083
  await this.setMode("remote", targetDeviceId);
21084
+ this.restoreWorkspaceSelection(targetDeviceId);
20686
21085
  this.logger.info("remote Harness session reconnected", { targetDeviceId: shortId(targetDeviceId) });
20687
21086
  return;
20688
21087
  } catch (error) {
21088
+ const code = safeErrorCode(error);
21089
+ if (code !== void 0 && CREDENTIAL_FAILURE_CODES.has(code)) {
21090
+ this.logger.warn("remote Harness reconnect stopped: authorization is required", {
21091
+ targetDeviceId: shortId(targetDeviceId),
21092
+ code
21093
+ });
21094
+ this.reconnecting = void 0;
21095
+ return;
21096
+ }
20689
21097
  if (attempt < 3 || attempt % 10 === 0) {
20690
21098
  this.logger.warn("remote Harness reconnect attempt failed", {
20691
21099
  targetDeviceId: shortId(targetDeviceId),
20692
21100
  attempt,
20693
- code: safeErrorCode(error)
21101
+ code
20694
21102
  });
20695
21103
  }
20696
21104
  }
20697
21105
  attempt += 1;
20698
21106
  }
20699
21107
  }
21108
+ /**
21109
+ * Persist the target a later boot may have to restore.
21110
+ *
21111
+ * A failure here must not fail a connect: the only cost is that a killed process comes back to
21112
+ * the local shell, which is where it would have been without this record.
21113
+ * @param target - the target to record.
21114
+ */
21115
+ async rememberTarget(target2) {
21116
+ if (this.targetStore === void 0) return;
21117
+ try {
21118
+ await this.targetStore.save({
21119
+ ...target2,
21120
+ ...this.config.serverUrl === void 0 ? {} : { serverUrl: this.config.serverUrl }
21121
+ });
21122
+ } catch (error) {
21123
+ this.logger.warn("could not record the remote target", { code: safeErrorCode(error) });
21124
+ }
21125
+ }
21126
+ /**
21127
+ * Reconnect to the target this device was last using, with the usual backoff.
21128
+ *
21129
+ * A resumed app can miss the transport close entirely - Android may reclaim the process - so
21130
+ * nothing would start the retry loop and the user would face a local shell behind a remote
21131
+ * workspace view. Restoring the recorded target gives that case the same loop a dropped
21132
+ * transport gets. A target recorded for another Server is left alone: it is not reachable
21133
+ * through the configured one.
21134
+ * @returns true when a retry loop was started.
21135
+ */
21136
+ async restoreLastTarget() {
21137
+ if (this.closed || this.connected !== void 0) return false;
21138
+ const record7 = await this.targetStore?.load();
21139
+ if (record7 === void 0 || record7.mode !== "remote" || record7.hostDeviceId === void 0) return false;
21140
+ if (record7.serverUrl !== void 0 && record7.serverUrl !== this.config.serverUrl) {
21141
+ this.logger.info("recorded remote target belongs to another Server; not restoring", {
21142
+ targetDeviceId: shortId(record7.hostDeviceId)
21143
+ });
21144
+ return false;
21145
+ }
21146
+ this.reconnecting = {
21147
+ targetDeviceId: record7.hostDeviceId,
21148
+ ...record7.hostName === void 0 ? {} : { targetName: record7.hostName },
21149
+ phase: "restore"
21150
+ };
21151
+ this.logger.info("restoring the remote target of the previous run", {
21152
+ targetDeviceId: shortId(record7.hostDeviceId)
21153
+ });
21154
+ void this.reconnectRemoteSession(record7.hostDeviceId);
21155
+ return true;
21156
+ }
20700
21157
  async listRemoteDirectory(targetDeviceId, path, signal) {
20701
21158
  const remote = await this.ensureConnected(targetDeviceId, signal);
20702
21159
  if (remote.features.remoteGateway) {
@@ -20751,7 +21208,7 @@ var ClientModeRuntime = class {
20751
21208
  await this.closeCodexVirtual();
20752
21209
  this.selectRemoteTarget(remote, transport);
20753
21210
  const workspaceId = workspaceRecordId(workspace.workspace);
20754
- this.pendingWorkspaceSelection = { targetDeviceId: remote.target.deviceId, workspaceId };
21211
+ this.rememberWorkspaceSelection({ targetDeviceId: remote.target.deviceId, workspaceId });
20755
21212
  this.logger.info("Remote workspace opened", { targetDeviceId: shortId(remote.target.deviceId) });
20756
21213
  return { ...this.status(), workspace };
20757
21214
  }
@@ -20785,12 +21242,12 @@ var ClientModeRuntime = class {
20785
21242
  this.codexVirtual = virtual;
20786
21243
  this.selectCodexTarget(virtual, remote);
20787
21244
  const preferredSessionId = await virtual.preferredSessionId(signal);
20788
- this.pendingWorkspaceSelection = {
21245
+ this.rememberWorkspaceSelection({
20789
21246
  targetDeviceId: remote.target.deviceId,
20790
21247
  workspaceId,
20791
21248
  backend: "codex",
20792
21249
  ...preferredSessionId === void 0 ? {} : { sessionId: preferredSessionId }
20793
- };
21250
+ });
20794
21251
  this.logger.info("CodeX virtual workspace opened", { targetDeviceId: shortId(remote.target.deviceId) });
20795
21252
  return { ...this.status(), workspace };
20796
21253
  }
@@ -20815,6 +21272,7 @@ var ClientModeRuntime = class {
20815
21272
  return this.openCodexWorkspace(targetDeviceId, codexProjectWorkspaceId(project.id), signal);
20816
21273
  }
20817
21274
  consumeWorkspaceSelection(selection) {
21275
+ this.lastWorkspaceSelection = { ...selection };
20818
21276
  const pending = this.pendingWorkspaceSelection;
20819
21277
  if (pending?.targetDeviceId === selection.targetDeviceId && pending.workspaceId === selection.workspaceId && (pending.backend ?? "harness") === (selection.backend ?? "harness")) {
20820
21278
  this.pendingWorkspaceSelection = void 0;
@@ -20825,6 +21283,7 @@ var ClientModeRuntime = class {
20825
21283
  await this.closePreview();
20826
21284
  if (this.closed) return;
20827
21285
  this.closed = true;
21286
+ this.stopLivenessWatch();
20828
21287
  this.proxySwitch?.selectLocal();
20829
21288
  await this.closePreview();
20830
21289
  this.gatewaySwitch.selectLocal();
@@ -20892,10 +21351,10 @@ var ClientModeRuntime = class {
20892
21351
  }
20893
21352
  }
20894
21353
  stream.unsubscribe = remote.client.onEvent((event) => {
20895
- if (event.event === "codex.app.frame" && isRecord8(event.data) && event.data.streamId === value.streamId) {
21354
+ if (event.event === "codex.app.frame" && isRecord9(event.data) && event.data.streamId === value.streamId) {
20896
21355
  this.appendCodexFrame(stream, event.data);
20897
21356
  }
20898
- if (event.event === "codex.app.stream.closed" && isRecord8(event.data) && event.data.streamId === value.streamId) {
21357
+ if (event.event === "codex.app.stream.closed" && isRecord9(event.data) && event.data.streamId === value.streamId) {
20899
21358
  stream.closed = typeof event.data.reason === "string" ? event.data.reason : "closed";
20900
21359
  stream.wake();
20901
21360
  }
@@ -20922,7 +21381,7 @@ var ClientModeRuntime = class {
20922
21381
  stream.wake();
20923
21382
  };
20924
21383
  appendCodexFrame(stream, data2) {
20925
- if (!isRecord8(data2) || !isRecord8(data2.frame) || typeof data2.frame.method !== "string") return;
21384
+ if (!isRecord9(data2) || !isRecord9(data2.frame) || typeof data2.frame.method !== "string") return;
20926
21385
  if (stream.frames.length >= 256) {
20927
21386
  stream.closed = "overflow";
20928
21387
  } else {
@@ -21128,21 +21587,15 @@ var ClientModeRuntime = class {
21128
21587
  connectedPreference === void 0 ? void 0 : [connectedPreference]
21129
21588
  );
21130
21589
  connectedClient.onClose(() => {
21131
- if (this.connected?.client !== connectedClient) return;
21132
- void this.closePreview();
21133
- this.connected = void 0;
21134
- this.connectionProgress = void 0;
21135
- this.pendingWorkspaceSelection = void 0;
21136
- void this.closeCodexVirtual();
21137
- this.proxySwitch?.selectLocal();
21138
- this.gatewaySwitch.selectLocal();
21139
- this.fellBackToLocal = true;
21140
- void connectedClient.close().catch(() => void 0);
21141
- this.logger.warn("remote Harness transport closed; reconnecting", {
21142
- targetDeviceId: shortId(target2.deviceId)
21143
- });
21144
- void this.reconnectRemoteSession(target2.deviceId);
21590
+ if (this.supersededClient === connectedClient) {
21591
+ this.logger.info("ignoring the close of the transport a reconnect is replacing", {
21592
+ targetDeviceId: shortId(target2.deviceId)
21593
+ });
21594
+ return;
21595
+ }
21596
+ this.handleRemoteTransportLost(connectedClient, target2.deviceId, "transport-closed");
21145
21597
  });
21598
+ this.armLivenessWatch(LIVENESS_INTERVAL_MS);
21146
21599
  const connectionDetails = await connectedTransport.connectionDetails().catch(() => void 0);
21147
21600
  this.logger.info("remote Harness transport ready", {
21148
21601
  targetDeviceId: shortId(target2.deviceId),
@@ -21213,6 +21666,7 @@ var ClientModeRuntime = class {
21213
21666
  async handleControl(endpoint, payload, signal) {
21214
21667
  try {
21215
21668
  if (endpoint === "status") return ok2(await this.detailedStatus());
21669
+ if (endpoint === "client.connection.verify") return ok2(await this.verifyRemoteConnection());
21216
21670
  if (endpoint === "devices") return ok2(await this.devices());
21217
21671
  if (endpoint === "client.account.login") {
21218
21672
  const value = record4(payload);
@@ -21426,7 +21880,7 @@ function record4(value) {
21426
21880
  }
21427
21881
  return value;
21428
21882
  }
21429
- function isRecord8(value) {
21883
+ function isRecord9(value) {
21430
21884
  return typeof value === "object" && value !== null && !Array.isArray(value);
21431
21885
  }
21432
21886
  function ok2(value) {
@@ -21451,11 +21905,11 @@ async function readRemoteWorkspaceBaseline(gateway, signal) {
21451
21905
  const iterator = source[Symbol.asyncIterator]();
21452
21906
  try {
21453
21907
  const first = await iterator.next();
21454
- if (first.done || !isRecord8(first.value) || first.value.type !== "baseline" || !isRecord8(first.value.value) || !Array.isArray(first.value.value.items)) {
21908
+ if (first.done || !isRecord9(first.value) || first.value.type !== "baseline" || !isRecord9(first.value.value) || !Array.isArray(first.value.value.items)) {
21455
21909
  throw new ClientModeError("INVALID_MESSAGE", "The remote Host returned an invalid Workspace baseline.");
21456
21910
  }
21457
21911
  return first.value.value.items.map((item) => {
21458
- if (!isRecord8(item) || typeof item.workspaceId !== "string" || typeof item.path !== "string" || typeof item.title !== "string") {
21912
+ if (!isRecord9(item) || typeof item.workspaceId !== "string" || typeof item.path !== "string" || typeof item.title !== "string") {
21459
21913
  throw new ClientModeError("INVALID_MESSAGE", "The remote Host returned an invalid Workspace row.");
21460
21914
  }
21461
21915
  return { workspaceId: item.workspaceId, path: item.path, title: item.title };
@@ -21521,7 +21975,7 @@ async function probeRemoteHostFeatures(client, clientVersion) {
21521
21975
  if (error instanceof Error && "code" in error && error.code === "METHOD_NOT_FOUND") return fallback;
21522
21976
  throw error;
21523
21977
  }
21524
- if (!isRecord8(value) || !Array.isArray(value.capabilities) || value.capabilities.some((capability) => typeof capability !== "string")) {
21978
+ if (!isRecord9(value) || !Array.isArray(value.capabilities) || value.capabilities.some((capability) => typeof capability !== "string")) {
21525
21979
  throw new ClientModeError("INVALID_MESSAGE", "The remote Host returned invalid transport capabilities.");
21526
21980
  }
21527
21981
  const capabilities = new Set(value.capabilities);
@@ -21606,6 +22060,67 @@ function iceServersForAttempt(attempt, iceServers) {
21606
22060
  return attempt === "direct" ? stunOnlyIceServers(iceServers) : iceServers;
21607
22061
  }
21608
22062
 
22063
+ // src/client-target-store.ts
22064
+ import { randomUUID as randomUUID2 } from "node:crypto";
22065
+ import { chmod as chmod2, mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2 } from "node:fs/promises";
22066
+ import { join as join5 } from "node:path";
22067
+ var FILE_NAME = "client-target.json";
22068
+ var ClientTargetStore = class {
22069
+ constructor(directory) {
22070
+ this.directory = directory;
22071
+ }
22072
+ get file() {
22073
+ return join5(this.directory, FILE_NAME);
22074
+ }
22075
+ /** Read the record. A missing, unreadable or malformed file reads as "no record". */
22076
+ async load() {
22077
+ let raw;
22078
+ try {
22079
+ raw = await readFile3(this.file, "utf8");
22080
+ } catch {
22081
+ return void 0;
22082
+ }
22083
+ try {
22084
+ const value = JSON.parse(raw);
22085
+ if (typeof value !== "object" || value === null) return void 0;
22086
+ const record7 = value;
22087
+ if (record7.schemaVersion !== 1) return void 0;
22088
+ if (record7.mode !== "local" && record7.mode !== "remote") return void 0;
22089
+ if (record7.mode === "remote" && (typeof record7.hostDeviceId !== "string" || record7.hostDeviceId.length === 0)) {
22090
+ return void 0;
22091
+ }
22092
+ return {
22093
+ schemaVersion: 1,
22094
+ mode: record7.mode,
22095
+ ...typeof record7.serverUrl === "string" && record7.serverUrl.length > 0 ? { serverUrl: record7.serverUrl } : {},
22096
+ ...typeof record7.hostDeviceId === "string" && record7.hostDeviceId.length > 0 ? { hostDeviceId: record7.hostDeviceId } : {},
22097
+ ...typeof record7.hostName === "string" && record7.hostName.length > 0 ? { hostName: record7.hostName } : {},
22098
+ savedAt: typeof record7.savedAt === "number" ? record7.savedAt : 0
22099
+ };
22100
+ } catch {
22101
+ return void 0;
22102
+ }
22103
+ }
22104
+ /** Record the current target. Failures reach the caller so a boot can log them. */
22105
+ async save(target2) {
22106
+ const value = {
22107
+ schemaVersion: 1,
22108
+ mode: target2.mode,
22109
+ ...target2.serverUrl === void 0 ? {} : { serverUrl: target2.serverUrl },
22110
+ ...target2.hostDeviceId === void 0 ? {} : { hostDeviceId: target2.hostDeviceId },
22111
+ ...target2.hostName === void 0 ? {} : { hostName: target2.hostName },
22112
+ savedAt: Date.now()
22113
+ };
22114
+ await mkdir2(this.directory, { recursive: true, mode: 448 });
22115
+ await sweepStaleTemporaries(this.directory);
22116
+ const temporary = this.file + "." + String(process.pid) + "." + randomUUID2() + ".tmp";
22117
+ await writeFile2(temporary, JSON.stringify(value, null, 2) + "\n", { encoding: "utf8", mode: 384, flag: "wx" });
22118
+ await chmod2(temporary, 384);
22119
+ await replaceFile(temporary, this.file);
22120
+ await chmod2(this.file, 384);
22121
+ }
22122
+ };
22123
+
21609
22124
  // src/control-runtime.ts
21610
22125
  import { hostname as hostname2 } from "node:os";
21611
22126
  import { existsSync as existsSync3 } from "node:fs";
@@ -21613,9 +22128,9 @@ import { execFileSync } from "node:child_process";
21613
22128
 
21614
22129
  // src/identity-store.ts
21615
22130
  import { createHash } from "node:crypto";
21616
- import { chmod as chmod2, mkdir as mkdir2, readFile as readFile3, rename as rename2, rm as rm2, stat as stat2, writeFile as writeFile2 } from "node:fs/promises";
22131
+ import { chmod as chmod3, mkdir as mkdir3, readFile as readFile4, rm as rm3, stat as stat3, writeFile as writeFile3 } from "node:fs/promises";
21617
22132
  import { homedir } from "node:os";
21618
- import { dirname as dirname4, join as join4 } from "node:path";
22133
+ import { dirname as dirname4, join as join6 } from "node:path";
21619
22134
  var identitySchema = external_exports.object({
21620
22135
  schemaVersion: external_exports.literal(1),
21621
22136
  deviceId: external_exports.string().uuid(),
@@ -21644,14 +22159,14 @@ var IdentityStore = class {
21644
22159
  peers = /* @__PURE__ */ new Map();
21645
22160
  constructor(options = {}) {
21646
22161
  const env = options.env ?? process.env;
21647
- const dshHome = env.DSH_HOME || join4(options.homeDirectory ?? homedir(), ".dsh");
21648
- this.directory = options.directory ?? join4(dshHome, "remote");
22162
+ const dshHome = env.DSH_HOME || join6(options.homeDirectory ?? homedir(), ".dsh");
22163
+ this.directory = options.directory ?? join6(dshHome, "remote");
21649
22164
  }
21650
22165
  async loadOrCreate(deviceName) {
21651
- await mkdir2(this.directory, { recursive: true, mode: 448 });
21652
- await chmod2(this.directory, 448);
21653
- const devicePath = join4(this.directory, "device.json");
21654
- const keyPath = join4(this.directory, "device.key");
22166
+ await mkdir3(this.directory, { recursive: true, mode: 448 });
22167
+ await chmod3(this.directory, 448);
22168
+ const devicePath = join6(this.directory, "device.json");
22169
+ const keyPath = join6(this.directory, "device.key");
21655
22170
  const [hasDevice, hasKey] = await Promise.all([exists2(devicePath), exists2(keyPath)]);
21656
22171
  if (hasDevice !== hasKey) {
21657
22172
  throw new IdentityInvalidError("device identity is incomplete; repair it explicitly before reconnecting");
@@ -21665,8 +22180,8 @@ var IdentityStore = class {
21665
22180
  }
21666
22181
  await assertPrivateMode2(keyPath);
21667
22182
  try {
21668
- let record7 = identitySchema.parse(JSON.parse(await readFile3(devicePath, "utf8")));
21669
- const privateKey = (await readFile3(keyPath, "utf8")).trim();
22183
+ let record7 = identitySchema.parse(JSON.parse(await readFile4(devicePath, "utf8")));
22184
+ const privateKey = (await readFile4(keyPath, "utf8")).trim();
21670
22185
  const regenerated = generateKeyPair(fromBase64Url2(privateKey));
21671
22186
  if (regenerated.publicKey !== record7.publicKey) {
21672
22187
  throw new IdentityInvalidError("device public and private keys do not match");
@@ -21688,7 +22203,7 @@ var IdentityStore = class {
21688
22203
  return this.identity;
21689
22204
  }
21690
22205
  async reset(deviceName) {
21691
- await rm2(this.directory, { recursive: true, force: true });
22206
+ await rm3(this.directory, { recursive: true, force: true });
21692
22207
  this.identity = void 0;
21693
22208
  this.peers.clear();
21694
22209
  return this.loadOrCreate(deviceName);
@@ -21720,11 +22235,11 @@ var IdentityStore = class {
21720
22235
  return removed;
21721
22236
  }
21722
22237
  async loadPeers() {
21723
- const path = join4(this.directory, "trusted-peers.json");
22238
+ const path = join6(this.directory, "trusted-peers.json");
21724
22239
  if (!await exists2(path)) {
21725
22240
  await atomicJsonWrite(path, { schemaVersion: 1, peers: [] }, 384);
21726
22241
  }
21727
- const parsed = trustedPeersSchema.parse(JSON.parse(await readFile3(path, "utf8")));
22242
+ const parsed = trustedPeersSchema.parse(JSON.parse(await readFile4(path, "utf8")));
21728
22243
  const peers = /* @__PURE__ */ new Map();
21729
22244
  for (const peer of parsed.peers) {
21730
22245
  if (peer.fingerprint !== fingerprint(peer.publicKey)) {
@@ -21736,7 +22251,7 @@ var IdentityStore = class {
21736
22251
  this.peers = peers;
21737
22252
  }
21738
22253
  async savePeers() {
21739
- await atomicJsonWrite(join4(this.directory, "trusted-peers.json"), {
22254
+ await atomicJsonWrite(join6(this.directory, "trusted-peers.json"), {
21740
22255
  schemaVersion: 1,
21741
22256
  peers: [...this.peers.values()]
21742
22257
  }, 384);
@@ -21745,7 +22260,7 @@ var IdentityStore = class {
21745
22260
  function serverStorageDirectory(root, serverUrl, role) {
21746
22261
  const origin = new URL(serverUrl).origin;
21747
22262
  const scope = createHash("sha256").update(origin).digest("hex").slice(0, 24);
21748
- return join4(root, "servers", scope, role);
22263
+ return join6(root, "servers", scope, role);
21749
22264
  }
21750
22265
  function fingerprint(publicKey) {
21751
22266
  const compact = createHash("sha256").update(fromBase64Url2(publicKey)).digest("hex").slice(0, 12).toUpperCase();
@@ -21753,7 +22268,7 @@ function fingerprint(publicKey) {
21753
22268
  }
21754
22269
  async function assertPrivateMode2(path) {
21755
22270
  if (process.platform === "win32") return;
21756
- const mode = (await stat2(path)).mode & 511;
22271
+ const mode = (await stat3(path)).mode & 511;
21757
22272
  if ((mode & 63) !== 0) {
21758
22273
  throw new IdentityInvalidError(`private key permissions must be 0600, got ${mode.toString(8).padStart(3, "0")}`);
21759
22274
  }
@@ -21763,16 +22278,18 @@ async function atomicJsonWrite(path, value, mode) {
21763
22278
  `, mode);
21764
22279
  }
21765
22280
  async function atomicTextWrite(path, value, mode) {
21766
- await mkdir2(dirname4(path), { recursive: true, mode: 448 });
22281
+ const directory = dirname4(path);
22282
+ await mkdir3(directory, { recursive: true, mode: 448 });
22283
+ await sweepStaleTemporaries(directory);
21767
22284
  const temporary = `${path}.${process.pid}.${uuidV7()}.tmp`;
21768
- await writeFile2(temporary, value, { encoding: "utf8", mode, flag: "wx" });
21769
- await chmod2(temporary, mode);
21770
- await rename2(temporary, path);
21771
- await chmod2(path, mode);
22285
+ await writeFile3(temporary, value, { encoding: "utf8", mode, flag: "wx" });
22286
+ await chmod3(temporary, mode);
22287
+ await replaceFile(temporary, path);
22288
+ await chmod3(path, mode);
21772
22289
  }
21773
22290
  async function exists2(path) {
21774
22291
  try {
21775
- await stat2(path);
22292
+ await stat3(path);
21776
22293
  return true;
21777
22294
  } catch (error) {
21778
22295
  if (isNodeError(error) && error.code === "ENOENT") return false;
@@ -21930,11 +22447,11 @@ var ControlStatusStream = class {
21930
22447
  };
21931
22448
 
21932
22449
  // src/codex/domain.ts
21933
- import { randomUUID as randomUUID2 } from "node:crypto";
22450
+ import { randomUUID as randomUUID3 } from "node:crypto";
21934
22451
  import { accessSync, constants, existsSync as existsSync2, readFileSync, readdirSync as readdirSync2, statSync as statSync2 } from "node:fs";
21935
- import { readdir, realpath, stat as stat3 } from "node:fs/promises";
22452
+ import { readdir as readdir2, realpath, stat as stat4 } from "node:fs/promises";
21936
22453
  import { homedir as homedir2 } from "node:os";
21937
- import { basename as basename2, isAbsolute as isAbsolute2, join as join5, relative, resolve } from "node:path";
22454
+ import { basename as basename2, isAbsolute as isAbsolute2, join as join7, posix, relative, resolve } from "node:path";
21938
22455
 
21939
22456
  // src/codex/app-server.ts
21940
22457
  import { spawn as spawn2 } from "node:child_process";
@@ -22120,7 +22637,7 @@ var CodexAppServerClient = class {
22120
22637
  this.handleProcessFailure("CODEX_INVALID_RESPONSE", new Error("Codex App Server emitted invalid JSON."));
22121
22638
  return;
22122
22639
  }
22123
- if (!isRecord9(value)) {
22640
+ if (!isRecord10(value)) {
22124
22641
  this.handleProcessFailure("CODEX_INVALID_RESPONSE", new Error("Codex App Server emitted an invalid message."));
22125
22642
  return;
22126
22643
  }
@@ -22165,12 +22682,12 @@ var CodexAppServerClient = class {
22165
22682
  }
22166
22683
  };
22167
22684
  function safeUpstreamError(value) {
22168
- if (!isRecord9(value) || typeof value.message !== "string") return "Codex App Server rejected the request.";
22685
+ if (!isRecord10(value) || typeof value.message !== "string") return "Codex App Server rejected the request.";
22169
22686
  const message = value.message.toLowerCase();
22170
22687
  if (message.includes("active writer")) return "Codex thread already has an active writer.";
22171
22688
  return message.includes("not initialized") ? "Codex App Server is not initialized." : "Codex App Server rejected the request.";
22172
22689
  }
22173
- function isRecord9(value) {
22690
+ function isRecord10(value) {
22174
22691
  return typeof value === "object" && value !== null && !Array.isArray(value);
22175
22692
  }
22176
22693
 
@@ -22430,15 +22947,15 @@ function decodeCanonicalBase64(value) {
22430
22947
  }
22431
22948
  return decoded;
22432
22949
  }
22433
- function isRecord10(value) {
22950
+ function isRecord11(value) {
22434
22951
  return typeof value === "object" && value !== null && !Array.isArray(value);
22435
22952
  }
22436
22953
  function safeErrorCode2(error) {
22437
- if (isRecord10(error) && typeof error.code === "string") return error.code;
22954
+ if (isRecord11(error) && typeof error.code === "string") return error.code;
22438
22955
  return "UNKNOWN";
22439
22956
  }
22440
22957
  function safeMethod(input2) {
22441
- return isRecord10(input2) && typeof input2.method === "string" ? input2.method : "invalid";
22958
+ return isRecord11(input2) && typeof input2.method === "string" ? input2.method : "invalid";
22442
22959
  }
22443
22960
  function concatChunks(chunks, totalBytes) {
22444
22961
  const output = new Uint8Array(totalBytes);
@@ -22453,6 +22970,7 @@ function concatChunks(chunks, totalBytes) {
22453
22970
  // src/codex/domain.ts
22454
22971
  var APPROVAL_TTL_MS = 5 * 6e4;
22455
22972
  var DEFAULT_RESTART_DELAYS_MS = [1e3, 2e3, 4e3, 8e3, 15e3];
22973
+ var CODEX_START_BUDGET_MS = 5e3;
22456
22974
  var CODEX_PAGE_LIMIT2 = 100;
22457
22975
  var MAX_CODEX_PAGES2 = 32;
22458
22976
  var CODEX_HISTORY_PAGE_LIMIT = 25;
@@ -22485,11 +23003,11 @@ var CodexRemoteDomain = class {
22485
23003
  if (!this.config.enabled) return;
22486
23004
  try {
22487
23005
  this.state = "starting";
22488
- await this.launchAppServer();
23006
+ await this.launchWithinBudget();
22489
23007
  } catch (error) {
22490
23008
  this.available = false;
22491
23009
  this.state = "unavailable";
22492
- this.unavailableCode = errorCode2(error);
23010
+ this.unavailableCode = errorCode3(error);
22493
23011
  await this.disposeAppServer(this.appServer);
22494
23012
  this.logger.warn("Codex Remote domain unavailable", { code: this.unavailableCode });
22495
23013
  }
@@ -22716,6 +23234,32 @@ var CodexRemoteDomain = class {
22716
23234
  await this.appServer?.close();
22717
23235
  this.appServer = void 0;
22718
23236
  }
23237
+ /**
23238
+ * Launch the App Server within {@link CODEX_START_BUDGET_MS}.
23239
+ *
23240
+ * The losing side of the race keeps running until the disposal in start()'s catch stops it, so
23241
+ * it needs its own handler: an unhandled rejection here would surface as a process-level error
23242
+ * for what is only an optional feature being unavailable.
23243
+ * @returns nothing once a candidate is ready.
23244
+ */
23245
+ async launchWithinBudget() {
23246
+ let timer;
23247
+ const attempt = this.launchAppServer();
23248
+ attempt.catch(() => void 0);
23249
+ try {
23250
+ await Promise.race([
23251
+ attempt,
23252
+ new Promise((_resolve, reject) => {
23253
+ timer = setTimeout(() => {
23254
+ reject(new RpcError("CODEX_START_TIMEOUT", `Codex App Server did not start within ${CODEX_START_BUDGET_MS}ms.`));
23255
+ }, CODEX_START_BUDGET_MS);
23256
+ timer.unref?.();
23257
+ })
23258
+ ]);
23259
+ } finally {
23260
+ if (timer !== void 0) clearTimeout(timer);
23261
+ }
23262
+ }
22719
23263
  async launchAppServer() {
22720
23264
  let lastError;
22721
23265
  for (const binary of codexBinaryCandidates(this.config.binary)) {
@@ -22734,7 +23278,7 @@ var CodexRemoteDomain = class {
22734
23278
  this.appServer = appServer;
22735
23279
  this.unsubscribeInbound = appServer.onInbound((message) => {
22736
23280
  void this.handleInbound(message).catch((error) => {
22737
- this.logger.warn("Codex inbound handling failed", { code: errorCode2(error) });
23281
+ this.logger.warn("Codex inbound handling failed", { code: errorCode3(error) });
22738
23282
  });
22739
23283
  });
22740
23284
  this.unsubscribeUnavailable = appServer.onUnavailable((code) => {
@@ -22800,7 +23344,7 @@ var CodexRemoteDomain = class {
22800
23344
  } catch (error) {
22801
23345
  this.available = false;
22802
23346
  this.state = "restarting";
22803
- this.unavailableCode = errorCode2(error);
23347
+ this.unavailableCode = errorCode3(error);
22804
23348
  this.logger.warn("Codex App Server restart failed", {
22805
23349
  attempt: this.restartAttempt,
22806
23350
  code: this.unavailableCode
@@ -22868,14 +23412,14 @@ var CodexRemoteDomain = class {
22868
23412
  await this.assertResultThreadAllowed(result);
22869
23413
  if (codexPermissionPresetFromResponse(result) !== params.permissionPreset) {
22870
23414
  await this.callUpstream("thread/settings/update", { threadId, ...settings });
22871
- result = { ...isRecord11(result) ? result : {}, ...settings, sandbox: settings.sandboxPolicy };
23415
+ result = { ...isRecord12(result) ? result : {}, ...settings, sandbox: settings.sandboxPolicy };
22872
23416
  }
22873
23417
  }
22874
23418
  await this.publishPermission(threadId, result);
22875
23419
  return result;
22876
23420
  }
22877
23421
  async publishPermission(threadId, value) {
22878
- const source = isRecord11(value) ? value : {};
23422
+ const source = isRecord12(value) ? value : {};
22879
23423
  const threadSettings = { approvalPolicy: source.approvalPolicy, sandboxPolicy: source.sandboxPolicy ?? source.sandbox };
22880
23424
  await this.handleInbound({ kind: "notification", method: "thread/settings/updated", params: { threadId, threadSettings } });
22881
23425
  }
@@ -22893,7 +23437,7 @@ var CodexRemoteDomain = class {
22893
23437
  await appServer.respond(message.id, { decision: "decline" });
22894
23438
  return;
22895
23439
  }
22896
- const requestHandle = randomUUID2();
23440
+ const requestHandle = randomUUID3();
22897
23441
  this.approvals.set(requestHandle, {
22898
23442
  upstreamId: message.id,
22899
23443
  connectionId: owner.connectionId,
@@ -22971,9 +23515,9 @@ var CodexRemoteDomain = class {
22971
23515
  itemsView,
22972
23516
  ...cursor2 === void 0 ? {} : { cursor: cursor2 }
22973
23517
  });
22974
- const pageResult = isRecord11(result) ? result : {};
23518
+ const pageResult = isRecord12(result) ? result : {};
22975
23519
  for (const rawTurn of array2(pageResult.data)) {
22976
- if (!isRecord11(rawTurn)) continue;
23520
+ if (!isRecord12(rawTurn)) continue;
22977
23521
  const turnId = typeof rawTurn.id === "string" ? rawTurn.id : void 0;
22978
23522
  const items = rawTurn.itemsView === "full" || turnId === void 0 ? array2(rawTurn.items) : await this.readThreadItems(connectionId, threadId, turnId, array2(rawTurn.items));
22979
23523
  turns.push({ ...rawTurn, items });
@@ -22995,9 +23539,9 @@ var CodexRemoteDomain = class {
22995
23539
  sortDirection: "asc",
22996
23540
  ...cursor2 === void 0 ? {} : { cursor: cursor2 }
22997
23541
  });
22998
- const pageResult = isRecord11(result) ? result : {};
23542
+ const pageResult = isRecord12(result) ? result : {};
22999
23543
  for (const entry of array2(pageResult.data)) {
23000
- if (isRecord11(entry) && entry.item !== void 0) items.push(entry.item);
23544
+ if (isRecord12(entry) && entry.item !== void 0) items.push(entry.item);
23001
23545
  }
23002
23546
  cursor2 = typeof pageResult.nextCursor === "string" && pageResult.nextCursor.length > 0 ? pageResult.nextCursor : void 0;
23003
23547
  if (cursor2 === void 0) break;
@@ -23013,7 +23557,7 @@ var CodexRemoteDomain = class {
23013
23557
  this.logger.warn("Codex history read fallback", {
23014
23558
  connectionId: maskId(connectionId),
23015
23559
  stage,
23016
- code: errorCode2(error)
23560
+ code: errorCode3(error)
23017
23561
  });
23018
23562
  }
23019
23563
  async assertResultThreadAllowed(result) {
@@ -23061,7 +23605,7 @@ var CodexRemoteDomain = class {
23061
23605
  return [...this.peers.values()].some((peer) => peer.hasThreadSubscription(threadId));
23062
23606
  }
23063
23607
  resolveUpstreamApproval(params) {
23064
- if (!isRecord11(params) || typeof params.requestId !== "string" && typeof params.requestId !== "number") return;
23608
+ if (!isRecord12(params) || typeof params.requestId !== "string" && typeof params.requestId !== "number") return;
23065
23609
  for (const [handle, approval] of this.approvals) {
23066
23610
  if (approval.upstreamId === params.requestId) this.approvals.delete(handle);
23067
23611
  }
@@ -23084,7 +23628,7 @@ var CodexRemoteDomain = class {
23084
23628
  this.approvalExpiryTimer = setTimeout(() => {
23085
23629
  this.approvalExpiryTimer = void 0;
23086
23630
  void this.expireApprovals().catch((error) => {
23087
- this.logger.warn("Codex approval expiry failed", { code: errorCode2(error) });
23631
+ this.logger.warn("Codex approval expiry failed", { code: errorCode3(error) });
23088
23632
  });
23089
23633
  }, Math.max(0, nextExpiry - Date.now()));
23090
23634
  this.approvalExpiryTimer.unref?.();
@@ -23174,7 +23718,7 @@ var CodexRemoteDomain = class {
23174
23718
  let candidate;
23175
23719
  try {
23176
23720
  candidate = await realpath(path);
23177
- if (!(await stat3(candidate)).isDirectory()) throw new Error("not a directory");
23721
+ if (!(await stat4(candidate)).isDirectory()) throw new Error("not a directory");
23178
23722
  } catch {
23179
23723
  throw new RpcError("CODEX_PATH_NOT_ALLOWED", "The CodeX working directory is not available as a Workspace.");
23180
23724
  }
@@ -23194,7 +23738,7 @@ var CodexRemoteDomain = class {
23194
23738
  }
23195
23739
  try {
23196
23740
  const canonical = await realpath(resolve(path));
23197
- if (!(await stat3(canonical)).isDirectory()) throw new Error("not a directory");
23741
+ if (!(await stat4(canonical)).isDirectory()) throw new Error("not a directory");
23198
23742
  return canonical;
23199
23743
  } catch {
23200
23744
  throw new RpcError("CODEX_PATH_NOT_ALLOWED", "The CodeX project directory must be an existing absolute directory.");
@@ -23202,7 +23746,7 @@ var CodexRemoteDomain = class {
23202
23746
  }
23203
23747
  async listCodexDirectory(path) {
23204
23748
  const target2 = await this.resolveCodexDirectory(path);
23205
- const rows = await readdir(target2.path, { withFileTypes: true }).catch(() => void 0);
23749
+ const rows = await readdir2(target2.path, { withFileTypes: true }).catch(() => void 0);
23206
23750
  if (rows === void 0) {
23207
23751
  throw new RpcError("CODEX_PATH_NOT_ALLOWED", "The CodeX working directory is not available as a Workspace.");
23208
23752
  }
@@ -23211,12 +23755,12 @@ var CodexRemoteDomain = class {
23211
23755
  const child = resolve(target2.path, row.name);
23212
23756
  let directory = row.isDirectory();
23213
23757
  if (!directory && row.isSymbolicLink()) {
23214
- directory = await stat3(child).then((value) => value.isDirectory()).catch(() => false);
23758
+ directory = await stat4(child).then((value) => value.isDirectory()).catch(() => false);
23215
23759
  }
23216
23760
  if (!directory) continue;
23217
23761
  try {
23218
23762
  const canonicalChild = await realpath(child);
23219
- if (!(await stat3(canonicalChild)).isDirectory()) continue;
23763
+ if (!(await stat4(canonicalChild)).isDirectory()) continue;
23220
23764
  if (!containsCodexPath(target2.canonicalRoot, canonicalChild)) continue;
23221
23765
  } catch {
23222
23766
  continue;
@@ -23245,7 +23789,7 @@ var CodexRemoteDomain = class {
23245
23789
  let canonicalCandidate;
23246
23790
  try {
23247
23791
  canonicalCandidate = await realpath(path);
23248
- if (!(await stat3(canonicalCandidate)).isDirectory()) throw new Error("not a directory");
23792
+ if (!(await stat4(canonicalCandidate)).isDirectory()) throw new Error("not a directory");
23249
23793
  } catch {
23250
23794
  throw new RpcError("CODEX_PATH_NOT_ALLOWED", "The CodeX working directory is not available as a Workspace.");
23251
23795
  }
@@ -23275,15 +23819,15 @@ function codexBinaryCandidates(configured, hostPlatform = process.platform, user
23275
23819
  if (hostPlatform !== "darwin") return [configured];
23276
23820
  const bundledCandidates = [
23277
23821
  "/Applications/ChatGPT.app",
23278
- join5(userHome, "Applications", "ChatGPT.app")
23822
+ posix.join(userHome, "Applications", "ChatGPT.app")
23279
23823
  ].flatMap((chatGptApp) => {
23280
- const codexCli = join5(chatGptApp, "Contents", "Resources", "codex-cli");
23824
+ const codexCli = posix.join(chatGptApp, "Contents", "Resources", "codex-cli");
23281
23825
  try {
23282
- const manifest = JSON.parse(readFileSync(join5(codexCli, "codex-package.json"), "utf8"));
23283
- if (!isRecord11(manifest) || typeof manifest.entrypoint !== "string" || manifest.entrypoint.length === 0) {
23826
+ const manifest = JSON.parse(readFileSync(posix.join(codexCli, "codex-package.json"), "utf8"));
23827
+ if (!isRecord12(manifest) || typeof manifest.entrypoint !== "string" || manifest.entrypoint.length === 0) {
23284
23828
  return [];
23285
23829
  }
23286
- const candidate = join5(codexCli, manifest.entrypoint);
23830
+ const candidate = posix.join(codexCli, manifest.entrypoint);
23287
23831
  if (!existsSync2(candidate)) return [];
23288
23832
  accessSync(candidate, constants.X_OK);
23289
23833
  return [candidate];
@@ -23294,14 +23838,14 @@ function codexBinaryCandidates(configured, hostPlatform = process.platform, user
23294
23838
  return [.../* @__PURE__ */ new Set([
23295
23839
  ...bundledCandidates,
23296
23840
  "/Applications/ChatGPT.app/Contents/Resources/codex",
23297
- join5(userHome, "Applications", "ChatGPT.app", "Contents", "Resources", "codex"),
23841
+ posix.join(userHome, "Applications", "ChatGPT.app", "Contents", "Resources", "codex"),
23298
23842
  configured
23299
23843
  ])];
23300
23844
  }
23301
23845
  function bundledWindowsCodex(userHome) {
23302
- const bin = join5(userHome, "AppData", "Local", "OpenAI", "Codex", "bin");
23846
+ const bin = join7(userHome, "AppData", "Local", "OpenAI", "Codex", "bin");
23303
23847
  try {
23304
- const newest = readdirSync2(bin, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => join5(bin, entry.name, "codex.exe")).filter((candidate2) => existsSync2(candidate2)).map((candidate2) => ({ candidate: candidate2, modified: statSync2(candidate2).mtimeMs })).sort((left, right) => right.modified - left.modified);
23848
+ const newest = readdirSync2(bin, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => join7(bin, entry.name, "codex.exe")).filter((candidate2) => existsSync2(candidate2)).map((candidate2) => ({ candidate: candidate2, modified: statSync2(candidate2).mtimeMs })).sort((left, right) => right.modified - left.modified);
23305
23849
  const candidate = newest[0]?.candidate;
23306
23850
  return candidate === void 0 ? [] : [candidate];
23307
23851
  } catch {
@@ -23309,24 +23853,24 @@ function bundledWindowsCodex(userHome) {
23309
23853
  }
23310
23854
  }
23311
23855
  function parseCallEnvelope(input2) {
23312
- if (!isRecord11(input2) || typeof input2.method !== "string" || !("params" in input2) || Object.keys(input2).some((key) => key !== "method" && key !== "params")) {
23856
+ if (!isRecord12(input2) || typeof input2.method !== "string" || !("params" in input2) || Object.keys(input2).some((key) => key !== "method" && key !== "params")) {
23313
23857
  throw new RpcError("INVALID_MESSAGE", "The Codex call envelope is invalid.");
23314
23858
  }
23315
23859
  return { method: input2.method, params: input2.params };
23316
23860
  }
23317
23861
  function parseRespond(input2) {
23318
- if (!isRecord11(input2) || typeof input2.requestHandle !== "string" || !["accept", "decline", "cancel"].includes(String(input2.decision)) || Object.keys(input2).some((key) => key !== "requestHandle" && key !== "decision")) {
23862
+ if (!isRecord12(input2) || typeof input2.requestHandle !== "string" || !["accept", "decline", "cancel"].includes(String(input2.decision)) || Object.keys(input2).some((key) => key !== "requestHandle" && key !== "decision")) {
23319
23863
  throw new RpcError("INVALID_MESSAGE", "The Codex approval response is invalid.");
23320
23864
  }
23321
23865
  return input2;
23322
23866
  }
23323
23867
  function accountCanRun(result) {
23324
- if (!isRecord11(result) || typeof result.requiresOpenaiAuth !== "boolean") return false;
23325
- return result.requiresOpenaiAuth === false || isRecord11(result.account);
23868
+ if (!isRecord12(result) || typeof result.requiresOpenaiAuth !== "boolean") return false;
23869
+ return result.requiresOpenaiAuth === false || isRecord12(result.account);
23326
23870
  }
23327
23871
  function sanitizeAccount(result) {
23328
- if (!isRecord11(result)) throw new RpcError("CODEX_INVALID_RESPONSE", "Codex App Server returned invalid account state.");
23329
- const account = isRecord11(result.account) ? result.account : void 0;
23872
+ if (!isRecord12(result)) throw new RpcError("CODEX_INVALID_RESPONSE", "Codex App Server returned invalid account state.");
23873
+ const account = isRecord12(result.account) ? result.account : void 0;
23330
23874
  return {
23331
23875
  authenticated: account !== void 0 || result.requiresOpenaiAuth === false,
23332
23876
  requiresOpenaiAuth: result.requiresOpenaiAuth === true,
@@ -23339,11 +23883,11 @@ function sanitizeAccount(result) {
23339
23883
  };
23340
23884
  }
23341
23885
  function sanitizeThreadList(result) {
23342
- if (!isRecord11(result) || !Array.isArray(result.data)) {
23886
+ if (!isRecord12(result) || !Array.isArray(result.data)) {
23343
23887
  throw new RpcError("CODEX_INVALID_RESPONSE", "Codex App Server returned an invalid thread list.");
23344
23888
  }
23345
23889
  const data2 = result.data.flatMap((value) => {
23346
- if (!isRecord11(value) || typeof value.id !== "string") return [];
23890
+ if (!isRecord12(value) || typeof value.id !== "string") return [];
23347
23891
  return [{
23348
23892
  id: value.id,
23349
23893
  ...typeof value.sessionId === "string" ? { sessionId: value.sessionId } : {},
@@ -23355,7 +23899,7 @@ function sanitizeThreadList(result) {
23355
23899
  ...typeof value.updatedAt === "number" && Number.isFinite(value.updatedAt) ? { updatedAt: value.updatedAt } : {},
23356
23900
  ...typeof value.archived === "boolean" ? { archived: value.archived } : {},
23357
23901
  ...typeof value.isPinned === "boolean" ? { isPinned: value.isPinned } : {},
23358
- ...isRecord11(value.status) ? { status: value.status } : {}
23902
+ ...isRecord12(value.status) ? { status: value.status } : {}
23359
23903
  }];
23360
23904
  });
23361
23905
  return {
@@ -23365,18 +23909,18 @@ function sanitizeThreadList(result) {
23365
23909
  };
23366
23910
  }
23367
23911
  function filterThreadListByWorkspaceAuthority(result, authority) {
23368
- if (!isRecord11(result) || !Array.isArray(result.data)) {
23912
+ if (!isRecord12(result) || !Array.isArray(result.data)) {
23369
23913
  throw new RpcError("CODEX_INVALID_RESPONSE", "Codex App Server returned an invalid thread list.");
23370
23914
  }
23371
23915
  return {
23372
23916
  ...result,
23373
- data: result.data.map((record7) => isRecord11(record7) ? record7 : void 0).filter((thread) => thread !== void 0 && isThreadAllowedByWorkspaceAuthority(thread, authority))
23917
+ data: result.data.map((record7) => isRecord12(record7) ? record7 : void 0).filter((thread) => thread !== void 0 && isThreadAllowedByWorkspaceAuthority(thread, authority))
23374
23918
  };
23375
23919
  }
23376
23920
  function sanitizeProject(value) {
23377
- if (!isRecord11(value) || typeof value.id !== "string" || typeof value.name !== "string") return void 0;
23921
+ if (!isRecord12(value) || typeof value.id !== "string" || typeof value.name !== "string") return void 0;
23378
23922
  const roots = Array.isArray(value.roots) ? value.roots.flatMap((root) => {
23379
- const path = isRecord11(root) && typeof root.path === "string" && root.path.length > 0 ? root.path : void 0;
23923
+ const path = isRecord12(root) && typeof root.path === "string" && root.path.length > 0 ? root.path : void 0;
23380
23924
  return path === void 0 || !isAbsolute2(path) ? [] : [{ path }];
23381
23925
  }) : [];
23382
23926
  if (roots.length === 0) return void 0;
@@ -23390,7 +23934,7 @@ function sanitizeProject(value) {
23390
23934
  };
23391
23935
  }
23392
23936
  function sanitizeProjectList(result) {
23393
- if (!isRecord11(result) || !Array.isArray(result.data)) {
23937
+ if (!isRecord12(result) || !Array.isArray(result.data)) {
23394
23938
  throw new RpcError("CODEX_INVALID_RESPONSE", "Codex App Server returned an invalid project list.");
23395
23939
  }
23396
23940
  const data2 = result.data.flatMap((value) => sanitizeProject(value) ?? []);
@@ -23400,7 +23944,7 @@ function sanitizeProjectList(result) {
23400
23944
  };
23401
23945
  }
23402
23946
  function sanitizeProjectCreate(result) {
23403
- const project = isRecord11(result) ? sanitizeProject(result.project) : void 0;
23947
+ const project = isRecord12(result) ? sanitizeProject(result.project) : void 0;
23404
23948
  if (project === void 0) {
23405
23949
  throw new RpcError("CODEX_INVALID_RESPONSE", "Codex App Server returned an invalid created project.");
23406
23950
  }
@@ -23433,19 +23977,19 @@ function codexDirectoryCrumbs(root, path) {
23433
23977
  return crumbs2;
23434
23978
  }
23435
23979
  function extractThread(result) {
23436
- return isRecord11(result) && isRecord11(result.thread) ? result.thread : void 0;
23980
+ return isRecord12(result) && isRecord12(result.thread) ? result.thread : void 0;
23437
23981
  }
23438
23982
  function extractTurnId(result) {
23439
- if (!isRecord11(result)) return void 0;
23983
+ if (!isRecord12(result)) return void 0;
23440
23984
  if (typeof result.turnId === "string" && result.turnId.length > 0) return result.turnId;
23441
- if (isRecord11(result.turn) && typeof result.turn.id === "string" && result.turn.id.length > 0) return result.turn.id;
23985
+ if (isRecord12(result.turn) && typeof result.turn.id === "string" && result.turn.id.length > 0) return result.turn.id;
23442
23986
  return void 0;
23443
23987
  }
23444
23988
  function extractThreadId(params) {
23445
- if (!isRecord11(params)) return void 0;
23989
+ if (!isRecord12(params)) return void 0;
23446
23990
  if (typeof params.threadId === "string") return params.threadId;
23447
- if (isRecord11(params.thread) && typeof params.thread.id === "string") return params.thread.id;
23448
- if (isRecord11(params.turn) && typeof params.turn.threadId === "string") return params.turn.threadId;
23991
+ if (isRecord12(params.thread) && typeof params.thread.id === "string") return params.thread.id;
23992
+ if (isRecord12(params.turn) && typeof params.turn.threadId === "string") return params.turn.threadId;
23449
23993
  return void 0;
23450
23994
  }
23451
23995
  function optionalInteger2(value) {
@@ -23487,13 +24031,13 @@ function mapCodexImageInputs(params) {
23487
24031
  return {
23488
24032
  ...params,
23489
24033
  input: params.input.map((value) => {
23490
- if (!isRecord11(value) || value.type !== "image" || typeof value.mediaType !== "string" || typeof value.data !== "string") return value;
24034
+ if (!isRecord12(value) || value.type !== "image" || typeof value.mediaType !== "string" || typeof value.data !== "string") return value;
23491
24035
  return { type: "image", url: `data:${value.mediaType};base64,${value.data}` };
23492
24036
  })
23493
24037
  };
23494
24038
  }
23495
24039
  function sanitizeApprovalParams(params, requestHandle) {
23496
- if (!isRecord11(params)) return { requestHandle };
24040
+ if (!isRecord12(params)) return { requestHandle };
23497
24041
  const safe = { ...params };
23498
24042
  delete safe.proposedExecpolicyAmendment;
23499
24043
  delete safe.additionalPermissions;
@@ -23511,7 +24055,7 @@ function mapAppServerError(error) {
23511
24055
  }
23512
24056
  return new RpcError("CODEX_UPSTREAM_ERROR", "Codex App Server could not complete the request.");
23513
24057
  }
23514
- function errorCode2(error) {
24058
+ function errorCode3(error) {
23515
24059
  if (error instanceof RpcError || error instanceof CodexAppServerError) return error.code;
23516
24060
  return "CODEX_START_FAILED";
23517
24061
  }
@@ -23524,7 +24068,7 @@ function isProjectListFallbackError(error) {
23524
24068
  function canTryNextBinary(error) {
23525
24069
  return !(error instanceof RpcError) || !["CODEX_AUTH_REQUIRED", "CODEX_CLOSED"].includes(error.code);
23526
24070
  }
23527
- function isRecord11(value) {
24071
+ function isRecord12(value) {
23528
24072
  return typeof value === "object" && value !== null && !Array.isArray(value);
23529
24073
  }
23530
24074
  function array2(value) {
@@ -23960,11 +24504,11 @@ function editableConfig(config) {
23960
24504
  ...config.acp === void 0 ? {} : { acp: { enabled: config.acp.enabled, backends: config.acp.backends } }
23961
24505
  };
23962
24506
  }
23963
- function isRecord12(value) {
24507
+ function isRecord13(value) {
23964
24508
  return typeof value === "object" && value !== null && !Array.isArray(value);
23965
24509
  }
23966
24510
  function record5(value) {
23967
- if (!isRecord12(value)) throw new ClientModeError("INVALID_MESSAGE", "The control request payload is invalid.");
24511
+ if (!isRecord13(value)) throw new ClientModeError("INVALID_MESSAGE", "The control request payload is invalid.");
23968
24512
  return value;
23969
24513
  }
23970
24514
  function ok3(value) {
@@ -24089,7 +24633,7 @@ var TerminalPolicy = class {
24089
24633
  };
24090
24634
 
24091
24635
  // src/service.ts
24092
- import { randomUUID as randomUUID3 } from "node:crypto";
24636
+ import { randomUUID as randomUUID4 } from "node:crypto";
24093
24637
 
24094
24638
  // src/connection-controller.ts
24095
24639
  var ConnectionController = class {
@@ -24714,7 +25258,7 @@ var HostServerConnection = class {
24714
25258
  await this.connectOnce();
24715
25259
  delayMs = this.config.reconnect.initialDelayMs;
24716
25260
  } catch (error) {
24717
- const code = errorCode3(error);
25261
+ const code = errorCode4(error);
24718
25262
  if (code === "CREDENTIALS_REFRESHED") continue;
24719
25263
  this.terminalError = code;
24720
25264
  this.logger.warn("server control connection failed", {
@@ -24729,7 +25273,7 @@ var HostServerConnection = class {
24729
25273
  try {
24730
25274
  await this.api.clearAuthorization();
24731
25275
  } catch (clearError) {
24732
- this.logger.error("failed to clear revoked Host authorization", { code: errorCode3(clearError) });
25276
+ this.logger.error("failed to clear revoked Host authorization", { code: errorCode4(clearError) });
24733
25277
  }
24734
25278
  }
24735
25279
  if (TERMINAL_AUTH_ERRORS.has(code) || !this.config.reconnect.enabled) return;
@@ -24809,7 +25353,7 @@ var HostServerConnection = class {
24809
25353
  if (!acknowledged) throw new ControlConnectionError("INVALID_MESSAGE", "Server sent a frame before hello.ack.");
24810
25354
  await this.handleFrame(frame);
24811
25355
  }).catch((error) => {
24812
- const code = errorCode3(error);
25356
+ const code = errorCode4(error);
24813
25357
  this.terminalError = code;
24814
25358
  this.logger.error("server control frame failed", {
24815
25359
  code,
@@ -24921,7 +25465,7 @@ var HostServerConnection = class {
24921
25465
  this.sendControl("connect.rejected", { connectionId: payload.connectionId });
24922
25466
  this.logger.warn("connection rejected by account authorization", {
24923
25467
  clientDeviceId: shortId3(payload.clientDeviceId),
24924
- code: errorCode3(error)
25468
+ code: errorCode4(error)
24925
25469
  });
24926
25470
  return;
24927
25471
  }
@@ -25070,7 +25614,7 @@ var HostServerConnection = class {
25070
25614
  } catch (error) {
25071
25615
  this.logger.warn("TURN credentials unavailable; trying direct candidates", {
25072
25616
  connectionId: shortId3(tunnel.connectionId),
25073
- code: errorCode3(error)
25617
+ code: errorCode4(error)
25074
25618
  });
25075
25619
  }
25076
25620
  if (!tunnel.preferredTransports.includes("turn")) iceServers = stunOnlyIceServers(iceServers);
@@ -25507,11 +26051,11 @@ function rtcDiagnostics(rtc) {
25507
26051
  return void 0;
25508
26052
  }
25509
26053
  }
25510
- function errorCode3(error) {
26054
+ function errorCode4(error) {
25511
26055
  return error instanceof ServerApiError || error instanceof ControlConnectionError ? error.code : "CONNECTION_FAILED";
25512
26056
  }
25513
26057
  function isRetryable(error) {
25514
- return !TERMINAL_AUTH_ERRORS.has(errorCode3(error)) && (!(error instanceof ServerApiError) || error.retryable);
26058
+ return !TERMINAL_AUTH_ERRORS.has(errorCode4(error)) && (!(error instanceof ServerApiError) || error.retryable);
25515
26059
  }
25516
26060
  function closeCode(code) {
25517
26061
  if (code === 4002) return "AUTH_INVALID";
@@ -25523,7 +26067,7 @@ function closeCode(code) {
25523
26067
  }
25524
26068
 
25525
26069
  // src/remote-directory-browser.ts
25526
- import { readdir as readdir2, stat as stat4 } from "node:fs/promises";
26070
+ import { readdir as readdir3, stat as stat5 } from "node:fs/promises";
25527
26071
  import { homedir as homedir3, platform as platform2 } from "node:os";
25528
26072
  import { basename as basename3, dirname as dirname5, isAbsolute as isAbsolute3, parse, resolve as resolve2 } from "node:path";
25529
26073
  var MAX_ENTRIES = 500;
@@ -25532,13 +26076,13 @@ async function listRemoteDirectory(path, signal) {
25532
26076
  const home = resolve2(homedir3());
25533
26077
  const target2 = path === void 0 || path.trim() === "" ? home : resolve2(path);
25534
26078
  if (!isAbsolute3(target2)) throw new Error("The remote directory path must be absolute.");
25535
- const rows = await readdir2(target2, { withFileTypes: true });
26079
+ const rows = await readdir3(target2, { withFileTypes: true });
25536
26080
  const directories = [];
25537
26081
  for (const row of rows) {
25538
26082
  signal?.throwIfAborted();
25539
26083
  const child = resolve2(target2, row.name);
25540
26084
  let directory = row.isDirectory();
25541
- if (!directory && row.isSymbolicLink()) directory = await stat4(child).then((value) => value.isDirectory()).catch(() => false);
26085
+ if (!directory && row.isSymbolicLink()) directory = await stat5(child).then((value) => value.isDirectory()).catch(() => false);
25542
26086
  if (!directory) continue;
25543
26087
  directories.push({ name: row.name, path: child, hidden: platform2() !== "win32" && row.name.startsWith(".") });
25544
26088
  }
@@ -26765,11 +27309,11 @@ var HarnessRemoteBridge = class {
26765
27309
  };
26766
27310
  function normalizeWorkspaceChangesPayload(endpoint, payload, harnessVersion) {
26767
27311
  if (endpoint !== "workspaceFiles/changes" || !requiresWorkspaceChangePath(harnessVersion)) return payload;
26768
- if (!isRecord13(payload) || !isRecord13(payload.args) || Object.hasOwn(payload.args, "path")) return payload;
27312
+ if (!isRecord14(payload) || !isRecord14(payload.args) || Object.hasOwn(payload.args, "path")) return payload;
26769
27313
  return { ...payload, args: { ...payload.args, path: "." } };
26770
27314
  }
26771
27315
  function normalizeWorkspaceRequestPayload(endpoint, payload, harnessVersion) {
26772
- if (!isRecord13(payload) || !isRecord13(payload.args)) return payload;
27316
+ if (!isRecord14(payload) || !isRecord14(payload.args)) return payload;
26773
27317
  const args = payload.args;
26774
27318
  if (endpoint === "workspaceFiles/readBytes" && requiresWorkspaceChangePath(harnessVersion)) {
26775
27319
  if (!Object.hasOwn(args, "range") || Object.hasOwn(args, "options")) return payload;
@@ -26837,13 +27381,13 @@ function needsDirectoryFallback(result) {
26837
27381
  }
26838
27382
  function requestArgs(payload) {
26839
27383
  const root = record6(payload);
26840
- const args = isRecord13(root.args) ? root.args : root;
27384
+ const args = isRecord14(root.args) ? root.args : root;
26841
27385
  return record6(args.request ?? args._request ?? args);
26842
27386
  }
26843
27387
  function record6(value) {
26844
- return isRecord13(value) ? value : {};
27388
+ return isRecord14(value) ? value : {};
26845
27389
  }
26846
- function isRecord13(value) {
27390
+ function isRecord14(value) {
26847
27391
  return typeof value === "object" && value !== null && !Array.isArray(value);
26848
27392
  }
26849
27393
 
@@ -26963,8 +27507,8 @@ import { execFileSync as execFileSync2 } from "node:child_process";
26963
27507
 
26964
27508
  // src/codex-workspace-bridge.ts
26965
27509
  import { spawn as spawn4 } from "node:child_process";
26966
- import { realpath as realpath2, lstat, readdir as readdir3, readFile as readFile4, stat as stat5, watch } from "node:fs/promises";
26967
- import { isAbsolute as isAbsolute4, join as join6, relative as relative2, resolve as resolve3 } from "node:path";
27510
+ import { realpath as realpath2, lstat, readdir as readdir4, readFile as readFile5, stat as stat6, watch } from "node:fs/promises";
27511
+ import { isAbsolute as isAbsolute4, join as join8, relative as relative2, resolve as resolve3 } from "node:path";
26968
27512
  var MAX_READ_BYTES = 4 * 1024 * 1024;
26969
27513
  var MAX_INPUT_BYTES = 64 * 1024;
26970
27514
  var MAX_COLS = 240;
@@ -27051,17 +27595,17 @@ var CodexWorkspaceBridge = class {
27051
27595
  const target2 = await this.safePath(root, path, endpoint === "workspaceFiles/list");
27052
27596
  try {
27053
27597
  if (endpoint === "workspaceFiles/list") {
27054
- const entries = await readdir3(target2, { withFileTypes: true });
27598
+ const entries = await readdir4(target2, { withFileTypes: true });
27055
27599
  const result = [];
27056
27600
  for (const entry of entries.slice(0, 500)) {
27057
- const item = join6(target2, entry.name);
27601
+ const item = join8(target2, entry.name);
27058
27602
  const info2 = await lstat(item);
27059
27603
  if (info2.isSymbolicLink()) continue;
27060
27604
  result.push({ name: entry.name, type: info2.isDirectory() ? "directory" : info2.isFile() ? "file" : "other", ...info2.isFile() ? { size: info2.size } : {} });
27061
27605
  }
27062
27606
  return { ok: true, value: { path, entries: result, truncated: entries.length > 500 } };
27063
27607
  }
27064
- const info = await stat5(target2);
27608
+ const info = await stat6(target2);
27065
27609
  const absolutePath = target2;
27066
27610
  const version = `${info.mtimeMs}:${info.size}`;
27067
27611
  if (endpoint === "workspaceFiles/stat") {
@@ -27070,7 +27614,7 @@ var CodexWorkspaceBridge = class {
27070
27614
  if (!info.isFile()) throw new RpcError("CODEX_WORKSPACE_INVALID_PATH", "The requested workspace path is not a file.");
27071
27615
  const offset = readOffset(args);
27072
27616
  const limit = readLimit(args);
27073
- const bytes = await readFile4(target2);
27617
+ const bytes = await readFile5(target2);
27074
27618
  if (bytes.byteLength > MAX_READ_BYTES) throw new RpcError("CODEX_WORKSPACE_TOO_LARGE", "The requested workspace file is too large.");
27075
27619
  if (endpoint === "workspaceFiles/readBytes") {
27076
27620
  const slice2 = bytes.subarray(offset, Math.min(offset + limit, bytes.length));
@@ -27194,7 +27738,7 @@ var CodexWorkspaceBridge = class {
27194
27738
  throw new RpcError("METHOD_NOT_FOUND", "The requested terminal method does not exist.");
27195
27739
  }
27196
27740
  async createTerminal(sessionId, cwd, args) {
27197
- const request = isRecord14(args.request) ? args.request : {};
27741
+ const request = isRecord15(args.request) ? args.request : {};
27198
27742
  const id4 = stringId(request.id);
27199
27743
  if ([...this.terminals.values()].some((item) => item.sessionId === sessionId && item.id === id4)) throw new RpcError("REQUEST_CONFLICT", "The terminal id is already active.");
27200
27744
  if (this.terminals.size >= MAX_TERMINALS) throw new RpcError("RATE_LIMITED", "Too many remote terminals are active.", void 0, true);
@@ -27268,7 +27812,7 @@ var CodexWorkspaceBridge = class {
27268
27812
  if (!cwd) throw new RpcError("CODEX_WORKSPACE_UNAVAILABLE", "The CodeX thread has no available workspace.");
27269
27813
  try {
27270
27814
  const root = await realpath2(cwd);
27271
- const info = await stat5(root);
27815
+ const info = await stat6(root);
27272
27816
  if (!info.isDirectory()) throw new Error();
27273
27817
  return root;
27274
27818
  } catch {
@@ -27395,10 +27939,10 @@ function screenOf(terminal) {
27395
27939
  return (terminal.truncated ? "\x1Bc" : "") + terminal.screen.join("");
27396
27940
  }
27397
27941
  function argsOf(payload) {
27398
- const value = isRecord14(payload) ? payload : {};
27399
- return isRecord14(value.args) ? value.args : value;
27942
+ const value = isRecord15(payload) ? payload : {};
27943
+ return isRecord15(value.args) ? value.args : value;
27400
27944
  }
27401
- function isRecord14(value) {
27945
+ function isRecord15(value) {
27402
27946
  return typeof value === "object" && value !== null && !Array.isArray(value);
27403
27947
  }
27404
27948
  function stringId(value) {
@@ -27409,9 +27953,9 @@ function bounded(value, fallback, max) {
27409
27953
  return typeof value === "number" && Number.isInteger(value) && value > 0 ? Math.min(value, max) : fallback;
27410
27954
  }
27411
27955
  function byteOrLineRange(args) {
27412
- const options = isRecord14(args.options) ? args.options : void 0;
27413
- if (options !== void 0 && isRecord14(options.range)) return options.range;
27414
- return isRecord14(args.range) ? args.range : args;
27956
+ const options = isRecord15(args.options) ? args.options : void 0;
27957
+ if (options !== void 0 && isRecord15(options.range)) return options.range;
27958
+ return isRecord15(args.range) ? args.range : args;
27415
27959
  }
27416
27960
  function readOffset(args) {
27417
27961
  const range = byteOrLineRange(args);
@@ -27502,6 +28046,14 @@ var HostPluginRuntime = class {
27502
28046
  * device identity, unlike clearing the authorization.
27503
28047
  */
27504
28048
  paused;
28049
+ /**
28050
+ * Whether start() has finished wiring the Server connection.
28051
+ *
28052
+ * The Codex domain is optional business that waits on an external binary, so it runs in the
28053
+ * background; without this flag a Host that is merely still starting would report itself as
28054
+ * offline and look broken to the user and to other clients.
28055
+ */
28056
+ starting = true;
27505
28057
  harnessVersion;
27506
28058
  closed = false;
27507
28059
  codex;
@@ -27529,7 +28081,6 @@ var HostPluginRuntime = class {
27529
28081
  fingerprint: this.identity.fingerprint,
27530
28082
  server: this.config.serverUrl ?? "not configured"
27531
28083
  });
27532
- await this.codex.start();
27533
28084
  if (this.serverApi !== void 0) {
27534
28085
  this.harnessVersion = await this.readHarnessVersion();
27535
28086
  this.serverApi.setHarnessVersion(this.harnessVersion);
@@ -27537,6 +28088,9 @@ var HostPluginRuntime = class {
27537
28088
  this.serverConnection = this.createServerConnection(this.identity);
27538
28089
  if (!this.paused) this.serverConnection.start();
27539
28090
  }
28091
+ this.starting = false;
28092
+ void this.codex.start().catch(() => {
28093
+ });
27540
28094
  }
27541
28095
  currentIdentity() {
27542
28096
  if (this.identity === void 0) throw new Error("remote runtime has not started");
@@ -27554,6 +28108,7 @@ var HostPluginRuntime = class {
27554
28108
  configured: this.serverApi !== void 0,
27555
28109
  online: this.serverConnection?.isOnline() ?? false,
27556
28110
  reconnecting: this.serverConnection?.isReconnecting() ?? false,
28111
+ starting: this.starting,
27557
28112
  ...this.serverConnection?.lastActivity() === void 0 ? {} : { lastActiveAt: this.serverConnection.lastActivity() },
27558
28113
  ...error === void 0 ? {} : { error },
27559
28114
  ...authorization?.account === void 0 ? {} : { account: authorization.account },
@@ -27757,12 +28312,12 @@ var HostPluginRuntime = class {
27757
28312
  }
27758
28313
  async readHarnessVersion() {
27759
28314
  let reportedVersion;
27760
- let errorCode5;
28315
+ let errorCode6;
27761
28316
  try {
27762
- const response = await this.apiProxy?.host.describe({ rpcId: randomUUID3(), payload: {} });
28317
+ const response = await this.apiProxy?.host.describe({ rpcId: randomUUID4(), payload: {} });
27763
28318
  if (response === void 0) throw new Error("ApiProxy is unavailable");
27764
28319
  if (!response.result.ok) {
27765
- errorCode5 = response.result.error.code;
28320
+ errorCode6 = response.result.error.code;
27766
28321
  } else {
27767
28322
  reportedVersion = normalizeHarnessVersion(response.result.value.version);
27768
28323
  }
@@ -27771,7 +28326,7 @@ var HostPluginRuntime = class {
27771
28326
  const distributionVersion = reportedVersion === void 0 || reportedVersion === "0.0.1" ? await readHarnessDistributionVersion() : void 0;
27772
28327
  const version = selectHarnessVersion(reportedVersion, distributionVersion);
27773
28328
  if (version !== void 0) return version;
27774
- this.logger.warn("Harness version is unavailable", errorCode5 === void 0 ? void 0 : { code: errorCode5 });
28329
+ this.logger.warn("Harness version is unavailable", errorCode6 === void 0 ? void 0 : { code: errorCode6 });
27775
28330
  return void 0;
27776
28331
  }
27777
28332
  hostCapabilities() {
@@ -27827,9 +28382,9 @@ function isPlainRecord(value) {
27827
28382
  }
27828
28383
 
27829
28384
  // src/cli.ts
27830
- import { stat as stat6 } from "node:fs/promises";
28385
+ import { stat as stat7 } from "node:fs/promises";
27831
28386
  import { hostname as hostname3 } from "node:os";
27832
- import { join as join7 } from "node:path";
28387
+ import { join as join9 } from "node:path";
27833
28388
  var QR_POLL_INTERVAL_MS = 2e3;
27834
28389
  var TERMINAL_QR_MARGIN = 4;
27835
28390
  async function runCli(args = process.argv.slice(2), dependencies = {}) {
@@ -27917,7 +28472,7 @@ async function status(args, runtime) {
27917
28472
  `Server: ${serverUrl}`,
27918
28473
  "Host control: enabled (dsh-TUI default)"
27919
28474
  ];
27920
- if (!await exists3(join7(directory, "device.json"))) {
28475
+ if (!await exists3(join9(directory, "device.json"))) {
27921
28476
  lines.push("Device: not initialized", "Authorization: logged out", "Credential: unavailable");
27922
28477
  write(runtime.stdout, `${lines.join("\n")}
27923
28478
 
@@ -27961,7 +28516,7 @@ async function logout(args, runtime) {
27961
28516
  const serverUrl = selectedServer();
27962
28517
  const root = new IdentityStore({ env: runtime.env }).directory;
27963
28518
  const directory = serverStorageDirectory(root, serverUrl, "host");
27964
- if (!await exists3(join7(directory, "device.json"))) {
28519
+ if (!await exists3(join9(directory, "device.json"))) {
27965
28520
  await new ServerCredentialStore(directory).clear();
27966
28521
  write(runtime.stdout, "This Host is already logged out.\n");
27967
28522
  return 0;
@@ -28081,7 +28636,7 @@ function terminalLink(url, target2) {
28081
28636
  }
28082
28637
  async function exists3(path) {
28083
28638
  try {
28084
- await stat6(path);
28639
+ await stat7(path);
28085
28640
  return true;
28086
28641
  } catch (error) {
28087
28642
  if (error instanceof Error && "code" in error && error.code === "ENOENT") return false;
@@ -28148,7 +28703,7 @@ function installTuiRemoteCommand(ctx, resolveTarget) {
28148
28703
  } catch (error) {
28149
28704
  return {
28150
28705
  kind: "error",
28151
- text: `Local Remote Host credentials were cleared, but Server revocation failed (${errorCode4(error)}).`
28706
+ text: `Local Remote Host credentials were cleared, but Server revocation failed (${errorCode5(error)}).`
28152
28707
  };
28153
28708
  }
28154
28709
  }
@@ -28204,7 +28759,7 @@ function registerOptional(ctx, feature, register2) {
28204
28759
  return register2() ?? (() => {
28205
28760
  });
28206
28761
  } catch (error) {
28207
- ctx.logger.warn(`dsh-TUI Remote ${feature} is unavailable`, { code: errorCode4(error) });
28762
+ ctx.logger.warn(`dsh-TUI Remote ${feature} is unavailable`, { code: errorCode5(error) });
28208
28763
  return () => {
28209
28764
  };
28210
28765
  }
@@ -28247,7 +28802,7 @@ function remoteStatusLines(target2) {
28247
28802
  const diagnostics = runtime.diagnostics();
28248
28803
  const codex = runtime.codexStatus();
28249
28804
  const capabilities = new Set(diagnostics.capabilities);
28250
- const connection = status2.online ? "online" : status2.reconnecting ? "reconnecting" : status2.accountRequired ? "authorization required" : "offline";
28805
+ const connection = status2.online ? "online" : status2.starting ? "starting" : status2.reconnecting ? "reconnecting" : status2.accountRequired ? "authorization required" : "offline";
28251
28806
  return [
28252
28807
  `Server: ${config.serverUrl ?? "not configured"}`,
28253
28808
  "Host control: enabled",
@@ -28317,7 +28872,7 @@ var RemoteLoginController = class {
28317
28872
  await this.poll(attempt, provider, session, target2.runtime);
28318
28873
  } catch (error) {
28319
28874
  if (attempt !== this.attempt) return;
28320
- this.update({ phase: "error", provider, error: errorCode4(error) });
28875
+ this.update({ phase: "error", provider, error: errorCode5(error) });
28321
28876
  }
28322
28877
  }
28323
28878
  async poll(attempt, provider, session, runtime) {
@@ -28435,7 +28990,7 @@ function terminalLink2(url) {
28435
28990
  function printableWidth(value) {
28436
28991
  return value.replace(/\u001B\[[0-9;]*m/gu, "").length;
28437
28992
  }
28438
- function errorCode4(error) {
28993
+ function errorCode5(error) {
28439
28994
  if (error instanceof ServerApiError) return error.code;
28440
28995
  if (error instanceof Error && "code" in error && typeof error.code === "string") return error.code;
28441
28996
  return "CONNECTION_FAILED";
@@ -28478,7 +29033,7 @@ function createRemoteFileContentProvider(call, options = {}) {
28478
29033
  const offset = request.offset + received;
28479
29034
  const range = await call("fileviewer.readRange", { path: locator, offset, length }, request.signal);
28480
29035
  if (range.offset !== offset) throw new Error("The Remote Host returned a mismatched file range.");
28481
- const bytes = decodeBase64(range.data);
29036
+ const bytes = decodeBase642(range.data);
28482
29037
  if (bytes.byteLength > length) throw new Error("The Remote Host returned more file bytes than requested.");
28483
29038
  chunks.push(bytes);
28484
29039
  received += bytes.byteLength;
@@ -28510,7 +29065,7 @@ function currentSaveAsAllowed(value) {
28510
29065
  function currentSaveAsMaxBytes(value) {
28511
29066
  return typeof value === "function" ? value() : value ?? REMOTE_FILE_SAVE_AS_MAX_BYTES;
28512
29067
  }
28513
- function decodeBase64(value) {
29068
+ function decodeBase642(value) {
28514
29069
  const binary = atob(value);
28515
29070
  const bytes = new Uint8Array(binary.length);
28516
29071
  for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);
@@ -28663,7 +29218,10 @@ async function activate(ctx, readConfig, entryId, tuiBinding) {
28663
29218
  apiProxy,
28664
29219
  nativeTypertGateway,
28665
29220
  logger,
28666
- hostControl
29221
+ hostControl,
29222
+ // The RTC factory keeps its default; the target store lives beside the plugin state.
29223
+ void 0,
29224
+ new ClientTargetStore(defaultIdentityDirectory)
28667
29225
  );
28668
29226
  }
28669
29227
  const controlRuntime = connection === void 0 ? void 0 : new PluginControlRuntime(config, defaultIdentityDirectory, settingsBinding, clientRuntime, hostControl, deepseekSession);
@@ -28678,6 +29236,7 @@ async function activate(ctx, readConfig, entryId, tuiBinding) {
28678
29236
  await runtime.start();
28679
29237
  if (clientRuntime !== void 0) {
28680
29238
  await clientRuntime.start();
29239
+ await clientRuntime.restoreLastTarget();
28681
29240
  } else {
28682
29241
  logger.warn("client remote mode is unavailable", {
28683
29242
  serverConfigured: config.serverUrl !== void 0,