ds-harness-remote 0.4.8 → 0.4.10

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.
package/dist/index.js CHANGED
@@ -4055,6 +4055,10 @@ var HARNESS_API_TRANSFER_CHUNK_BYTES = 512 * 1024;
4055
4055
  var CODEX_APP_TRANSFER_CHUNK_BYTES = 512 * 1024;
4056
4056
  var MAX_HARNESS_API_TRANSFER_BYTES = 288 * 1024 * 1024;
4057
4057
  var MAX_CODEX_APP_TRANSFER_BYTES = 288 * 1024 * 1024;
4058
+ var MAX_DEVICE_NAME_LENGTH = 128;
4059
+ var MAX_DEVICE_PLATFORM_LENGTH = 64;
4060
+ var MAX_DEVICE_VERSION_LENGTH = 64;
4061
+ var MAX_AUTH_TOKEN_LENGTH = 8 * 1024;
4058
4062
  var SECURE_FRAGMENT_MAGIC = new Uint8Array([68, 83, 72, 70]);
4059
4063
  var SECURE_FRAGMENT_VERSION = 1;
4060
4064
  var SECURE_FRAGMENT_HEADER_BYTES = 17;
@@ -4138,6 +4142,73 @@ var messageTypeSchema = external_exports.enum(messageTypes);
4138
4142
  var controlFrameTypeSchema = external_exports.enum(controlFrameTypes);
4139
4143
  var uniqueStrings = (values) => new Set(values).size === values.length;
4140
4144
  var uniqueNumbers = (values) => new Set(values).size === values.length;
4145
+ var utf8Length = (value) => new TextEncoder().encode(value).byteLength;
4146
+ var boundedUtf8Schema = (minimum, maximum) => external_exports.string().refine((value) => utf8Length(value) >= minimum && utf8Length(value) <= maximum);
4147
+ var deviceIdSchema = external_exports.string().refine((value) => /^[0-9A-HJKMNP-TV-Z]{26}$/iu.test(value) || /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu.test(value), "Device ID must be a ULID or UUID.");
4148
+ var deviceNameSchema = boundedUtf8Schema(1, MAX_DEVICE_NAME_LENGTH).refine((value) => value.trim().length > 0, "Device name must contain visible text.");
4149
+ var devicePlatformSchema = external_exports.string().min(1).max(MAX_DEVICE_PLATFORM_LENGTH).regex(/^[a-z0-9][a-z0-9._-]*$/u);
4150
+ var identityKeySchema = external_exports.string().regex(/^[A-Za-z0-9_-]{42}[AEIMQUYcgkosw048]$/u);
4151
+ var deviceVersionSchema = boundedUtf8Schema(1, MAX_DEVICE_VERSION_LENGTH);
4152
+ var authTokenSchema = boundedUtf8Schema(16, MAX_AUTH_TOKEN_LENGTH);
4153
+ var expiresAtSchema = external_exports.number().int().positive().safe();
4154
+ var hostDeviceDescriptorSchema = external_exports.object({
4155
+ deviceId: deviceIdSchema,
4156
+ name: deviceNameSchema,
4157
+ role: external_exports.literal("host"),
4158
+ platform: devicePlatformSchema,
4159
+ identityKey: identityKeySchema,
4160
+ clientVersion: deviceVersionSchema,
4161
+ harnessVersion: deviceVersionSchema.optional()
4162
+ }).strict();
4163
+ var clientDeviceDescriptorSchema = external_exports.object({
4164
+ deviceId: deviceIdSchema,
4165
+ name: deviceNameSchema,
4166
+ role: external_exports.literal("client"),
4167
+ platform: devicePlatformSchema,
4168
+ identityKey: identityKeySchema,
4169
+ clientVersion: deviceVersionSchema
4170
+ }).strict();
4171
+ var accountDeviceDescriptorSchema = external_exports.union([
4172
+ hostDeviceDescriptorSchema,
4173
+ clientDeviceDescriptorSchema
4174
+ ]);
4175
+ var deviceRegistrationRequestSchema = external_exports.object({
4176
+ v: external_exports.literal(PROTOCOL_VERSION),
4177
+ device: accountDeviceDescriptorSchema
4178
+ }).strict();
4179
+ var hostRegistrationCodeRequestSchema = external_exports.object({
4180
+ v: external_exports.literal(PROTOCOL_VERSION),
4181
+ code: external_exports.string().regex(/^[A-Z0-9]{4}-[A-Z0-9]{4}$/u),
4182
+ device: hostDeviceDescriptorSchema
4183
+ }).strict();
4184
+ var deviceRefreshRequestSchema = external_exports.object({
4185
+ deviceId: deviceIdSchema,
4186
+ refreshToken: authTokenSchema
4187
+ }).strict();
4188
+ var deviceTokenPairSchema = external_exports.object({
4189
+ accessToken: authTokenSchema,
4190
+ accessTokenExpiresAt: expiresAtSchema,
4191
+ refreshToken: authTokenSchema,
4192
+ refreshTokenExpiresAt: expiresAtSchema
4193
+ }).strict();
4194
+ var browserAuthorizationExchangeRequestSchema = external_exports.object({
4195
+ v: external_exports.literal(PROTOCOL_VERSION),
4196
+ device: external_exports.object({
4197
+ deviceId: deviceIdSchema,
4198
+ name: deviceNameSchema,
4199
+ role: external_exports.literal("client"),
4200
+ platform: external_exports.literal("browser"),
4201
+ identityKey: identityKeySchema,
4202
+ clientVersion: deviceVersionSchema
4203
+ }).strict()
4204
+ }).strict();
4205
+ var browserAuthorizationExchangeResponseSchema = external_exports.object({
4206
+ accessToken: authTokenSchema,
4207
+ accessTokenExpiresAt: expiresAtSchema,
4208
+ refreshToken: authTokenSchema,
4209
+ refreshTokenExpiresAt: expiresAtSchema,
4210
+ account: boundedUtf8Schema(1, 254).refine((value) => value.trim().length > 0)
4211
+ }).strict();
4141
4212
  var remoteMessageSchema = external_exports.object({
4142
4213
  v: external_exports.literal(PROTOCOL_VERSION),
4143
4214
  id: external_exports.string().min(1),
@@ -14395,6 +14466,25 @@ function hasErrorCode(error, code) {
14395
14466
  return error instanceof Error && "code" in error && error.code === code;
14396
14467
  }
14397
14468
 
14469
+ // src/codex/permissions.ts
14470
+ function codexPermissionPresetFromResponse(value) {
14471
+ const root = record(value);
14472
+ for (const settings of [root, record(root.threadSettings)]) {
14473
+ const sandbox = settings.sandbox ?? settings.sandboxPolicy;
14474
+ const type = record(sandbox).type;
14475
+ if (settings.approvalPolicy === "never" && (sandbox === "danger-full-access" || type === "dangerFullAccess")) {
14476
+ return "danger-full-access";
14477
+ }
14478
+ if (settings.approvalPolicy === "on-request" && (sandbox === "workspace-write" || type === "workspaceWrite")) {
14479
+ return "workspace-write";
14480
+ }
14481
+ }
14482
+ return void 0;
14483
+ }
14484
+ function record(value) {
14485
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
14486
+ }
14487
+
14398
14488
  // src/codex/virtual-harness.ts
14399
14489
  var CODEX_SESSION_PREFIX = "codex:";
14400
14490
  var CODEX_WORKSPACE_PREFIX = "codex-workspace:";
@@ -14643,10 +14733,11 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
14643
14733
  return this.selectedModels.get(sessionId) ?? directory?.default ?? modelSelection();
14644
14734
  }
14645
14735
  permissionSelection(sessionId) {
14646
- return this.selectedPermissions.get(sessionId) ?? CODEX_DEFAULT_PERMISSION;
14736
+ return this.selectedPermissions.get(sessionId);
14647
14737
  }
14648
14738
  setPermissionSelection(sessionId, preset) {
14649
- this.selectedPermissions.set(sessionId, preset);
14739
+ if (preset === void 0) this.selectedPermissions.delete(sessionId);
14740
+ else this.selectedPermissions.set(sessionId, preset);
14650
14741
  this.publishProjection(sessionId, "permissions", codexPermissionsProjection(preset), this.nextProjectionSeq());
14651
14742
  }
14652
14743
  commandList(args) {
@@ -14670,17 +14761,22 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
14670
14761
  result: { kind: "error", text: "CodeX Remote permission commands do not accept attachments." }
14671
14762
  };
14672
14763
  const requested = (match[1] ?? "").trim();
14673
- if (requested === "") return {
14674
- commandId,
14675
- result: { kind: "success", text: `current preset ${this.permissionSelection(sessionId)}` }
14676
- };
14764
+ if (requested === "") {
14765
+ await this.readHistoryPage(threadId, { maxMessages: 1 }, signal);
14766
+ return {
14767
+ commandId,
14768
+ result: { kind: "success", text: `current preset ${this.permissionSelection(sessionId) ?? "Host settings (not reported)"}` }
14769
+ };
14770
+ }
14677
14771
  if (!isCodexPermissionPreset(requested)) return {
14678
14772
  commandId,
14679
14773
  result: { kind: "error", text: `unknown preset "${requested}" (available: workspace-write, danger-full-access)` }
14680
14774
  };
14681
14775
  const result = await this.client.request("thread/resume", { threadId, permissionPreset: requested }, signal);
14682
- const effective = codexPermissionPresetFromResponse(result) ?? requested;
14776
+ const effective = codexPermissionPresetFromResponse(result);
14777
+ if (effective === void 0) throw new Error("The Host did not confirm the CodeX permission preset.");
14683
14778
  this.setPermissionSelection(sessionId, effective);
14779
+ if (effective !== requested) throw new Error("The Host did not apply the requested CodeX permission preset.");
14684
14780
  return { commandId, result: { kind: "success", text: `preset ${effective}` } };
14685
14781
  }
14686
14782
  sessionTitle(sessionId) {
@@ -14812,7 +14908,7 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
14812
14908
  return queue.iterate(() => this.eventStreams.delete(clientId));
14813
14909
  }
14814
14910
  async sessionFollow(request, signal) {
14815
- const sessionId = sessionIdFromAddress(record(request.address));
14911
+ const sessionId = sessionIdFromAddress(record2(request.address));
14816
14912
  const threadId = nativeThreadId(sessionId);
14817
14913
  const history = await this.readHistoryPage(threadId, {
14818
14914
  maxMessages: optionalPositiveInteger(request.maxMessages)
@@ -14875,7 +14971,7 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
14875
14971
  });
14876
14972
  }
