@codexhost/cli-linux-x64 0.2.5 → 0.3.0-test.1

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.
@@ -36,6 +36,7 @@
36
36
  #ownershipRequestGenerations = /* @__PURE__ */ new WeakMap();
37
37
  #states = /* @__PURE__ */ new WeakMap();
38
38
  #switching = /* @__PURE__ */ new Set();
39
+ #pendingSubmissions = /* @__PURE__ */ new Set();
39
40
  #composerSequence = 0;
40
41
  #modelRequestSequence = 0;
41
42
  #ownershipRequestSequence = 0;
@@ -95,6 +96,7 @@
95
96
  rebindConversation(composer, target) {
96
97
  if (!isConversationTarget(target)) return null;
97
98
  const previous = this.#state(composer);
99
+ this.#pendingSubmissions.delete(previous);
98
100
  this.#modelRequestGenerations.set(previous, ++this.#modelRequestSequence);
99
101
  this.#ownershipRequestGenerations.set(previous, ++this.#ownershipRequestSequence);
100
102
  let state = this.#conversationState(target);
@@ -114,6 +116,7 @@
114
116
  restore(composer, agent, model, thinkingOptionId, permissionModeId) {
115
117
  if (!this.#enabledAgents.has(agent)) return null;
116
118
  const state = this.#state(composer);
119
+ this.#pendingSubmissions.delete(state);
117
120
  state.agent = agent;
118
121
  state.phase = "locked";
119
122
  if (agent === "pi" && model) state.piModel = model;
@@ -204,9 +207,21 @@
204
207
  }
205
208
  lock(composer) {
206
209
  const state = this.#state(composer);
210
+ this.#pendingSubmissions.delete(state);
207
211
  state.phase = "locked";
208
212
  return state;
209
213
  }
214
+ markSubmissionPending(composer) {
215
+ const state = this.#state(composer);
216
+ if (state.phase === "draft") this.#pendingSubmissions.add(state);
217
+ return state;
218
+ }
219
+ isSubmissionPending(composer) {
220
+ return this.#pendingSubmissions.has(this.#state(composer));
221
+ }
222
+ clearPendingSubmission(composer) {
223
+ this.#pendingSubmissions.delete(this.#state(composer));
224
+ }
210
225
  recordSubmission(composer) {
211
226
  const state = this.#state(composer);
212
227
  this.#lastSubmittedAgent = state.agent;
@@ -224,6 +239,9 @@
224
239
  if (isConversationTarget(target) && !bound) {
225
240
  this.#conversationStates.push({ target, state });
226
241
  }
242
+ if (isConversationTarget(target) && this.#pendingSubmissions.delete(state)) {
243
+ state.phase = "locked";
244
+ }
227
245
  return true;
228
246
  }
229
247
  async switchAgent(composer, nextAgent, operations) {
@@ -231,6 +249,7 @@
231
249
  if (!this.#enabledAgents.has(nextAgent)) return false;
232
250
  if (state.phase !== "draft" || this.#switching.has(state)) return false;
233
251
  if (state.agent === nextAgent) return true;
252
+ this.#pendingSubmissions.delete(state);
234
253
  this.#switching.add(state);
235
254
  try {
236
255
  if (!operations.applyAgent(nextAgent)) return false;
@@ -14848,8 +14867,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
14848
14867
  code: external_exports.string().min(1),
14849
14868
  message: external_exports.string().min(1),
14850
14869
  retryable: external_exports.boolean(),
14851
- diagnostic: external_exports.string().min(1).optional()
14852
- }).superRefine(rejectExplicitUndefined(["diagnostic"]));
14870
+ diagnostic: external_exports.string().min(1).optional(),
14871
+ stage: external_exports.string().min(1).optional(),
14872
+ durationMs: external_exports.number().int().nonnegative().optional(),
14873
+ stderrTail: external_exports.string().min(1).optional()
14874
+ }).superRefine(rejectExplicitUndefined(["diagnostic", "stage", "durationMs", "stderrTail"]));
14853
14875
 
14854
14876
  // ../shared-contracts/dist/ids.js
14855
14877
  var opaqueIdSchema = external_exports.string().refine((value) => value.trim().length > 0, {
@@ -15194,6 +15216,47 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
15194
15216
  }
15195
15217
  });
15196
15218
 
15219
+ // ../shared-contracts/dist/harness-commands.js
15220
+ var commandIdSchema = external_exports.string().trim().min(1).max(128).regex(/^[A-Za-z0-9._:-]+$/u).brand();
15221
+ var commandInvocationSchema = external_exports.string().min(1).max(128);
15222
+ var commandLabelSchema = external_exports.string().trim().min(1).max(128);
15223
+ var commandDescriptionSchema = external_exports.string().trim().min(1).max(512);
15224
+ var harnessCommandDescriptorSchema = external_exports.object({
15225
+ id: commandIdSchema,
15226
+ invocation: commandInvocationSchema,
15227
+ label: commandLabelSchema,
15228
+ description: commandDescriptionSchema.optional(),
15229
+ argumentMode: external_exports.enum(["none", "text"])
15230
+ }).strict();
15231
+ var harnessCommandCatalogSchema = external_exports.object({
15232
+ commands: external_exports.array(harnessCommandDescriptorSchema)
15233
+ }).strict().superRefine((catalog, context) => {
15234
+ const ids = /* @__PURE__ */ new Set();
15235
+ for (const [index, command] of catalog.commands.entries()) {
15236
+ if (ids.has(command.id)) {
15237
+ context.addIssue({
15238
+ code: "custom",
15239
+ message: "Harness command IDs must be unique",
15240
+ path: ["commands", index, "id"]
15241
+ });
15242
+ }
15243
+ ids.add(command.id);
15244
+ }
15245
+ });
15246
+ var threadCommandsInspectParamsSchema = external_exports.object({
15247
+ threadId: hostThreadIdSchema
15248
+ }).strict();
15249
+ var threadCommandExecuteParamsSchema = external_exports.object({
15250
+ threadId: hostThreadIdSchema,
15251
+ commandId: commandIdSchema,
15252
+ turnId: hostTurnIdSchema.optional(),
15253
+ arguments: jsonObjectSchema.optional()
15254
+ }).strict();
15255
+ var threadCommandExecuteResultSchema = external_exports.object({
15256
+ accepted: external_exports.literal(true),
15257
+ turnId: hostTurnIdSchema
15258
+ }).strict();
15259
+
15197
15260
  // ../shared-contracts/dist/json-rpc.js
15198
15261
  var jsonRpcVersionSchema = external_exports.literal("2.0").optional();
15199
15262
  var absentSchema = external_exports.never().optional();
@@ -15405,6 +15468,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
15405
15468
  grok: "https://grok.com/"
15406
15469
  };
15407
15470
  var CONTROL_ATTRIBUTE = "data-codexhost-agent-control";
15471
+ var AGENT_MENU_WIDTH = 200;
15408
15472
  function rendererAgentPickerView(state, adapterState, switching, agents, availability = {}) {
15409
15473
  const optionDisabled = Object.fromEntries(
15410
15474
  agents.map((agent) => [
@@ -15425,7 +15489,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
15425
15489
  }
15426
15490
  function setMenuPosition(control) {
15427
15491
  const rect = control.trigger.getBoundingClientRect();
15428
- const width = 190;
15492
+ const width = AGENT_MENU_WIDTH;
15429
15493
  const left = Math.max(8, Math.min(rect.right - width, window.innerWidth - width - 8));
15430
15494
  control.menu.style.left = `${left}px`;
15431
15495
  control.menu.style.bottom = `${Math.max(8, window.innerHeight - rect.top + 6)}px`;
@@ -15495,13 +15559,16 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
15495
15559
  menu.hidden = typeof menu.showPopover !== "function";
15496
15560
  menu.style.position = "fixed";
15497
15561
  menu.style.inset = "auto";
15498
- menu.style.width = "190px";
15562
+ menu.style.width = `${AGENT_MENU_WIDTH}px`;
15499
15563
  menu.style.padding = "4px";
15500
15564
  menu.style.border = "0";
15501
15565
  menu.style.borderRadius = "6px";
15502
15566
  menu.style.background = "Canvas";
15503
15567
  menu.style.color = "CanvasText";
15504
15568
  menu.style.boxShadow = "0 8px 24px rgba(0, 0, 0, 0.28)";
15569
+ menu.style.boxSizing = "border-box";
15570
+ menu.style.overflowX = "hidden";
15571
+ menu.style.overflowY = "auto";
15505
15572
  menu.style.zIndex = "2147483647";
15506
15573
  trigger.setAttribute("aria-controls", menu.id);
15507
15574
  const options = {};
@@ -15533,6 +15600,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
15533
15600
  button.style.display = "flex";
15534
15601
  button.style.alignItems = "center";
15535
15602
  button.style.gap = "8px";
15603
+ button.style.minWidth = "0";
15536
15604
  button.style.width = "100%";
15537
15605
  button.style.flex = "1 1 auto";
15538
15606
  button.style.height = "36px";
@@ -15556,8 +15624,9 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
15556
15624
  const check2 = document.createElement("span");
15557
15625
  check2.textContent = "\u2713";
15558
15626
  check2.setAttribute("aria-hidden", "true");
15559
- check2.style.width = "12px";
15627
+ check2.style.width = "24px";
15560
15628
  check2.style.flex = "none";
15629
+ check2.style.textAlign = "center";
15561
15630
  check2.style.visibility = "hidden";
15562
15631
  const label = document.createElement("span");
15563
15632
  label.textContent = RENDERER_AGENT_LABELS[agent];
@@ -15566,7 +15635,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
15566
15635
  label.style.overflow = "hidden";
15567
15636
  label.style.textOverflow = "ellipsis";
15568
15637
  label.style.whiteSpace = "nowrap";
15569
- button.append(check2, createRendererAgentIcon(agent), label);
15638
+ button.append(createRendererAgentIcon(agent), label);
15570
15639
  button.addEventListener("click", () => {
15571
15640
  const selected = button.getAttribute("aria-pressed") === "true";
15572
15641
  close();
@@ -15576,9 +15645,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
15576
15645
  const download = agent === "codex" ? null : (() => {
15577
15646
  const control2 = document.createElement("button");
15578
15647
  control2.type = "button";
15579
- control2.textContent = "\u2193";
15648
+ control2.textContent = "+";
15580
15649
  control2.setAttribute("aria-label", `Install ${RENDERER_AGENT_LABELS[agent]}`);
15581
- control2.title = `Open ${RENDERER_AGENT_LABELS[agent]} installation page`;
15650
+ control2.title = `Install ${RENDERER_AGENT_LABELS[agent]}`;
15651
+ control2.style.position = "absolute";
15652
+ control2.style.inset = "0";
15582
15653
  control2.style.display = "inline-flex";
15583
15654
  control2.style.alignItems = "center";
15584
15655
  control2.style.justifyContent = "center";
@@ -15591,7 +15662,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
15591
15662
  control2.style.background = "transparent";
15592
15663
  control2.style.color = "inherit";
15593
15664
  control2.style.cursor = "pointer";
15594
- control2.style.font = "600 16px/1 system-ui, sans-serif";
15665
+ control2.style.font = "600 18px/1 system-ui, sans-serif";
15595
15666
  control2.style.opacity = "0.72";
15596
15667
  control2.addEventListener("pointerenter", () => {
15597
15668
  if (!control2.disabled) control2.style.background = "rgba(127, 127, 127, 0.16)";
@@ -15606,16 +15677,20 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
15606
15677
  return control2;
15607
15678
  })();
15608
15679
  options[agent] = { button, check: check2, download };
15609
- if (download) {
15610
- const row = document.createElement("div");
15611
- row.style.display = "flex";
15612
- row.style.alignItems = "center";
15613
- row.style.gap = "2px";
15614
- row.append(button, download);
15615
- menu.append(row);
15616
- } else {
15617
- menu.append(button);
15618
- }
15680
+ const row = document.createElement("div");
15681
+ row.style.display = "flex";
15682
+ row.style.alignItems = "center";
15683
+ row.style.gap = "2px";
15684
+ const actionSlot = document.createElement("span");
15685
+ actionSlot.style.position = "relative";
15686
+ actionSlot.style.display = "inline-block";
15687
+ actionSlot.style.width = "24px";
15688
+ actionSlot.style.height = "24px";
15689
+ actionSlot.style.flex = "none";
15690
+ actionSlot.append(check2);
15691
+ if (download) actionSlot.append(download);
15692
+ row.append(actionSlot, button);
15693
+ menu.append(row);
15619
15694
  }
15620
15695
  root.append(trigger, menu);
15621
15696
  const onTriggerClick = () => {
@@ -15716,30 +15791,138 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
15716
15791
  option.check.style.visibility = selected ? "visible" : "hidden";
15717
15792
  if (option.download) {
15718
15793
  const visible = view.downloadVisible[agent] === true;
15719
- option.download.hidden = !visible;
15794
+ option.download.hidden = false;
15720
15795
  option.download.disabled = !visible;
15721
- option.download.style.display = visible ? "inline-flex" : "none";
15796
+ option.download.setAttribute("aria-hidden", String(!visible));
15797
+ option.download.style.display = "inline-flex";
15798
+ option.download.style.visibility = visible ? "visible" : "hidden";
15799
+ option.download.style.pointerEvents = visible ? "auto" : "none";
15722
15800
  }
15723
15801
  }
15724
15802
  return view;
15725
15803
  }
15726
15804
 
15805
+ // src/renderer-model-picker-positioning.ts
15806
+ var MENU_GAP = 4;
15807
+ var MAIN_MENU_SIDE_OFFSET = 8;
15808
+ var COLLISION_PADDING = 8;
15809
+ var RENDERER_MODEL_PICKER_MAIN_MENU_WIDTH = 260;
15810
+ var RENDERER_MODEL_PICKER_MODEL_MENU_WIDTH = 280;
15811
+ var RENDERER_MODEL_PICKER_MODEL_MENU_MAX_HEIGHT = 360;
15812
+ function clampPosition(value, minimum, maximum) {
15813
+ return Math.min(Math.max(value, minimum), Math.max(minimum, maximum));
15814
+ }
15815
+ function fitWidth(preferredWidth, viewportWidth) {
15816
+ return Math.max(
15817
+ COLLISION_PADDING,
15818
+ Math.min(preferredWidth, viewportWidth - COLLISION_PADDING * 2)
15819
+ );
15820
+ }
15821
+ function fitHeight(viewport, top = COLLISION_PADDING) {
15822
+ return Math.max(
15823
+ COLLISION_PADDING,
15824
+ Math.min(
15825
+ RENDERER_MODEL_PICKER_MODEL_MENU_MAX_HEIGHT,
15826
+ viewport.height * 0.6,
15827
+ viewport.height - top - COLLISION_PADDING
15828
+ )
15829
+ );
15830
+ }
15831
+ function rendererModelPickerMainMenuPlacement(triggerRect, viewport, width = RENDERER_MODEL_PICKER_MAIN_MENU_WIDTH) {
15832
+ const maxLeft = viewport.width - COLLISION_PADDING - width;
15833
+ return {
15834
+ left: clampPosition(triggerRect.right - width, COLLISION_PADDING, maxLeft),
15835
+ width,
15836
+ bottom: Math.max(COLLISION_PADDING, viewport.height - triggerRect.top + MAIN_MENU_SIDE_OFFSET)
15837
+ };
15838
+ }
15839
+ function rendererModelPickerStandaloneModelMenuPlacement(triggerRect, viewport) {
15840
+ const width = fitWidth(RENDERER_MODEL_PICKER_MODEL_MENU_WIDTH, viewport.width);
15841
+ const maxLeft = viewport.width - COLLISION_PADDING - width;
15842
+ return {
15843
+ left: clampPosition(triggerRect.right - width, COLLISION_PADDING, maxLeft),
15844
+ width,
15845
+ maxHeight: fitHeight(viewport),
15846
+ bottom: Math.max(COLLISION_PADDING, viewport.height - triggerRect.top + MAIN_MENU_SIDE_OFFSET)
15847
+ };
15848
+ }
15849
+ function rendererModelPickerModelMenuPlacement(mainRect, viewport) {
15850
+ const preferredWidth = fitWidth(RENDERER_MODEL_PICKER_MODEL_MENU_WIDTH, viewport.width);
15851
+ const rightLeft = mainRect.right + MENU_GAP;
15852
+ const leftLeft = mainRect.left - MENU_GAP - preferredWidth;
15853
+ const rightAvailable = viewport.width - rightLeft - COLLISION_PADDING;
15854
+ const leftAvailable = mainRect.left - MENU_GAP - COLLISION_PADDING;
15855
+ let width;
15856
+ let left;
15857
+ if (rightAvailable >= preferredWidth) {
15858
+ width = preferredWidth;
15859
+ left = rightLeft;
15860
+ } else if (leftAvailable >= preferredWidth) {
15861
+ width = preferredWidth;
15862
+ left = leftLeft;
15863
+ } else if (rightAvailable >= leftAvailable) {
15864
+ width = Math.max(COLLISION_PADDING, rightAvailable);
15865
+ left = rightLeft;
15866
+ } else {
15867
+ width = Math.max(COLLISION_PADDING, leftAvailable);
15868
+ left = mainRect.left - MENU_GAP - width;
15869
+ }
15870
+ const top = clampPosition(mainRect.top, COLLISION_PADDING, viewport.height - COLLISION_PADDING);
15871
+ const maxHeight = fitHeight(viewport, top);
15872
+ return {
15873
+ left: clampPosition(left, COLLISION_PADDING, viewport.width - COLLISION_PADDING - width),
15874
+ top,
15875
+ width,
15876
+ maxHeight
15877
+ };
15878
+ }
15879
+
15727
15880
  // src/renderer-model-picker.ts
15728
15881
  var RENDERER_MODEL_TRIGGER_FALLBACK_CLASSES = "border-token-border no-drag cursor-interaction items-center gap-1 border whitespace-nowrap select-none focus:outline-none disabled:cursor-not-allowed disabled:opacity-40 flex rounded-full text-token-text-tertiary enabled:hover:bg-token-list-hover-background enabled:active:bg-token-foreground/15 data-[state=open]:bg-token-list-hover-background border-transparent h-token-button-composer px-2 py-0 text-sm leading-[18px] min-w-0";
15729
15882
  var MENU_CLASSES = "fixed z-50 overflow-hidden rounded-xl bg-token-dropdown-background/90 text-token-foreground shadow-lg backdrop-blur-xl";
15883
+ var SEARCH_INPUT_CLASSES = "mb-1 w-full shrink-0 rounded-lg border border-token-border bg-token-dropdown-background/95 px-2 py-1.5 text-sm text-token-foreground outline-none placeholder:text-token-text-tertiary disabled:cursor-not-allowed disabled:opacity-40";
15730
15884
  var OPTION_CLASSES = "flex w-full cursor-interaction items-center gap-2 rounded-lg px-2 py-2 text-left text-sm text-token-foreground outline-none enabled:hover:bg-token-list-hover-background enabled:active:bg-token-foreground/15 disabled:cursor-not-allowed disabled:opacity-40";
15731
15885
  var HEADING_CLASSES = "px-2 pb-1 pt-1.5 text-sm text-token-text-tertiary";
15732
- var MAIN_MENU_MIN_WIDTH = 180;
15733
- var MAIN_MENU_MAX_WIDTH = 210;
15734
- var MODEL_MENU_PREFERRED_WIDTH = 320;
15735
- var MODEL_MENU_MAX_WIDTH = 380;
15736
- var MODEL_MENU_MAX_HEIGHT = 360;
15737
- var MAIN_MENU_LEFT_OFFSET = 96;
15738
- var MENU_GAP = 4;
15739
- var VIEWPORT_MARGIN = 8;
15886
+ var MODEL_TRIGGER_MAX_WIDTH = "min(200px, 26vw)";
15887
+ var MODEL_SCROLLBAR_STYLE_ATTRIBUTE = "data-codexhost-model-picker-scrollbar";
15740
15888
  function popoverOpen2(menu) {
15741
15889
  return menu.matches(":popover-open");
15742
15890
  }
15891
+ function ensureModelScrollbarStyle(ownerDocument) {
15892
+ if (ownerDocument.querySelector(`style[${MODEL_SCROLLBAR_STYLE_ATTRIBUTE}]`)) return;
15893
+ const style = ownerDocument.createElement("style");
15894
+ style.setAttribute(MODEL_SCROLLBAR_STYLE_ATTRIBUTE, "true");
15895
+ style.textContent = `
15896
+ [data-codexhost-model-scrollable] {
15897
+ scrollbar-width: thin;
15898
+ scrollbar-color: rgba(255, 255, 255, 0.28) transparent;
15899
+ }
15900
+ [data-codexhost-model-scrollable]::-webkit-scrollbar {
15901
+ width: 6px;
15902
+ height: 6px;
15903
+ }
15904
+ [data-codexhost-model-scrollable]::-webkit-scrollbar-track {
15905
+ background: transparent;
15906
+ }
15907
+ [data-codexhost-model-scrollable]::-webkit-scrollbar-thumb {
15908
+ min-height: 28px;
15909
+ border: 1px solid transparent;
15910
+ border-radius: 999px;
15911
+ background: rgba(255, 255, 255, 0.28);
15912
+ background-clip: padding-box;
15913
+ }
15914
+ [data-codexhost-model-scrollable]::-webkit-scrollbar-thumb:hover {
15915
+ background: rgba(255, 255, 255, 0.42);
15916
+ background-clip: padding-box;
15917
+ }
15918
+ [data-codexhost-model-scrollable]::-webkit-scrollbar-button {
15919
+ display: none;
15920
+ width: 0;
15921
+ height: 0;
15922
+ }
15923
+ `;
15924
+ (ownerDocument.head ?? ownerDocument.documentElement).append(style);
15925
+ }
15743
15926
  function thinkingOptionsForModel(catalog, selected) {
15744
15927
  const supported = catalog?.models.find(
15745
15928
  (model) => model.ref.id === selected?.id
@@ -15753,6 +15936,9 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
15753
15936
  function shouldCloseRendererModelPicker(view) {
15754
15937
  return isRendererModelPickerDisabled(view) && view.status !== "selecting";
15755
15938
  }
15939
+ function isTransientPickerState(view) {
15940
+ return view.status === "idle" || view.status === "loading";
15941
+ }
15756
15942
  function rendererModelPickerPresentation(view) {
15757
15943
  const selectedModel = view.catalog?.models.find((model) => model.ref.id === view.selected?.id);
15758
15944
  const thinkingOptions = view.thinkingSelectionSupported === false ? [] : thinkingOptionsForModel(view.catalog, view.selected);
@@ -15777,52 +15963,43 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
15777
15963
  }
15778
15964
  function positionMainMenu(control) {
15779
15965
  const triggerRect = control.trigger.getBoundingClientRect();
15780
- const width = Math.min(MAIN_MENU_MAX_WIDTH, Math.max(MAIN_MENU_MIN_WIDTH, triggerRect.width));
15781
- const modelMenuWidth = Math.min(
15782
- MODEL_MENU_MAX_WIDTH,
15783
- Math.min(MODEL_MENU_PREFERRED_WIDTH, window.innerWidth - VIEWPORT_MARGIN * 2)
15966
+ const placement = rendererModelPickerMainMenuPlacement(
15967
+ triggerRect,
15968
+ { width: window.innerWidth, height: window.innerHeight },
15969
+ RENDERER_MODEL_PICKER_MAIN_MENU_WIDTH
15784
15970
  );
15785
- const preferredLeft = triggerRect.right - width - MAIN_MENU_LEFT_OFFSET;
15786
- const rightReservedLeft = window.innerWidth - VIEWPORT_MARGIN - modelMenuWidth - MENU_GAP - width;
15787
- const maxLeft = window.innerWidth - VIEWPORT_MARGIN - width;
15788
- const left = Math.max(
15789
- VIEWPORT_MARGIN,
15790
- Math.min(preferredLeft, Math.max(rightReservedLeft, VIEWPORT_MARGIN), maxLeft)
15791
- );
15792
- control.menu.style.setProperty("width", `${width}px`, "important");
15793
- control.menu.style.left = `${left}px`;
15794
- control.menu.style.maxWidth = `${width}px`;
15971
+ control.menu.style.setProperty("width", `${placement.width}px`, "important");
15972
+ control.menu.style.left = `${placement.left}px`;
15973
+ control.menu.style.maxWidth = `${placement.width}px`;
15795
15974
  control.menu.style.right = "auto";
15796
15975
  control.menu.style.top = "auto";
15797
- control.menu.style.bottom = `${Math.max(
15798
- VIEWPORT_MARGIN,
15799
- window.innerHeight - triggerRect.top + 6
15800
- )}px`;
15801
- }
15802
- function positionModelMenu(control) {
15803
- const mainRect = control.menu.getBoundingClientRect();
15804
- const availableRight = window.innerWidth - mainRect.right - VIEWPORT_MARGIN;
15805
- const width = Math.min(MODEL_MENU_MAX_WIDTH, Math.max(VIEWPORT_MARGIN, availableRight));
15806
- const left = mainRect.right + MENU_GAP;
15807
- control.modelMenu.style.setProperty("width", `${width}px`, "important");
15808
- control.modelMenu.style.left = `${left}px`;
15809
- control.modelMenu.style.maxWidth = `${width}px`;
15810
- const maxHeight = Math.max(
15811
- VIEWPORT_MARGIN,
15812
- Math.min(MODEL_MENU_MAX_HEIGHT, window.innerHeight * 0.6, mainRect.bottom - VIEWPORT_MARGIN)
15813
- );
15814
- control.modelMenu.style.maxHeight = `${maxHeight}px`;
15976
+ control.menu.style.bottom = `${placement.bottom}px`;
15977
+ }
15978
+ function positionAdvancedMenus(control) {
15979
+ positionMainMenu(control);
15980
+ positionModelMenu(control);
15981
+ }
15982
+ function positionModelMenu(control, standalone = false) {
15983
+ const anchorRect = standalone ? control.trigger.getBoundingClientRect() : control.menu.getBoundingClientRect();
15984
+ const placement = standalone ? rendererModelPickerStandaloneModelMenuPlacement(anchorRect, {
15985
+ width: window.innerWidth,
15986
+ height: window.innerHeight
15987
+ }) : rendererModelPickerModelMenuPlacement(anchorRect, {
15988
+ width: window.innerWidth,
15989
+ height: window.innerHeight
15990
+ });
15991
+ control.modelMenu.style.setProperty("width", `${placement.width}px`, "important");
15992
+ control.modelMenu.style.left = `${placement.left}px`;
15993
+ control.modelMenu.style.maxWidth = `${placement.width}px`;
15994
+ control.modelMenu.style.maxHeight = `${placement.maxHeight}px`;
15815
15995
  control.modelMenu.style.right = "auto";
15816
- control.modelMenu.style.top = "auto";
15817
- control.modelMenu.style.bottom = `${Math.max(
15818
- VIEWPORT_MARGIN,
15819
- window.innerHeight - mainRect.bottom
15820
- )}px`;
15996
+ control.modelMenu.style.top = placement.top === void 0 ? "auto" : `${placement.top}px`;
15997
+ control.modelMenu.style.bottom = placement.bottom === void 0 ? "auto" : `${placement.bottom}px`;
15821
15998
  }
15822
15999
  function syncRendererModelTriggerClass(control, nativeClassName) {
15823
16000
  control.trigger.className = nativeClassName?.trim() || RENDERER_MODEL_TRIGGER_FALLBACK_CLASSES;
15824
16001
  control.trigger.style.width = "fit-content";
15825
- control.trigger.style.maxWidth = "min(320px, 38vw)";
16002
+ control.trigger.style.maxWidth = MODEL_TRIGGER_MAX_WIDTH;
15826
16003
  }
15827
16004
  function createCheck() {
15828
16005
  const check2 = document.createElement("span");
@@ -15845,6 +16022,16 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
15845
16022
  element.textContent = text;
15846
16023
  return true;
15847
16024
  }
16025
+ function applyModelSearchFilter(control) {
16026
+ const query = control.searchInput.value.trim().toLowerCase();
16027
+ let visibleCount = 0;
16028
+ for (const option of control.options.values()) {
16029
+ const matches = query.length === 0 || option.searchText.includes(query);
16030
+ option.button.hidden = !matches;
16031
+ if (matches) visibleCount += 1;
16032
+ }
16033
+ control.searchEmpty.hidden = query.length === 0 || visibleCount > 0;
16034
+ }
15848
16035
  function mountRendererModelPicker(composerId, nativeClassName, onSelectModel, onSelectThinking) {
15849
16036
  const root = document.createElement("div");
15850
16037
  root.setAttribute("data-codexhost-model-control", composerId);
@@ -15874,7 +16061,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
15874
16061
  menu.id = `${composerId}-model-menu`;
15875
16062
  menu.setAttribute("role", "menu");
15876
16063
  menu.setAttribute("aria-label", "Model and Thinking");
15877
- menu.setAttribute("popover", "auto");
16064
+ menu.setAttribute("popover", "manual");
15878
16065
  menu.className = MENU_CLASSES;
15879
16066
  menu.style.position = "fixed";
15880
16067
  menu.style.inset = "auto";
@@ -15895,52 +16082,115 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
15895
16082
  modelMenu.setAttribute("aria-label", "Model");
15896
16083
  modelMenu.setAttribute("popover", "manual");
15897
16084
  modelMenu.className = MENU_CLASSES;
16085
+ modelMenu.dataset.codexhostModelScrollable = "true";
16086
+ ensureModelScrollbarStyle(document);
15898
16087
  modelMenu.style.position = "fixed";
15899
16088
  modelMenu.style.inset = "auto";
15900
16089
  modelMenu.style.margin = "0";
15901
16090
  modelMenu.style.padding = "4px";
15902
16091
  modelMenu.style.border = "0";
15903
- modelMenu.style.maxHeight = "min(360px, 60vh)";
16092
+ modelMenu.style.maxHeight = `min(${RENDERER_MODEL_PICKER_MODEL_MENU_MAX_HEIGHT}px, 60vh)`;
15904
16093
  modelMenu.style.overflowY = "auto";
15905
16094
  modelButton.setAttribute("aria-controls", modelMenu.id);
15906
16095
  const options = /* @__PURE__ */ new Map();
15907
16096
  const thinkingOptions = /* @__PURE__ */ new Map();
16097
+ const searchInput = document.createElement("input");
16098
+ searchInput.type = "search";
16099
+ searchInput.placeholder = "Search models";
16100
+ searchInput.setAttribute("aria-label", "Search models");
16101
+ searchInput.autocomplete = "off";
16102
+ searchInput.spellcheck = false;
16103
+ searchInput.className = SEARCH_INPUT_CLASSES;
16104
+ const searchHeader = document.createElement("div");
16105
+ searchHeader.style.position = "sticky";
16106
+ searchHeader.style.top = "0";
16107
+ searchHeader.style.zIndex = "2";
16108
+ searchHeader.style.margin = "-4px";
16109
+ searchHeader.style.padding = "4px";
16110
+ searchHeader.style.backgroundColor = "Canvas";
16111
+ const searchEmpty = document.createElement("div");
16112
+ searchEmpty.dataset.codexhostModelSearchEmpty = "true";
16113
+ searchEmpty.textContent = "No matching models";
16114
+ searchEmpty.className = "block px-2 py-2 text-sm text-token-text-tertiary";
16115
+ searchEmpty.hidden = true;
16116
+ const onSearchInput = () => applyModelSearchFilter(control);
16117
+ searchInput.addEventListener("input", onSearchInput);
16118
+ const silencedEventTypes = [
16119
+ "keydown",
16120
+ "keypress",
16121
+ "keyup",
16122
+ "beforeinput",
16123
+ "input",
16124
+ "compositionstart",
16125
+ "compositionupdate",
16126
+ "compositionend",
16127
+ "change"
16128
+ ];
16129
+ const silenceForHarness = (event) => {
16130
+ event.stopPropagation();
16131
+ };
16132
+ for (const type of silencedEventTypes) {
16133
+ searchInput.addEventListener(type, silenceForHarness);
16134
+ }
16135
+ const onSearchBlur = () => {
16136
+ if (!popoverOpen2(modelMenu)) return;
16137
+ const active = document.activeElement;
16138
+ const movedToComposer = active === document.body || active instanceof Element && (active.matches('textarea, [contenteditable="true"], [role="textbox"]') || active.closest('textarea, [contenteditable="true"], [role="textbox"]') !== null);
16139
+ if (!movedToComposer) return;
16140
+ requestAnimationFrame(() => {
16141
+ if (popoverOpen2(modelMenu) && searchInput.isConnected) searchInput.focus();
16142
+ });
16143
+ };
16144
+ searchInput.addEventListener("blur", onSearchBlur);
15908
16145
  const closeModelMenu = () => {
15909
16146
  if (popoverOpen2(modelMenu)) modelMenu.hidePopover();
15910
16147
  modelButton.setAttribute("aria-expanded", "false");
16148
+ if (searchInput.value !== "") {
16149
+ searchInput.value = "";
16150
+ applyModelSearchFilter(control);
16151
+ }
15911
16152
  };
16153
+ const pickerOpen = () => popoverOpen2(menu) || popoverOpen2(modelMenu);
15912
16154
  const close = () => {
15913
16155
  closeModelMenu();
15914
16156
  if (popoverOpen2(menu)) menu.hidePopover();
15915
16157
  };
15916
- const openModelMenu = () => {
15917
- if (!popoverOpen2(menu) || popoverOpen2(modelMenu)) return;
16158
+ const openModelMenu = (standalone = false) => {
16159
+ if (!standalone && !popoverOpen2(menu) || popoverOpen2(modelMenu)) return;
15918
16160
  modelMenu.showPopover();
15919
- positionModelMenu(control);
16161
+ positionModelMenu(control, standalone);
15920
16162
  modelButton.setAttribute("aria-expanded", "true");
15921
16163
  };
15922
16164
  const open = () => {
15923
- if (trigger.disabled || popoverOpen2(menu)) return;
16165
+ if (trigger.disabled || pickerOpen()) return;
16166
+ if (control.thinkingOptions.size === 0) {
16167
+ openModelMenu(true);
16168
+ return;
16169
+ }
15924
16170
  menu.showPopover();
15925
- positionMainMenu(control);
16171
+ positionAdvancedMenus(control);
15926
16172
  };
15927
16173
  const onTriggerClick = () => {
15928
- if (popoverOpen2(menu)) close();
16174
+ if (pickerOpen()) close();
15929
16175
  else open();
15930
16176
  };
15931
16177
  const onToggle = () => {
15932
16178
  const openState = popoverOpen2(menu);
15933
- trigger.setAttribute("aria-expanded", String(openState));
16179
+ trigger.setAttribute("aria-expanded", String(openState || popoverOpen2(modelMenu)));
15934
16180
  trigger.setAttribute("data-state", openState ? "open" : "closed");
15935
16181
  if (!openState) closeModelMenu();
15936
16182
  };
15937
16183
  const onModelToggle = () => {
15938
- modelButton.setAttribute("aria-expanded", String(popoverOpen2(modelMenu)));
16184
+ const openState = popoverOpen2(modelMenu);
16185
+ modelButton.setAttribute("aria-expanded", String(openState));
16186
+ trigger.setAttribute("aria-expanded", String(openState || popoverOpen2(menu)));
16187
+ trigger.setAttribute("data-state", openState || popoverOpen2(menu) ? "open" : "closed");
15939
16188
  };
15940
16189
  const onRootClick = (event) => {
15941
16190
  const target = event.target instanceof Element ? event.target.closest("button") : null;
15942
16191
  if (target?.dataset.openModelMenu) {
15943
16192
  openModelMenu();
16193
+ control.searchInput.focus();
15944
16194
  return;
15945
16195
  }
15946
16196
  if (target?.dataset.thinkingOptionId) {
@@ -15952,25 +16202,44 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
15952
16202
  const onModelMenuClick = (event) => {
15953
16203
  const target = event.target instanceof Element ? event.target.closest("button[data-model-id]") : null;
15954
16204
  if (!target?.dataset.modelId) return;
15955
- closeModelMenu();
15956
- modelButton.focus();
16205
+ close();
16206
+ trigger.focus();
15957
16207
  onSelectModel(target.dataset.modelId);
15958
16208
  };
15959
16209
  const onModelHover = () => openModelMenu();
16210
+ const onDocumentPointerDown = (event) => {
16211
+ if (!popoverOpen2(menu) && !popoverOpen2(modelMenu)) return;
16212
+ const target = event.target instanceof Node ? event.target : null;
16213
+ if (target && (root.contains(target) || menu.contains(target) || modelMenu.contains(target))) {
16214
+ return;
16215
+ }
16216
+ close();
16217
+ };
16218
+ const onDocumentKeyDown = (event) => {
16219
+ if (event.key !== "Escape") return;
16220
+ if (!popoverOpen2(menu) && !popoverOpen2(modelMenu)) return;
16221
+ event.preventDefault();
16222
+ close();
16223
+ trigger.focus();
16224
+ };
15960
16225
  const onViewportChange = () => {
15961
- if (popoverOpen2(menu)) positionMainMenu(control);
15962
- if (popoverOpen2(modelMenu)) positionModelMenu(control);
16226
+ if (popoverOpen2(menu)) positionAdvancedMenus(control);
16227
+ else if (popoverOpen2(modelMenu)) positionModelMenu(control, true);
15963
16228
  };
15964
16229
  trigger.addEventListener("click", onTriggerClick);
15965
16230
  menu.addEventListener("toggle", onToggle);
15966
16231
  modelMenu.addEventListener("toggle", onModelToggle);
15967
16232
  modelButton.addEventListener("mouseenter", onModelHover);
15968
- root.addEventListener("click", onRootClick);
16233
+ menu.addEventListener("click", onRootClick);
15969
16234
  modelMenu.addEventListener("click", onModelMenuClick);
16235
+ document.addEventListener("pointerdown", onDocumentPointerDown, true);
16236
+ document.addEventListener("keydown", onDocumentKeyDown, true);
15970
16237
  window.addEventListener("resize", onViewportChange);
15971
16238
  window.addEventListener("scroll", onViewportChange, true);
15972
- root.append(trigger, menu);
15973
- document.body.append(modelMenu);
16239
+ root.append(trigger);
16240
+ document.body.append(menu, modelMenu);
16241
+ searchHeader.append(searchInput);
16242
+ modelMenu.append(searchHeader, searchEmpty);
15974
16243
  const control = {
15975
16244
  root,
15976
16245
  trigger,
@@ -15979,6 +16248,9 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
15979
16248
  menu,
15980
16249
  modelMenu,
15981
16250
  modelButton,
16251
+ searchInput,
16252
+ searchHeader,
16253
+ searchEmpty,
15982
16254
  options,
15983
16255
  thinkingOptions,
15984
16256
  close,
@@ -15988,10 +16260,18 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
15988
16260
  menu.removeEventListener("toggle", onToggle);
15989
16261
  modelMenu.removeEventListener("toggle", onModelToggle);
15990
16262
  modelButton.removeEventListener("mouseenter", onModelHover);
15991
- root.removeEventListener("click", onRootClick);
16263
+ menu.removeEventListener("click", onRootClick);
15992
16264
  modelMenu.removeEventListener("click", onModelMenuClick);
16265
+ searchInput.removeEventListener("input", onSearchInput);
16266
+ for (const type of silencedEventTypes) {
16267
+ searchInput.removeEventListener(type, silenceForHarness);
16268
+ }
16269
+ searchInput.removeEventListener("blur", onSearchBlur);
16270
+ document.removeEventListener("pointerdown", onDocumentPointerDown, true);
16271
+ document.removeEventListener("keydown", onDocumentKeyDown, true);
15993
16272
  window.removeEventListener("resize", onViewportChange);
15994
16273
  window.removeEventListener("scroll", onViewportChange, true);
16274
+ menu.remove();
15995
16275
  modelMenu.remove();
15996
16276
  root.remove();
15997
16277
  }
@@ -16004,7 +16284,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
16004
16284
  control.options.clear();
16005
16285
  control.thinkingOptions.clear();
16006
16286
  control.menu.replaceChildren();
16007
- control.modelMenu.replaceChildren(createHeading("Model"));
16287
+ control.modelMenu.replaceChildren(
16288
+ createHeading("Model"),
16289
+ control.searchHeader,
16290
+ control.searchEmpty
16291
+ );
16008
16292
  if (presentation.showThinkingSection) {
16009
16293
  control.menu.append(createHeading("Thinking"));
16010
16294
  for (const option of presentation.thinkingOptions) {
@@ -16048,9 +16332,15 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
16048
16332
  text.title = model.label;
16049
16333
  const check2 = createCheck();
16050
16334
  button.append(text, check2);
16051
- control.options.set(model.ref.id, { button, check: check2 });
16335
+ control.options.set(model.ref.id, {
16336
+ button,
16337
+ check: check2,
16338
+ searchText: `${model.label} ${model.ref.id}`.toLowerCase()
16339
+ });
16052
16340
  control.modelMenu.append(button);
16053
16341
  }
16342
+ applyModelSearchFilter(control);
16343
+ if (popoverOpen2(control.modelMenu)) control.searchInput.focus();
16054
16344
  }
16055
16345
  function renderRendererModelPicker(control, view, visible) {
16056
16346
  control.root.style.display = visible ? "block" : "none";
@@ -16065,11 +16355,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
16065
16355
  showThinkingSection: presentation.showThinkingSection,
16066
16356
  modelLabel: presentation.modelLabel
16067
16357
  });
16068
- if (control.root.dataset.catalogSignature !== catalogSignature) {
16358
+ const keepOpenMenu = popoverOpen2(control.menu) && isTransientPickerState(view);
16359
+ if (control.root.dataset.catalogSignature !== catalogSignature && !keepOpenMenu) {
16069
16360
  rebuildOptions(control, view);
16070
16361
  control.root.dataset.catalogSignature = catalogSignature;
16071
16362
  }
16072
16363
  syncRendererLabelText(control.label, presentation.modelLabel);
16364
+ control.label.title = presentation.modelLabel;
16073
16365
  const secondaryLabel = presentation.thinkingLabel ?? presentation.resolvedModelLabel;
16074
16366
  syncRendererLabelText(control.thinkingLabel, secondaryLabel ?? "");
16075
16367
  control.thinkingLabel.hidden = secondaryLabel === void 0;
@@ -16081,7 +16373,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
16081
16373
  String(view.status === "loading" || view.status === "selecting")
16082
16374
  );
16083
16375
  control.trigger.disabled = isRendererModelPickerDisabled(view);
16084
- if (shouldCloseRendererModelPicker(view)) control.close();
16376
+ if (shouldCloseRendererModelPicker(view) && !keepOpenMenu) control.close();
16085
16377
  control.modelButton.disabled = control.trigger.disabled;
16086
16378
  for (const [modelId, option] of control.options) {
16087
16379
  const selected = modelId === view.selected?.id;
@@ -16928,6 +17220,322 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
16928
17220
  return true;
16929
17221
  }
16930
17222
 
17223
+ // src/renderer-harness-command-control.ts
17224
+ var CONTROL_ATTRIBUTE2 = "data-codexhost-harness-command-control";
17225
+ var MENU_ATTRIBUTE = "data-codexhost-harness-command-menu";
17226
+ var MENU_WIDTH = 320;
17227
+ var VIEWPORT_MARGIN = 8;
17228
+ var MENU_GAP2 = 8;
17229
+ var SVG_NS = "http://www.w3.org/2000/svg";
17230
+ var COMMAND_ICON_PATHS = [
17231
+ "M409.6 377.6h204.8a32 32 0 0 1 32 32v204.8a32 32 0 0 1-32 32H409.6a32 32 0 0 1-32-32V409.6a32 32 0 0 1 32-32z m172.8 64h-140.8v140.8h140.8z",
17232
+ "M332.8 800a108.8 108.8 0 0 1 0-217.6h76.8a32 32 0 0 1 32 32v76.8a108.928 108.928 0 0 1-108.8 108.8z m0-153.6a44.8 44.8 0 1 0 44.8 44.8v-44.8zM409.6 441.6H332.8a108.8 108.8 0 1 1 108.8-108.8v76.8a32 32 0 0 1-32 32z m-76.8-153.6a44.8 44.8 0 0 0 0 89.6h44.8V332.8A44.842667 44.842667 0 0 0 332.8 288zM691.2 441.6h-76.8a32 32 0 0 1-32-32V332.8a108.8 108.8 0 1 1 108.8 108.8z m-44.8-64h44.8a44.8 44.8 0 1 0-44.8-44.8zM691.2 800a108.928 108.928 0 0 1-108.8-108.8v-76.8a32 32 0 0 1 32-32h76.8a108.8 108.8 0 0 1 0 217.6z m-44.8-153.6v44.8a44.8 44.8 0 1 0 44.8-44.8z",
17233
+ "M640 970.666667H384c-118.186667 0-198.272-25.002667-251.946667-78.72S53.333333 758.186667 53.333333 640V384c0-118.186667 25.002667-198.272 78.72-251.946667S265.813333 53.333333 384 53.333333h256c118.186667 0 198.272 25.002667 251.946667 78.72S970.666667 265.813333 970.666667 384v256c0 118.186667-25.002667 198.272-78.72 251.946667S758.186667 970.666667 640 970.666667z m-256-853.333334c-100.096 0-165.802667 19.2-206.72 59.946667S117.333333 283.904 117.333333 384v256c0 100.096 19.072 165.802667 59.946667 206.72S283.904 906.666667 384 906.666667h256c100.096 0 165.802667-19.072 206.72-59.946667S906.666667 740.096 906.666667 640V384c0-100.096-19.072-165.802667-59.946667-206.72S740.096 117.333333 640 117.333333z"
17234
+ ];
17235
+ function commandIcon(ownerDocument) {
17236
+ const svg = ownerDocument.createElementNS(SVG_NS, "svg");
17237
+ svg.setAttribute("viewBox", "0 0 1024 1024");
17238
+ svg.setAttribute("width", "15");
17239
+ svg.setAttribute("height", "15");
17240
+ svg.setAttribute("fill", "currentColor");
17241
+ svg.setAttribute("aria-hidden", "true");
17242
+ for (const path of COMMAND_ICON_PATHS) {
17243
+ const element = ownerDocument.createElementNS(SVG_NS, "path");
17244
+ element.setAttribute("d", path);
17245
+ svg.append(element);
17246
+ }
17247
+ return svg;
17248
+ }
17249
+ function menuItem(ownerDocument, command, onSelect) {
17250
+ const item = ownerDocument.createElement("button");
17251
+ item.type = "button";
17252
+ item.setAttribute("role", "menuitem");
17253
+ item.setAttribute("data-command-id", command.id);
17254
+ item.setAttribute("aria-label", `${command.invocation} ${command.label}`);
17255
+ item.style.display = "flex";
17256
+ item.style.alignItems = "center";
17257
+ item.style.width = "100%";
17258
+ item.style.minHeight = "38px";
17259
+ item.style.gap = "8px";
17260
+ item.style.padding = "6px 8px";
17261
+ item.style.border = "0";
17262
+ item.style.borderRadius = "6px";
17263
+ item.style.background = "transparent";
17264
+ item.style.color = "inherit";
17265
+ item.style.textAlign = "left";
17266
+ item.style.cursor = "pointer";
17267
+ const updateHighlight = (active) => {
17268
+ item.style.background = active ? "rgba(127, 127, 127, 0.12)" : "transparent";
17269
+ };
17270
+ item.addEventListener("pointerenter", () => updateHighlight(true));
17271
+ item.addEventListener("pointerleave", () => updateHighlight(false));
17272
+ item.addEventListener("focus", () => updateHighlight(true));
17273
+ item.addEventListener("blur", () => updateHighlight(false));
17274
+ item.addEventListener("click", onSelect);
17275
+ const copy = ownerDocument.createElement("span");
17276
+ copy.style.display = "flex";
17277
+ copy.style.flexDirection = "column";
17278
+ copy.style.minWidth = "0";
17279
+ copy.style.flex = "1 1 auto";
17280
+ const title = ownerDocument.createElement("span");
17281
+ title.textContent = command.invocation;
17282
+ title.style.font = "600 13px/18px system-ui, sans-serif";
17283
+ title.style.whiteSpace = "nowrap";
17284
+ const description = ownerDocument.createElement("span");
17285
+ description.textContent = command.description ?? command.label;
17286
+ description.style.overflow = "hidden";
17287
+ description.style.color = "rgba(127, 127, 127, 0.9)";
17288
+ description.style.font = "400 11px/16px system-ui, sans-serif";
17289
+ description.style.textOverflow = "ellipsis";
17290
+ description.style.whiteSpace = "nowrap";
17291
+ const hint = ownerDocument.createElement("span");
17292
+ hint.textContent = command.argumentMode === "text" ? "Text" : "\u21B5";
17293
+ hint.style.flex = "0 0 auto";
17294
+ hint.style.color = "rgba(127, 127, 127, 0.75)";
17295
+ hint.style.font = "400 11px/16px ui-monospace, SFMono-Regular, Menlo, monospace";
17296
+ copy.append(title, description);
17297
+ item.append(copy, hint);
17298
+ return item;
17299
+ }
17300
+ function clamp(value, minimum, maximum) {
17301
+ return Math.max(minimum, Math.min(value, maximum));
17302
+ }
17303
+ function setButtonClass(button) {
17304
+ button.style.display = "inline-flex";
17305
+ button.style.alignItems = "center";
17306
+ button.style.justifyContent = "center";
17307
+ button.style.gap = "0";
17308
+ button.style.width = "28px";
17309
+ button.style.height = "28px";
17310
+ button.style.padding = "0";
17311
+ button.style.border = "0";
17312
+ button.style.borderRadius = "8px";
17313
+ button.style.background = "transparent";
17314
+ button.style.color = "inherit";
17315
+ button.style.cursor = "pointer";
17316
+ button.style.whiteSpace = "nowrap";
17317
+ }
17318
+ function mountRendererHarnessCommandControl(parent, insertBefore, onCommandSelected) {
17319
+ const ownerDocument = parent.ownerDocument;
17320
+ const root = ownerDocument.createElement("div");
17321
+ root.setAttribute(CONTROL_ATTRIBUTE2, "true");
17322
+ root.style.display = "inline-flex";
17323
+ root.style.alignItems = "center";
17324
+ root.style.minWidth = "0";
17325
+ const trigger = ownerDocument.createElement("button");
17326
+ trigger.type = "button";
17327
+ trigger.setAttribute("aria-haspopup", "menu");
17328
+ trigger.setAttribute("aria-expanded", "false");
17329
+ trigger.setAttribute("aria-label", "Harness commands");
17330
+ trigger.title = "Harness commands";
17331
+ setButtonClass(trigger);
17332
+ trigger.append(commandIcon(ownerDocument));
17333
+ root.append(trigger);
17334
+ const menu = ownerDocument.createElement("div");
17335
+ menu.setAttribute(MENU_ATTRIBUTE, "true");
17336
+ menu.setAttribute("role", "menu");
17337
+ menu.setAttribute("aria-label", "Harness commands");
17338
+ menu.hidden = true;
17339
+ menu.style.position = "fixed";
17340
+ menu.style.inset = "auto";
17341
+ menu.style.zIndex = "2147483647";
17342
+ menu.style.width = `${MENU_WIDTH}px`;
17343
+ menu.style.maxWidth = `calc(100vw - ${VIEWPORT_MARGIN * 2}px)`;
17344
+ menu.style.maxHeight = "min(360px, calc(100vh - 16px))";
17345
+ menu.style.overflowY = "auto";
17346
+ menu.style.padding = "4px";
17347
+ menu.style.border = "1px solid rgba(127, 127, 127, 0.24)";
17348
+ menu.style.borderRadius = "10px";
17349
+ menu.style.background = "Canvas";
17350
+ menu.style.color = "CanvasText";
17351
+ menu.style.boxShadow = "0 12px 32px rgba(0, 0, 0, 0.22)";
17352
+ ownerDocument.body.append(menu);
17353
+ if (insertBefore?.parentElement === parent) parent.insertBefore(root, insertBefore);
17354
+ else parent.append(root);
17355
+ let commands = [];
17356
+ let items = [];
17357
+ let activeIndex = 0;
17358
+ let executingCommandId = null;
17359
+ let triggerHovered = false;
17360
+ let disposed = false;
17361
+ const positionMenu2 = () => {
17362
+ const rect = trigger.getBoundingClientRect();
17363
+ const menuHeight = menu.getBoundingClientRect().height;
17364
+ const opensAbove = rect.top >= menuHeight + MENU_GAP2 + VIEWPORT_MARGIN;
17365
+ const left = clamp(
17366
+ rect.left,
17367
+ VIEWPORT_MARGIN,
17368
+ window.innerWidth - MENU_WIDTH - VIEWPORT_MARGIN
17369
+ );
17370
+ menu.style.left = `${left}px`;
17371
+ menu.style.top = opensAbove ? `${Math.max(VIEWPORT_MARGIN, rect.top - menuHeight - MENU_GAP2)}px` : `${Math.min(window.innerHeight - menuHeight - VIEWPORT_MARGIN, rect.bottom + MENU_GAP2)}px`;
17372
+ };
17373
+ const focusActive = () => {
17374
+ const item = items[activeIndex];
17375
+ if (!item || item.disabled) return;
17376
+ item.focus();
17377
+ item.scrollIntoView({ block: "nearest" });
17378
+ };
17379
+ const syncTriggerBackground = () => {
17380
+ trigger.style.background = !trigger.disabled && (triggerHovered || !menu.hidden) ? "rgba(127, 127, 127, 0.16)" : "transparent";
17381
+ };
17382
+ const close = () => {
17383
+ menu.hidden = true;
17384
+ trigger.setAttribute("aria-expanded", "false");
17385
+ syncTriggerBackground();
17386
+ };
17387
+ const open = (shouldFocus = true) => {
17388
+ if (commands.length === 0 || executingCommandId !== null) return;
17389
+ menu.hidden = false;
17390
+ positionMenu2();
17391
+ trigger.setAttribute("aria-expanded", "true");
17392
+ syncTriggerBackground();
17393
+ if (shouldFocus) queueMicrotask(focusActive);
17394
+ };
17395
+ let closeTimer = null;
17396
+ const cancelClose = () => {
17397
+ if (closeTimer === null) return;
17398
+ window.clearTimeout(closeTimer);
17399
+ closeTimer = null;
17400
+ };
17401
+ const scheduleClose = () => {
17402
+ cancelClose();
17403
+ closeTimer = window.setTimeout(() => {
17404
+ closeTimer = null;
17405
+ if (!trigger.matches(":hover") && !menu.matches(":hover")) close();
17406
+ }, 140);
17407
+ };
17408
+ const select = (command) => {
17409
+ close();
17410
+ onCommandSelected(command);
17411
+ };
17412
+ const renderItems = () => {
17413
+ menu.replaceChildren();
17414
+ const header = ownerDocument.createElement("div");
17415
+ header.textContent = "Commands";
17416
+ header.style.padding = "5px 8px 4px";
17417
+ header.style.color = "rgba(127, 127, 127, 0.75)";
17418
+ header.style.font = "600 11px/16px system-ui, sans-serif";
17419
+ menu.append(header);
17420
+ items = commands.map((command) => menuItem(ownerDocument, command, () => select(command)));
17421
+ menu.append(...items);
17422
+ activeIndex = Math.min(activeIndex, Math.max(0, items.length - 1));
17423
+ if (executingCommandId !== null) {
17424
+ for (const item of items) {
17425
+ const isExecuting = item.dataset.commandId === executingCommandId;
17426
+ item.disabled = true;
17427
+ item.style.opacity = isExecuting ? "1" : "0.5";
17428
+ if (isExecuting) item.setAttribute("aria-busy", "true");
17429
+ }
17430
+ }
17431
+ };
17432
+ const onTriggerKeyDown = (event) => {
17433
+ if (event.key === "ArrowDown" || event.key === "Enter" || event.key === " ") {
17434
+ event.preventDefault();
17435
+ open();
17436
+ }
17437
+ };
17438
+ const onMenuKeyDown = (event) => {
17439
+ if (event.key === "Escape") {
17440
+ event.preventDefault();
17441
+ close();
17442
+ trigger.focus();
17443
+ return;
17444
+ }
17445
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
17446
+ event.preventDefault();
17447
+ const delta = event.key === "ArrowDown" ? 1 : -1;
17448
+ activeIndex = (activeIndex + delta + items.length) % items.length;
17449
+ focusActive();
17450
+ return;
17451
+ }
17452
+ if (event.key === "Enter") {
17453
+ event.preventDefault();
17454
+ items[activeIndex]?.click();
17455
+ }
17456
+ };
17457
+ const onDocumentPointerDown = (event) => {
17458
+ if (!menu.hidden && !root.contains(event.target) && !menu.contains(event.target)) {
17459
+ close();
17460
+ }
17461
+ };
17462
+ const onViewportChange = () => {
17463
+ if (!menu.hidden) positionMenu2();
17464
+ };
17465
+ trigger.addEventListener("click", () => {
17466
+ cancelClose();
17467
+ open(true);
17468
+ });
17469
+ trigger.addEventListener("pointerenter", () => {
17470
+ triggerHovered = true;
17471
+ syncTriggerBackground();
17472
+ cancelClose();
17473
+ if (menu.hidden) open(false);
17474
+ });
17475
+ trigger.addEventListener("pointerleave", () => {
17476
+ triggerHovered = false;
17477
+ syncTriggerBackground();
17478
+ scheduleClose();
17479
+ });
17480
+ trigger.addEventListener("keydown", onTriggerKeyDown);
17481
+ menu.addEventListener("pointerenter", cancelClose);
17482
+ menu.addEventListener("pointerleave", scheduleClose);
17483
+ menu.addEventListener("keydown", onMenuKeyDown);
17484
+ ownerDocument.addEventListener("pointerdown", onDocumentPointerDown, true);
17485
+ ownerDocument.defaultView?.addEventListener("resize", onViewportChange);
17486
+ ownerDocument.defaultView?.addEventListener("scroll", onViewportChange, true);
17487
+ const control = {
17488
+ root,
17489
+ trigger,
17490
+ menu,
17491
+ placeBefore(reference) {
17492
+ if (!reference?.parentElement) return false;
17493
+ if (root.parentElement === reference.parentElement && root.nextElementSibling === reference) {
17494
+ return true;
17495
+ }
17496
+ reference.parentElement.insertBefore(root, reference);
17497
+ return true;
17498
+ },
17499
+ setCommands(nextCommands) {
17500
+ commands = [...nextCommands];
17501
+ root.hidden = commands.length === 0;
17502
+ if (commands.length === 0) close();
17503
+ renderItems();
17504
+ },
17505
+ setExecuting(commandId) {
17506
+ executingCommandId = commandId;
17507
+ for (const item of items) {
17508
+ const isExecuting = item.dataset.commandId === commandId;
17509
+ item.disabled = commandId !== null;
17510
+ item.style.opacity = commandId !== null && !isExecuting ? "0.5" : "1";
17511
+ if (isExecuting) item.setAttribute("aria-busy", "true");
17512
+ else item.removeAttribute("aria-busy");
17513
+ }
17514
+ trigger.disabled = commandId !== null;
17515
+ trigger.style.opacity = commandId !== null ? "0.65" : "1";
17516
+ syncTriggerBackground();
17517
+ },
17518
+ close,
17519
+ dispose() {
17520
+ if (disposed) return;
17521
+ disposed = true;
17522
+ cancelClose();
17523
+ close();
17524
+ ownerDocument.removeEventListener("pointerdown", onDocumentPointerDown, true);
17525
+ ownerDocument.defaultView?.removeEventListener("resize", onViewportChange);
17526
+ ownerDocument.defaultView?.removeEventListener("scroll", onViewportChange, true);
17527
+ trigger.removeEventListener("keydown", onTriggerKeyDown);
17528
+ menu.removeEventListener("pointerenter", cancelClose);
17529
+ menu.removeEventListener("pointerleave", scheduleClose);
17530
+ menu.removeEventListener("keydown", onMenuKeyDown);
17531
+ menu.remove();
17532
+ root.remove();
17533
+ }
17534
+ };
17535
+ root.hidden = true;
17536
+ return control;
17537
+ }
17538
+
16931
17539
  // src/renderer-composer-dom.ts
16932
17540
  var CODEX_COMPOSER_SELECTOR = "[data-codex-composer-root]";
16933
17541
  var EDITOR_SELECTOR = 'textarea, [contenteditable="true"], [role="textbox"]';
@@ -16935,23 +17543,86 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
16935
17543
  if (target instanceof Element) return target;
16936
17544
  return target instanceof Node ? target.parentElement : null;
16937
17545
  }
17546
+ function controlDescription(element) {
17547
+ const typed = element;
17548
+ const read = (name) => typeof element.getAttribute === "function" ? element.getAttribute(name) : null;
17549
+ return [typed.type, read("aria-label"), read("title"), read("data-testid")].filter((value) => typeof value === "string").join(" ").toLowerCase();
17550
+ }
16938
17551
  function buttonText(button) {
16939
- return [
16940
- button.type,
16941
- button.getAttribute("aria-label"),
16942
- button.getAttribute("title"),
16943
- button.getAttribute("data-testid")
16944
- ].filter((value) => typeof value === "string").join(" ").toLowerCase();
17552
+ return controlDescription(button);
17553
+ }
17554
+ function isOwnedRendererControl(element) {
17555
+ return element.hasAttribute(CONTROL_ATTRIBUTE) || element.hasAttribute("data-codexhost-model-control") || element.hasAttribute("data-codexhost-permission-mode-control") || element.hasAttribute("data-codexhost-usage-control") || element.hasAttribute("data-codexhost-credits-control") || element.hasAttribute("data-codexhost-harness-command-control");
16945
17556
  }
16946
17557
  function isComposerSubmitButton(button) {
16947
17558
  if (button.type === "submit") return true;
16948
17559
  return /(^|\s)(send|submit|发送|提交)(\s|$)/u.test(buttonText(button));
16949
17560
  }
17561
+ var VOICE_CONTROL_PATTERN = /(dictat|microphone|speech(?:[-_\s]?to[-_\s]?text)?|voice[-_\s]?input|(^|\s)voice(\s|$)|composer[-_](?:speech|dictat|mic)|语音|听写|麦克风|pause|暂停|stop recording|stop dictation|停止录音|停止听写|(^|\s)stop(\s|$))/iu;
17562
+ var CANCEL_CONTROL_PATTERN = /(cancel|discard|close|dismiss|取消|关闭|丢弃)/iu;
17563
+ var ATTACH_CONTROL_PATTERN = /(add files|attach files|attach file|attachment|composer[-_]attach|添加文件|附件)/iu;
17564
+ var TRAILING_ACTION_WALK_DEPTH = 3;
17565
+ function isComposerCancelButton(element) {
17566
+ if (isOwnedRendererControl(element)) return false;
17567
+ return CANCEL_CONTROL_PATTERN.test(controlDescription(element));
17568
+ }
17569
+ function isComposerVoiceButton(element) {
17570
+ if (isOwnedRendererControl(element) || isComposerCancelButton(element)) return false;
17571
+ const description = controlDescription(element);
17572
+ if (/(^|\s)(send|submit|发送|提交)(\s|$)/u.test(description)) return false;
17573
+ return VOICE_CONTROL_PATTERN.test(description);
17574
+ }
17575
+ function isComposerAttachButton(element) {
17576
+ if (isOwnedRendererControl(element)) return false;
17577
+ return ATTACH_CONTROL_PATTERN.test(controlDescription(element));
17578
+ }
17579
+ function isComposerTrailingActionButton(element) {
17580
+ return isComposerVoiceButton(element) || isComposerSubmitButton(element);
17581
+ }
17582
+ function isTrailingActionNode(element) {
17583
+ if (isComposerCancelButton(element)) return false;
17584
+ if (isComposerTrailingActionButton(element)) return true;
17585
+ if (typeof element.querySelectorAll !== "function") return false;
17586
+ const buttons = [...element.querySelectorAll("button")];
17587
+ return buttons.length > 0 && buttons.every((button) => isComposerTrailingActionButton(button));
17588
+ }
17589
+ function attachControlWithin(root) {
17590
+ if (isComposerAttachButton(root)) return root;
17591
+ if (typeof root.querySelectorAll !== "function") return null;
17592
+ const matches = [
17593
+ ...root.querySelectorAll("button, [aria-label], [title], [data-testid]")
17594
+ ].filter(isComposerAttachButton);
17595
+ return matches.length === 1 ? matches[0] ?? null : null;
17596
+ }
16950
17597
  function sendButtonWithin(root) {
16951
17598
  return [...root.querySelectorAll("button")].find(
16952
17599
  (button) => isComposerSubmitButton(button)
16953
17600
  ) ?? null;
16954
17601
  }
17602
+ function leftmostTrailingSibling(container, before) {
17603
+ for (const child of container.children) {
17604
+ if (child === before) break;
17605
+ if (typeof child.hasAttribute !== "function") continue;
17606
+ const element = child;
17607
+ if (isOwnedRendererControl(element) || isComposerCancelButton(element)) continue;
17608
+ if (isTrailingActionNode(element)) return element;
17609
+ }
17610
+ return null;
17611
+ }
17612
+ function trailingActionAnchor(sendButton) {
17613
+ let container = sendButton.parentElement;
17614
+ let before = sendButton;
17615
+ for (let depth = 0; container && depth < TRAILING_ACTION_WALK_DEPTH; depth += 1) {
17616
+ if (typeof container.matches === "function" && container.matches(CODEX_COMPOSER_SELECTOR)) {
17617
+ break;
17618
+ }
17619
+ const candidate = leftmostTrailingSibling(container, before);
17620
+ if (candidate) return candidate;
17621
+ before = container;
17622
+ container = container.parentElement;
17623
+ }
17624
+ return sendButton;
17625
+ }
16955
17626
  function editorForElement(element) {
16956
17627
  return element.matches(EDITOR_SELECTOR) ? element : element.closest(EDITOR_SELECTOR);
16957
17628
  }
@@ -17097,7 +17768,9 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17097
17768
  for (const child of parent.children) {
17098
17769
  if (typeof child.hasAttribute !== "function") continue;
17099
17770
  const element = child;
17100
- if (element.hasAttribute("data-codexhost-credits-control")) continue;
17771
+ if (element.hasAttribute("data-codexhost-credits-control") || element.hasAttribute("data-codexhost-harness-command-control") || isTrailingActionNode(element)) {
17772
+ continue;
17773
+ }
17101
17774
  return element;
17102
17775
  }
17103
17776
  return null;
@@ -17108,12 +17781,28 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17108
17781
  const parent = current.parentElement;
17109
17782
  if (!parent) break;
17110
17783
  const first = firstMaterialChild(parent);
17111
- if (first && first !== current && !first.contains(usageRoot)) return first;
17784
+ if (first && first !== current && !first.contains(usageRoot)) {
17785
+ return attachControlWithin(first) ?? first;
17786
+ }
17112
17787
  if (parent === composer) break;
17113
17788
  current = parent;
17114
17789
  }
17115
17790
  return null;
17116
17791
  }
17792
+ function refreshTrailingClusterPlacement(control) {
17793
+ const sendButton = control.sendButton;
17794
+ const modelRoot = control.modelPicker?.root;
17795
+ const agentRoot = control.root ?? control.picker?.root;
17796
+ if (!sendButton || !modelRoot || !agentRoot) return;
17797
+ const anchor = trailingActionAnchor(sendButton);
17798
+ const parent = anchor.parentElement;
17799
+ if (!parent || typeof parent.insertBefore !== "function") return;
17800
+ if (modelRoot.parentElement === parent && agentRoot.parentElement === parent && modelRoot.nextElementSibling === agentRoot && agentRoot.nextElementSibling === anchor) {
17801
+ return;
17802
+ }
17803
+ parent.insertBefore(modelRoot, anchor);
17804
+ parent.insertBefore(agentRoot, anchor);
17805
+ }
17117
17806
  function refreshUsagePlacement(control) {
17118
17807
  const anchor = usagePlacementAnchor(control);
17119
17808
  if (!anchor) {
@@ -17123,7 +17812,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17123
17812
  control.credits.anchor = null;
17124
17813
  return;
17125
17814
  }
17815
+ const previousUsageParent = control.usage.root.parentElement;
17816
+ const previousUsageNextSibling = control.usage.root.nextElementSibling;
17126
17817
  control.usage.place(anchor);
17818
+ const usagePositionChanged = previousUsageParent !== control.usage.root.parentElement || previousUsageNextSibling !== control.usage.root.nextElementSibling;
17819
+ if (usagePositionChanged) control.harnessCommands?.placeBefore(control.usage.root);
17127
17820
  const leading = creditsPlacementAnchor(control.composer, control.usage.root);
17128
17821
  if (!leading) {
17129
17822
  if (control.credits.anchor) control.credits.root.remove();
@@ -17162,12 +17855,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17162
17855
  }
17163
17856
  function reconcileComposerNativeControls(control, hideModel, hidePermissionMode) {
17164
17857
  refreshNativeModelControl(control);
17858
+ refreshTrailingClusterPlacement(control);
17165
17859
  refreshUsagePlacement(control);
17166
17860
  refreshNativePermissionModeControl(control);
17167
17861
  setNativeControlHidden(control.nativeModelControl, hideModel);
17168
17862
  setNativeControlHidden(control.nativePermissionModeControl, hidePermissionMode);
17169
17863
  }
17170
- function mountComposerAgentControl(composer, composerId, sendButton, enabledAgents, onSelect, onDownload, onSelectModel, onSelectThinking, onSelectPermissionMode) {
17864
+ function mountComposerAgentControl(composer, composerId, sendButton, enabledAgents, onSelect, onDownload, onSelectModel, onSelectThinking, onSelectPermissionMode, onSelectCommand) {
17171
17865
  const nativeModelControl = captureNativeControl(nativeModelControlForComposer(composer));
17172
17866
  const semanticNativePermissionModeControl = semanticNativePermissionModeControlForComposer(composer);
17173
17867
  const nativePermissionModeControl = captureNativeControl(semanticNativePermissionModeControl);
@@ -17186,19 +17880,19 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17186
17880
  );
17187
17881
  const usage = mountRendererUsageControl(composerId, nativeModelControl?.element.className);
17188
17882
  const credits = mountRendererCreditsControl(composerId, nativeModelControl?.element.className);
17883
+ const toolbar = sendButton.parentElement;
17884
+ const harnessCommands = mountRendererHarnessCommandControl(
17885
+ toolbar ?? composer,
17886
+ trailingActionAnchor(sendButton),
17887
+ onSelectCommand
17888
+ );
17189
17889
  const permissionParent = nativePermissionModeControl?.element.parentElement;
17190
17890
  if (permissionParent && nativePermissionModeControl && nativePermissionModeControlVerified) {
17191
17891
  permissionParent.insertBefore(permissionModePicker.root, nativePermissionModeControl.element);
17192
17892
  } else {
17193
17893
  composer.append(permissionModePicker.root);
17194
17894
  }
17195
- const toolbar = sendButton.parentElement;
17196
- if (toolbar) {
17197
- toolbar.insertBefore(modelPicker.root, sendButton);
17198
- toolbar.insertBefore(picker.root, sendButton);
17199
- } else {
17200
- composer.append(modelPicker.root, picker.root);
17201
- }
17895
+ if (!toolbar) composer.append(modelPicker.root, picker.root);
17202
17896
  const control = {
17203
17897
  composer,
17204
17898
  root: picker.root,
@@ -17210,9 +17904,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17210
17904
  nativePermissionModeControlVerified,
17211
17905
  credits,
17212
17906
  usage,
17907
+ harnessCommands,
17213
17908
  sendButton,
17214
17909
  sendDisabledBeforeSwitch: null
17215
17910
  };
17911
+ refreshTrailingClusterPlacement(control);
17216
17912
  refreshUsagePlacement(control);
17217
17913
  return control;
17218
17914
  }
@@ -17264,6 +17960,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17264
17960
  restoreNativeControl(control.nativePermissionModeControl);
17265
17961
  control.credits.dispose();
17266
17962
  control.usage.dispose();
17963
+ control.harnessCommands.dispose();
17267
17964
  control.permissionModePicker.dispose();
17268
17965
  control.modelPicker.dispose();
17269
17966
  control.picker.dispose();
@@ -17701,6 +18398,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17701
18398
  var HARNESS_INSPECT_METHOD = "codexhost/harness/inspect";
17702
18399
  var THREAD_FORK_METHOD = "codexhost/thread/fork";
17703
18400
  var THREAD_INSPECT_METHOD = "codexhost/thread/inspect";
18401
+ var THREAD_COMMANDS_INSPECT_METHOD = "codexhost/thread/commands/inspect";
18402
+ var THREAD_COMMAND_EXECUTE_METHOD = "codexhost/thread/command/execute";
17704
18403
  var THREAD_MODEL_SELECT_METHOD = "codexhost/thread/model/select";
17705
18404
  var THREAD_THINKING_SELECT_METHOD = "codexhost/thread/thinking/select";
17706
18405
  var THREAD_PERMISSION_MODE_SELECT_METHOD = "codexhost/thread/permission-mode/select";
@@ -17722,6 +18421,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17722
18421
  const parsed = hostThreadIdSchema.safeParse(params.threadId);
17723
18422
  return parsed.success ? parsed.data : null;
17724
18423
  }
18424
+ function usageNotificationTarget(manager) {
18425
+ if (typeof manager.addNotificationCallback === "function") return manager;
18426
+ const nested = manager.requestClient;
18427
+ return nested && typeof nested.addNotificationCallback === "function" ? nested : null;
18428
+ }
17725
18429
  function createThreadUsageSubscriptionRelay() {
17726
18430
  const listeners = /* @__PURE__ */ new Set();
17727
18431
  let removeNotificationCallback = null;
@@ -17763,6 +18467,16 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17763
18467
  const result = await manager.sendRequest(HARNESS_INSPECT_METHOD, params);
17764
18468
  return harnessInspectionSchema.parse(result);
17765
18469
  };
18470
+ const inspectThreadCommands = async (input) => {
18471
+ const params = threadCommandsInspectParamsSchema.parse(input);
18472
+ const result = await manager.sendRequest(THREAD_COMMANDS_INSPECT_METHOD, params);
18473
+ return harnessCommandCatalogSchema.parse(result);
18474
+ };
18475
+ const executeThreadCommand = async (input) => {
18476
+ const params = threadCommandExecuteParamsSchema.parse(input);
18477
+ const result = await manager.sendRequest(THREAD_COMMAND_EXECUTE_METHOD, params);
18478
+ return threadCommandExecuteResultSchema.parse(result);
18479
+ };
17766
18480
  const inspectThreadUsage = async (input) => {
17767
18481
  const params = threadUsageInspectionParamsSchema.parse(input);
17768
18482
  const result = await manager.sendRequest(THREAD_USAGE_INSPECT_METHOD, params);
@@ -17795,6 +18509,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17795
18509
  const result = await manager.sendRequest(THREAD_INSPECT_METHOD, params);
17796
18510
  return threadInspectionSchema.parse(result);
17797
18511
  },
18512
+ inspectThreadCommands,
18513
+ executeThreadCommand,
17798
18514
  async listThreadOwnership(input) {
17799
18515
  const params = threadOwnershipListParamsSchema.parse(input);
17800
18516
  const value = await manager.sendRequest(THREAD_OWNERSHIP_LIST_METHOD, params);
@@ -17806,10 +18522,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17806
18522
  },
17807
18523
  inspectThreadUsage,
17808
18524
  subscribeThreadUsage(listener) {
17809
- if (typeof manager.addNotificationCallback !== "function") return () => void 0;
18525
+ const notifications = usageNotificationTarget(manager);
18526
+ if (!notifications?.addNotificationCallback) {
18527
+ throw new Error("Renderer Usage notification callback is unavailable");
18528
+ }
17810
18529
  let disposed = false;
17811
18530
  const generations = /* @__PURE__ */ new Map();
17812
- const removeNotificationCallback = manager.addNotificationCallback(
18531
+ const removeNotificationCallback = notifications.addNotificationCallback(
17813
18532
  THREAD_TOKEN_USAGE_UPDATED_METHOD,
17814
18533
  (notification) => {
17815
18534
  const threadId = notifiedThreadId(notification);
@@ -17910,28 +18629,38 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17910
18629
  }
17911
18630
  return `${CLAUDE_CODE_TRANSPORT_MODEL_PREFIX}${parsedModel.id}${parsedPermissionMode ? `@${parsedPermissionMode}` : ""}`;
17912
18631
  }
17913
- function grokTransportModelId(model, thinkingOptionId) {
18632
+ function grokTransportModelId(model, permissionModeId, thinkingOptionId) {
17914
18633
  if (!model) {
17915
- if (thinkingOptionId) throw new Error("Grok transport Thinking requires a Model Ref");
18634
+ if (permissionModeId || thinkingOptionId) {
18635
+ throw new Error("Grok transport configuration requires a Model Ref");
18636
+ }
17916
18637
  return GROK_TRANSPORT_MODEL_ID;
17917
18638
  }
17918
18639
  const parsedModel = harnessModelRefSchema.parse(model);
18640
+ const parsedPermissionMode = permissionModeId ? harnessPermissionModeIdSchema.parse(permissionModeId) : void 0;
17919
18641
  const parsedThinking = thinkingOptionId ? harnessThinkingOptionIdSchema.parse(thinkingOptionId) : void 0;
17920
- return `${GROK_TRANSPORT_MODEL_PREFIX}${parsedModel.id}${parsedThinking ? `@@${parsedThinking}` : ""}`;
18642
+ if (parsedThinking) {
18643
+ return `${GROK_TRANSPORT_MODEL_PREFIX}${parsedModel.id}@${parsedPermissionMode ?? ""}@${parsedThinking}`;
18644
+ }
18645
+ return `${GROK_TRANSPORT_MODEL_PREFIX}${parsedModel.id}${parsedPermissionMode ? `@${parsedPermissionMode}` : ""}`;
17921
18646
  }
17922
18647
  function decodeGrokTransportModelId(value) {
17923
18648
  if (value === GROK_TRANSPORT_MODEL_ID) return {};
17924
18649
  if (typeof value !== "string" || !value.startsWith(GROK_TRANSPORT_MODEL_PREFIX)) return null;
17925
18650
  const components = value.slice(GROK_TRANSPORT_MODEL_PREFIX.length).split("@");
17926
- if (components.length !== 1 && components.length !== 3) return null;
17927
- const [modelId, emptyPermissionMode, thinkingOptionId] = components;
17928
- if (components.length === 3 && (emptyPermissionMode !== "" || !thinkingOptionId)) return null;
18651
+ if (components.length < 1 || components.length > 3) return null;
18652
+ const [modelId, permissionModeId, thinkingOptionId] = components;
18653
+ if (components.length === 2 && !permissionModeId) return null;
18654
+ if (components.length === 3 && !thinkingOptionId) return null;
17929
18655
  const model = harnessModelRefSchema.safeParse({ id: modelId });
17930
18656
  if (!model.success) return null;
18657
+ const permissionMode = permissionModeId ? harnessPermissionModeIdSchema.safeParse(permissionModeId) : null;
18658
+ if (permissionMode && !permissionMode.success) return null;
17931
18659
  const thinking = thinkingOptionId ? harnessThinkingOptionIdSchema.safeParse(thinkingOptionId) : null;
17932
18660
  if (thinking && !thinking.success) return null;
17933
18661
  return {
17934
18662
  model: model.data,
18663
+ ...permissionMode?.success ? { permissionModeId: permissionMode.data } : {},
17935
18664
  ...thinking?.success ? { thinkingOptionId: thinking.data } : {}
17936
18665
  };
17937
18666
  }
@@ -17971,14 +18700,21 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17971
18700
  });
17972
18701
  return model.success ? { model: model.data } : null;
17973
18702
  }
17974
- function isPiTransportModelId(value) {
17975
- if (value === PI_TRANSPORT_MODEL_ID) return true;
17976
- if (typeof value !== "string" || !value.startsWith(PI_TRANSPORT_MODEL_PREFIX)) return false;
18703
+ function decodePiTransportModelId(value) {
18704
+ if (value === PI_TRANSPORT_MODEL_ID) return {};
18705
+ if (typeof value !== "string" || !value.startsWith(PI_TRANSPORT_MODEL_PREFIX)) return null;
17977
18706
  const components = value.slice(PI_TRANSPORT_MODEL_PREFIX.length).split("@");
17978
- if (components.length < 1 || components.length > 2) return false;
18707
+ if (components.length < 1 || components.length > 2) return null;
17979
18708
  const [modelId, thinkingOptionId] = components;
17980
- if (!harnessModelRefSchema.safeParse({ id: modelId }).success) return false;
17981
- return components.length === 1 || thinkingOptionId !== void 0 && harnessThinkingOptionIdSchema.safeParse(thinkingOptionId).success;
18709
+ if (components.length === 2 && !thinkingOptionId) return null;
18710
+ const model = harnessModelRefSchema.safeParse({ id: modelId });
18711
+ if (!model.success) return null;
18712
+ const thinking = thinkingOptionId ? harnessThinkingOptionIdSchema.safeParse(thinkingOptionId) : null;
18713
+ if (thinking && !thinking.success) return null;
18714
+ return {
18715
+ model: model.data,
18716
+ ...thinking?.success ? { thinkingOptionId: thinking.data } : {}
18717
+ };
17982
18718
  }
17983
18719
  function threadIdFromComposerModelTarget(target) {
17984
18720
  if (target?.[0] !== "conversation" || typeof target[1] !== "string" || target[1].trim().length === 0) {
@@ -17996,7 +18732,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17996
18732
  }
17997
18733
  function matchesCurrentPrewarmSignature(target) {
17998
18734
  const bridge = target.requestClient ?? target;
17999
- const stableApiShape = bridge.hostId === "local" && typeof bridge.sendRequest === "function" && typeof bridge.prewarmThreadStart === "function" && typeof bridge.enqueueRequest === "function";
18735
+ const stableApiShape = typeof bridge.hostId === "string" && bridge.hostId.length > 0 && typeof bridge.sendRequest === "function" && typeof bridge.prewarmThreadStart === "function" && typeof bridge.enqueueRequest === "function";
18000
18736
  if (stableApiShape) return true;
18001
18737
  const prewarm = target.prewarmThreadStart ?? target.requestClient?.prewarmThreadStart;
18002
18738
  if (!prewarm) return false;
@@ -18034,7 +18770,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
18034
18770
  if (isActiveRequestManager(hookState) && matchesCurrentPrewarmSignature(hookState)) {
18035
18771
  targets.add(hookState);
18036
18772
  } else if (hasPrewarmMethod(requestClient) && matchesCurrentPrewarmSignature(requestClient)) {
18037
- targets.add(requestClient);
18773
+ targets.add(typeof hookState.sendRequest === "function" ? hookState : requestClient);
18038
18774
  }
18039
18775
  }
18040
18776
  hook = typeof hook.next === "object" && hook.next !== null ? hook.next : null;
@@ -18087,10 +18823,17 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
18087
18823
  return false;
18088
18824
  }
18089
18825
  }
18090
- function findComposerModelTarget(composer) {
18091
- const conversationThreadId = findComposerConversationThreadId(composer);
18092
- if (conversationThreadId === null) return null;
18093
- if (conversationThreadId !== void 0) return ["conversation", conversationThreadId];
18826
+ function findComposerDomIdentity(composer) {
18827
+ const children = Array.from(composer.children ?? []);
18828
+ const portals = children.filter((child) => child.hasAttribute("data-above-composer-portal"));
18829
+ if (portals.length === 0) return { kind: "unsupported" };
18830
+ if (portals.length !== 1) return { kind: "ambiguous" };
18831
+ const value = portals[0]?.getAttribute("data-above-composer-conversation-id");
18832
+ if (value === null) return { kind: "draft" };
18833
+ const candidate = hostThreadIdSchema.safeParse(value);
18834
+ return candidate.success ? { kind: "conversation", threadId: candidate.data } : { kind: "ambiguous" };
18835
+ }
18836
+ function findComposerDraftIds(composer) {
18094
18837
  const draftIds = /* @__PURE__ */ new Set();
18095
18838
  let fiber = findComposerFiber(composer);
18096
18839
  for (let depth = 0; fiber && depth < 120; depth += 1) {
@@ -18105,6 +18848,21 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
18105
18848
  const parent = fiber.return;
18106
18849
  fiber = (typeof parent === "object" || typeof parent === "function") && parent !== null ? parent : null;
18107
18850
  }
18851
+ return draftIds;
18852
+ }
18853
+ function findComposerModelTarget(composer) {
18854
+ const draftIds = findComposerDraftIds(composer);
18855
+ const domIdentity = findComposerDomIdentity(composer);
18856
+ if (domIdentity.kind === "ambiguous") return null;
18857
+ if (domIdentity.kind === "conversation") {
18858
+ return ["conversation", domIdentity.threadId];
18859
+ }
18860
+ if (domIdentity.kind === "draft") {
18861
+ return draftIds.size === 1 ? ["default", draftIds.values().next().value] : null;
18862
+ }
18863
+ const conversationThreadId = findComposerConversationThreadId(composer);
18864
+ if (conversationThreadId === null) return null;
18865
+ if (conversationThreadId !== void 0) return ["conversation", conversationThreadId];
18108
18866
  if (draftIds.size !== 1) return null;
18109
18867
  return ["default", draftIds.values().next().value];
18110
18868
  }
@@ -18112,7 +18870,34 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
18112
18870
  return isRecord5(value) && value.state === "ready";
18113
18871
  }
18114
18872
  function isDraftPrewarmPolicyReady(value) {
18115
- return isRecord5(value) && value.state === "ready" && typeof value.select === "function" && typeof value.clear === "function";
18873
+ return isRecord5(value) && value.state === "ready" && typeof value.hostId === "string" && value.hostId.length > 0 && typeof value.select === "function" && typeof value.clear === "function";
18874
+ }
18875
+ function activeRendererDraftPrewarmTargets(policy, targets) {
18876
+ if (!isDraftPrewarmPolicyReady(policy)) return null;
18877
+ const activeTargets = targets.filter((target) => {
18878
+ const bridge = target.requestClient ?? target;
18879
+ return (target.getHostId?.() ?? bridge.hostId) === policy.hostId;
18880
+ });
18881
+ return activeTargets.length === 1 ? activeTargets : null;
18882
+ }
18883
+ function resolveRendererRequestRoute(policy, discoveredTargets, previous) {
18884
+ const activeTargets = activeRendererDraftPrewarmTargets(policy, discoveredTargets);
18885
+ if (isDraftPrewarmPolicyReady(policy) && activeTargets) {
18886
+ return { policy, targets: activeTargets };
18887
+ }
18888
+ return discoveredTargets.length === 0 && isDraftPrewarmPolicyReady(policy) && previous?.policy === policy ? previous : null;
18889
+ }
18890
+ function createRendererRequestRouteResolver(readPolicy, discoverTargets) {
18891
+ let route = null;
18892
+ return {
18893
+ resolve() {
18894
+ route = resolveRendererRequestRoute(readPolicy(), discoverTargets(), route);
18895
+ return route;
18896
+ },
18897
+ clear() {
18898
+ route = null;
18899
+ }
18900
+ };
18116
18901
  }
18117
18902
  async function waitForRendererDraftPrewarmPolicy(target) {
18118
18903
  const deadline = Date.now() + DRAFT_PREWARM_POLICY_WAIT_TIMEOUT_MS;
@@ -18127,7 +18912,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
18127
18912
  }
18128
18913
  }
18129
18914
  function modelSelectionForAgent(officialSelection, reasoningEffort, agent, model, thinkingOptionId, permissionModeId) {
18130
- const transportModelId = agent === "pi" ? piTransportModelId(model, thinkingOptionId) : agent === "claude-code" ? claudeTransportModelId(model, permissionModeId, thinkingOptionId) : agent === "deepseek-harness" ? deepSeekHarnessTransportModelId(model) : agent === "grok" ? grokTransportModelId(model, thinkingOptionId) : transportModelIdForAgent(agent);
18915
+ const transportModelId = agent === "pi" ? piTransportModelId(model, thinkingOptionId) : agent === "claude-code" ? claudeTransportModelId(model, permissionModeId, thinkingOptionId) : agent === "deepseek-harness" ? deepSeekHarnessTransportModelId(model) : agent === "grok" ? grokTransportModelId(model, permissionModeId, thinkingOptionId) : transportModelIdForAgent(agent);
18131
18916
  return transportModelId ? { model: transportModelId, reasoningEffort } : officialSelection;
18132
18917
  }
18133
18918
  function installCurrentRendererAdapter() {
@@ -18153,8 +18938,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
18153
18938
  }
18154
18939
  });
18155
18940
  const usageSubscription = createThreadUsageSubscriptionRelay();
18941
+ const requestRouteResolver = createRendererRequestRouteResolver(
18942
+ () => window.__codexhostDraftPrewarmPolicyV1,
18943
+ () => findActivePrewarmTargets(document)
18944
+ );
18945
+ const currentRequestRoute = () => requestRouteResolver.resolve();
18156
18946
  const currentModelClient = () => {
18157
- const client = createRendererModelClient(findActivePrewarmTargets(document));
18947
+ const client = createRendererModelClient(currentRequestRoute()?.targets ?? []);
18158
18948
  if (!client) throw new Error("Renderer Model request manager is unavailable");
18159
18949
  usageSubscription.connect(client);
18160
18950
  return client;
@@ -18163,6 +18953,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
18163
18953
  forkThread: (input) => currentModelClient().forkThread(input),
18164
18954
  inspectHarness: (input) => currentModelClient().inspectHarness(input),
18165
18955
  inspectThread: (input) => currentModelClient().inspectThread(input),
18956
+ inspectThreadCommands: (input) => currentModelClient().inspectThreadCommands(input),
18957
+ executeThreadCommand: (input) => currentModelClient().executeThreadCommand(input),
18166
18958
  inspectThreadUsage: (input) => currentModelClient().inspectThreadUsage(input),
18167
18959
  subscribeThreadUsage: (listener) => usageSubscription.subscribe(listener),
18168
18960
  listThreadOwnership: (input) => currentModelClient().listThreadOwnership(input),
@@ -18188,10 +18980,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
18188
18980
  });
18189
18981
  let routingPolicy = null;
18190
18982
  let policyTimer = null;
18983
+ let desiredCarrier = null;
18191
18984
  const captureRoutingPolicy = () => {
18192
- const discovered = window.__codexhostDraftPrewarmPolicyV1;
18193
- if (!isDraftPrewarmPolicyReady(discovered)) return false;
18194
- routingPolicy = discovered;
18985
+ const route = currentRequestRoute();
18986
+ if (!route) return false;
18987
+ routingPolicy = route.policy;
18988
+ routingPolicy.select(desiredCarrier);
18195
18989
  if (policyTimer !== null) {
18196
18990
  window.clearInterval(policyTimer);
18197
18991
  policyTimer = null;
@@ -18203,8 +18997,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
18203
18997
  updateStatus("installing", "draft-routing-policy-unavailable", null);
18204
18998
  policyTimer = window.setInterval(captureRoutingPolicy, DRAFT_PREWARM_POLICY_POLL_INTERVAL_MS);
18205
18999
  }
19000
+ const handleRoutingPolicyChange = () => {
19001
+ captureRoutingPolicy();
19002
+ };
19003
+ window.addEventListener("codexhost:draft-prewarm-policy-changed", handleRoutingPolicyChange);
18206
19004
  const applyAgent = (agent, model, thinkingOptionId, permissionModeId) => {
18207
- if (disposed || routingPolicy === null) return false;
19005
+ if (disposed) return false;
18208
19006
  const selection = modelSelectionForAgent(
18209
19007
  null,
18210
19008
  null,
@@ -18215,7 +19013,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
18215
19013
  );
18216
19014
  const carrier = selection?.model;
18217
19015
  if (carrier !== null && carrier !== void 0 && typeof carrier !== "string") return false;
18218
- if (routingPolicy.select(carrier ?? null)) {
19016
+ desiredCarrier = carrier ?? null;
19017
+ const route = currentRequestRoute();
19018
+ if (!route) return false;
19019
+ routingPolicy = route.policy;
19020
+ if (route.policy.select(desiredCarrier)) {
18219
19021
  modelUpdates += 1;
18220
19022
  liveStatus.modelUpdates = modelUpdates;
18221
19023
  }
@@ -18229,8 +19031,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
18229
19031
  if (disposed) return;
18230
19032
  disposed = true;
18231
19033
  if (policyTimer !== null) window.clearInterval(policyTimer);
19034
+ window.removeEventListener(
19035
+ "codexhost:draft-prewarm-policy-changed",
19036
+ handleRoutingPolicyChange
19037
+ );
18232
19038
  routingPolicy?.select(null);
18233
19039
  routingPolicy = null;
19040
+ requestRouteResolver.clear();
18234
19041
  forkControl.dispose();
18235
19042
  usageSubscription.dispose();
18236
19043
  }
@@ -18282,9 +19089,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
18282
19089
  const model = harnessModelRefSchema.safeParse(value.model);
18283
19090
  if (!model.success) return void 0;
18284
19091
  const thinkingOptionId = harnessThinkingOptionIdSchema.safeParse(value.thinkingOptionId);
19092
+ const permissionModeId = harnessPermissionModeIdSchema.safeParse(value.permissionModeId);
18285
19093
  return {
18286
19094
  model: model.data,
18287
- ...thinkingOptionId.success ? { thinkingOptionId: thinkingOptionId.data } : {}
19095
+ ...thinkingOptionId.success ? { thinkingOptionId: thinkingOptionId.data } : {},
19096
+ ...permissionModeId.success ? { permissionModeId: permissionModeId.data } : {}
18288
19097
  };
18289
19098
  }
18290
19099
  function readPreference(storage) {
@@ -18324,15 +19133,17 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
18324
19133
  const agent = readPreference(storage)?.lastAgent;
18325
19134
  return agent && enabledAgents.has(agent) ? agent : void 0;
18326
19135
  }
18327
- function readNewThreadExternalConfigurationPreference(agent, catalog, storage = rendererStorage2()) {
19136
+ function readNewThreadExternalConfigurationPreference(agent, catalog, permissionModes, storage = rendererStorage2()) {
18328
19137
  const preference = readPreference(storage)?.externalByAgent[agent];
18329
19138
  if (!preference) return void 0;
18330
19139
  const catalogModel = catalog.models.find(({ ref }) => ref.id === preference.model.id);
18331
19140
  if (!catalogModel) return void 0;
18332
19141
  const thinkingOptionId = preference.thinkingOptionId && catalogModel.supportedThinkingOptionIds?.includes(preference.thinkingOptionId) ? preference.thinkingOptionId : void 0;
19142
+ const permissionModeId = preference.permissionModeId && permissionModes?.modes.some(({ id }) => id === preference.permissionModeId) ? preference.permissionModeId : void 0;
18333
19143
  return {
18334
19144
  model: catalogModel.ref,
18335
- ...thinkingOptionId ? { thinkingOptionId } : {}
19145
+ ...thinkingOptionId ? { thinkingOptionId } : {},
19146
+ ...permissionModeId ? { permissionModeId } : {}
18336
19147
  };
18337
19148
  }
18338
19149
  function writeNewThreadAgentPreference(agent, storage = rendererStorage2()) {
@@ -18346,7 +19157,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
18346
19157
  storage
18347
19158
  );
18348
19159
  }
18349
- function writeNewThreadExternalConfigurationPreference(agent, model, thinkingOptionId, storage = rendererStorage2()) {
19160
+ function writeNewThreadExternalConfigurationPreference(agent, model, thinkingOptionId, permissionModeId, storage = rendererStorage2()) {
18350
19161
  const current = readPreference(storage);
18351
19162
  writePreference(
18352
19163
  {
@@ -18356,7 +19167,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
18356
19167
  ...current?.externalByAgent,
18357
19168
  [agent]: {
18358
19169
  model: harnessModelRefSchema.parse(model),
18359
- ...thinkingOptionId ? { thinkingOptionId: harnessThinkingOptionIdSchema.parse(thinkingOptionId) } : {}
19170
+ ...thinkingOptionId ? { thinkingOptionId: harnessThinkingOptionIdSchema.parse(thinkingOptionId) } : {},
19171
+ ...permissionModeId ? { permissionModeId: harnessPermissionModeIdSchema.parse(permissionModeId) } : {}
18360
19172
  }
18361
19173
  }
18362
19174
  },
@@ -18554,6 +19366,30 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
18554
19366
  inDevelopment: "In development",
18555
19367
  notAvailable: "Not available",
18556
19368
  runtimeCapabilityNotInstalled: "This runtime capability is not installed yet.",
19369
+ connectionsDescription: "Inspect the adapter and each Agent runtime used by the picker. Failed checks keep their error details here.",
19370
+ connectionAdapter: "Renderer adapter",
19371
+ connectionReason: "Reason",
19372
+ connectionRefresh: "Run connection diagnostics",
19373
+ connectionRefreshing: "Running diagnostics...",
19374
+ connectionViewDetails: "View details",
19375
+ connectionCopyDetails: "Copy diagnostics",
19376
+ connectionCopyAll: "Copy all diagnostics",
19377
+ connectionCopied: "Copied",
19378
+ connectionCopyFailed: "Copy failed",
19379
+ connectionErrorCode: "Error code",
19380
+ connectionErrorMessage: "Error message",
19381
+ connectionRetryable: "Retryable",
19382
+ connectionFailureStage: "Failure stage",
19383
+ connectionDuration: "Duration",
19384
+ connectionDiagnostic: "Diagnostic",
19385
+ connectionNoRuntime: "The renderer request bridge is not available yet.",
19386
+ connectionStatusReady: "Ready",
19387
+ connectionStatusChecking: "Checking",
19388
+ connectionStatusNotInstalled: "Not installed",
19389
+ connectionStatusUnavailable: "Unavailable",
19390
+ connectionStatusError: "Error",
19391
+ connectionStatusInstalling: "Installing",
19392
+ connectionStatusUnsupported: "Unsupported",
18557
19393
  openSettings: "Open codexhost settings",
18558
19394
  settingsButtonTitle: "codexhost settings",
18559
19395
  settingsUnavailableTitle: "codexhost settings unavailable",
@@ -18598,6 +19434,30 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
18598
19434
  inDevelopment: "\u5F00\u53D1\u4E2D",
18599
19435
  notAvailable: "\u6682\u4E0D\u53EF\u7528",
18600
19436
  runtimeCapabilityNotInstalled: "\u8FD0\u884C\u65F6\u5C1A\u672A\u5B89\u88C5\u8BE5\u9879\u80FD\u529B\uFF0C\u56E0\u6B64\u6682\u4E0D\u53EF\u7528\u3002",
19437
+ connectionsDescription: "\u67E5\u770B\u9002\u914D\u5668\u548C Agent \u8FD0\u884C\u65F6\u7684\u771F\u5B9E\u68C0\u67E5\u7ED3\u679C\u3002\u5931\u8D25\u68C0\u67E5\u4F1A\u4FDD\u7559\u9519\u8BEF\u8BE6\u60C5\uFF0C\u65B9\u4FBF\u6392\u67E5\u65E0\u6CD5\u9009\u62E9\u7684\u95EE\u9898\u3002",
19438
+ connectionAdapter: "Renderer \u9002\u914D\u5668",
19439
+ connectionReason: "\u539F\u56E0",
19440
+ connectionRefresh: "\u91CD\u65B0\u8BCA\u65AD\u8FDE\u63A5",
19441
+ connectionRefreshing: "\u6B63\u5728\u8BCA\u65AD...",
19442
+ connectionViewDetails: "\u67E5\u770B\u8BE6\u60C5",
19443
+ connectionCopyDetails: "\u590D\u5236\u8BCA\u65AD\u4FE1\u606F",
19444
+ connectionCopyAll: "\u590D\u5236\u5168\u90E8\u8BCA\u65AD\u4FE1\u606F",
19445
+ connectionCopied: "\u5DF2\u590D\u5236",
19446
+ connectionCopyFailed: "\u590D\u5236\u5931\u8D25",
19447
+ connectionErrorCode: "\u9519\u8BEF\u7801",
19448
+ connectionErrorMessage: "\u9519\u8BEF\u4FE1\u606F",
19449
+ connectionRetryable: "\u53EF\u91CD\u8BD5",
19450
+ connectionFailureStage: "\u5931\u8D25\u9636\u6BB5",
19451
+ connectionDuration: "\u68C0\u67E5\u8017\u65F6",
19452
+ connectionDiagnostic: "\u8BCA\u65AD\u4FE1\u606F",
19453
+ connectionNoRuntime: "Renderer \u8BF7\u6C42\u6865\u5C1A\u672A\u53EF\u7528\u3002",
19454
+ connectionStatusReady: "\u6B63\u5E38",
19455
+ connectionStatusChecking: "\u68C0\u67E5\u4E2D",
19456
+ connectionStatusNotInstalled: "\u672A\u5B89\u88C5",
19457
+ connectionStatusUnavailable: "\u4E0D\u53EF\u7528",
19458
+ connectionStatusError: "\u9519\u8BEF",
19459
+ connectionStatusInstalling: "\u5B89\u88C5\u4E2D",
19460
+ connectionStatusUnsupported: "\u4E0D\u652F\u6301",
18601
19461
  openSettings: "\u6253\u5F00 codexhost \u8BBE\u7F6E",
18602
19462
  settingsButtonTitle: "codexhost \u8BBE\u7F6E",
18603
19463
  settingsUnavailableTitle: "codexhost \u8BBE\u7F6E\u4E0D\u53EF\u7528",
@@ -18690,6 +19550,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
18690
19550
  ["path", { d: "M19.08 19.08A10 10 0 1 1 4.92 4.92" }]
18691
19551
  ];
18692
19552
 
19553
+ // ../../node_modules/lucide/dist/esm/icons/copy.mjs
19554
+ var Copy = [
19555
+ ["rect", { width: "14", height: "14", x: "8", y: "8", rx: "2", ry: "2" }],
19556
+ ["path", { d: "M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" }]
19557
+ ];
19558
+
18693
19559
  // ../../node_modules/lucide/dist/esm/icons/download.mjs
18694
19560
  var Download = [
18695
19561
  ["path", { d: "M12 15V3" }],
@@ -18758,6 +19624,15 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
18758
19624
  ["circle", { cx: "12", cy: "12", r: "3" }]
18759
19625
  ];
18760
19626
 
19627
+ // ../../node_modules/lucide/dist/esm/icons/stethoscope.mjs
19628
+ var Stethoscope = [
19629
+ ["path", { d: "M11 2v2" }],
19630
+ ["path", { d: "M5 2v2" }],
19631
+ ["path", { d: "M5 3H4a2 2 0 0 0-2 2v4a6 6 0 0 0 12 0V5a2 2 0 0 0-2-2h-1" }],
19632
+ ["path", { d: "M8 15a6 6 0 0 0 12 0v-3" }],
19633
+ ["circle", { cx: "20", cy: "10", r: "2" }]
19634
+ ];
19635
+
18761
19636
  // ../../node_modules/lucide/dist/esm/icons/star.mjs
18762
19637
  var Star = [
18763
19638
  [
@@ -18790,7 +19665,9 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
18790
19665
  "updates",
18791
19666
  "external-link",
18792
19667
  "refresh",
18793
- "unavailable"
19668
+ "unavailable",
19669
+ "diagnose",
19670
+ "copy"
18794
19671
  ];
18795
19672
  var iconNodes = {
18796
19673
  settings: Settings,
@@ -18804,7 +19681,9 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
18804
19681
  updates: Download,
18805
19682
  "external-link": ExternalLink,
18806
19683
  refresh: RefreshCw,
18807
- unavailable: CircleOff
19684
+ unavailable: CircleOff,
19685
+ diagnose: Stethoscope,
19686
+ copy: Copy
18808
19687
  };
18809
19688
  function isRendererSettingsIconName(value) {
18810
19689
  return RENDERER_SETTINGS_ICON_NAMES.includes(value);
@@ -19186,6 +20065,268 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
19186
20065
  }
19187
20066
  });
19188
20067
  }
20068
+ function connectionStatusLabel(availability, messages) {
20069
+ if (availability === "ready") return messages.connectionStatusReady;
20070
+ if (availability === "checking") return messages.connectionStatusChecking;
20071
+ if (availability === "notInstalled") return messages.connectionStatusNotInstalled;
20072
+ if (availability === "unavailable" || availability === "error") {
20073
+ return availability === "error" ? messages.connectionStatusError : messages.connectionStatusUnavailable;
20074
+ }
20075
+ return availability === "installing" ? messages.connectionStatusInstalling : messages.connectionStatusUnsupported;
20076
+ }
20077
+ function connectionStatusTone(availability) {
20078
+ if (availability === "ready") return "ready";
20079
+ if (availability === "checking" || availability === "installing") return "checking";
20080
+ return "failed";
20081
+ }
20082
+ function diagnosticText(name, snapshot) {
20083
+ const error51 = snapshot.error;
20084
+ const lines = [
20085
+ "codexhost connection diagnostics",
20086
+ `agent: ${name}`,
20087
+ `status: ${snapshot.availability}`,
20088
+ ...error51 ? [
20089
+ `error.code: ${error51.code}`,
20090
+ `error.message: ${error51.message}`,
20091
+ `retryable: ${error51.retryable}`,
20092
+ ...error51.stage ? [`stage: ${error51.stage}`] : [],
20093
+ ...error51.durationMs !== void 0 ? [`durationMs: ${error51.durationMs}`] : [],
20094
+ ...error51.diagnostic ? [`diagnostic: ${error51.diagnostic}`] : [],
20095
+ ...error51.stderrTail ? [`stderr:
20096
+ ${error51.stderrTail}`] : []
20097
+ ] : []
20098
+ ];
20099
+ return lines.join("\n");
20100
+ }
20101
+ function detailLine(document2, label, value) {
20102
+ const line = document2.createElement("div");
20103
+ line.className = "settings-connection-detail-line";
20104
+ const name = document2.createElement("span");
20105
+ name.textContent = label;
20106
+ const content = document2.createElement("code");
20107
+ content.textContent = value;
20108
+ line.append(name, content);
20109
+ return line;
20110
+ }
20111
+ function setCopyButtonLabel(button, label) {
20112
+ button.replaceChildren(createRendererSettingsIcon("copy", 16), label);
20113
+ }
20114
+ function showCopyButtonFeedback(button, label, restoreLabel) {
20115
+ setCopyButtonLabel(button, label);
20116
+ button.ownerDocument.defaultView?.setTimeout(() => {
20117
+ setCopyButtonLabel(button, restoreLabel);
20118
+ }, 2e3);
20119
+ }
20120
+ function copyDiagnosticsToClipboard(document2, button, report, messages, restoreLabel) {
20121
+ const clipboard = document2.defaultView?.navigator.clipboard;
20122
+ if (!clipboard) {
20123
+ showCopyButtonFeedback(button, messages.connectionCopyFailed, restoreLabel);
20124
+ return;
20125
+ }
20126
+ void clipboard.writeText(report).then(
20127
+ () => showCopyButtonFeedback(button, messages.connectionCopied, restoreLabel),
20128
+ () => showCopyButtonFeedback(button, messages.connectionCopyFailed, restoreLabel)
20129
+ );
20130
+ }
20131
+ function appendConnectionRow(document2, parent, name, availability, detail, error51, messages, rowAttribute, diagnosticSnapshot) {
20132
+ const row = document2.createElement("div");
20133
+ row.className = "settings-connection-row";
20134
+ if (rowAttribute) row.dataset.connectionAgent = rowAttribute;
20135
+ const identity = document2.createElement("div");
20136
+ identity.className = "settings-connection-row__identity";
20137
+ const dot = document2.createElement("span");
20138
+ dot.className = "settings-connection-row__dot";
20139
+ dot.dataset.connectionTone = connectionStatusTone(availability);
20140
+ dot.setAttribute("aria-hidden", "true");
20141
+ const label = document2.createElement("strong");
20142
+ label.textContent = name;
20143
+ identity.append(dot, label);
20144
+ const status = document2.createElement("span");
20145
+ status.className = "settings-status-badge";
20146
+ status.dataset.connectionTone = connectionStatusTone(availability);
20147
+ status.textContent = connectionStatusLabel(availability, messages);
20148
+ const detailElement = document2.createElement("div");
20149
+ detailElement.className = "settings-connection-row__detail";
20150
+ const summary = document2.createElement("span");
20151
+ summary.className = "settings-connection-row__reason";
20152
+ summary.textContent = error51 ? `${messages.connectionReason}: ${error51.code}: ${error51.message}` : detail ? `${messages.connectionReason}: ${detail}` : "";
20153
+ detailElement.append(summary);
20154
+ let toggle = null;
20155
+ if (error51) {
20156
+ toggle = document2.createElement("button");
20157
+ toggle.type = "button";
20158
+ toggle.className = "settings-connection-details-toggle";
20159
+ toggle.textContent = messages.connectionViewDetails;
20160
+ toggle.setAttribute("aria-expanded", "true");
20161
+ detailElement.append(toggle);
20162
+ }
20163
+ const details = document2.createElement("div");
20164
+ details.className = "settings-connection-details";
20165
+ details.hidden = !error51;
20166
+ if (error51) {
20167
+ details.append(
20168
+ detailLine(document2, messages.connectionErrorCode, error51.code),
20169
+ detailLine(document2, messages.connectionErrorMessage, error51.message),
20170
+ detailLine(document2, messages.connectionRetryable, String(error51.retryable))
20171
+ );
20172
+ if (error51.stage)
20173
+ details.append(detailLine(document2, messages.connectionFailureStage, error51.stage));
20174
+ if (error51.durationMs !== void 0) {
20175
+ details.append(detailLine(document2, messages.connectionDuration, `${error51.durationMs} ms`));
20176
+ }
20177
+ if (error51.diagnostic)
20178
+ details.append(detailLine(document2, messages.connectionDiagnostic, error51.diagnostic));
20179
+ if (error51.stderrTail) {
20180
+ const stderr = document2.createElement("pre");
20181
+ stderr.className = "settings-connection-stderr";
20182
+ stderr.textContent = error51.stderrTail;
20183
+ details.append(stderr);
20184
+ }
20185
+ const copy = document2.createElement("button");
20186
+ copy.type = "button";
20187
+ copy.className = "settings-command-button settings-command-button--secondary";
20188
+ setCopyButtonLabel(copy, messages.connectionCopyDetails);
20189
+ copy.addEventListener("click", () => {
20190
+ if (!diagnosticSnapshot) return;
20191
+ copyDiagnosticsToClipboard(
20192
+ document2,
20193
+ copy,
20194
+ diagnosticText(name, diagnosticSnapshot),
20195
+ messages,
20196
+ messages.connectionCopyDetails
20197
+ );
20198
+ });
20199
+ details.append(copy);
20200
+ }
20201
+ if (toggle) {
20202
+ toggle.addEventListener("click", () => {
20203
+ details.hidden = !details.hidden;
20204
+ toggle?.setAttribute("aria-expanded", String(!details.hidden));
20205
+ });
20206
+ }
20207
+ row.append(identity, status, detailElement, details);
20208
+ parent.append(row);
20209
+ }
20210
+ function connectionsPage(messages, getDiagnostics) {
20211
+ return Object.freeze({
20212
+ id: "connections",
20213
+ label: messages.pageLabels.connections,
20214
+ icon: "connections",
20215
+ mount(context) {
20216
+ const document2 = context.content.ownerDocument;
20217
+ const heading = document2.createElement("div");
20218
+ heading.className = "settings-section-label";
20219
+ heading.textContent = messages.pageLabels.connections;
20220
+ const description = document2.createElement("p");
20221
+ description.className = "settings-page-description";
20222
+ description.textContent = messages.connectionsDescription;
20223
+ const actions = document2.createElement("div");
20224
+ actions.className = "settings-connection-actions";
20225
+ const refresh = document2.createElement("button");
20226
+ refresh.type = "button";
20227
+ refresh.className = "settings-command-button settings-command-button--secondary";
20228
+ refresh.dataset.connectionAction = "refresh";
20229
+ refresh.append(createRendererSettingsIcon("diagnose", 16), messages.connectionRefresh);
20230
+ const copyAll = document2.createElement("button");
20231
+ copyAll.type = "button";
20232
+ copyAll.className = "settings-command-button settings-command-button--secondary";
20233
+ copyAll.dataset.connectionAction = "copy-all";
20234
+ copyAll.append(createRendererSettingsIcon("copy", 16), messages.connectionCopyAll);
20235
+ actions.append(refresh, copyAll);
20236
+ const content = document2.createElement("div");
20237
+ content.className = "settings-connection-list";
20238
+ context.content.append(heading, description, actions, content);
20239
+ let pending = false;
20240
+ const render = (snapshot) => {
20241
+ content.replaceChildren();
20242
+ if (!snapshot) {
20243
+ const empty = document2.createElement("div");
20244
+ empty.className = "settings-empty";
20245
+ empty.textContent = messages.connectionNoRuntime;
20246
+ content.append(empty);
20247
+ return;
20248
+ }
20249
+ appendConnectionRow(
20250
+ document2,
20251
+ content,
20252
+ messages.connectionAdapter,
20253
+ snapshot.adapter.state,
20254
+ `reason=${snapshot.adapter.reason}, hook=${snapshot.adapter.hook ?? "none"}`,
20255
+ null,
20256
+ messages,
20257
+ "renderer-adapter"
20258
+ );
20259
+ for (const agent of snapshot.agents) {
20260
+ appendConnectionRow(
20261
+ document2,
20262
+ content,
20263
+ RENDERER_AGENT_LABELS[agent.agent],
20264
+ agent.availability,
20265
+ null,
20266
+ agent.error,
20267
+ messages,
20268
+ agent.agent,
20269
+ agent
20270
+ );
20271
+ }
20272
+ };
20273
+ const diagnostics = getDiagnostics();
20274
+ render(diagnostics?.snapshot() ?? null);
20275
+ if (!diagnostics) {
20276
+ copyAll.disabled = true;
20277
+ return void 0;
20278
+ }
20279
+ copyAll.addEventListener("click", () => {
20280
+ const snapshot = diagnostics.snapshot();
20281
+ const report = snapshot.agents.map((agent) => diagnosticText(RENDERER_AGENT_LABELS[agent.agent], agent)).join("\n\n");
20282
+ copyDiagnosticsToClipboard(document2, copyAll, report, messages, messages.connectionCopyAll);
20283
+ });
20284
+ const unsubscribe = diagnostics.subscribe(() => render(diagnostics.snapshot()));
20285
+ refresh.addEventListener("click", () => {
20286
+ if (pending) return;
20287
+ pending = true;
20288
+ refresh.disabled = true;
20289
+ refresh.replaceChildren(
20290
+ createRendererSettingsIcon("diagnose", 16),
20291
+ messages.connectionRefreshing
20292
+ );
20293
+ void context.runLatest(() => diagnostics.refresh(), {
20294
+ success() {
20295
+ pending = false;
20296
+ refresh.disabled = false;
20297
+ refresh.replaceChildren(
20298
+ createRendererSettingsIcon("diagnose", 16),
20299
+ messages.connectionRefresh
20300
+ );
20301
+ render(diagnostics.snapshot());
20302
+ },
20303
+ failure(error51) {
20304
+ pending = false;
20305
+ refresh.disabled = false;
20306
+ refresh.replaceChildren(
20307
+ createRendererSettingsIcon("diagnose", 16),
20308
+ messages.connectionRefresh
20309
+ );
20310
+ render({
20311
+ ...diagnostics.snapshot(),
20312
+ agents: diagnostics.snapshot().agents.map((agent) => ({
20313
+ ...agent,
20314
+ availability: "error",
20315
+ error: {
20316
+ code: "internalError",
20317
+ message: error51 instanceof Error ? error51.message : String(error51),
20318
+ retryable: true,
20319
+ stage: "request"
20320
+ }
20321
+ }))
20322
+ });
20323
+ }
20324
+ });
20325
+ });
20326
+ return unsubscribe;
20327
+ }
20328
+ });
20329
+ }
19189
20330
  function versionRow(context, label, version2) {
19190
20331
  const row = context.content.ownerDocument.createElement("div");
19191
20332
  row.className = "settings-update-version-row";
@@ -19459,23 +20600,24 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
19459
20600
  }
19460
20601
  });
19461
20602
  }
19462
- function createDefaultRendererSettingsPages(messages = DEFAULT_RENDERER_SETTINGS_MESSAGES, getUpdateClient = () => null) {
20603
+ function createDefaultRendererSettingsPages(messages = DEFAULT_RENDERER_SETTINGS_MESSAGES, getUpdateClient = () => null, getDiagnostics = () => null) {
19463
20604
  const unavailableIds = DEFAULT_RENDERER_SETTINGS_PAGE_IDS.filter(
19464
- (id) => id !== "updates"
20605
+ (id) => id !== "updates" && id !== "connections"
19465
20606
  );
19466
20607
  return Object.freeze([
20608
+ connectionsPage(messages, getDiagnostics),
19467
20609
  ...unavailableIds.map((id) => unavailablePage(id, messages)),
19468
20610
  updatesPage(messages, getUpdateClient)
19469
20611
  ]);
19470
20612
  }
19471
- function createDefaultRendererSettingsRegistry(messages = DEFAULT_RENDERER_SETTINGS_MESSAGES, getUpdateClient = () => null) {
20613
+ function createDefaultRendererSettingsRegistry(messages = DEFAULT_RENDERER_SETTINGS_MESSAGES, getUpdateClient = () => null, getDiagnostics = () => null) {
19472
20614
  return createRendererSettingsPageRegistry(
19473
- createDefaultRendererSettingsPages(messages, getUpdateClient)
20615
+ createDefaultRendererSettingsPages(messages, getUpdateClient, getDiagnostics)
19474
20616
  );
19475
20617
  }
19476
20618
 
19477
20619
  // src/settings/shell.css
19478
- var shell_default = ':host {\n --settings-bg: #181818;\n --settings-sidebar: #1c1c1c;\n --settings-panel: transparent;\n --settings-text: #f5f5f5;\n --settings-muted: #a1a1a1;\n --settings-border: rgb(255 255 255 / 10%);\n --settings-divider: rgb(255 255 255 / 10%);\n --settings-hover: rgb(255 255 255 / 6%);\n --settings-active: rgb(51 156 255 / 12%);\n --settings-focus: #339cff;\n color: var(--settings-text);\n color-scheme: dark;\n font:\n 14px/1.5 system-ui,\n -apple-system,\n BlinkMacSystemFont,\n "Segoe UI",\n sans-serif;\n letter-spacing: 0;\n}\n\n*,\n*::before,\n*::after {\n box-sizing: border-box;\n letter-spacing: 0;\n}\n\nbutton,\ninput,\nselect {\n font: inherit;\n}\n\nbutton {\n color: inherit;\n}\n\n[hidden] {\n display: none !important;\n}\n\n.codexhost-settings-dialog {\n width: min(1120px, calc(100vw - 32px));\n height: min(780px, calc(100vh - 32px));\n max-width: none;\n max-height: none;\n margin: auto;\n padding: 0;\n overflow: hidden;\n color: var(--settings-text);\n background: var(--settings-bg);\n border: 1px solid var(--settings-border);\n border-radius: 12px;\n box-shadow: 0 24px 64px rgb(0 0 0 / 38%);\n}\n\n.codexhost-settings-dialog::backdrop {\n background: rgb(0 0 0 / 52%);\n}\n\n.settings-frame,\n.settings-layout {\n width: 100%;\n height: 100%;\n min-width: 0;\n min-height: 0;\n}\n\n.settings-frame {\n display: flex;\n flex-direction: column;\n}\n\n.settings-layout {\n flex: 1;\n display: grid;\n grid-template-columns: 240px minmax(0, 1fr);\n background: var(--settings-bg);\n}\n\n.settings-sidebar {\n display: flex;\n min-width: 0;\n min-height: 0;\n flex-direction: column;\n overflow: hidden;\n background: var(--settings-sidebar);\n border-right: 1px solid var(--settings-border);\n padding-top: 28px;\n}\n\n.settings-header {\n display: flex;\n align-items: center;\n min-width: 0;\n height: 64px;\n flex: none;\n padding: 0 16px;\n border-bottom: 1px solid var(--settings-border);\n}\n\n.settings-header-actions {\n display: flex;\n align-items: center;\n flex: none;\n gap: 12px;\n margin-left: auto;\n}\n\n.settings-icon-button.settings-star-link {\n width: auto;\n gap: 6px;\n padding-inline: 8px;\n font-size: 12px;\n line-height: 18px;\n text-decoration: none;\n border: 2px dashed #f5c542;\n white-space: nowrap;\n}\n\n.settings-star-link .codexhost-settings-icon {\n color: #f5c542;\n fill: currentColor;\n stroke: currentColor;\n}\n\n.settings-brand {\n display: flex;\n align-items: center;\n min-width: 0;\n gap: 8px;\n}\n\n.settings-brand__mark {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 36px;\n height: 36px;\n flex: none;\n color: var(--settings-text);\n}\n\n.settings-brand__mark .codexhost-settings-icon {\n width: 32px;\n height: 32px;\n object-fit: contain;\n}\n\n.settings-brand__copy {\n display: flex;\n min-width: 0;\n align-items: baseline;\n gap: 0;\n line-height: 20px;\n}\n\n.settings-brand__name {\n overflow: hidden;\n color: var(--settings-text);\n font-size: 14px;\n font-weight: 500;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.settings-brand__title {\n margin-left: 14px;\n padding-left: 14px;\n color: var(--settings-muted);\n font-size: 14px;\n font-weight: 400;\n border-left: 1px solid var(--settings-border);\n}\n\n.settings-nav {\n display: flex;\n min-width: 0;\n min-height: 0;\n flex: 1;\n flex-direction: column;\n gap: 4px;\n padding: 0 12px 16px;\n overflow-y: auto;\n}\n\n.settings-nav-button {\n display: grid;\n grid-template-columns: 16px minmax(0, 1fr);\n align-items: center;\n width: 100%;\n position: relative;\n min-height: 40px;\n flex: none;\n gap: 12px;\n padding: 8px 12px;\n color: var(--settings-text);\n font-size: 14px;\n line-height: 21px;\n text-align: left;\n background: transparent;\n border: 0;\n border-radius: 10px;\n cursor: pointer;\n}\n\n.settings-nav-button .codexhost-settings-icon {\n width: 18px;\n height: 18px;\n opacity: 0.9;\n}\n\n.settings-nav-button:hover {\n background: var(--settings-hover);\n}\n\n.settings-nav-button[aria-current="page"] {\n background: var(--settings-active);\n color: var(--settings-focus);\n}\n\n.settings-nav-button[aria-current="page"]::before {\n position: absolute;\n left: 0;\n width: 3px;\n height: 22px;\n content: "";\n background: var(--settings-focus);\n border-radius: 0 3px 3px 0;\n}\n\n.settings-nav-button span {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.settings-icon-button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n flex: none;\n padding: 0;\n color: var(--settings-muted);\n background: transparent;\n border: 0;\n border-radius: 8px;\n cursor: pointer;\n}\n\n.settings-icon-button:hover {\n color: var(--settings-text);\n background: var(--settings-hover);\n}\n\n.settings-icon-button:focus-visible,\n.settings-nav-button:focus-visible {\n outline: 2px solid var(--settings-focus);\n outline-offset: 1px;\n}\n\n.settings-page {\n display: block;\n position: relative;\n min-width: 0;\n min-height: 0;\n overflow-y: auto;\n background: var(--settings-bg);\n scrollbar-gutter: stable;\n}\n\n.settings-page__content {\n width: min(930px, calc(100% - 80px));\n margin-inline: auto;\n}\n\n.settings-page__content {\n min-width: 0;\n padding: 44px 0 56px;\n}\n\n.settings-section-label {\n min-height: auto;\n padding: 0 0 28px;\n color: var(--settings-text);\n font-size: 24px;\n font-weight: 600;\n line-height: 30px;\n}\n\n.settings-status-list,\n.settings-empty {\n overflow: hidden;\n background: var(--settings-panel);\n}\n\n.settings-status-row {\n display: grid;\n grid-template-columns: minmax(170px, 1fr) auto minmax(280px, 1.45fr);\n position: relative;\n align-items: center;\n min-height: 88px;\n gap: 28px;\n padding: 16px 0;\n}\n\n.settings-status-row:not(:last-child)::after {\n content: "";\n position: absolute;\n right: 0;\n bottom: 0;\n left: 0;\n height: 1px;\n background: var(--settings-divider);\n}\n\n.settings-status-row__identity {\n display: inline-flex;\n align-items: center;\n min-width: 0;\n color: var(--settings-text);\n gap: 14px;\n font-size: 15px;\n font-weight: 500;\n line-height: 22px;\n}\n\n.settings-status-row__icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 44px;\n height: 44px;\n flex: none;\n color: var(--settings-muted);\n background: rgb(255 255 255 / 7%);\n border-radius: 50%;\n}\n\n.settings-status-badge {\n flex: none;\n padding: 5px 12px;\n color: var(--settings-muted);\n font-size: 13px;\n font-weight: 500;\n line-height: 18px;\n background: rgb(255 255 255 / 9%);\n border: 1px solid rgb(255 255 255 / 6%);\n border-radius: 6px;\n}\n\n.settings-status-row__detail {\n min-width: 0;\n color: var(--settings-muted);\n font-size: 13px;\n line-height: 20px;\n}\n\n.settings-empty {\n display: flex;\n align-items: center;\n min-height: 72px;\n padding: 12px 16px;\n}\n\n.settings-empty > div {\n display: grid;\n min-width: 0;\n gap: 2px;\n}\n\n.settings-empty strong {\n color: var(--settings-text);\n font-size: 13px;\n font-weight: 500;\n line-height: 19px;\n}\n\n.settings-empty span {\n color: var(--settings-muted);\n font-size: 13px;\n line-height: 19px;\n}\n\n.settings-page-error {\n padding: 16px;\n color: var(--settings-text);\n font-size: 13px;\n background: var(--settings-panel);\n border: 1px solid var(--settings-border);\n border-radius: 20px;\n}\n\n.settings-update-metadata {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 24px;\n margin-bottom: 24px;\n padding: 0 4px 20px;\n border-bottom: 1px solid var(--settings-divider);\n}\n\n.settings-update-metadata__item {\n display: grid;\n min-width: 0;\n gap: 4px;\n}\n\n.settings-update-metadata__item span {\n color: var(--settings-muted);\n font-size: 12px;\n line-height: 18px;\n}\n\n.settings-update-metadata__item strong {\n min-width: 0;\n overflow-wrap: anywhere;\n color: var(--settings-text);\n font-size: 15px;\n font-weight: 500;\n line-height: 22px;\n}\n\n.settings-update-panel {\n display: grid;\n gap: 16px;\n min-height: 128px;\n padding: 20px;\n color: var(--settings-text);\n background: var(--settings-panel);\n border: 1px solid var(--settings-border);\n border-radius: 8px;\n}\n\n.settings-update-panel > strong,\n.settings-update-panel > span,\n.settings-update-summary,\n.settings-update-error {\n margin: 0;\n overflow-wrap: anywhere;\n font-size: 13px;\n line-height: 20px;\n}\n\n.settings-update-panel > span,\n.settings-update-summary {\n color: var(--settings-muted);\n}\n\n.settings-update-error {\n color: #ef4444;\n}\n\n.settings-update-progress {\n width: 100%;\n height: 8px;\n accent-color: #238be8;\n}\n\n.settings-update-progress-detail {\n color: var(--settings-muted);\n font-size: 12px;\n line-height: 18px;\n}\n\n.settings-update-notes {\n display: grid;\n min-width: 0;\n gap: 12px;\n color: var(--settings-muted);\n font-size: 14px;\n line-height: 22px;\n overflow-wrap: anywhere;\n}\n\n.settings-update-notes > :first-child {\n margin-top: 0;\n}\n\n.settings-update-notes > :last-child {\n margin-bottom: 0;\n}\n\n.settings-update-notes h1,\n.settings-update-notes h2,\n.settings-update-notes h3,\n.settings-update-notes h4,\n.settings-update-notes h5,\n.settings-update-notes h6 {\n margin: 6px 0 0;\n color: var(--settings-text);\n font-weight: 600;\n}\n\n.settings-update-notes h1 {\n font-size: 18px;\n line-height: 26px;\n}\n\n.settings-update-notes h2 {\n font-size: 17px;\n line-height: 24px;\n}\n\n.settings-update-notes h3,\n.settings-update-notes h4,\n.settings-update-notes h5,\n.settings-update-notes h6 {\n font-size: 14px;\n line-height: 22px;\n}\n\n.settings-update-notes p,\n.settings-update-notes ul,\n.settings-update-notes ol,\n.settings-update-notes pre,\n.settings-update-notes blockquote {\n margin: 0;\n}\n\n.settings-update-notes ul,\n.settings-update-notes ol {\n padding-left: 24px;\n}\n\n.settings-update-notes li {\n padding-left: 4px;\n}\n\n.settings-update-notes li::marker {\n color: var(--settings-muted);\n font-size: 0.85em;\n}\n\n.settings-update-notes li + li {\n margin-top: 14px;\n}\n\n.settings-update-notes .release-note-translation {\n display: block;\n margin-top: 3px;\n color: var(--settings-muted);\n font-size: 13px;\n line-height: 20px;\n}\n\n.settings-update-notes blockquote {\n padding: 8px 12px;\n color: var(--settings-muted);\n border-left: 3px solid var(--settings-focus);\n background: rgb(255 255 255 / 4%);\n}\n\n.settings-update-notes hr {\n width: 100%;\n margin: 2px 0;\n border: 0;\n border-top: 1px solid var(--settings-divider);\n}\n\n.settings-update-notes strong {\n color: var(--settings-text);\n font-weight: 600;\n}\n\n.settings-update-notes a {\n color: #7cb8ff;\n text-decoration: none;\n}\n\n.settings-update-notes a:hover {\n text-decoration: underline;\n}\n\n.settings-update-notes code {\n font-family: ui-monospace, "SFMono-Regular", Consolas, "Liberation Mono", monospace;\n font-size: 12px;\n}\n\n.settings-update-notes :not(pre) > code {\n padding: 1px 5px;\n background: rgb(255 255 255 / 8%);\n border-radius: 4px;\n}\n\n.settings-update-notes pre {\n min-width: 0;\n max-width: 100%;\n padding: 10px 12px;\n overflow-x: auto;\n overflow-y: hidden;\n background: rgb(0 0 0 / 28%);\n border-radius: 6px;\n}\n\n.settings-update-notes pre code {\n line-height: 18px;\n white-space: pre-wrap;\n}\n\n.settings-update-version-row {\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto;\n align-items: center;\n gap: 20px;\n min-height: 32px;\n color: var(--settings-muted);\n font-size: 13px;\n}\n\n.settings-update-version-row strong {\n color: var(--settings-text);\n font-size: 15px;\n}\n\n.settings-update-manual {\n display: grid;\n min-width: 0;\n gap: 6px;\n margin-top: 14px;\n padding-inline: 4px;\n}\n\n.settings-update-manual[hidden] {\n display: none;\n}\n\n.settings-update-manual span {\n color: var(--settings-muted);\n font-size: 12px;\n line-height: 18px;\n}\n\n.settings-update-manual code {\n overflow-wrap: anywhere;\n color: var(--settings-text);\n font-family: ui-monospace, "SFMono-Regular", Consolas, "Liberation Mono", monospace;\n font-size: 12px;\n line-height: 18px;\n}\n\n.settings-update-actions {\n display: flex;\n min-width: 0;\n margin-top: 16px;\n}\n\n.settings-command-button,\n.settings-update-link {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: fit-content;\n min-height: 36px;\n gap: 8px;\n padding: 8px 12px;\n color: white;\n font: inherit;\n font-size: 13px;\n text-decoration: none;\n background: #1677d2;\n border: 1px solid #238be8;\n border-radius: 6px;\n cursor: pointer;\n}\n\n.settings-command-button--secondary,\n.settings-update-link {\n color: var(--settings-text);\n background: transparent;\n border-color: var(--settings-border);\n}\n\n.settings-command-button:hover,\n.settings-update-link:hover {\n filter: brightness(1.08);\n}\n\n.settings-command-button:focus-visible,\n.settings-update-link:focus-visible {\n outline: 2px solid var(--settings-focus);\n outline-offset: 2px;\n}\n\n.codexhost-settings-icon {\n display: block;\n flex: none;\n stroke: currentColor;\n}\n\n@media (max-width: 720px) {\n .codexhost-settings-dialog {\n width: calc(100vw - 16px);\n height: calc(100vh - 16px);\n border-radius: 10px;\n }\n\n .settings-layout {\n grid-template-columns: minmax(0, 1fr);\n grid-template-rows: auto minmax(0, 1fr);\n }\n\n .settings-sidebar {\n border-right: 0;\n border-bottom: 1px solid var(--settings-border);\n }\n\n .settings-header {\n height: 56px;\n padding-inline: 14px;\n }\n\n .settings-icon-button.settings-star-link {\n width: 28px;\n padding: 0;\n }\n\n .settings-star-link span {\n display: none;\n }\n\n .settings-sidebar {\n padding-top: 20px;\n }\n\n .settings-nav {\n flex: none;\n flex-direction: row;\n gap: 4px;\n padding: 7px 8px 8px;\n overflow-x: auto;\n overflow-y: hidden;\n }\n\n .settings-nav-button {\n width: auto;\n min-width: max-content;\n min-height: 34px;\n grid-template-columns: 16px auto;\n border-radius: 10px;\n }\n\n .settings-page__content {\n width: calc(100% - 40px);\n }\n\n .settings-page__content {\n padding-top: 32px;\n padding-bottom: 32px;\n }\n\n .settings-section-label {\n padding-bottom: 20px;\n font-size: 20px;\n line-height: 26px;\n }\n\n .settings-status-row {\n grid-template-columns: minmax(0, 1fr) auto;\n gap: 16px;\n }\n\n .settings-status-row__detail {\n grid-column: 1 / -1;\n margin: -8px 0 0 58px;\n }\n}\n\n@media (forced-colors: active) {\n :host,\n :host([data-theme="dark"]) {\n --settings-bg: Canvas;\n --settings-sidebar: Canvas;\n --settings-panel: Canvas;\n --settings-text: CanvasText;\n --settings-muted: GrayText;\n --settings-border: ButtonBorder;\n --settings-divider: ButtonBorder;\n --settings-hover: Highlight;\n --settings-active: Highlight;\n --settings-focus: Highlight;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n *,\n *::before,\n *::after {\n scroll-behavior: auto !important;\n }\n}\n';
20620
+ var shell_default = ':host {\n --settings-bg: #181818;\n --settings-sidebar: #1c1c1c;\n --settings-panel: transparent;\n --settings-text: #f5f5f5;\n --settings-muted: #a1a1a1;\n --settings-border: rgb(255 255 255 / 10%);\n --settings-divider: rgb(255 255 255 / 10%);\n --settings-hover: rgb(255 255 255 / 6%);\n --settings-active: rgb(51 156 255 / 12%);\n --settings-focus: #339cff;\n color: var(--settings-text);\n color-scheme: dark;\n font:\n 14px/1.5 system-ui,\n -apple-system,\n BlinkMacSystemFont,\n "Segoe UI",\n sans-serif;\n letter-spacing: 0;\n}\n\n*,\n*::before,\n*::after {\n box-sizing: border-box;\n letter-spacing: 0;\n}\n\nbutton,\ninput,\nselect {\n font: inherit;\n}\n\nbutton {\n color: inherit;\n}\n\n[hidden] {\n display: none !important;\n}\n\n.codexhost-settings-dialog {\n width: min(1120px, calc(100vw - 32px));\n height: min(780px, calc(100vh - 32px));\n max-width: none;\n max-height: none;\n margin: auto;\n padding: 0;\n overflow: hidden;\n color: var(--settings-text);\n background: var(--settings-bg);\n border: 1px solid var(--settings-border);\n border-radius: 12px;\n box-shadow: 0 24px 64px rgb(0 0 0 / 38%);\n}\n\n.codexhost-settings-dialog::backdrop {\n background: rgb(0 0 0 / 52%);\n}\n\n.settings-frame,\n.settings-layout {\n width: 100%;\n height: 100%;\n min-width: 0;\n min-height: 0;\n}\n\n.settings-frame {\n display: flex;\n flex-direction: column;\n}\n\n.settings-layout {\n flex: 1;\n display: grid;\n grid-template-columns: 240px minmax(0, 1fr);\n background: var(--settings-bg);\n}\n\n.settings-sidebar {\n display: flex;\n min-width: 0;\n min-height: 0;\n flex-direction: column;\n overflow: hidden;\n background: var(--settings-sidebar);\n border-right: 1px solid var(--settings-border);\n padding-top: 28px;\n}\n\n.settings-header {\n display: flex;\n align-items: center;\n min-width: 0;\n height: 64px;\n flex: none;\n padding: 0 16px;\n border-bottom: 1px solid var(--settings-border);\n}\n\n.settings-header-actions {\n display: flex;\n align-items: center;\n flex: none;\n gap: 12px;\n margin-left: auto;\n}\n\n.settings-icon-button.settings-star-link {\n width: auto;\n gap: 6px;\n padding-inline: 8px;\n font-size: 12px;\n line-height: 18px;\n text-decoration: none;\n border: 2px dashed #f5c542;\n white-space: nowrap;\n}\n\n.settings-star-link .codexhost-settings-icon {\n color: #f5c542;\n fill: currentColor;\n stroke: currentColor;\n}\n\n.settings-brand {\n display: flex;\n align-items: center;\n min-width: 0;\n gap: 8px;\n}\n\n.settings-brand__mark {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 36px;\n height: 36px;\n flex: none;\n color: var(--settings-text);\n}\n\n.settings-brand__mark .codexhost-settings-icon {\n width: 32px;\n height: 32px;\n object-fit: contain;\n}\n\n.settings-brand__copy {\n display: flex;\n min-width: 0;\n align-items: baseline;\n gap: 0;\n line-height: 20px;\n}\n\n.settings-brand__name {\n overflow: hidden;\n color: var(--settings-text);\n font-size: 14px;\n font-weight: 500;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.settings-brand__title {\n margin-left: 14px;\n padding-left: 14px;\n color: var(--settings-muted);\n font-size: 14px;\n font-weight: 400;\n border-left: 1px solid var(--settings-border);\n}\n\n.settings-nav {\n display: flex;\n min-width: 0;\n min-height: 0;\n flex: 1;\n flex-direction: column;\n gap: 4px;\n padding: 0 12px 16px;\n overflow-y: auto;\n}\n\n.settings-nav-button {\n display: grid;\n grid-template-columns: 16px minmax(0, 1fr);\n align-items: center;\n width: 100%;\n position: relative;\n min-height: 40px;\n flex: none;\n gap: 12px;\n padding: 8px 12px;\n color: var(--settings-text);\n font-size: 14px;\n line-height: 21px;\n text-align: left;\n background: transparent;\n border: 0;\n border-radius: 10px;\n cursor: pointer;\n}\n\n.settings-nav-button .codexhost-settings-icon {\n width: 18px;\n height: 18px;\n opacity: 0.9;\n}\n\n.settings-nav-button:hover {\n background: var(--settings-hover);\n}\n\n.settings-nav-button[aria-current="page"] {\n background: var(--settings-active);\n color: var(--settings-focus);\n}\n\n.settings-nav-button[aria-current="page"]::before {\n position: absolute;\n left: 0;\n width: 3px;\n height: 22px;\n content: "";\n background: var(--settings-focus);\n border-radius: 0 3px 3px 0;\n}\n\n.settings-nav-button span {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.settings-icon-button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n flex: none;\n padding: 0;\n color: var(--settings-muted);\n background: transparent;\n border: 0;\n border-radius: 8px;\n cursor: pointer;\n}\n\n.settings-icon-button:hover {\n color: var(--settings-text);\n background: var(--settings-hover);\n}\n\n.settings-icon-button:focus-visible,\n.settings-nav-button:focus-visible {\n outline: 2px solid var(--settings-focus);\n outline-offset: 1px;\n}\n\n.settings-page {\n display: block;\n position: relative;\n min-width: 0;\n min-height: 0;\n overflow-y: auto;\n background: var(--settings-bg);\n scrollbar-gutter: stable;\n}\n\n.settings-page__content {\n width: min(930px, calc(100% - 80px));\n margin-inline: auto;\n}\n\n.settings-page__content {\n min-width: 0;\n padding: 44px 0 56px;\n}\n\n.settings-section-label {\n min-height: auto;\n padding: 0 0 28px;\n color: var(--settings-text);\n font-size: 24px;\n font-weight: 600;\n line-height: 30px;\n}\n\n.settings-status-list,\n.settings-empty {\n overflow: hidden;\n background: var(--settings-panel);\n}\n\n.settings-status-row {\n display: grid;\n grid-template-columns: minmax(170px, 1fr) auto minmax(280px, 1.45fr);\n position: relative;\n align-items: center;\n min-height: 88px;\n gap: 28px;\n padding: 16px 0;\n}\n\n.settings-status-row:not(:last-child)::after {\n content: "";\n position: absolute;\n right: 0;\n bottom: 0;\n left: 0;\n height: 1px;\n background: var(--settings-divider);\n}\n\n.settings-status-row__identity {\n display: inline-flex;\n align-items: center;\n min-width: 0;\n color: var(--settings-text);\n gap: 14px;\n font-size: 15px;\n font-weight: 500;\n line-height: 22px;\n}\n\n.settings-status-row__icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 44px;\n height: 44px;\n flex: none;\n color: var(--settings-muted);\n background: rgb(255 255 255 / 7%);\n border-radius: 50%;\n}\n\n.settings-status-badge {\n flex: none;\n padding: 5px 12px;\n color: var(--settings-muted);\n font-size: 13px;\n font-weight: 500;\n line-height: 18px;\n background: rgb(255 255 255 / 9%);\n border: 1px solid rgb(255 255 255 / 6%);\n border-radius: 6px;\n}\n\n.settings-status-row__detail {\n min-width: 0;\n color: var(--settings-muted);\n font-size: 13px;\n line-height: 20px;\n}\n\n.settings-page-description {\n max-width: 760px;\n margin: -12px 0 24px;\n color: var(--settings-muted);\n font-size: 13px;\n line-height: 20px;\n}\n\n.settings-connection-actions {\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n margin-bottom: 16px;\n}\n\n.settings-connection-list {\n min-width: 0;\n border-top: 1px solid var(--settings-divider);\n}\n\n.settings-connection-row {\n display: grid;\n grid-template-columns: minmax(180px, 1fr) auto minmax(260px, 1.5fr);\n align-items: center;\n min-width: 0;\n min-height: 76px;\n gap: 24px;\n padding: 14px 0;\n border-bottom: 1px solid var(--settings-divider);\n}\n\n.settings-connection-row__identity {\n display: inline-flex;\n align-items: center;\n min-width: 0;\n gap: 10px;\n color: var(--settings-text);\n font-size: 14px;\n line-height: 20px;\n}\n\n.settings-connection-row__identity strong {\n min-width: 0;\n overflow-wrap: anywhere;\n font-weight: 500;\n}\n\n.settings-connection-row__dot {\n width: 9px;\n height: 9px;\n flex: none;\n border-radius: 50%;\n background: var(--settings-muted);\n}\n\n.settings-connection-row__dot[data-connection-tone="ready"] {\n background: #22c55e;\n}\n\n.settings-connection-row__dot[data-connection-tone="checking"] {\n background: #f5c542;\n}\n\n.settings-connection-row__dot[data-connection-tone="failed"] {\n background: #ef4444;\n}\n\n.settings-status-badge[data-connection-tone="ready"] {\n color: #4ade80;\n}\n\n.settings-status-badge[data-connection-tone="checking"] {\n color: #f5c542;\n}\n\n.settings-status-badge[data-connection-tone="failed"] {\n color: #f87171;\n}\n\n.settings-connection-row__detail {\n display: grid;\n min-width: 0;\n align-items: center;\n gap: 2px 12px;\n color: var(--settings-muted);\n font-size: 12px;\n line-height: 18px;\n}\n\n.settings-connection-row__reason {\n min-width: 0;\n overflow-wrap: anywhere;\n white-space: pre-wrap;\n}\n\n.settings-connection-details-toggle {\n width: fit-content;\n padding: 0;\n color: var(--settings-focus);\n font: inherit;\n font-size: 12px;\n line-height: 18px;\n background: transparent;\n border: 0;\n cursor: pointer;\n}\n\n.settings-connection-details-toggle:hover {\n text-decoration: underline;\n}\n\n.settings-connection-details {\n display: grid;\n grid-column: 1 / -1;\n gap: 6px;\n min-width: 0;\n margin: 4px 0 2px 32px;\n padding: 12px;\n color: var(--settings-muted);\n background: rgb(255 255 255 / 4%);\n border-left: 2px solid #ef4444;\n border-radius: 4px;\n}\n\n.settings-connection-detail-line {\n display: grid;\n grid-template-columns: 112px minmax(0, 1fr);\n gap: 12px;\n min-width: 0;\n font-size: 12px;\n line-height: 18px;\n}\n\n.settings-connection-detail-line span {\n color: var(--settings-muted);\n}\n\n.settings-connection-detail-line code {\n min-width: 0;\n overflow-wrap: anywhere;\n color: var(--settings-text);\n font-family: ui-monospace, "SFMono-Regular", Consolas, "Liberation Mono", monospace;\n white-space: pre-wrap;\n}\n\n.settings-connection-stderr {\n max-height: 220px;\n min-width: 0;\n margin: 4px 0 0;\n padding: 10px;\n overflow: auto;\n color: #fca5a5;\n font:\n 12px/18px ui-monospace,\n "SFMono-Regular",\n Consolas,\n "Liberation Mono",\n monospace;\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n background: rgb(0 0 0 / 28%);\n border-radius: 4px;\n}\n\n.settings-connection-details .settings-command-button {\n justify-self: start;\n min-height: 30px;\n font-size: 12px;\n}\n\n.settings-connection-row__detail time {\n color: var(--settings-muted);\n opacity: 0.8;\n}\n\n.settings-empty {\n display: flex;\n align-items: center;\n min-height: 72px;\n padding: 12px 16px;\n}\n\n.settings-empty > div {\n display: grid;\n min-width: 0;\n gap: 2px;\n}\n\n.settings-empty strong {\n color: var(--settings-text);\n font-size: 13px;\n font-weight: 500;\n line-height: 19px;\n}\n\n.settings-empty span {\n color: var(--settings-muted);\n font-size: 13px;\n line-height: 19px;\n}\n\n.settings-page-error {\n padding: 16px;\n color: var(--settings-text);\n font-size: 13px;\n background: var(--settings-panel);\n border: 1px solid var(--settings-border);\n border-radius: 20px;\n}\n\n.settings-update-metadata {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 24px;\n margin-bottom: 24px;\n padding: 0 4px 20px;\n border-bottom: 1px solid var(--settings-divider);\n}\n\n.settings-update-metadata__item {\n display: grid;\n min-width: 0;\n gap: 4px;\n}\n\n.settings-update-metadata__item span {\n color: var(--settings-muted);\n font-size: 12px;\n line-height: 18px;\n}\n\n.settings-update-metadata__item strong {\n min-width: 0;\n overflow-wrap: anywhere;\n color: var(--settings-text);\n font-size: 15px;\n font-weight: 500;\n line-height: 22px;\n}\n\n.settings-update-panel {\n display: grid;\n gap: 16px;\n min-height: 128px;\n padding: 20px;\n color: var(--settings-text);\n background: var(--settings-panel);\n border: 1px solid var(--settings-border);\n border-radius: 8px;\n}\n\n.settings-update-panel > strong,\n.settings-update-panel > span,\n.settings-update-summary,\n.settings-update-error {\n margin: 0;\n overflow-wrap: anywhere;\n font-size: 13px;\n line-height: 20px;\n}\n\n.settings-update-panel > span,\n.settings-update-summary {\n color: var(--settings-muted);\n}\n\n.settings-update-error {\n color: #ef4444;\n}\n\n.settings-update-progress {\n width: 100%;\n height: 8px;\n accent-color: #238be8;\n}\n\n.settings-update-progress-detail {\n color: var(--settings-muted);\n font-size: 12px;\n line-height: 18px;\n}\n\n.settings-update-notes {\n display: grid;\n min-width: 0;\n gap: 12px;\n color: var(--settings-muted);\n font-size: 14px;\n line-height: 22px;\n overflow-wrap: anywhere;\n}\n\n.settings-update-notes > :first-child {\n margin-top: 0;\n}\n\n.settings-update-notes > :last-child {\n margin-bottom: 0;\n}\n\n.settings-update-notes h1,\n.settings-update-notes h2,\n.settings-update-notes h3,\n.settings-update-notes h4,\n.settings-update-notes h5,\n.settings-update-notes h6 {\n margin: 6px 0 0;\n color: var(--settings-text);\n font-weight: 600;\n}\n\n.settings-update-notes h1 {\n font-size: 18px;\n line-height: 26px;\n}\n\n.settings-update-notes h2 {\n font-size: 17px;\n line-height: 24px;\n}\n\n.settings-update-notes h3,\n.settings-update-notes h4,\n.settings-update-notes h5,\n.settings-update-notes h6 {\n font-size: 14px;\n line-height: 22px;\n}\n\n.settings-update-notes p,\n.settings-update-notes ul,\n.settings-update-notes ol,\n.settings-update-notes pre,\n.settings-update-notes blockquote {\n margin: 0;\n}\n\n.settings-update-notes ul,\n.settings-update-notes ol {\n padding-left: 24px;\n}\n\n.settings-update-notes li {\n padding-left: 4px;\n}\n\n.settings-update-notes li::marker {\n color: var(--settings-muted);\n font-size: 0.85em;\n}\n\n.settings-update-notes li + li {\n margin-top: 14px;\n}\n\n.settings-update-notes .release-note-translation {\n display: block;\n margin-top: 3px;\n color: var(--settings-muted);\n font-size: 13px;\n line-height: 20px;\n}\n\n.settings-update-notes blockquote {\n padding: 8px 12px;\n color: var(--settings-muted);\n border-left: 3px solid var(--settings-focus);\n background: rgb(255 255 255 / 4%);\n}\n\n.settings-update-notes hr {\n width: 100%;\n margin: 2px 0;\n border: 0;\n border-top: 1px solid var(--settings-divider);\n}\n\n.settings-update-notes strong {\n color: var(--settings-text);\n font-weight: 600;\n}\n\n.settings-update-notes a {\n color: #7cb8ff;\n text-decoration: none;\n}\n\n.settings-update-notes a:hover {\n text-decoration: underline;\n}\n\n.settings-update-notes code {\n font-family: ui-monospace, "SFMono-Regular", Consolas, "Liberation Mono", monospace;\n font-size: 12px;\n}\n\n.settings-update-notes :not(pre) > code {\n padding: 1px 5px;\n background: rgb(255 255 255 / 8%);\n border-radius: 4px;\n}\n\n.settings-update-notes pre {\n min-width: 0;\n max-width: 100%;\n padding: 10px 12px;\n overflow-x: auto;\n overflow-y: hidden;\n background: rgb(0 0 0 / 28%);\n border-radius: 6px;\n}\n\n.settings-update-notes pre code {\n line-height: 18px;\n white-space: pre-wrap;\n}\n\n.settings-update-version-row {\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto;\n align-items: center;\n gap: 20px;\n min-height: 32px;\n color: var(--settings-muted);\n font-size: 13px;\n}\n\n.settings-update-version-row strong {\n color: var(--settings-text);\n font-size: 15px;\n}\n\n.settings-update-manual {\n display: grid;\n min-width: 0;\n gap: 6px;\n margin-top: 14px;\n padding-inline: 4px;\n}\n\n.settings-update-manual[hidden] {\n display: none;\n}\n\n.settings-update-manual span {\n color: var(--settings-muted);\n font-size: 12px;\n line-height: 18px;\n}\n\n.settings-update-manual code {\n overflow-wrap: anywhere;\n color: var(--settings-text);\n font-family: ui-monospace, "SFMono-Regular", Consolas, "Liberation Mono", monospace;\n font-size: 12px;\n line-height: 18px;\n}\n\n.settings-update-actions {\n display: flex;\n min-width: 0;\n margin-top: 16px;\n}\n\n.settings-command-button,\n.settings-update-link {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: fit-content;\n min-height: 36px;\n gap: 8px;\n padding: 8px 12px;\n color: white;\n font: inherit;\n font-size: 13px;\n text-decoration: none;\n background: #1677d2;\n border: 1px solid #238be8;\n border-radius: 6px;\n cursor: pointer;\n}\n\n.settings-command-button--secondary,\n.settings-update-link {\n color: var(--settings-text);\n background: transparent;\n border-color: var(--settings-border);\n}\n\n.settings-command-button:hover,\n.settings-update-link:hover {\n filter: brightness(1.08);\n}\n\n.settings-command-button:focus-visible,\n.settings-update-link:focus-visible {\n outline: 2px solid var(--settings-focus);\n outline-offset: 2px;\n}\n\n.codexhost-settings-icon {\n display: block;\n flex: none;\n stroke: currentColor;\n}\n\n@media (max-width: 720px) {\n .codexhost-settings-dialog {\n width: calc(100vw - 16px);\n height: calc(100vh - 16px);\n border-radius: 10px;\n }\n\n .settings-layout {\n grid-template-columns: minmax(0, 1fr);\n grid-template-rows: auto minmax(0, 1fr);\n }\n\n .settings-sidebar {\n border-right: 0;\n border-bottom: 1px solid var(--settings-border);\n }\n\n .settings-header {\n height: 56px;\n padding-inline: 14px;\n }\n\n .settings-icon-button.settings-star-link {\n width: 28px;\n padding: 0;\n }\n\n .settings-star-link span {\n display: none;\n }\n\n .settings-sidebar {\n padding-top: 20px;\n }\n\n .settings-nav {\n flex: none;\n flex-direction: row;\n gap: 4px;\n padding: 7px 8px 8px;\n overflow-x: auto;\n overflow-y: hidden;\n }\n\n .settings-nav-button {\n width: auto;\n min-width: max-content;\n min-height: 34px;\n grid-template-columns: 16px auto;\n border-radius: 10px;\n }\n\n .settings-page__content {\n width: calc(100% - 40px);\n }\n\n .settings-page__content {\n padding-top: 32px;\n padding-bottom: 32px;\n }\n\n .settings-section-label {\n padding-bottom: 20px;\n font-size: 20px;\n line-height: 26px;\n }\n\n .settings-status-row {\n grid-template-columns: minmax(0, 1fr) auto;\n gap: 16px;\n }\n\n .settings-connection-row {\n grid-template-columns: minmax(0, 1fr) auto;\n gap: 10px 16px;\n }\n\n .settings-connection-row__detail {\n grid-column: 1 / -1;\n }\n\n .settings-status-row__detail {\n grid-column: 1 / -1;\n margin: -8px 0 0 58px;\n }\n}\n\n@media (forced-colors: active) {\n :host,\n :host([data-theme="dark"]) {\n --settings-bg: Canvas;\n --settings-sidebar: Canvas;\n --settings-panel: Canvas;\n --settings-text: CanvasText;\n --settings-muted: GrayText;\n --settings-border: ButtonBorder;\n --settings-divider: ButtonBorder;\n --settings-hover: Highlight;\n --settings-active: Highlight;\n --settings-focus: Highlight;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n *,\n *::before,\n *::after {\n scroll-behavior: auto !important;\n }\n}\n';
19479
20621
 
19480
20622
  // src/settings/shell.ts
19481
20623
  var SETTINGS_SHELL_ATTRIBUTE = "data-codexhost-settings-shell";
@@ -19945,7 +21087,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
19945
21087
  const messages = rendererSettingsMessages(locale);
19946
21088
  const definitions = createDefaultRendererSettingsPages(
19947
21089
  messages,
19948
- options.getUpdateClient ?? (() => null)
21090
+ options.getUpdateClient ?? (() => null),
21091
+ options.getConnectionDiagnostics ?? (() => null)
19949
21092
  );
19950
21093
  const nextShell = installRendererSettingsShell(definitions, messages, ownerWindow.document);
19951
21094
  const nextTrigger = installRendererSettingsHeaderTrigger({
@@ -20137,17 +21280,22 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
20137
21280
  function draftPermissionMode(catalog, requested) {
20138
21281
  return catalog.modes.find(({ id }) => id === requested)?.id ?? catalog.modes.find(({ id }) => id === catalog.defaultModeId)?.id ?? catalog.defaultModeId;
20139
21282
  }
21283
+ function shouldPersistNewThreadConfigurationSelection(phase) {
21284
+ return phase === "draft";
21285
+ }
20140
21286
  function restoredThreadOwnership(inspection) {
20141
21287
  if (inspection.owner === "codex") return { agent: "codex" };
20142
21288
  if (inspection.harnessId === "pi") {
20143
- if (!isPiTransportModelId(inspection.transportModelId)) {
21289
+ const transportSelection = decodePiTransportModelId(inspection.transportModelId);
21290
+ if (!transportSelection) {
20144
21291
  throw new Error("Pi Thread reported an incompatible transport Model");
20145
21292
  }
20146
- const piThinkingOptionId = selectableThinkingOptionId(inspection);
21293
+ const model = inspection.effectiveModel ?? transportSelection.model;
21294
+ const thinkingOptionId = selectableThinkingOptionId(inspection) ?? transportSelection.thinkingOptionId;
20147
21295
  return {
20148
21296
  agent: "pi",
20149
- ...inspection.effectiveModel ? { model: inspection.effectiveModel } : {},
20150
- ...piThinkingOptionId ? { thinkingOptionId: piThinkingOptionId } : {},
21297
+ ...model ? { model } : {},
21298
+ ...thinkingOptionId ? { thinkingOptionId } : {},
20151
21299
  ...inspection.effectivePermissionModeId ? { permissionModeId: inspection.effectivePermissionModeId } : {}
20152
21300
  };
20153
21301
  }
@@ -20158,10 +21306,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
20158
21306
  }
20159
21307
  const model = inspection.effectiveModel ?? transportSelection.model;
20160
21308
  const thinkingOptionId = selectableThinkingOptionId(inspection) ?? transportSelection.thinkingOptionId;
21309
+ const permissionModeId = inspection.effectivePermissionModeId ?? transportSelection.permissionModeId;
20161
21310
  return {
20162
21311
  agent: "grok",
20163
21312
  ...model ? { model } : {},
20164
- ...thinkingOptionId ? { thinkingOptionId } : {}
21313
+ ...thinkingOptionId ? { thinkingOptionId } : {},
21314
+ ...permissionModeId ? { permissionModeId } : {}
20165
21315
  };
20166
21316
  }
20167
21317
  if (inspection.harnessId === "claude-code") {
@@ -20192,10 +21342,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
20192
21342
  function isOwnershipSubmissionBlocked(status) {
20193
21343
  return status === "loading" || status === "error";
20194
21344
  }
20195
- function shouldTransferComposerState(sourceTarget, replacementTarget, sourcePhase) {
21345
+ function shouldTransferComposerState(sourceTarget, replacementTarget, sourcePhase, submissionPending = false) {
20196
21346
  if (!sourceTarget || !replacementTarget) return false;
20197
21347
  if (sourceTarget === replacementTarget) return true;
20198
- return sourcePhase === "locked" && sourceTarget[0] === "default" && replacementTarget[0] === "conversation";
21348
+ return (sourcePhase === "locked" || submissionPending) && sourceTarget[0] === "default" && replacementTarget[0] === "conversation";
20199
21349
  }
20200
21350
  function isLateConversationTarget(mountedTarget, currentTarget) {
20201
21351
  if (currentTarget?.[0] !== "conversation") return false;
@@ -20204,13 +21354,16 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
20204
21354
  if (mountedTarget?.[0] !== "conversation") return false;
20205
21355
  return mountedTarget.length !== currentTarget.length || mountedTarget.some((value, index) => value !== currentTarget[index]);
20206
21356
  }
20207
- function lateConversationTargetResolution(mountedTarget, currentTarget, sourcePhase) {
21357
+ function lateConversationTargetResolution(mountedTarget, currentTarget, sourcePhase, submissionPending = false) {
20208
21358
  if (!isLateConversationTarget(mountedTarget, currentTarget)) return "none";
20209
- return mountedTarget?.[0] === "default" && sourcePhase === "locked" ? "transfer" : "inspect";
21359
+ return mountedTarget?.[0] === "default" && (sourcePhase === "locked" || submissionPending) ? "transfer" : "inspect";
20210
21360
  }
20211
21361
  function isComposerModelWriteAllowed(target) {
20212
21362
  return target?.[0] === "default";
20213
21363
  }
21364
+ function shouldApplyDraftAgentCarrier(agent, model) {
21365
+ return agent === "codex" || model !== void 0;
21366
+ }
20214
21367
  function applyComposerModelWrite(target, write) {
20215
21368
  if (target?.[0] === "conversation") return true;
20216
21369
  if (!isComposerModelWriteAllowed(target)) return false;
@@ -20275,8 +21428,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
20275
21428
  getClient: () => modelControl,
20276
21429
  getLocalAgent: localAgentForSidebarThread
20277
21430
  });
21431
+ let connectionDiagnostics = null;
20278
21432
  const settingsLifecycle = installRendererSettingsLifecycle(window, {
20279
- getUpdateClient: () => modelControl
21433
+ getUpdateClient: () => modelControl,
21434
+ getConnectionDiagnostics: () => connectionDiagnostics
20280
21435
  });
20281
21436
  let adapterStatus = {
20282
21437
  state: "installing",
@@ -20287,6 +21442,16 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
20287
21442
  let harnessAvailability = Object.fromEntries(
20288
21443
  externalAgents.map((agent) => [agent, "checking"])
20289
21444
  );
21445
+ const harnessAvailabilityErrors = {
21446
+ pi: void 0,
21447
+ "claude-code": void 0,
21448
+ "deepseek-harness": void 0,
21449
+ grok: void 0
21450
+ };
21451
+ const connectionListeners = /* @__PURE__ */ new Set();
21452
+ const publishConnectionStatus = () => {
21453
+ for (const listener of connectionListeners) listener();
21454
+ };
20290
21455
  let availabilityRequestGeneration = 0;
20291
21456
  let availabilityRequest = null;
20292
21457
  let availabilityRetryTimer = null;
@@ -20306,7 +21471,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
20306
21471
  writeNewThreadExternalConfigurationPreference(
20307
21472
  state.agent,
20308
21473
  model,
20309
- controller.thinkingOptionForAgent(composer, state.agent)
21474
+ controller.thinkingOptionForAgent(composer, state.agent),
21475
+ controller.permissionModeForAgent(composer, state.agent)
20310
21476
  );
20311
21477
  }
20312
21478
  }
@@ -20333,6 +21499,40 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
20333
21499
  mounted.accountCredits
20334
21500
  );
20335
21501
  };
21502
+ const refreshCommands = async (mounted) => {
21503
+ const state = controller.get(mounted.composer);
21504
+ const threadId = threadIdFromComposerModelTarget(mounted.modelTarget);
21505
+ if (state.agent === "codex" || !threadId || !modelControl) {
21506
+ mounted.control.harnessCommands.setCommands([]);
21507
+ return;
21508
+ }
21509
+ try {
21510
+ const catalog = await modelControl.inspectThreadCommands({ threadId });
21511
+ if (disposed || mountedByComposer.get(mounted.composer) !== mounted || threadIdFromComposerModelTarget(mounted.modelTarget) !== threadId || controller.get(mounted.composer).agent === "codex") {
21512
+ return;
21513
+ }
21514
+ mounted.control.harnessCommands.setCommands(catalog.commands);
21515
+ } catch {
21516
+ if (mountedByComposer.get(mounted.composer) === mounted) {
21517
+ mounted.control.harnessCommands.setCommands([]);
21518
+ }
21519
+ }
21520
+ };
21521
+ const executeCommand = async (mounted, command) => {
21522
+ const threadId = threadIdFromComposerModelTarget(mounted.modelTarget);
21523
+ if (!threadId || !modelControl || controller.get(mounted.composer).agent === "codex") return;
21524
+ mounted.control.harnessCommands.setExecuting(command.id);
21525
+ try {
21526
+ await modelControl.executeThreadCommand({ threadId, commandId: command.id });
21527
+ } catch (error51) {
21528
+ console.error(
21529
+ "codexhost Harness command failed",
21530
+ error51 instanceof Error ? error51.message : String(error51)
21531
+ );
21532
+ } finally {
21533
+ mounted.control.harnessCommands.setExecuting(null);
21534
+ }
21535
+ };
20336
21536
  const applyThreadUsageUpdate = (update) => {
20337
21537
  for (const mounted of mountedByComposer.values()) {
20338
21538
  if (threadIdFromComposerModelTarget(mounted.modelTarget) !== update.threadId) continue;
@@ -20464,6 +21664,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
20464
21664
  } finally {
20465
21665
  if (isCurrentOwnershipRequest(mounted, generation)) {
20466
21666
  renderMounted(mounted);
21667
+ if (mounted.ownershipStatus !== "error") void refreshCommands(mounted);
20467
21668
  sidebarAgentIcons.refresh();
20468
21669
  if (mounted.ownershipStatus !== "error" && shouldRetryExternalThreadUsage(
20469
21670
  controller.get(mounted.composer).agent,
@@ -20480,7 +21681,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
20480
21681
  const resolution = lateConversationTargetResolution(
20481
21682
  mounted.modelTarget,
20482
21683
  currentTarget,
20483
- controller.get(mounted.composer).phase
21684
+ controller.get(mounted.composer).phase,
21685
+ controller.isSubmissionPending(mounted.composer)
20484
21686
  );
20485
21687
  if (resolution === "none") return false;
20486
21688
  const previousTarget = mounted.modelTarget;
@@ -20547,6 +21749,16 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
20547
21749
  }
20548
21750
  if (inspection.status !== "ready") throw new Error(inspection.error.message);
20549
21751
  const current = controller.get(mounted.composer);
21752
+ const previousModel = controller.modelForAgent(mounted.composer, agent);
21753
+ const previousModelAvailable = previousModel !== void 0 && inspection.catalog.models.some((model) => model.ref.id === previousModel.id);
21754
+ if (current.phase === "locked" && previousModel && !previousModelAvailable) {
21755
+ throw new Error("Existing Thread Model is absent from the current Catalog");
21756
+ }
21757
+ const preferredConfiguration = current.phase === "draft" && !previousModelAvailable ? readNewThreadExternalConfigurationPreference(
21758
+ agent,
21759
+ inspection.catalog,
21760
+ inspection.permissionModes
21761
+ ) : void 0;
20550
21762
  const previousPermissionModeId = controller.permissionModeForAgent(mounted.composer, agent);
20551
21763
  let selectedPermissionModeId;
20552
21764
  if (inspection.capabilities.configuration.selectPermissionMode) {
@@ -20556,7 +21768,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
20556
21768
  }
20557
21769
  mounted.permissionModeView = { status: "loading", catalog: permissionModes };
20558
21770
  const effectivePermissionModeId = current.phase === "locked" ? mounted.threadConfiguration?.effectivePermissionModeId : void 0;
20559
- const preferredPermissionModeId = agent === "claude-code" ? readClaudePermissionModePreference(permissionModes) : void 0;
21771
+ const preferredPermissionModeId = preferredConfiguration?.permissionModeId ?? (agent === "claude-code" ? readClaudePermissionModePreference(permissionModes) : void 0);
20560
21772
  selectedPermissionModeId = draftPermissionMode(
20561
21773
  permissionModes,
20562
21774
  effectivePermissionModeId ?? previousPermissionModeId ?? preferredPermissionModeId
@@ -20585,12 +21797,6 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
20585
21797
  }
20586
21798
  return;
20587
21799
  }
20588
- const previousModel = controller.modelForAgent(mounted.composer, agent);
20589
- const previousModelAvailable = previousModel !== void 0 && inspection.catalog.models.some((model) => model.ref.id === previousModel.id);
20590
- if (current.phase === "locked" && previousModel && !previousModelAvailable) {
20591
- throw new Error("Existing Thread Model is absent from the current Catalog");
20592
- }
20593
- const preferredConfiguration = current.phase === "draft" && !previousModelAvailable ? readNewThreadExternalConfigurationPreference(agent, inspection.catalog) : void 0;
20594
21800
  const selected = previousModelAvailable ? previousModel : preferredConfiguration?.model ?? inspection.catalog.defaultModel;
20595
21801
  if (!selected) throw new Error("External Harness did not report its default Model");
20596
21802
  const effectiveCatalog = current.phase === "locked" && mounted.threadConfiguration ? catalogWithConfigurationState(inspection.catalog, selected, mounted.threadConfiguration) : inspection.catalog;
@@ -20670,6 +21876,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
20670
21876
  }
20671
21877
  };
20672
21878
  const selectExternalModel = async (mounted, modelId) => {
21879
+ controller.clearPendingSubmission(mounted.composer);
20673
21880
  const current = controller.get(mounted.composer);
20674
21881
  if (current.agent === "codex") return;
20675
21882
  const agent = current.agent;
@@ -20741,13 +21948,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
20741
21948
  effectiveThinkingOptionId = supportsThinkingSelection ? selectableThinkingOptionId(state) : void 0;
20742
21949
  effectiveCatalog = supportsThinkingSelection ? catalogWithConfigurationState(catalog, effectiveModel, state) : catalog;
20743
21950
  resolvedModelLabel = state.resolvedModelLabel;
20744
- const effectivePermissionModeId = state.effectivePermissionModeId ?? previousPermissionModeId;
21951
+ const effectivePermissionModeId2 = state.effectivePermissionModeId ?? previousPermissionModeId;
20745
21952
  if (!applyExternalConfiguration(
20746
21953
  mounted,
20747
21954
  agent,
20748
21955
  effectiveModel,
20749
21956
  effectiveThinkingOptionId,
20750
- effectivePermissionModeId
21957
+ effectivePermissionModeId2
20751
21958
  )) {
20752
21959
  throw new Error("Confirmed external Model could not be applied to the Composer");
20753
21960
  }
@@ -20756,11 +21963,18 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
20756
21963
  if (!isCurrentModelRequest(mounted, generation)) return;
20757
21964
  controller.setExternalModel(mounted.composer, agent, effectiveModel);
20758
21965
  controller.setExternalThinkingOption(mounted.composer, agent, effectiveThinkingOptionId);
20759
- writeNewThreadExternalConfigurationPreference(
20760
- agent,
20761
- effectiveModel,
20762
- effectiveThinkingOptionId
20763
- );
21966
+ const effectivePermissionModeId = mounted.threadConfiguration?.effectivePermissionModeId ?? previousPermissionModeId;
21967
+ if (effectivePermissionModeId) {
21968
+ controller.setExternalPermissionMode(mounted.composer, agent, effectivePermissionModeId);
21969
+ }
21970
+ if (shouldPersistNewThreadConfigurationSelection(current.phase)) {
21971
+ writeNewThreadExternalConfigurationPreference(
21972
+ agent,
21973
+ effectiveModel,
21974
+ effectiveThinkingOptionId,
21975
+ effectivePermissionModeId
21976
+ );
21977
+ }
20764
21978
  mounted.modelView = {
20765
21979
  status: "ready",
20766
21980
  catalog: effectiveCatalog,
@@ -20793,6 +22007,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
20793
22007
  }
20794
22008
  };
20795
22009
  const selectPermissionMode = async (mounted, permissionModeId) => {
22010
+ controller.clearPendingSubmission(mounted.composer);
20796
22011
  const current = controller.get(mounted.composer);
20797
22012
  if (current.agent === "codex") return;
20798
22013
  const agent = current.agent;
@@ -20801,9 +22016,6 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
20801
22016
  const model = controller.modelForAgent(mounted.composer, agent);
20802
22017
  if (!catalog || !selectedPermissionModeId || !model || !modelControl) return;
20803
22018
  const previousPermissionModeId = controller.permissionModeForAgent(mounted.composer, agent);
20804
- if (agent === "claude-code") {
20805
- writeClaudePermissionModePreference(selectedPermissionModeId);
20806
- }
20807
22019
  const thinkingOptionId = controller.thinkingOptionForAgent(mounted.composer, agent);
20808
22020
  const generation = controller.beginModelRequest(mounted.composer);
20809
22021
  mounted.permissionModeView = {
@@ -20868,6 +22080,17 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
20868
22080
  }
20869
22081
  if (!isCurrentModelRequest(mounted, generation)) return;
20870
22082
  controller.setExternalPermissionMode(mounted.composer, agent, effectivePermissionModeId);
22083
+ if (shouldPersistNewThreadConfigurationSelection(current.phase)) {
22084
+ writeNewThreadExternalConfigurationPreference(
22085
+ agent,
22086
+ model,
22087
+ thinkingOptionId,
22088
+ effectivePermissionModeId
22089
+ );
22090
+ if (agent === "claude-code") {
22091
+ writeClaudePermissionModePreference(effectivePermissionModeId);
22092
+ }
22093
+ }
20871
22094
  mounted.permissionModeView = {
20872
22095
  status: "ready",
20873
22096
  catalog,
@@ -20895,6 +22118,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
20895
22118
  }
20896
22119
  };
20897
22120
  const selectExternalThinking = async (mounted, thinkingOptionId) => {
22121
+ controller.clearPendingSubmission(mounted.composer);
20898
22122
  const current = controller.get(mounted.composer);
20899
22123
  if (current.agent === "codex") return;
20900
22124
  const agent = current.agent;
@@ -20973,7 +22197,18 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
20973
22197
  }
20974
22198
  if (!isCurrentModelRequest(mounted, generation)) return;
20975
22199
  controller.setExternalThinkingOption(mounted.composer, agent, effectiveThinkingOptionId);
20976
- writeNewThreadExternalConfigurationPreference(agent, model, effectiveThinkingOptionId);
22200
+ const effectivePermissionModeId = mounted.threadConfiguration?.effectivePermissionModeId ?? permissionModeId;
22201
+ if (effectivePermissionModeId) {
22202
+ controller.setExternalPermissionMode(mounted.composer, agent, effectivePermissionModeId);
22203
+ }
22204
+ if (shouldPersistNewThreadConfigurationSelection(current.phase)) {
22205
+ writeNewThreadExternalConfigurationPreference(
22206
+ agent,
22207
+ model,
22208
+ effectiveThinkingOptionId,
22209
+ effectivePermissionModeId
22210
+ );
22211
+ }
20977
22212
  mounted.modelView = {
20978
22213
  status: "ready",
20979
22214
  catalog: effectiveCatalog,
@@ -20998,13 +22233,16 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
20998
22233
  };
20999
22234
  const switchComposerAgent = async (mounted, agent) => {
21000
22235
  if (agent !== "codex" && harnessAvailability[agent] !== "ready") return false;
22236
+ controller.clearPendingSubmission(mounted.composer);
21001
22237
  const composerId = controller.get(mounted.composer).composerId;
21002
22238
  controller.invalidateModelRequests(mounted.composer);
21003
22239
  const switching = controller.switchAgent(mounted.composer, agent, {
21004
22240
  applyAgent(nextAgent) {
22241
+ const model = controller.modelForAgent(mounted.composer, nextAgent);
22242
+ if (!shouldApplyDraftAgentCarrier(nextAgent, model)) return true;
21005
22243
  return applyAdapterAgent?.(
21006
22244
  nextAgent,
21007
- controller.modelForAgent(mounted.composer, nextAgent),
22245
+ model,
21008
22246
  nextAgent !== "codex" ? controller.thinkingOptionForAgent(mounted.composer, nextAgent) : void 0,
21009
22247
  nextAgent !== "codex" ? controller.permissionModeForAgent(mounted.composer, nextAgent) : void 0,
21010
22248
  mounted.composer
@@ -21070,6 +22308,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
21070
22308
  harnessAvailability[agent] === "ready" ? "ready" : "checking"
21071
22309
  ])
21072
22310
  );
22311
+ publishConnectionStatus();
21073
22312
  for (const mounted of mountedByComposer.values()) renderMounted(mounted);
21074
22313
  const generation = ++availabilityRequestGeneration;
21075
22314
  const promise2 = (async () => {
@@ -21082,8 +22321,28 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
21082
22321
  refresh
21083
22322
  });
21084
22323
  status = inspection.status === "ready" ? "ready" : inspection.status;
21085
- } catch {
22324
+ if (inspection.status === "ready") {
22325
+ harnessAvailabilityErrors[agent] = void 0;
22326
+ } else {
22327
+ const error51 = inspection.error;
22328
+ harnessAvailabilityErrors[agent] = {
22329
+ code: error51.code,
22330
+ message: error51.message,
22331
+ retryable: error51.retryable,
22332
+ ...error51.diagnostic ? { diagnostic: error51.diagnostic } : {},
22333
+ ...error51.stage ? { stage: error51.stage } : {},
22334
+ ...error51.durationMs !== void 0 ? { durationMs: error51.durationMs } : {},
22335
+ ...error51.stderrTail ? { stderrTail: error51.stderrTail } : {}
22336
+ };
22337
+ }
22338
+ } catch (error51) {
21086
22339
  status = "error";
22340
+ harnessAvailabilityErrors[agent] = {
22341
+ code: "internalError",
22342
+ message: error51 instanceof Error ? error51.message : String(error51),
22343
+ retryable: true,
22344
+ stage: "request"
22345
+ };
21087
22346
  }
21088
22347
  if (generation !== availabilityRequestGeneration || disposed) return;
21089
22348
  harnessAvailability = { ...harnessAvailability, [agent]: status };
@@ -21100,6 +22359,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
21100
22359
  }
21101
22360
  renderMounted(mounted);
21102
22361
  }
22362
+ publishConnectionStatus();
21103
22363
  })
21104
22364
  );
21105
22365
  if (externalAgents.every((agent) => harnessAvailability[agent] === "ready")) {
@@ -21120,6 +22380,25 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
21120
22380
  );
21121
22381
  return promise2;
21122
22382
  };