14877
14973
  acceptCodexFrame(follow, frame) {
14878
- const params = record(frame.params);
14974
+ const params = record2(frame.params);
14879
14975
  if (frame.method === "thread/name/updated") {
14880
14976
  const name2 = string(params.threadName);
14881
14977
  if (name2 === void 0) return;
@@ -14887,11 +14983,11 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
14887
14983
  }
14888
14984
  if (frame.method === "thread/settings/updated") {
14889
14985
  const preset = codexPermissionPresetFromResponse(params);
14890
- if (preset !== void 0) this.setPermissionSelection(follow.sessionId, preset);
14986
+ this.setPermissionSelection(follow.sessionId, preset);
14891
14987
  return;
14892
14988
  }
14893
14989
  if (frame.method === "turn/started") {
14894
- const turn = record(params.turn);
14990
+ const turn = record2(params.turn);
14895
14991
  this.resetLiveTurn(follow);
14896
14992
  follow.turn += 1;
14897
14993
  follow.activeTurnId = string(turn.id);
@@ -14900,11 +14996,11 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
14900
14996
  this.pushEvent(follow, "turn/start", { turn: follow.turn });
14901
14997
  this.pushEvent(follow, "step/start", { turn: follow.turn, step: 1 });
14902
14998
  this.emitRemoteEvent("api-session/status", [follow.sessionId, true]);
14903
- for (const item of array(turn.items)) this.acceptCompletedItem(follow, record(item), false);
14999
+ for (const item of array(turn.items)) this.acceptCompletedItem(follow, record2(item), false);
14904
15000
  return;
14905
15001
  }
14906
15002
  if (frame.method === "item/started" || frame.method === "item/completed") {
14907
- const item = record(params.item);
15003
+ const item = record2(params.item);
14908
15004
  const itemId = string(item.id);
14909
15005
  if (frame.method === "item/started" && itemId !== void 0) {
14910
15006
  follow.startedItems.add(itemId);
@@ -14941,7 +15037,7 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
14941
15037
  }
14942
15038
  if (frame.method === "turn/plan/updated") {
14943
15039
  const todos = array(params.plan).flatMap((value) => {
14944
- const item = record(value);
15040
+ const item = record2(value);
14945
15041
  const content = string(item.step);
14946
15042
  if (content === void 0) return [];
14947
15043
  const status2 = item.status === "inProgress" ? "in_progress" : item.status === "completed" ? "completed" : "pending";
@@ -14975,7 +15071,7 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
14975
15071
  return;
14976
15072
  }
14977
15073
  if (frame.method === "thread/status/changed") {
14978
- const status2 = record(params.status);
15074
+ const status2 = record2(params.status);
14979
15075
  const running = status2.type === "active";
14980
15076
  this.emitRemoteEvent("api-session/status", [follow.sessionId, running]);
14981
15077
  if (status2.type === "systemError") {
@@ -15002,8 +15098,8 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
15002
15098
  return;
15003
15099
  }
15004
15100
  if (frame.method === "turn/completed") {
15005
- const turn = record(params.turn);
15006
- for (const item of array(turn.items)) this.acceptCompletedItem(follow, record(item), true);
15101
+ const turn = record2(params.turn);
15102
+ for (const item of array(turn.items)) this.acceptCompletedItem(follow, record2(item), true);
15007
15103
  if (follow.stepOpen) {
15008
15104
  this.closeAllStreamBlocks(follow);
15009
15105
  if (follow.streamActive) this.finishStream(follow);
@@ -15098,7 +15194,7 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
15098
15194
  }
15099
15195
  cacheImageBlocks(sessionId, value) {
15100
15196
  for (const block of collectImageBlocks(value)) {
15101
- const attachment = record(block.attachment);
15197
+ const attachment = record2(block.attachment);
15102
15198
  const attachmentId = string(attachment.attachmentId);
15103
15199
  const data = string(block.data);
15104
15200
  if (attachmentId === void 0 || data === void 0 || !attachmentId.startsWith(CODEX_IMAGE_ATTACHMENT_PREFIX)) continue;
@@ -15239,7 +15335,7 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
15239
15335
  }
15240
15336
  async answerRemoteEvent(args, signal) {
15241
15337
  const eventId = string(args.eventId);
15242
- const outcome = record(args.outcome);
15338
+ const outcome = record2(args.outcome);
15243
15339
  if (eventId === void 0) throw new Error("The CodeX approval result is missing its event id.");
15244
15340
  const pending = this.pendingApprovals.get(eventId);
15245
15341
  if (pending === void 0) return void 0;
@@ -15290,7 +15386,7 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
15290
15386
  if (!containsPath(workspace.path, path)) {
15291
15387
  return failure("workspace-not-found", "The selected directory is outside the current CodeX virtual Workspace.");
15292
15388
  }
15293
- const listing = record(await this.client.request("dsh/directoryList", { path }, signal));
15389
+ const listing = record2(await this.client.request("dsh/directoryList", { path }, signal));
15294
15390
  if (!isCodexDirectoryListing(listing)) {
15295
15391
  return failure("internal", "CodeX Remote returned an invalid directory listing.");
15296
15392
  }
@@ -15324,13 +15420,13 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
15324
15420
  const directory = await this.models(signal);
15325
15421
  const selection = directory.default;
15326
15422
  const requestedPreset = CODEX_DEFAULT_PERMISSION;
15327
- const result = record(await this.client.request("thread/start", {
15423
+ const result = record2(await this.client.request("thread/start", {
15328
15424
  cwd,
15329
15425
  model: selection.model,
15330
15426
  permissionPreset: requestedPreset
15331
15427
  }, signal));
15332
15428
  const permissionPreset2 = codexPermissionPresetFromResponse(result) ?? requestedPreset;
15333
- const thread = { ...record(result.thread), cwd, turns: array(record(result.thread).turns) };
15429
+ const thread = { ...record2(result.thread), cwd, turns: array(record2(result.thread).turns) };
15334
15430
  const projected = projectCodexThread(thread);
15335
15431
  if (projected === void 0) return failure("internal", "CodeX returned an invalid Thread.");
15336
15432
  this.pendingThreads.set(projected.nativeId, thread);
@@ -15350,16 +15446,14 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
15350
15446
  }
15351
15447
  async forkSession(request, signal) {
15352
15448
  const sessionId = requiredString(request.sessionId, "sessionId");
15353
- let permissionPreset2 = this.selectedPermissions.get(sessionId);
15354
- const result = record(await this.client.request("thread/fork", {
15355
- threadId: nativeThreadId(sessionId),
15356
- ...permissionPreset2 === void 0 ? {} : { permissionPreset: permissionPreset2 }
15449
+ const result = record2(await this.client.request("thread/fork", {
15450
+ threadId: nativeThreadId(sessionId)
15357
15451
  }, signal));
15358
- permissionPreset2 = codexPermissionPresetFromResponse(result) ?? permissionPreset2 ?? CODEX_DEFAULT_PERMISSION;
15359
- const projected = projectCodexThread(record(result.thread));
15452
+ const permissionPreset2 = codexPermissionPresetFromResponse(result);
15453
+ const projected = projectCodexThread(record2(result.thread));
15360
15454
  if (projected === void 0) return failure("internal", "CodeX returned an invalid forked Thread.");
15361
15455
  this.selectedModels.set(projected.id, this.modelSelection(sessionId, await this.models(signal)));
15362
- this.selectedPermissions.set(projected.id, permissionPreset2);
15456
+ this.setPermissionSelection(projected.id, permissionPreset2);
15363
15457
  await this.refreshAndPublishWorkspaces();
15364
15458
  const seq = this.nextProjectionSeq();
15365
15459
  this.publishProjection(projected.id, "title", displayTitle(projected), seq);
@@ -15372,7 +15466,6 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
15372
15466
  if (input2 === void 0) return failure("attachment-error", "CodeX virtual Sessions accept text and pasted PNG, JPEG, WebP, or GIF images only.");
15373
15467
  const threadId = nativeThreadId(sessionId);
15374
15468
  const selection = this.modelSelection(sessionId, await this.models(signal));
15375
- let permissionPreset2 = this.selectedPermissions.get(sessionId);
15376
15469
  const isFreshBlankThread = this.blankThreads.has(threadId) || this.pendingThreads.has(threadId);
15377
15470
  await this.ensureRcFollow(sessionId);
15378
15471
  if (!isFreshBlankThread) {
@@ -15381,8 +15474,7 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
15381
15474
  model: selection.model
15382
15475
  }, signal);
15383
15476
  const serverPreset = codexPermissionPresetFromResponse(resumeResult);
15384
- if (permissionPreset2 === void 0 && serverPreset !== void 0) {
15385
- permissionPreset2 = serverPreset;
15477
+ if (serverPreset !== void 0) {
15386
15478
  this.setPermissionSelection(sessionId, serverPreset);
15387
15479
  }
15388
15480
  }
@@ -15396,8 +15488,7 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
15396
15488
  await this.client.request(mode, {
15397
15489
  threadId,
15398
15490
  input: input2,
15399
- ...codexModelParams(selection),
15400
- ...permissionPreset2 === void 0 ? {} : { permissionPreset: permissionPreset2 }
15491
+ ...codexModelParams(selection)
15401
15492
  }, signal);
15402
15493
  }
15403
15494
  this.blankThreads.delete(threadId);
@@ -15418,8 +15509,8 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
15418
15509
  }
15419
15510
  async hydrateImageAttachments(sessionId, signal) {
15420
15511
  const threadId = nativeThreadId(sessionId);
15421
- const result = record(await this.client.request("thread/read", { threadId, includeTurns: true }, signal));
15422
- const thread = record(result.thread);
15512
+ const result = record2(await this.client.request("thread/read", { threadId, includeTurns: true }, signal));
15513
+ const thread = record2(result.thread);
15423
15514
  if (string(thread.id) !== threadId) throw new Error("CodeX returned an invalid Thread history.");
15424
15515
  this.cacheHistoryImages(projectCodexNativeHistory(thread, sessionId));
15425
15516
  }
@@ -15441,7 +15532,7 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
15441
15532
  return success({ title, seq });
15442
15533
  }
15443
15534
  async sessionPage(request, signal) {
15444
- const sessionId = sessionIdFromAddress(record(request.address));
15535
+ const sessionId = sessionIdFromAddress(record2(request.address));
15445
15536
  const page = await this.readHistoryPage(nativeThreadId(sessionId), {
15446
15537
  beforeSeq: optionalNonNegativeInteger(request.beforeSeq),
15447
15538
  throughSeq: optionalInteger(request.throughSeq),
@@ -15454,15 +15545,15 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
15454
15545
  return void 0;
15455
15546
  }
15456
15547
  async fetchThread(threadId, signal) {
15457
- const result = record(await this.client.request("thread/read", { threadId, includeTurns: false }, signal));
15458
- const thread = record(result.thread);
15548
+ const result = record2(await this.client.request("thread/read", { threadId, includeTurns: false }, signal));
15549
+ const thread = record2(result.thread);
15459
15550
  if (string(thread.id) !== threadId) throw new Error("CodeX returned an invalid Thread history.");
15460
15551
  return thread;
15461
15552
  }
15462
15553
  async readHistoryPage(threadId, page, signal) {
15463
15554
  let value;
15464
15555
  try {
15465
- value = record(await this.client.request("dsh/sessionHistory", {
15556
+ value = record2(await this.client.request("dsh/sessionHistory", {
15466
15557
  threadId,
15467
15558
  ...page.beforeSeq === void 0 ? {} : { beforeSeq: page.beforeSeq },
15468
15559
  ...page.throughSeq === void 0 ? {} : { throughSeq: page.throughSeq },
@@ -15470,8 +15561,8 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
15470
15561
  }, signal));
15471
15562
  } catch (error) {
15472
15563
  if (!isLegacySessionHistoryUnsupported(error)) throw error;
15473
- const result = record(await this.client.request("thread/read", { threadId, includeTurns: true }, signal));
15474
- const thread = record(result.thread);
15564
+ const result = record2(await this.client.request("thread/read", { threadId, includeTurns: true }, signal));
15565
+ const thread = record2(result.thread);
15475
15566
  if (string(thread.id) !== threadId) throw new Error("CodeX returned an invalid Thread history.");
15476
15567
  value = paginateCodexNativeHistory(
15477
15568
  projectCodexNativeHistory(thread, `codex:${threadId}`),
@@ -15485,6 +15576,10 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
15485
15576
  if (!isCodexNativeHistoryPage(value, `codex:${threadId}`)) {
15486
15577
  throw new Error("CodeX Remote returned an invalid paginated History.");
15487
15578
  }
15579
+ if (Object.hasOwn(value, "permissionPreset")) {
15580
+ const preset = value.permissionPreset;
15581
+ this.setPermissionSelection(`codex:${threadId}`, preset === "workspace-write" || preset === "danger-full-access" ? preset : void 0);
15582
+ }
15488
15583
  this.cacheHistoryImages(value);
15489
15584
  return value;
15490
15585
  }
@@ -15692,7 +15787,7 @@ async function loadCatalog(client, signal, pendingThreads) {
15692
15787
  const threads = [];
15693
15788
  let cursor2;
15694
15789
  for (let page = 0; page < MAX_CODEX_PAGES; page += 1) {
15695
- const result = record(await client.request("thread/list", {
15790
+ const result = record2(await client.request("thread/list", {
15696
15791
  limit: CODEX_PAGE_LIMIT,
15697
15792
  sortKey: "updated_at",
15698
15793
  sortDirection: "desc",
@@ -15700,7 +15795,7 @@ async function loadCatalog(client, signal, pendingThreads) {
15700
15795
  ...cursor2 === void 0 ? {} : { cursor: cursor2 }
15701
15796
  }, signal));
15702
15797
  for (const value of array(result.data)) {
15703
- const thread = record(value);
15798
+ const thread = record2(value);
15704
15799
  if (string(thread.id) !== void 0) threads.push(thread);
15705
15800
  }
15706
15801
  cursor2 = typeof result.nextCursor === "string" && result.nextCursor.length > 0 ? result.nextCursor : void 0;
@@ -15752,7 +15847,7 @@ async function loadCodexProjects(client, signal) {
15752
15847
  let cursor2;
15753
15848
  try {
15754
15849
  for (let page = 0; page < MAX_CODEX_PAGES; page += 1) {
15755
- const result = record(await client.request("project/list", {
15850
+ const result = record2(await client.request("project/list", {
15756
15851
  limit: CODEX_PAGE_LIMIT,
15757
15852
  ...cursor2 === void 0 ? {} : { cursor: cursor2 }
15758
15853
  }, signal));
@@ -15774,18 +15869,18 @@ async function loadModelDirectory(client, signal) {
15774
15869
  let defaultModelId;
15775
15870
  let cursor2;
15776
15871
  for (let page = 0; page < MAX_CODEX_PAGES; page += 1) {
15777
- const result = record(await client.request("model/list", {
15872
+ const result = record2(await client.request("model/list", {
15778
15873
  limit: CODEX_PAGE_LIMIT,
15779
15874
  includeHidden: false,
15780
15875
  ...cursor2 === void 0 ? {} : { cursor: cursor2 }
15781
15876
  }, signal));
15782
15877
  for (const value of array(result.data)) {
15783
- const source = record(value);
15878
+ const source = record2(value);
15784
15879
  if (source.hidden === true) continue;
15785
15880
  const id2 = string(source.model) ?? string(source.id);
15786
15881
  if (id2 === void 0 || models.has(id2)) continue;
15787
15882
  const efforts = array(source.supportedReasoningEfforts).map((value2) => {
15788
- const effort = record(value2);
15883
+ const effort = record2(value2);
15789
15884
  const effortId = string(effort.reasoningEffort);
15790
15885
  if (effortId === void 0) return void 0;
15791
15886
  const description2 = string(effort.description);
@@ -15849,8 +15944,8 @@ function projectCodexNativeHistory(thread, sessionId) {
15849
15944
  return eventSeq;
15850
15945
  };
15851
15946
  for (const rawTurn of array(thread.turns)) {
15852
- const turn = record(rawTurn);
15853
- const turnItems = array(turn.items).map(record);
15947
+ const turn = record2(rawTurn);
15948
+ const turnItems = array(turn.items).map(record2);
15854
15949
  const toolImages = turnItems.flatMap(toolOutputImageBlocks);
15855
15950
  let imageOwner = -1;
15856
15951
  for (let index = 0; index < turnItems.length; index += 1) {
@@ -15999,15 +16094,15 @@ function messageContent(item, attachmentSeed, fallbackType, fallbackText, supple
15999
16094
  }
16000
16095
  function toolOutputImageBlocks(item) {
16001
16096
  const candidates = [
16002
- record(item.result).content,
16003
- record(item.output).content,
16097
+ record2(item.result).content,
16098
+ record2(item.output).content,
16004
16099
  item.contentItems,
16005
16100
  Array.isArray(item.output) ? item.output : void 0
16006
16101
  ];
16007
16102
  for (const candidate of candidates) {
16008
16103
  if (!Array.isArray(candidate)) continue;
16009
16104
  const images = candidate.flatMap((value) => {
16010
- const block = record(value);
16105
+ const block = record2(value);
16011
16106
  return isImageContent2(block) && parseImageBlock(block) !== void 0 ? [block] : [];
16012
16107
  });
16013
16108
  if (images.length > 0) return images;
@@ -16024,7 +16119,7 @@ function contentBlocks(value, attachmentSeed) {
16024
16119
  blocks.push({ type: "text", text: raw });
16025
16120
  continue;
16026
16121
  }
16027
- const block = record(raw);
16122
+ const block = record2(raw);
16028
16123
  const image = imageContentBlock(block, attachmentSeed, imageIndex);
16029
16124
  if (image !== void 0) {
16030
16125
  imageIndex += 1;
@@ -16063,7 +16158,7 @@ function isImageContent2(block) {
16063
16158
  return block.type === "image" || block.type === "input_image";
16064
16159
  }
16065
16160
  function parseImageBlock(block) {
16066
- const url = string(block.url) ?? string(block.image_url) ?? string(record(block.image_url).url);
16161
+ const url = string(block.url) ?? string(block.image_url) ?? string(record2(block.image_url).url);
16067
16162
  const parsed = url === void 0 ? void 0 : parseDataImageUrl2(url);
16068
16163
  if (parsed !== void 0) return parsed;
16069
16164
  const data = string(block.data);
@@ -16220,10 +16315,10 @@ function toolResultEvent(item, turn, step, id2, resultText) {
16220
16315
  };
16221
16316
  }
16222
16317
  function toolResultContentReplacement(event) {
16223
- const data = record(event.data);
16224
- const message = record(data.message);
16318
+ const data = record2(event.data);
16319
+ const message = record2(data.message);
16225
16320
  const content = array(message.content);
16226
- const result = record(content[0]);
16321
+ const result = record2(content[0]);
16227
16322
  if (result.isError === void 0) return event;
16228
16323
  const { isError: _isError, ...stableResult } = result;
16229
16324
  return {
@@ -16263,9 +16358,9 @@ function toolResultText(item, type, text) {
16263
16358
  if (type === "exitedReviewMode") return `Exited review mode${string(item.review) ? `: ${string(item.review)}` : "."}`;
16264
16359
  if (type === "sleep") return `Waited ${integer(item.durationMs) ?? 0} ms.`;
16265
16360
  if (type === "subAgentActivity") {
16266
- return [string(record(item.kind).type) ?? string(item.kind) ?? "Subagent activity", string(item.agentPath)].filter((value) => value !== void 0).join(": ");
16361
+ return [string(record2(item.kind).type) ?? string(item.kind) ?? "Subagent activity", string(item.agentPath)].filter((value) => value !== void 0).join(": ");
16267
16362
  }
16268
- return text ?? itemText2(record(item.result)) ?? itemText2(record(item.output)) ?? string(item.status) ?? type;
16363
+ return text ?? itemText2(record2(item.result)) ?? itemText2(record2(item.output)) ?? string(item.status) ?? type;
16269
16364
  }
16270
16365
  function isSurfaceEvent(type) {
16271
16366
  return type === "user/message" || type === "assistant/message" || type === "tool/result";
@@ -16337,10 +16432,10 @@ function toolResultView(item, type, resultText) {
16337
16432
  };
16338
16433
  }
16339
16434
  function toolDisplayName(item, type) {
16340
- return string(item.name) ?? string(item.tool) ?? string(record(item.tool).name) ?? (type === "mcpToolCall" ? "CodeX MCP tool" : humanizeItemType(type));
16435
+ return string(item.name) ?? string(item.tool) ?? string(record2(item.tool).name) ?? (type === "mcpToolCall" ? "CodeX MCP tool" : humanizeItemType(type));
16341
16436
  }
16342
16437
  function fileLocations(item) {
16343
- return array(item.changes).map((value) => string(record(value).path)).filter((path) => path !== void 0 && path.length > 0).map((path) => ({ path }));
16438
+ return array(item.changes).map((value) => string(record2(value).path)).filter((path) => path !== void 0 && path.length > 0).map((path) => ({ path }));
16344
16439
  }
16345
16440
  function nativeWorkspace(view) {
16346
16441
  const { sessionCount: _sessionCount, ...workspace } = view;
@@ -16362,10 +16457,10 @@ function projectWorkspace(project) {
16362
16457
  };
16363
16458
  }
16364
16459
  function projectCodexProject(value, fallbackPosition) {
16365
- const source = record(value);
16460
+ const source = record2(value);
16366
16461
  const id2 = string(source.id);
16367
16462
  if (id2 === void 0) return void 0;
16368
- const roots = array(source.roots).map((root) => string(record(root).path)).filter((path) => path !== void 0 && path.length > 0).map((path) => ({ path }));
16463
+ const roots = array(source.roots).map((root) => string(record2(root).path)).filter((path) => path !== void 0 && path.length > 0).map((path) => ({ path }));
16369
16464
  if (roots.length === 0) return void 0;
16370
16465
  return {
16371
16466
  id: id2,
@@ -16436,7 +16531,7 @@ function isCanonicalBase642(value) {
16436
16531
  function modelSelectionProjection(selection) {
16437
16532
  return { lastUsed: selection, next: selection };
16438
16533
  }
16439
- function codexPermissionsProjection(currentValue) {
16534
+ function codexPermissionsProjection(preset) {
16440
16535
  return {
16441
16536
  options: [
16442
16537
  {
@@ -16450,7 +16545,7 @@ function codexPermissionsProjection(currentValue) {
16450
16545
  description: "Full Host file access without approval prompts for subsequent turns in this virtual Session."
16451
16546
  }
16452
16547
  ],
16453
- currentValue
16548
+ currentValue: preset ?? "Host settings (not reported)"
16454
16549
  };
16455
16550
  }
16456
16551
  function codexImageLimitsProjection() {
@@ -16466,22 +16561,6 @@ function codexImageLimitsProjection() {
16466
16561
  function isCodexPermissionPreset(value) {
16467
16562
  return value === "workspace-write" || value === "danger-full-access";
16468
16563
  }
16469
- function codexPermissionPresetFromResponse(value) {
16470
- const root = record(value);
16471
- return codexPermissionPresetFromPolicyRecord(root) ?? codexPermissionPresetFromPolicyRecord(record(root.threadSettings));
16472
- }
16473
- function codexPermissionPresetFromPolicyRecord(value) {
16474
- const sandbox = value.sandbox ?? value.sandboxPolicy;
16475
- if (value.approvalPolicy === "never" && isDangerFullAccessSandbox(sandbox)) return "danger-full-access";
16476
- if (value.approvalPolicy === "on-request" && isWorkspaceWriteSandbox(sandbox)) return "workspace-write";
16477
- return void 0;
16478
- }
16479
- function isDangerFullAccessSandbox(value) {
16480
- return value === "danger-full-access" || record(value).type === "dangerFullAccess";
16481
- }
16482
- function isWorkspaceWriteSandbox(value) {
16483
- return value === "workspace-write" || record(value).type === "workspaceWrite";
16484
- }
16485
16564
  function modelCatalog(directory) {
16486
16565
  return {
16487
16566
  default: directory.default,
@@ -16520,7 +16599,7 @@ function itemText2(value) {
16520
16599
  if (content !== void 0) {
16521
16600
  const text = content.map((part) => {
16522
16601
  if (typeof part === "string") return part;
16523
- const block = record(part);
16602
+ const block = record2(part);
16524
16603
  return string(block.text) ?? string(block.content) ?? "";
16525
16604
  }).filter(Boolean).join("\n");
16526
16605
  if (text !== "") return text;
@@ -16534,8 +16613,8 @@ function toolArguments(item) {
16534
16613
  if (item.type === "commandExecution") return { command: commandText(item.command) ?? item.command ?? "" };
16535
16614
  if (item.type === "fileChange") return {
16536
16615
  changes: array(item.changes).map((value) => {
16537
- const change = record(value);
16538
- const kind = typeof change.kind === "string" ? change.kind : string(record(change.kind).type);
16616
+ const change = record2(value);
16617
+ const kind = typeof change.kind === "string" ? change.kind : string(record2(change.kind).type);
16539
16618
  return {
16540
16619
  ...typeof change.path === "string" ? { path: change.path } : {},
16541
16620
  ...kind === void 0 ? {} : { kind }
@@ -16556,7 +16635,7 @@ function toolArguments(item) {
16556
16635
  if (item.type === "subAgentActivity") return {
16557
16636
  agentThreadId: string(item.agentThreadId) ?? "",
16558
16637
  agentPath: string(item.agentPath) ?? "",
16559
- kind: string(record(item.kind).type) ?? string(item.kind) ?? "activity"
16638
+ kind: string(record2(item.kind).type) ?? string(item.kind) ?? "activity"
16560
16639
  };
16561
16640
  if (item.type === "sleep") return { durationMs: integer(item.durationMs) ?? 0 };
16562
16641
  if (item.type === "enteredReviewMode" || item.type === "exitedReviewMode") return {
@@ -16567,19 +16646,19 @@ function toolArguments(item) {
16567
16646
  }
16568
16647
  function fileChangeSummary(item) {
16569
16648
  const changes = array(item.changes).map((value) => {
16570
- const change = record(value);
16649
+ const change = record2(value);
16571
16650
  const path = string(change.path);
16572
- const kind = typeof change.kind === "string" ? change.kind : string(record(change.kind).type);
16651
+ const kind = typeof change.kind === "string" ? change.kind : string(record2(change.kind).type);
16573
16652
  return [kind, path].filter((part) => part !== void 0 && part.length > 0).join(" ");
16574
16653
  }).filter(Boolean);
16575
16654
  return changes.length === 0 ? "File changes completed." : changes.join("\n");
16576
16655
  }
16577
16656
  function sanitizedFileChanges(value) {
16578
16657
  return array(value).flatMap((raw) => {
16579
- const change = record(raw);
16658
+ const change = record2(raw);
16580
16659
  const path = string(change.path);
16581
16660
  if (path === void 0) return [];
16582
- const kind = typeof change.kind === "string" ? change.kind : string(record(change.kind).type);
16661
+ const kind = typeof change.kind === "string" ? change.kind : string(record2(change.kind).type);
16583
16662
  return [{ path, ...kind === void 0 ? {} : { kind } }];
16584
16663
  });
16585
16664
  }
@@ -16609,7 +16688,7 @@ function humanizeItemType(type) {
16609
16688
  function commandText(value) {
16610
16689
  if (typeof value === "string") return value;
16611
16690
  if (Array.isArray(value)) return value.filter((item) => typeof item === "string").join(" ");
16612
- const source = record(value);
16691
+ const source = record2(value);
16613
16692
  return string(source.command) ?? string(source.text);
16614
16693
  }
16615
16694
  function sessionIdFromAddress(address) {
@@ -16625,7 +16704,7 @@ function nativeThreadId(sessionId) {
16625
16704
  function activeTurnId(value) {
16626
16705
  if (!Array.isArray(value)) return void 0;
16627
16706
  for (let index = value.length - 1; index >= 0; index -= 1) {
16628
- const turn = record(value[index]);
16707
+ const turn = record2(value[index]);
16629
16708
  if (isActiveCodexTurn(turn) && typeof turn.id === "string" && turn.id.length > 0) return turn.id;
16630
16709
  }
16631
16710
  return void 0;
@@ -16634,10 +16713,10 @@ function isActiveCodexTurn(turn) {
16634
16713
  return turn.status === "inProgress" || turn.status === "running";
16635
16714
  }
16636
16715
  function carrierArgs(payload) {
16637
- return record(record(payload).args);
16716
+ return record2(record2(payload).args);
16638
16717
  }
16639
16718
  function requestArg(args) {
16640
- return record(args.request ?? args._request ?? args);
16719
+ return record2(args.request ?? args._request ?? args);
16641
16720
  }
16642
16721
  function rcEndpoint(endpoint) {
16643
16722
  if (endpoint === "workspace.list") return "workspace/list";
@@ -16653,20 +16732,20 @@ function ok(value) {
16653
16732
  return value === void 0 ? { ok: true } : { ok: true, value };
16654
16733
  }
16655
16734
  function business(value) {
16656
- const result = record(value);
16735
+ const result = record2(value);
16657
16736
  if (result.ok === true) return ok(result.value);
16658
16737
  if (result.ok === false) {
16659
- const error = record(result.error);
16660
- return fail(string(error.code) ?? "internal", string(error.message) ?? "CodeX virtual Harness rejected the request.", record(error.details));
16738
+ const error = record2(result.error);
16739
+ return fail(string(error.code) ?? "internal", string(error.message) ?? "CodeX virtual Harness rejected the request.", record2(error.details));
16661
16740
  }
16662
16741
  return ok(value);
16663
16742
  }
16664
16743
  function isCodexDirectoryListing(value) {
16665
- const listing = record(value);
16744
+ const listing = record2(value);
16666
16745
  return typeof listing.path === "string" && typeof listing.home === "string" && Array.isArray(listing.crumbs) && listing.crumbs.every(isCodexDirectoryEntry) && Array.isArray(listing.entries) && listing.entries.every(isCodexDirectoryEntry) && typeof listing.truncated === "boolean";
16667
16746
  }
16668
16747
  function isCodexDirectoryEntry(value) {
16669
- const entry = record(value);
16748
+ const entry = record2(value);
16670
16749
  return typeof entry.name === "string" && typeof entry.path === "string" && typeof entry.hidden === "boolean";
16671
16750
  }
16672
16751
  function fail(code, message, details = {}) {
@@ -16686,7 +16765,7 @@ function errorCode(error) {
16686
16765
  function errorDetails(error) {
16687
16766
  return "details" in error && isRecord3(error.details) ? error.details : {};
16688
16767
  }
16689
- function record(value) {
16768
+ function record2(value) {
16690
16769
  return isRecord3(value) ? value : {};
16691
16770
  }
16692
16771
  function isRecord3(value) {
@@ -16721,8 +16800,8 @@ function optionalPositiveInteger(value) {
16721
16800
  return parsed;
16722
16801
  }
16723
16802
  function isCodexNativeHistoryPage(value, sessionId) {
16724
- const page = record(value);
16725
- const header = record(page.header);
16803
+ const page = record2(value);
16804
+ const header = record2(page.header);
16726
16805
  return header.id === sessionId && integer(page.cursor) !== void 0 && integer(page.nextTurn) !== void 0 && Array.isArray(page.records) && typeof page.hasMore === "boolean" && (page.activeTurnId === void 0 || typeof page.activeTurnId === "string");
16727
16806
  }
16728
16807
  function truncateCodePoints(value, maximum) {
@@ -16903,7 +16982,7 @@ function normalizeServerUrl(value) {
16903
16982
  }
16904
16983
 
16905
16984
  // src/version.ts
16906
- var PLUGIN_VERSION = "0.4.8";
16985
+ var PLUGIN_VERSION = "0.4.10";
16907
16986
 
16908
16987
  // src/server-api.ts
16909
16988
  var TERMINAL_CONTROL_CHARACTERS = /[\u0000-\u001f\u007f-\u009f]/u;
@@ -17218,10 +17297,11 @@ var ServerApiError = class extends Error {
17218
17297
  }
17219
17298
  };
17220
17299
  function validateTokens(value) {
17221
- if (typeof value.accessToken !== "string" || value.accessToken.length < 16 || typeof value.refreshToken !== "string" || value.refreshToken.length < 16 || !Number.isSafeInteger(value.accessTokenExpiresAt) || !Number.isSafeInteger(value.refreshTokenExpiresAt)) {
17300
+ const parsed = deviceTokenPairSchema.safeParse(value);
17301
+ if (!parsed.success) {
17222
17302
  throw new ServerApiError("INVALID_MESSAGE", "The Server returned invalid device credentials.", false);
17223
17303
  }
17224
- return value;
17304
+ return parsed.data;
17225
17305
  }
17226
17306
  function validateWebLogin(value) {
17227
17307
  const item = requireRecord(value, "account login");
@@ -18516,6 +18596,7 @@ function isPrivate(ip) {
18516
18596
  var REMOTE_COMMAND_LIST_MIN_VERSION = [0, 3, 16];
18517
18597
  var REMOTE_FILE_VIEWER_MIN_VERSION = [0, 3, 17];
18518
18598
  var DIRECT_WEBRTC_NEGOTIATE_TIMEOUT_MS = 12e3;
18599
+ var DIRECT_LAN_PROGRESS_DISPLAY_MS = 1400;
18519
18600
  var ClientModeRuntime = class {
18520
18601
  constructor(config, identities, server, apiProxy, typertGateway, logger, host, rtcFactoryProvider = loadNodeRtcFactory) {
18521
18602
  this.config = config;
@@ -18534,6 +18615,8 @@ var ClientModeRuntime = class {
18534
18615
  proxySwitch;
18535
18616
  gatewaySwitch;
18536
18617
  codexStreams = /* @__PURE__ */ new Map();
18618
+ connectionProgress;
18619
+ connectionProgressRun = 0;
18537
18620
  closed = false;
18538
18621
  async start() {
18539
18622
  if (this.closed) throw new Error("client remote-mode runtime is closed");
@@ -18562,7 +18645,15 @@ var ClientModeRuntime = class {
18562
18645
  ...targetStatus,
18563
18646
  connected: this.connected !== void 0,
18564
18647
  transport: this.connected?.client.getStats().mode ?? "Disconnected",
18648
+ connectedTargetDeviceId: this.connected?.target.deviceId,
18565
18649
  preferredTransports: this.config.forceRelay ? ["relay"] : ["lan", "p2p", "turn", "relay"],
18650
+ ...this.connectionProgress === void 0 ? {} : {
18651
+ connectionProgress: {
18652
+ targetDeviceId: this.connectionProgress.targetDeviceId,
18653
+ phase: this.connectionProgress.phase,
18654
+ ...this.connectionProgress.activeTransports === void 0 ? {} : { activeTransports: [...this.connectionProgress.activeTransports] }
18655
+ }
18656
+ },
18566
18657
  remoteFeatures: this.connected?.features ?? remoteHostFeatures(),
18567
18658
  ...this.pendingWorkspaceSelection === void 0 ? {} : { workspaceSelection: { ...this.pendingWorkspaceSelection } },
18568
18659
  backend: this.codexVirtual === void 0 ? "harness" : "codex",
@@ -18631,6 +18722,7 @@ var ClientModeRuntime = class {
18631
18722
  async clearClientAuthorization() {
18632
18723
  const previous = this.connected;
18633
18724
  this.connected = void 0;
18725
+ this.connectionProgress = void 0;
18634
18726
  this.pendingWorkspaceSelection = void 0;
18635
18727
  await this.closeCodexVirtual();
18636
18728
  this.proxySwitch?.selectLocal();
@@ -18658,6 +18750,7 @@ var ClientModeRuntime = class {
18658
18750
  this.gatewaySwitch.selectLocal();
18659
18751
  const previous2 = this.connected;
18660
18752
  this.connected = void 0;
18753
+ this.connectionProgress = void 0;
18661
18754
  this.pendingWorkspaceSelection = void 0;
18662
18755
  await this.closeCodexStreams(previous2?.client);
18663
18756
  await previous2?.client.close().catch(() => void 0);
@@ -18671,11 +18764,13 @@ var ClientModeRuntime = class {
18671
18764
  try {
18672
18765
  this.assertRemoteCompatible(next);
18673
18766
  } catch (error) {
18767
+ this.clearConnectionProgress(next.progressRunId);
18674
18768
  await next.client.close().catch(() => void 0);
18675
18769
  throw error;
18676
18770
  }
18677
18771
  const previous = this.connected;
18678
18772
  this.connected = next;
18773
+ this.clearConnectionProgress(next.progressRunId);
18679
18774
  this.pendingWorkspaceSelection = void 0;
18680
18775
  await this.closeCodexVirtual();
18681
18776
  this.selectRemoteTarget(next);
@@ -18790,12 +18885,12 @@ var ClientModeRuntime = class {
18790
18885
  throw new ClientModeError("FEATURE_NOT_SUPPORTED", "The selected Host does not provide CodeX workspaces.");
18791
18886
  }
18792
18887
  this.assertRemoteCompatible(remote);
18793
- const result = record2(await new CodexRemoteClient(remote.client).request("project/create", {
18888
+ const result = record3(await new CodexRemoteClient(remote.client).request("project/create", {
18794
18889
  name: remoteWorkspaceTitle(trimmedPath),
18795
18890
  roots: [{ path: trimmedPath }],
18796
18891
  idempotencyKey: uuidV7()
18797
18892
  }, signal));
18798
- const project = record2(result.project);
18893
+ const project = record3(result.project);
18799
18894
  if (typeof project.id !== "string" || project.id.length === 0) {
18800
18895
  throw new ClientModeError("INVALID_MESSAGE", "The Host returned an invalid CodeX project.");
18801
18896
  }
@@ -18818,6 +18913,7 @@ var ClientModeRuntime = class {
18818
18913
  await this.closeCodexStreams(this.connected?.client);
18819
18914
  await this.connected?.client.close().catch(() => void 0);
18820
18915
  this.connected = void 0;
18916
+ this.connectionProgress = void 0;
18821
18917
  this.proxySwitch?.restore();
18822
18918
  this.gatewaySwitch.restore();
18823
18919
  }
@@ -18844,7 +18940,7 @@ var ClientModeRuntime = class {
18844
18940
  }
18845
18941
  async openCodexStream(payload, signal) {
18846
18942
  const remote = this.activeCodexRemote();
18847
- const value = record2(payload);
18943
+ const value = record3(payload);
18848
18944
  if (typeof value.streamId !== "string" || value.streamId.length === 0 || value.streamId.length > 128 || typeof value.threadId !== "string" || value.threadId.length === 0) {
18849
18945
  throw new ClientModeError("INVALID_MESSAGE", "A Codex stream and thread are required.");
18850
18946
  }
@@ -18929,7 +19025,7 @@ var ClientModeRuntime = class {
18929
19025
  };
18930
19026
  }
18931
19027
  async nextCodexFrames(payload, signal) {
18932
- const value = record2(payload);
19028
+ const value = record3(payload);
18933
19029
  if (typeof value.streamId !== "string") throw new ClientModeError("INVALID_MESSAGE", "A Codex stream is required.");
18934
19030
  const stream = this.codexStreams.get(value.streamId);
18935
19031
  if (stream === void 0) throw new ClientModeError("STREAM_NOT_FOUND", "The Codex stream is not open.");
@@ -18943,7 +19039,7 @@ var ClientModeRuntime = class {
18943
19039
  };
18944
19040
  }
18945
19041
  async closeCodexStream(payload) {
18946
- const value = record2(payload);
19042
+ const value = record3(payload);
18947
19043
  if (typeof value.streamId !== "string") throw new ClientModeError("INVALID_MESSAGE", "A Codex stream is required.");
18948
19044
  const stream = this.codexStreams.get(value.streamId);
18949
19045
  if (stream === void 0) return { closed: false, streamId: value.streamId };
@@ -18999,61 +19095,70 @@ var ClientModeRuntime = class {
18999
19095
  }
19000
19096
  async connect(targetDeviceId, signal) {
19001
19097
  signal?.throwIfAborted();
19098
+ const progressRunId = this.connectionProgressRun + 1;
19099
+ this.connectionProgressRun = progressRunId;
19100
+ this.connectionProgress = { runId: progressRunId, targetDeviceId, phase: "checking-host" };
19002
19101
  const identity = this.requireIdentity();
19003
- const serverDevice = (await this.server.listDevices()).find((device) => device.deviceId === targetDeviceId);
19004
- if (serverDevice === void 0) {
19005
- throw new ClientModeError("MEMBERSHIP_REQUIRED", "The selected Host is not authorized for this account.");
19006
- }
19007
- const target = await this.authorizeHostPeer(serverDevice);
19008
- const presence = await this.server.presenceFor(targetDeviceId);
19009
- if (!presence.online) throw new ClientModeError("HOST_OFFLINE", "The selected Host is offline.", true);
19010
- const credentials = await this.server.authenticate(identity);
19011
- const rtcFactory = this.config.forceRelay ? void 0 : await this.rtcFactoryProvider({ routeTargets: [this.server.baseUrl] }).catch(() => void 0);
19012
- if (!this.config.forceRelay && rtcFactory === void 0) {
19013
- this.logger.warn("remote Harness WebRTC backend unavailable; using relay", {
19014
- targetDeviceId: shortId(target.deviceId)
19015
- });
19016
- }
19017
- let webRtcFallback = false;
19018
- const createTransport = (attempt) => new AdaptiveTransport(
19019
- websocketUrl(this.server.baseUrl),
19020
- {
19021
- role: "client",
19022
- deviceId: identity.deviceId,
19023
- accessToken: credentials.accessToken,
19024
- targetDeviceId,
19025
- forceRelay: this.config.forceRelay || attempt === "relay",
19026
- preferredTransports: preferredTransportsForAttempt(attempt),
19027
- negotiateTimeoutMs: attempt === "direct" ? DIRECT_WEBRTC_NEGOTIATE_TIMEOUT_MS : void 0,
19028
- ...rtcFactory === void 0 || attempt === "relay" ? {} : { rtcFactory },
19029
- fetchIceServers: async (connectionId) => iceServersForAttempt(attempt, await this.server.turnCredentials(connectionId)),
19030
- onWebRtcFallback: (error, diagnostics) => {
19031
- webRtcFallback = true;
19032
- this.logger.warn(attempt === "direct" ? "remote Harness direct WebRTC failed; trying TURN" : "remote Harness TURN WebRTC failed; using relay", {
19033
- targetDeviceId: shortId(target.deviceId),
19034
- attempt,
19035
- reason: diagnosticReason(error)
19036
- });
19037
- if (diagnostics !== void 0) {
19038
- this.logger.debug("remote Harness WebRTC fallback diagnostics", {
19102
+ let client;
19103
+ try {
19104
+ const serverDevice = (await this.server.listDevices()).find((device) => device.deviceId === targetDeviceId);
19105
+ if (serverDevice === void 0) {
19106
+ throw new ClientModeError("MEMBERSHIP_REQUIRED", "The selected Host is not authorized for this account.");
19107
+ }
19108
+ this.updateConnectionProgress(progressRunId, "authorizing-peer");
19109
+ const target = await this.authorizeHostPeer(serverDevice);
19110
+ const presence = await this.server.presenceFor(targetDeviceId);
19111
+ if (!presence.online) throw new ClientModeError("HOST_OFFLINE", "The selected Host is offline.", true);
19112
+ const credentials = await this.server.authenticate(identity);
19113
+ const rtcFactory = this.config.forceRelay ? void 0 : await this.rtcFactoryProvider({ routeTargets: [this.server.baseUrl] }).catch(() => void 0);
19114
+ if (!this.config.forceRelay && rtcFactory === void 0) {
19115
+ this.logger.warn("remote Harness WebRTC backend unavailable; using relay", {
19116
+ targetDeviceId: shortId(target.deviceId)
19117
+ });
19118
+ }
19119
+ let webRtcFallback = false;
19120
+ const createTransport = (attempt) => new AdaptiveTransport(
19121
+ websocketUrl(this.server.baseUrl),
19122
+ {
19123
+ role: "client",
19124
+ deviceId: identity.deviceId,
19125
+ accessToken: credentials.accessToken,
19126
+ targetDeviceId,
19127
+ forceRelay: this.config.forceRelay || attempt === "relay",
19128
+ preferredTransports: preferredTransportsForAttempt(attempt),
19129
+ negotiateTimeoutMs: attempt === "direct" ? DIRECT_WEBRTC_NEGOTIATE_TIMEOUT_MS : void 0,
19130
+ ...rtcFactory === void 0 || attempt === "relay" ? {} : { rtcFactory },
19131
+ fetchIceServers: async (connectionId) => iceServersForAttempt(attempt, await this.server.turnCredentials(connectionId)),
19132
+ onWebRtcFallback: (error, diagnostics) => {
19133
+ webRtcFallback = true;
19134
+ this.logger.warn(attempt === "direct" ? "remote Harness direct WebRTC failed; trying TURN" : "remote Harness TURN WebRTC failed; using relay", {
19039
19135
  targetDeviceId: shortId(target.deviceId),
19040
19136
  attempt,
19041
- ...webrtcDiagnosticsLogFields(diagnostics)
19137
+ reason: diagnosticReason(error)
19042
19138
  });
19139
+ if (diagnostics !== void 0) {
19140
+ this.logger.debug("remote Harness WebRTC fallback diagnostics", {
19141
+ targetDeviceId: shortId(target.deviceId),
19142
+ attempt,
19143
+ ...webrtcDiagnosticsLogFields(diagnostics)
19144
+ });
19145
+ }
19043
19146
  }
19044
19147
  }
19045
- }
19046
- );
19047
- const attempts = this.config.forceRelay || rtcFactory === void 0 ? ["relay"] : ["direct", "turn", "relay"];
19048
- let transport;
19049
- let client;
19050
- try {
19148
+ );
19149
+ const attempts = this.config.forceRelay || rtcFactory === void 0 ? ["relay"] : ["direct", "turn", "relay"];
19150
+ let transport;
19051
19151
  for (const attempt of attempts) {
19052
19152
  webRtcFallback = false;
19053
- transport = createTransport(attempt);
19054
- client = new RemoteClientCore(new ClientSecureTransport(transport, identity, target), 6e4);
19055
- await client.connect();
19056
- signal?.throwIfAborted();
19153
+ const stopProgressTimer = this.beginAttemptProgress(progressRunId, attempt);
19154
+ try {
19155
+ transport = createTransport(attempt);
19156
+ client = new RemoteClientCore(new ClientSecureTransport(transport, identity, target), 6e4);
19157
+ await client.connect();
19158
+ signal?.throwIfAborted();
19159
+ } finally {
19160
+ stopProgressTimer();
19161
+ }
19057
19162
  if (attempt === "relay" || !webRtcFallback) break;
19058
19163
  await client.close();
19059
19164
  client = void 0;
@@ -19069,9 +19174,16 @@ var ClientModeRuntime = class {
19069
19174
  }
19070
19175
  const connectedClient = client;
19071
19176
  const connectedTransport = transport;
19177
+ const connectedPreference = transportPreferenceForMode(connectedClient.getStats().mode);
19178
+ this.updateConnectionProgress(
19179
+ progressRunId,
19180
+ "connected",
19181
+ connectedPreference === void 0 ? void 0 : [connectedPreference]
19182
+ );
19072
19183
  connectedClient.onClose(() => {
19073
19184
  if (this.connected?.client !== connectedClient) return;
19074
19185
  this.connected = void 0;
19186
+ this.connectionProgress = void 0;
19075
19187
  this.pendingWorkspaceSelection = void 0;
19076
19188
  void this.closeCodexVirtual();
19077
19189
  this.proxySwitch?.selectLocal();
@@ -19103,9 +19215,11 @@ var ClientModeRuntime = class {
19103
19215
  target,
19104
19216
  transport: connectedTransport,
19105
19217
  features,
19218
+ progressRunId,
19106
19219
  ...serverDevice.clientVersion === void 0 ? {} : { clientVersion: serverDevice.clientVersion }
19107
19220
  };
19108
19221
  } catch (error) {
19222
+ this.clearConnectionProgress(progressRunId);
19109
19223
  await client?.close().catch(() => void 0);
19110
19224
  throw error;
19111
19225
  }
@@ -19115,22 +19229,48 @@ var ClientModeRuntime = class {
19115
19229
  const next = await this.connect(targetDeviceId, signal);
19116
19230
  const previous = this.connected;
19117
19231
  this.connected = next;
19232
+ this.clearConnectionProgress(next.progressRunId);
19118
19233
  await previous?.client.close().catch(() => void 0);
19119
19234
  return next;
19120
19235
  }
19236
+ beginAttemptProgress(runId, attempt) {
19237
+ if (attempt !== "direct") {
19238
+ this.updateConnectionProgress(runId, "probing", [attempt]);
19239
+ return () => void 0;
19240
+ }
19241
+ this.updateConnectionProgress(runId, "probing", ["lan"]);
19242
+ const timer = setTimeout(() => {
19243
+ if (this.connectionProgress?.runId === runId && this.connectionProgress.phase === "probing") {
19244
+ this.updateConnectionProgress(runId, "probing", ["p2p"]);
19245
+ }
19246
+ }, DIRECT_LAN_PROGRESS_DISPLAY_MS);
19247
+ return () => clearTimeout(timer);
19248
+ }
19249
+ updateConnectionProgress(runId, phase, activeTransports) {
19250
+ if (this.connectionProgress?.runId !== runId) return;
19251
+ this.connectionProgress = {
19252
+ runId,
19253
+ targetDeviceId: this.connectionProgress.targetDeviceId,
19254
+ phase,
19255
+ ...activeTransports === void 0 ? {} : { activeTransports }
19256
+ };
19257
+ }
19258
+ clearConnectionProgress(runId) {
19259
+ if (this.connectionProgress?.runId === runId) this.connectionProgress = void 0;
19260
+ }
19121
19261
  async handleControl(endpoint, payload, signal) {
19122
19262
  try {
19123
19263
  if (endpoint === "status") return ok2(await this.detailedStatus());
19124
19264
  if (endpoint === "devices") return ok2(await this.devices());
19125
19265
  if (endpoint === "client.account.login") {
19126
- const value = record2(payload);
19266
+ const value = record3(payload);
19127
19267
  if (typeof value.email !== "string" || typeof value.password !== "string") {
19128
19268
  throw new ClientModeError("INVALID_MESSAGE", "Email and password are required.");
19129
19269
  }
19130
19270
  return ok2(await this.authorizeClientWithAccount(value.email, value.password));
19131
19271
  }
19132
19272
  if (endpoint === "client.account.qr.start") {
19133
- const value = record2(payload);
19273
+ const value = record3(payload);
19134
19274
  const provider = value.provider ?? "zhihu";
19135
19275
  if (provider !== "zhihu" && provider !== "github") {
19136
19276
  throw new ClientModeError("INVALID_MESSAGE", "A supported OAuth provider is required.");
@@ -19138,14 +19278,14 @@ var ClientModeRuntime = class {
19138
19278
  return ok2(await this.startClientOAuthQrLogin(provider));
19139
19279
  }
19140
19280
  if (endpoint === "client.account.qr.poll") {
19141
- const value = record2(payload);
19281
+ const value = record3(payload);
19142
19282
  if (typeof value.qrId !== "string" || value.qrId.length < 20) {
19143
19283
  throw new ClientModeError("INVALID_MESSAGE", "A QR login session is required.");
19144
19284
  }
19145
19285
  return ok2(await this.pollClientOAuthQrLogin(value.qrId));
19146
19286
  }
19147
19287
  if (endpoint === "directory.list") {
19148
- const value = record2(payload);
19288
+ const value = record3(payload);
19149
19289
  if (typeof value.targetDeviceId !== "string") throw new ClientModeError("INVALID_MESSAGE", "A Host is required.");
19150
19290
  return ok2(await this.listRemoteDirectory(
19151
19291
  value.targetDeviceId,
@@ -19154,38 +19294,38 @@ var ClientModeRuntime = class {
19154
19294
  ));
19155
19295
  }
19156
19296
  if (endpoint === "workspaces.list") {
19157
- const value = record2(payload);
19297
+ const value = record3(payload);
19158
19298
  if (typeof value.targetDeviceId !== "string") throw new ClientModeError("INVALID_MESSAGE", "A Host is required.");
19159
19299
  return ok2(await this.listRemoteWorkspaces(value.targetDeviceId, signal));
19160
19300
  }
19161
19301
  if (endpoint === "codex.workspaces.list") {
19162
- const value = record2(payload);
19302
+ const value = record3(payload);
19163
19303
  if (typeof value.targetDeviceId !== "string") throw new ClientModeError("INVALID_MESSAGE", "A Host is required.");
19164
19304
  return ok2(await this.listCodexWorkspaces(value.targetDeviceId, signal));
19165
19305
  }
19166
19306
  if (endpoint === "workspace.open") {
19167
- const value = record2(payload);
19307
+ const value = record3(payload);
19168
19308
  if (typeof value.targetDeviceId !== "string" || typeof value.path !== "string") {
19169
19309
  throw new ClientModeError("INVALID_MESSAGE", "A Host and working directory are required.");
19170
19310
  }
19171
19311
  return ok2(await this.openRemoteWorkspace(value.targetDeviceId, value.path, signal));
19172
19312
  }
19173
19313
  if (endpoint === "codex.workspace.open") {
19174
- const value = record2(payload);
19314
+ const value = record3(payload);
19175
19315
  if (typeof value.targetDeviceId !== "string" || typeof value.workspaceId !== "string") {
19176
19316
  throw new ClientModeError("INVALID_MESSAGE", "A Host and CodeX Workspace are required.");
19177
19317
  }
19178
19318
  return ok2(await this.openCodexWorkspace(value.targetDeviceId, value.workspaceId, signal));
19179
19319
  }
19180
19320
  if (endpoint === "codex.workspace.create") {
19181
- const value = record2(payload);
19321
+ const value = record3(payload);
19182
19322
  if (typeof value.targetDeviceId !== "string" || typeof value.path !== "string") {
19183
19323
  throw new ClientModeError("INVALID_MESSAGE", "A Host and CodeX project directory are required.");
19184
19324
  }
19185
19325
  return ok2(await this.createCodexWorkspace(value.targetDeviceId, value.path, signal));
19186
19326
  }
19187
19327
  if (endpoint === "workspace.selection.consume") {
19188
- const value = record2(payload);
19328
+ const value = record3(payload);
19189
19329
  if (typeof value.targetDeviceId !== "string" || typeof value.workspaceId !== "string") {
19190
19330
  throw new ClientModeError("INVALID_MESSAGE", "A Host and Workspace are required.");
19191
19331
  }
@@ -19201,7 +19341,7 @@ var ClientModeRuntime = class {
19201
19341
  return ok2(await this.callRemoteFileViewer(method, payload, signal));
19202
19342
  }
19203
19343
  if (endpoint === "codex.call") {
19204
- const value = record2(payload);
19344
+ const value = record3(payload);
19205
19345
  if (typeof value.method !== "string" || !("params" in value)) {
19206
19346
  throw new ClientModeError("INVALID_MESSAGE", "A Codex method and params are required.");
19207
19347
  }
@@ -19225,7 +19365,7 @@ var ClientModeRuntime = class {
19225
19365
  return ok2({ supported: local || remoteSupported, local, remote: remoteSupported });
19226
19366
  }
19227
19367
  if (endpoint === "codex.respond") {
19228
- const value = record2(payload);
19368
+ const value = record3(payload);
19229
19369
  const remote = this.activeCodexRemote();
19230
19370
  if (remote !== void 0) return ok2(await remote.client.rpc("codex.app.respond", value, signal));
19231
19371
  const host = this.requireLocalCodex();
@@ -19236,14 +19376,14 @@ var ClientModeRuntime = class {
19236
19376
  if (endpoint === "codex.stream.close") return ok2(await this.closeCodexStream(payload));
19237
19377
  if (endpoint === "host.account.login") {
19238
19378
  if (this.host === void 0) throw new ClientModeError("METHOD_NOT_ALLOWED", "This plugin is not running as a Host.");
19239
- const value = record2(payload);
19379
+ const value = record3(payload);
19240
19380
  if (typeof value.email !== "string" || typeof value.password !== "string") {
19241
19381
  throw new ClientModeError("INVALID_MESSAGE", "Email and password are required.");
19242
19382
  }
19243
19383
  return ok2(await this.host.authorizeHostWithAccount(value.email, value.password));
19244
19384
  }
19245
19385
  if (endpoint === "host.authorization.set") {
19246
- const value = record2(payload);
19386
+ const value = record3(payload);
19247
19387
  if (typeof value.enabled !== "boolean") {
19248
19388
  throw new ClientModeError("INVALID_MESSAGE", "Host authorization state is required.");
19249
19389
  }
@@ -19251,14 +19391,14 @@ var ClientModeRuntime = class {
19251
19391
  }
19252
19392
  if (endpoint === "host.registration-code.submit") {
19253
19393
  if (this.host === void 0) throw new ClientModeError("METHOD_NOT_ALLOWED", "This plugin is not running as a Host.");
19254
- const value = record2(payload);
19394
+ const value = record3(payload);
19255
19395
  if (typeof value.code !== "string" || value.code.trim() === "") {
19256
19396
  throw new ClientModeError("INVALID_MESSAGE", "A Host registration code is required.");
19257
19397
  }
19258
19398
  return ok2(await this.host.authorizeHostWithCode(value.code));
19259
19399
  }
19260
19400
  if (endpoint === "mode.set") {
19261
- const value = record2(payload);
19401
+ const value = record3(payload);
19262
19402
  if (value.mode !== "local" && value.mode !== "remote") throw new ClientModeError("INVALID_MESSAGE", "Mode must be local or remote.");
19263
19403
  return ok2(await this.setMode(value.mode, typeof value.targetDeviceId === "string" ? value.targetDeviceId : void 0, signal));
19264
19404
  }
@@ -19322,7 +19462,7 @@ function webrtcDiagnosticsLogFields(diagnostics) {
19322
19462
  ...diagnostics.selectedPath === void 0 ? {} : { rtcSelectedPath: diagnostics.selectedPath }
19323
19463
  };
19324
19464
  }
19325
- function record2(value) {
19465
+ function record3(value) {
19326
19466
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
19327
19467
  throw new ClientModeError("INVALID_MESSAGE", "The control request payload is invalid.");
19328
19468
  }
@@ -19486,6 +19626,13 @@ function preferredTransportsForAttempt(attempt) {
19486
19626
  if (attempt === "turn") return ["turn", "relay"];
19487
19627
  return ["relay"];
19488
19628
  }
19629
+ function transportPreferenceForMode(mode) {
19630
+ if (mode === "LAN") return "lan";
19631
+ if (mode === "P2P") return "p2p";
19632
+ if (mode === "TURN") return "turn";
19633
+ if (mode === "Relay") return "relay";
19634
+ return void 0;
19635
+ }
19489
19636
  function iceServersForAttempt(attempt, iceServers) {
19490
19637
  return attempt === "direct" ? stunOnlyIceServers(iceServers) : iceServers;
19491
19638
  }
@@ -19540,24 +19687,24 @@ var IdentityStore = class {
19540
19687
  }
19541
19688
  if (!hasDevice) {
19542
19689
  const keys = generateKeyPair();
19543
- const record5 = { schemaVersion: 1, deviceId: uuidV7(), name: deviceName, publicKey: keys.publicKey };
19544
- await atomicJsonWrite(devicePath, record5, 384);
19690
+ const record6 = { schemaVersion: 1, deviceId: uuidV7(), name: deviceName, publicKey: keys.publicKey };
19691
+ await atomicJsonWrite(devicePath, record6, 384);
19545
19692
  await atomicTextWrite(keyPath, `${keys.privateKey}
19546
19693
  `, 384);
19547
19694
  }
19548
19695
  await assertPrivateMode(keyPath);
19549
19696
  try {
19550
- let record5 = identitySchema.parse(JSON.parse(await readFile(devicePath, "utf8")));
19697
+ let record6 = identitySchema.parse(JSON.parse(await readFile(devicePath, "utf8")));
19551
19698
  const privateKey = (await readFile(keyPath, "utf8")).trim();
19552
19699
  const regenerated = generateKeyPair(fromBase64Url2(privateKey));
19553
- if (regenerated.publicKey !== record5.publicKey) {
19700
+ if (regenerated.publicKey !== record6.publicKey) {
19554
19701
  throw new IdentityInvalidError("device public and private keys do not match");
19555
19702
  }
19556
- if (record5.name !== deviceName) {
19557
- record5 = { ...record5, name: deviceName };
19558
- await atomicJsonWrite(devicePath, record5, 384);
19703
+ if (record6.name !== deviceName) {
19704
+ record6 = { ...record6, name: deviceName };
19705
+ await atomicJsonWrite(devicePath, record6, 384);
19559
19706
  }
19560
- this.identity = { ...record5, privateKey, fingerprint: fingerprint(record5.publicKey) };
19707
+ this.identity = { ...record6, privateKey, fingerprint: fingerprint(record6.publicKey) };
19561
19708
  await this.loadPeers();
19562
19709
  return this.identity;
19563
19710
  } catch (error) {
@@ -19699,10 +19846,10 @@ var ServerCredentialStore = class {
19699
19846
  return parsed.serverUrl === serverUrl && parsed.deviceId === deviceId ? parsed : void 0;
19700
19847
  }
19701
19848
  async save(credentials) {
19702
- const record5 = credentialSchema.parse({ schemaVersion: 1, ...credentials });
19703
- await atomicWrite(this.path, `${JSON.stringify(record5, null, 2)}
19849
+ const record6 = credentialSchema.parse({ schemaVersion: 1, ...credentials });
19850
+ await atomicWrite(this.path, `${JSON.stringify(record6, null, 2)}
19704
19851
  `);
19705
- return record5;
19852
+ return record6;
19706
19853
  }
19707
19854
  async clear() {
19708
19855
  await rm2(this.path, { force: true });
@@ -19769,7 +19916,7 @@ var PluginControlRuntime = class {
19769
19916
  if (endpoint === "devices") return ok3([]);
19770
19917
  if (endpoint === "host.account.login") {
19771
19918
  if (this.host === void 0) throw new ClientModeError("METHOD_NOT_ALLOWED", "This plugin is not running as a Host.");
19772
- const value = record3(payload);
19919
+ const value = record4(payload);
19773
19920
  if (typeof value.email !== "string" || typeof value.password !== "string") {
19774
19921
  throw new ClientModeError("INVALID_MESSAGE", "Email and password are required.");
19775
19922
  }
@@ -19777,13 +19924,13 @@ var PluginControlRuntime = class {
19777
19924
  }
19778
19925
  if (endpoint === "host.registration-code.submit") {
19779
19926
  if (this.host === void 0) throw new ClientModeError("METHOD_NOT_ALLOWED", "This plugin is not running as a Host.");
19780
- const value = record3(payload);
19927
+ const value = record4(payload);
19781
19928
  if (typeof value.code !== "string" || value.code.trim() === "") {
19782
19929
  throw new ClientModeError("INVALID_MESSAGE", "A Host registration code is required.");
19783
19930
  }
19784
19931
  return ok3(await this.host.authorizeHostWithCode(value.code));
19785
19932
  }
19786
- if (endpoint === "mode.set" && record3(payload).mode === "local") return ok3(this.hostOnlyStatus());
19933
+ if (endpoint === "mode.set" && record4(payload).mode === "local") return ok3(this.hostOnlyStatus());
19787
19934
  throw new ClientModeError("METHOD_NOT_ALLOWED", "Remote Client mode is disabled by the plugin role.");
19788
19935
  } catch (error) {
19789
19936
  return fail3(error);
@@ -19793,7 +19940,7 @@ var PluginControlRuntime = class {
19793
19940
  if (this.settings === void 0) {
19794
19941
  throw new ClientModeError("SETTINGS_UNAVAILABLE", "DSH user settings are unavailable in this profile.");
19795
19942
  }
19796
- const value = record3(payload);
19943
+ const value = record4(payload);
19797
19944
  if (value.role !== "host" && value.role !== "client") {
19798
19945
  throw new ClientModeError("INVALID_MESSAGE", "Role must be Host or Client.");
19799
19946
  }
@@ -19828,7 +19975,7 @@ var PluginControlRuntime = class {
19828
19975
  if (this.settings === void 0) {
19829
19976
  throw new ClientModeError("SETTINGS_UNAVAILABLE", "DSH user settings are unavailable in this profile.");
19830
19977
  }
19831
- const value = record3(payload);
19978
+ const value = record4(payload);
19832
19979
  if (typeof value.serverUrl !== "string") {
19833
19980
  throw new ClientModeError("INVALID_MESSAGE", "Server URL is required.");
19834
19981
  }
@@ -19841,7 +19988,7 @@ var PluginControlRuntime = class {
19841
19988
  if (this.settings === void 0) {
19842
19989
  throw new ClientModeError("SETTINGS_UNAVAILABLE", "DSH user settings are unavailable in this profile.");
19843
19990
  }
19844
- const role = record3(payload).role;
19991
+ const role = record4(payload).role;
19845
19992
  if (role !== "host" && role !== "client") {
19846
19993
  throw new ClientModeError("INVALID_MESSAGE", "Role must be Host or Client.");
19847
19994
  }
@@ -19857,7 +20004,7 @@ var PluginControlRuntime = class {
19857
20004
  if (this.settings === void 0) {
19858
20005
  throw new ClientModeError("SETTINGS_UNAVAILABLE", "DSH user settings are unavailable in this profile.");
19859
20006
  }
19860
- const enabled = record3(payload).enabled;
20007
+ const enabled = record4(payload).enabled;
19861
20008
  if (typeof enabled !== "boolean") {
19862
20009
  throw new ClientModeError("INVALID_MESSAGE", "Codex Remote enabled must be a boolean.");
19863
20010
  }
@@ -19966,7 +20113,7 @@ function editableConfig(config) {
19966
20113
  function isRecord6(value) {
19967
20114
  return typeof value === "object" && value !== null && !Array.isArray(value);
19968
20115
  }
19969
- function record3(value) {
20116
+ function record4(value) {
19970
20117
  if (!isRecord6(value)) throw new ClientModeError("INVALID_MESSAGE", "The control request payload is invalid.");
19971
20118
  return value;
19972
20119
  }
@@ -22573,11 +22720,11 @@ function needsDirectoryFallback(result) {
22573
22720
  return code.includes("capability") || code === "directory-picker/unavailable" || code === "directory-picker-unavailable" || message.includes("browse capability") || message.includes("browser capability") || message.includes("brower capability") || message.includes("directory-picker-unavailable");
22574
22721
  }
22575
22722
  function requestArgs(payload) {
22576
- const root = record4(payload);
22723
+ const root = record5(payload);
22577
22724
  const args = isRecord7(root.args) ? root.args : root;
22578
- return record4(args.request ?? args._request ?? args);
22725
+ return record5(args.request ?? args._request ?? args);
22579
22726
  }
22580
- function record4(value) {
22727
+ function record5(value) {
22581
22728
  return isRecord7(value) ? value : {};
22582
22729
  }
22583
22730
  function isRecord7(value) {
@@ -23236,6 +23383,7 @@ var CodexRemoteDomain = class {
23236
23383
  peerDeviceIds = /* @__PURE__ */ new Map();
23237
23384
  turnOwners = /* @__PURE__ */ new Map();
23238
23385
  approvals = /* @__PURE__ */ new Map();
23386
+ permissionPresets = /* @__PURE__ */ new Map();
23239
23387
  approvalExpiryTimer;
23240
23388
  restartTimer;
23241
23389
  restartAttempt = 0;
@@ -23314,6 +23462,7 @@ var CodexRemoteDomain = class {
23314
23462
  if (extractThread(result)?.id === void 0) {
23315
23463
  throw new RpcError("CODEX_INVALID_RESPONSE", "Codex App Server returned an invalid thread.");
23316
23464
  }
23465
+ this.rememberPermission(extractThread(result).id, result);
23317
23466
  return result;
23318
23467
  }
23319
23468
  const threadId = threadIdFromParams(call.params);
@@ -23328,12 +23477,23 @@ var CodexRemoteDomain = class {
23328
23477
  }
23329
23478
  );
23330
23479
  const activeTurnId2 = typeof page.activeTurnId === "string" ? page.activeTurnId : this.turnOwners.get(threadId)?.turnId;
23331
- return activeTurnId2 === void 0 ? page : { ...page, activeTurnId: activeTurnId2 };
23480
+ return {
23481
+ ...page,
23482
+ ...activeTurnId2 === void 0 ? {} : { activeTurnId: activeTurnId2 },
23483
+ permissionPreset: this.permissionPresets.get(threadId) ?? null
23484
+ };
23332
23485
  }
23333
23486
  if (call.method === "dsh/directoryList") {
23334
23487
  return this.listCodexDirectory(call.params.path);
23335
23488
  }
23336
23489
  const allowedThread = threadId === void 0 ? void 0 : await this.readKnownThread(threadId);
23490
+ if (call.method === "thread/resume" && call.params.permissionPreset !== void 0) {
23491
+ const owner = this.turnOwners.get(threadId);
23492
+ if (owner !== void 0 && owner.connectionId !== connectionId) {
23493
+ throw new RpcError("CODEX_THREAD_BUSY", "Another Remote client is writing to this CodeX thread.");
23494
+ }
23495
+ return this.changeThreadPermission(threadId, call.params, allowedThread);
23496
+ }
23337
23497
  if (call.method === "thread/read") {
23338
23498
  return this.callUpstream(call.method, call.params);
23339
23499
  }
@@ -23383,8 +23543,18 @@ var CodexRemoteDomain = class {
23383
23543
  if (call.method === "thread/resume" || call.method === "thread/fork" || call.method === "thread/unarchive") {
23384
23544
  await this.assertResultThreadAllowed(result);
23385
23545
  }
23546
+ if (call.method === "thread/resume" || call.method === "thread/fork") {
23547
+ const resultId = extractThread(result)?.id;
23548
+ if (typeof resultId === "string") {
23549
+ this.rememberPermission(resultId, result);
23550
+ if (call.params.permissionPreset !== void 0) await this.publishPermission(resultId, result);
23551
+ }
23552
+ }
23386
23553
  if (call.method === "turn/start" && threadId !== void 0) {
23387
23554
  this.rememberTurnId(threadId, connectionId, extractTurnId(result));
23555
+ if (call.params.permissionPreset !== void 0) {
23556
+ await this.publishPermission(threadId, codexTurnPermissionParams(permission, allowedThread?.cwd));
23557
+ }
23388
23558
  }
23389
23559
  return result;
23390
23560
  } catch (error) {
@@ -23433,6 +23603,7 @@ var CodexRemoteDomain = class {
23433
23603
  this.peers.clear();
23434
23604
  this.peerDeviceIds.clear();
23435
23605
  this.turnOwners.clear();
23606
+ this.permissionPresets.clear();
23436
23607
  if (this.approvalExpiryTimer !== void 0) clearTimeout(this.approvalExpiryTimer);
23437
23608
  this.approvalExpiryTimer = void 0;
23438
23609
  this.approvals.clear();
@@ -23495,6 +23666,7 @@ var CodexRemoteDomain = class {
23495
23666
  this.state = "restarting";
23496
23667
  this.unavailableCode = code;
23497
23668
  this.turnOwners.clear();
23669
+ this.permissionPresets.clear();
23498
23670
  this.approvals.clear();
23499
23671
  if (this.approvalExpiryTimer !== void 0) clearTimeout(this.approvalExpiryTimer);
23500
23672
  this.approvalExpiryTimer = void 0;
@@ -23550,6 +23722,10 @@ var CodexRemoteDomain = class {
23550
23722
  return;
23551
23723
  }
23552
23724
  const threadId = extractThreadId(message.params);
23725
+ if (message.method === "thread/settings/updated" && threadId !== void 0) {
23726
+ this.rememberPermission(threadId, message.params);
23727
+ }
23728
+ if (message.method === "thread/closed" && threadId !== void 0) this.permissionPresets.delete(threadId);
23553
23729
  if (message.method === "turn/completed" && threadId !== void 0) this.turnOwners.delete(threadId);
23554
23730
  if (message.method === "turn/started" && threadId !== void 0) {
23555
23731
  const owner = this.turnOwners.get(threadId);
@@ -23565,6 +23741,42 @@ var CodexRemoteDomain = class {
23565
23741
  params: message.params
23566
23742
  })));
23567
23743
  }
23744
+ rememberPermission(threadId, value) {
23745
+ const preset = codexPermissionPresetFromResponse(value);
23746
+ if (preset === void 0) this.permissionPresets.delete(threadId);
23747
+ else this.permissionPresets.set(threadId, preset);
23748
+ }
23749
+ async changeThreadPermission(threadId, params, allowed) {
23750
+ const permission = codexPermission(params);
23751
+ const settings = {
23752
+ ...codexTurnPermissionParams(permission, allowed.cwd),
23753
+ ...params.model === void 0 ? {} : { model: params.model }
23754
+ };
23755
+ let result;
23756
+ try {
23757
+ await this.callUpstream("thread/settings/update", { threadId, ...settings });
23758
+ result = { thread: allowed.thread, ...settings, sandbox: settings.sandboxPolicy };
23759
+ } catch {
23760
+ result = await this.callUpstream("thread/resume", {
23761
+ ...permission.params,
23762
+ ...allowed.cwd === void 0 ? {} : { cwd: allowed.cwd },
23763
+ ...codexThreadPermissionParams(permission),
23764
+ excludeTurns: true
23765
+ });
23766
+ await this.assertResultThreadAllowed(result);
23767
+ if (codexPermissionPresetFromResponse(result) !== params.permissionPreset) {
23768
+ await this.callUpstream("thread/settings/update", { threadId, ...settings });
23769
+ result = { ...isRecord10(result) ? result : {}, ...settings, sandbox: settings.sandboxPolicy };
23770
+ }
23771
+ }
23772
+ await this.publishPermission(threadId, result);
23773
+ return result;
23774
+ }
23775
+ async publishPermission(threadId, value) {
23776
+ const source = isRecord10(value) ? value : {};
23777
+ const threadSettings = { approvalPolicy: source.approvalPolicy, sandboxPolicy: source.sandboxPolicy ?? source.sandbox };
23778
+ await this.handleInbound({ kind: "notification", method: "thread/settings/updated", params: { threadId, threadSettings } });
23779
+ }
23568
23780
  async handleServerRequest(message) {
23569
23781
  const appServer = this.requireAppServer();
23570
23782
  await this.expireApprovals();
@@ -24025,7 +24237,7 @@ function filterThreadListByWorkspaceAuthority(result, authority) {
24025
24237
  }
24026
24238
  return {
24027
24239
  ...result,
24028
- data: result.data.map((record5) => isRecord10(record5) ? record5 : void 0).filter((thread) => thread !== void 0 && isThreadAllowedByWorkspaceAuthority(thread, authority))
24240
+ data: result.data.map((record6) => isRecord10(record6) ? record6 : void 0).filter((thread) => thread !== void 0 && isThreadAllowedByWorkspaceAuthority(thread, authority))
24029
24241
  };
24030
24242
  }
24031
24243
  function sanitizeProject(value) {