22383
+ connectionDiagnostics = {
22384
+ snapshot() {
22385
+ return {
22386
+ adapter: { ...adapterStatus },
22387
+ agents: externalAgents.map((agent) => ({
22388
+ agent,
22389
+ availability: harnessAvailability[agent] ?? "checking",
22390
+ error: harnessAvailabilityErrors[agent] ?? null
22391
+ }))
22392
+ };
22393
+ },
22394
+ refresh() {
22395
+ return refreshHarnessAvailability(true);
22396
+ },
22397
+ subscribe(listener) {
22398
+ connectionListeners.add(listener);
22399
+ return () => connectionListeners.delete(listener);
22400
+ }
22401
+ };
21123
22402
  const mount = (composer) => {
21124
22403
  if (mountedByComposer.has(composer) || !composer.isConnected || !composer.matches(CODEX_COMPOSER_SELECTOR)) {
21125
22404
  return;
@@ -21128,6 +22407,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
21128
22407
  const sendButton = sendButtonWithin(composer) ?? allButtons.at(-1) ?? null;
21129
22408
  if (!sendButton) return;
21130
22409
  const modelTarget = findComposerModelTarget(composer);
22410
+ const editor = composer.querySelector(EDITOR_SELECTOR);
22411
+ if (!editor) return;
21131
22412
  const state = controller.mount(
21132
22413
  composer,
21133
22414
  modelTarget,
@@ -21159,6 +22440,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
21159
22440
  const mounted2 = mountedByComposer.get(composer);
21160
22441
  if (!composer.isConnected || !mounted2) return;
21161
22442
  void selectPermissionMode(mounted2, permissionModeId);
22443
+ },
22444
+ (command) => {
22445
+ const mounted2 = mountedByComposer.get(composer);
22446
+ if (mounted2) void executeCommand(mounted2, command);
21162
22447
  }
21163
22448
  );
21164
22449
  const mounted = {
@@ -21176,13 +22461,16 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
21176
22461
  };
21177
22462
  mountedByComposer.set(composer, mounted);
21178
22463
  if (isComposerModelWriteAllowed(modelTarget)) {
21179
- applyAdapterAgent?.(
21180
- state.agent,
21181
- controller.modelForAgent(composer, state.agent),
21182
- state.agent !== "codex" ? controller.thinkingOptionForAgent(composer, state.agent) : void 0,
21183
- state.agent !== "codex" ? controller.permissionModeForAgent(composer, state.agent) : void 0,
21184
- composer
21185
- );
22464
+ const model = controller.modelForAgent(composer, state.agent);
22465
+ if (shouldApplyDraftAgentCarrier(state.agent, model)) {
22466
+ applyAdapterAgent?.(
22467
+ state.agent,
22468
+ model,
22469
+ state.agent !== "codex" ? controller.thinkingOptionForAgent(composer, state.agent) : void 0,
22470
+ state.agent !== "codex" ? controller.permissionModeForAgent(composer, state.agent) : void 0,
22471
+ composer
22472
+ );
22473
+ }
21186
22474
  }
21187
22475
  renderMounted(mounted);
21188
22476
  sidebarAgentIcons.refresh();
@@ -21193,6 +22481,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
21193
22481
  } else if (state.agent !== "codex" && !isExternalConfigurationReady(mounted)) {
21194
22482
  void loadExternalCatalog(mounted);
21195
22483
  }
22484
+ void refreshCommands(mounted);
21196
22485
  };
21197
22486
  const scan = () => {
21198
22487
  scanScheduled = false;
@@ -21206,7 +22495,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
21206
22495
  if (!shouldTransferComposerState(
21207
22496
  replacement.sourceModelTarget,
21208
22497
  replacementTarget,
21209
- sourceState.phase
22498
+ sourceState.phase,
22499
+ controller.isSubmissionPending(replacement.source.composer)
21210
22500
  ) || !controller.transfer(replacement.source.composer, target, replacementTarget)) {
21211
22501
  pendingReplacements.delete(target);
21212
22502
  }
@@ -21288,11 +22578,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
21288
22578
  return state.phase === "locked" && mounted.ownershipStatus === "ready";
21289
22579
  }
21290
22580
  if (!mounted || !isComposerModelWriteAllowed(mounted.modelTarget)) return false;
22581
+ const model = controller.modelForAgent(composer, state.agent);
22582
+ if (!shouldApplyDraftAgentCarrier(state.agent, model)) return false;
21291
22583
  return applyComposerModelWrite(
21292
22584
  mounted.modelTarget,
21293
22585
  () => applyAdapterAgent?.(
21294
22586
  state.agent,
21295
- controller.modelForAgent(composer, state.agent),
22587
+ model,
21296
22588
  state.agent !== "codex" ? controller.thinkingOptionForAgent(composer, state.agent) : void 0,
21297
22589
  state.agent !== "codex" ? controller.permissionModeForAgent(composer, state.agent) : void 0,
21298
22590
  composer
@@ -21314,7 +22606,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
21314
22606
  if (!isExternalConfigurationReady(mounted)) return false;
21315
22607
  if (current.phase === "locked") return true;
21316
22608
  if (!applyComposerAgent(composer)) return false;
21317
- controller.lock(composer);
22609
+ controller.markSubmissionPending(composer);
21318
22610
  renderMounted(mounted);
21319
22611
  return true;
21320
22612
  };
@@ -21327,6 +22619,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
21327
22619
  const onBeforeInput = (event) => {
21328
22620
  const composer = composerForTarget(event.target);
21329
22621
  if (!composer) return;
22622
+ controller.clearPendingSubmission(composer);
21330
22623
  const mounted = mountedByComposer.get(composer);
21331
22624
  if (mounted && isOwnershipSubmissionBlocked(mounted.ownershipStatus)) return;
21332
22625
  if (controller.isSwitching(composer) || !applyComposerAgent(composer)) blockEvent(event);
@@ -21385,6 +22678,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
21385
22678
  scheduleScan(mutations.some(mutationMayChangeComposerTarget));
21386
22679
  });
21387
22680
  const onAdapterStatus = () => {
22681
+ publishConnectionStatus();
21388
22682
  if (adapterStatus.state === "ready") {
21389
22683
  sidebarAgentIcons.refresh();
21390
22684
  void refreshHarnessAvailability();
@@ -21466,6 +22760,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
21466
22760
  usageNotificationDispose = null;
21467
22761
  }
21468
22762
  adapterStatus = status;
22763
+ publishConnectionStatus();
21469
22764
  const installedModelControl = modelControl;
21470
22765
  queueMicrotask(() => {
21471
22766
  if (disposed || modelControl !== installedModelControl) return;
@@ -21520,6 +22815,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
21520
22815
  }
21521
22816
  mountedByComposer.clear();
21522
22817
  pendingReplacements.clear();
22818
+ connectionListeners.clear();
22819
+ connectionDiagnostics = null;
21523
22820
  delete window.__codexhostRendererBindingProbeV1;
21524
22821
  }
21525
22822
  };
@@ -21565,6 +22862,7 @@ lucide/dist/esm/icons/shield.mjs:
21565
22862
  lucide/dist/esm/icons/shield-alert.mjs:
21566
22863
  lucide/dist/esm/icons/boxes.mjs:
21567
22864
  lucide/dist/esm/icons/circle-off.mjs:
22865
+ lucide/dist/esm/icons/copy.mjs:
21568
22866
  lucide/dist/esm/icons/download.mjs:
21569
22867
  lucide/dist/esm/icons/external-link.mjs:
21570
22868
  lucide/dist/esm/icons/languages.mjs:
@@ -21573,6 +22871,7 @@ lucide/dist/esm/icons/plug-zap.mjs:
21573
22871
  lucide/dist/esm/icons/refresh-cw.mjs:
21574
22872
  lucide/dist/esm/icons/route.mjs:
21575
22873
  lucide/dist/esm/icons/settings.mjs:
22874
+ lucide/dist/esm/icons/stethoscope.mjs:
21576
22875
  lucide/dist/esm/icons/star.mjs:
21577
22876
  lucide/dist/esm/icons/x.mjs:
21578
22877
  (**