super-dsh 0.1.9 → 0.1.10-e

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/README.md +25 -3
  2. package/agent-agy/cordis.patch.yml +7 -4
  3. package/agent-agy/dist/agent.d.ts +12 -11
  4. package/agent-agy/dist/agy-auth-gateway.d.ts +124 -0
  5. package/agent-agy/dist/agy-auth-gateway.js +233 -0
  6. package/agent-agy/dist/agy-auth.d.ts +153 -0
  7. package/agent-agy/dist/agy-auth.js +179 -0
  8. package/agent-agy/dist/agy-cli-client.d.ts +39 -9
  9. package/agent-agy/dist/agy-cli-client.js +82 -18
  10. package/agent-agy/dist/agy-store.d.ts +8 -0
  11. package/agent-agy/dist/index.d.ts +46 -16
  12. package/agent-agy/dist/index.js +82 -56
  13. package/agent-agy/dist/models.d.ts +70 -0
  14. package/agent-agy/dist/models.js +102 -3
  15. package/agent-agy/package.json +7 -5
  16. package/agent-agy-sdk/cordis.patch.yml +58 -0
  17. package/agent-agy-sdk/dist/adapter.d.ts +15 -0
  18. package/agent-agy-sdk/dist/adapter.js +42 -0
  19. package/agent-agy-sdk/dist/agent-preset-agy.d.ts +27 -0
  20. package/agent-agy-sdk/dist/agent-preset-agy.js +32 -0
  21. package/agent-agy-sdk/dist/agent.d.ts +138 -0
  22. package/agent-agy-sdk/dist/agent.js +494 -0
  23. package/{agent-agy → agent-agy-sdk}/dist/agy-client.d.ts +26 -6
  24. package/{agent-agy → agent-agy-sdk}/dist/agy-client.js +43 -25
  25. package/agent-agy-sdk/dist/agy-sessions.d.ts +25 -0
  26. package/agent-agy-sdk/dist/agy-sessions.js +107 -0
  27. package/agent-agy-sdk/dist/agy-store.d.ts +39 -0
  28. package/agent-agy-sdk/dist/agy-store.js +83 -0
  29. package/agent-agy-sdk/dist/inbox.d.ts +26 -0
  30. package/agent-agy-sdk/dist/inbox.js +53 -0
  31. package/agent-agy-sdk/dist/index.d.ts +93 -0
  32. package/agent-agy-sdk/dist/index.js +476 -0
  33. package/agent-agy-sdk/dist/models.d.ts +41 -0
  34. package/agent-agy-sdk/dist/models.js +74 -0
  35. package/agent-agy-sdk/dist/sdk-venv.d.ts +76 -0
  36. package/agent-agy-sdk/dist/sdk-venv.js +173 -0
  37. package/agent-agy-sdk/dist/world-plugin.d.ts +10 -0
  38. package/agent-agy-sdk/dist/world-plugin.js +82 -0
  39. package/agent-agy-sdk/package.json +64 -0
  40. package/agent-claude/dist/adapter.js +106 -31
  41. package/agent-claude/dist/agent.js +154 -17
  42. package/agent-claude/dist/claude-client.js +84 -43
  43. package/agent-claude/dist/claude-launch.js +101 -0
  44. package/agent-claude/dist/claude-native-config.js +169 -0
  45. package/agent-claude/dist/claude-provider-gateway.js +340 -0
  46. package/agent-claude/dist/claude-providers.js +391 -0
  47. package/agent-claude/dist/claude-route.js +152 -0
  48. package/agent-claude/dist/index.js +214 -37
  49. package/agent-claude/dist/models.js +14 -0
  50. package/agent-claude/dist/sdk-runtime.js +320 -0
  51. package/agent-claude/package.json +6 -4
  52. package/agent-codex/README.md +141 -36
  53. package/agent-codex/cordis.patch.yml +14 -10
  54. package/agent-codex/dist/adapter.js +64 -28
  55. package/agent-codex/dist/agent.js +255 -81
  56. package/agent-codex/dist/app-server-approvals.js +165 -0
  57. package/agent-codex/dist/app-server-client.js +723 -0
  58. package/agent-codex/dist/app-server-events.js +432 -0
  59. package/agent-codex/dist/app-server-protocol.js +95 -0
  60. package/agent-codex/dist/app-server-wire.js +299 -0
  61. package/agent-codex/dist/codex-launch.js +78 -0
  62. package/agent-codex/dist/codex-native-config.js +392 -0
  63. package/agent-codex/dist/codex-provider-gateway.js +402 -0
  64. package/agent-codex/dist/codex-providers.js +376 -0
  65. package/agent-codex/dist/codex-route.js +86 -0
  66. package/agent-codex/dist/index.js +256 -115
  67. package/agent-codex/dist/permission.js +75 -1
  68. package/agent-codex/package.json +1 -2
  69. package/agent-codex-sdk/README.md +79 -0
  70. package/agent-codex-sdk/cordis.patch.yml +81 -0
  71. package/agent-codex-sdk/dist/adapter.js +91 -0
  72. package/agent-codex-sdk/dist/agent-preset-codex.js +32 -0
  73. package/agent-codex-sdk/dist/agent.js +1085 -0
  74. package/agent-codex-sdk/dist/codex-native-config.js +392 -0
  75. package/agent-codex-sdk/dist/codex-provider-gateway.js +402 -0
  76. package/agent-codex-sdk/dist/codex-providers.js +376 -0
  77. package/agent-codex-sdk/dist/codex-route.js +86 -0
  78. package/{agent-codex/dist/codex-client.js → agent-codex-sdk/dist/codex-sdk-client.js} +253 -32
  79. package/agent-codex-sdk/dist/codex-sdk-launch.js +92 -0
  80. package/agent-codex-sdk/dist/codex-store.js +180 -0
  81. package/agent-codex-sdk/dist/inbox.js +53 -0
  82. package/agent-codex-sdk/dist/index.js +739 -0
  83. package/agent-codex-sdk/dist/permission.js +71 -0
  84. package/agent-codex-sdk/dist/sdk-runtime.js +307 -0
  85. package/agent-codex-sdk/dist/session-map.js +167 -0
  86. package/agent-codex-sdk/dist/world-plugin.js +93 -0
  87. package/agent-codex-sdk/package.json +79 -0
  88. package/agent-codex-sdk/scripts/fetch-sdk-runtime.mjs +57 -0
  89. package/agent-omp/README.md +72 -55
  90. package/agent-omp/dist/agent.js +36 -11
  91. package/agent-omp/dist/index.js +92 -52
  92. package/agent-omp/dist/lazy-rpc.js +11 -4
  93. package/agent-omp/dist/omp-cli.js +114 -97
  94. package/agent-omp/dist/omp-store.js +12 -4
  95. package/agent-omp/dist/permission.js +70 -5
  96. package/agent-omp/dist/rpc-client.js +409 -0
  97. package/agent-omp/dist/world-plugin.js +36 -3
  98. package/agent-omp/package.json +2 -6
  99. package/agent-omp-sdk/README.md +103 -0
  100. package/agent-omp-sdk/cordis.patch.yml +108 -0
  101. package/agent-omp-sdk/dist/adapter.d.ts +21 -0
  102. package/agent-omp-sdk/dist/adapter.js +36 -0
  103. package/agent-omp-sdk/dist/agent-preset-omp.d.ts +31 -0
  104. package/agent-omp-sdk/dist/agent-preset-omp.js +32 -0
  105. package/agent-omp-sdk/dist/agent.d.ts +64 -0
  106. package/agent-omp-sdk/dist/agent.js +1178 -0
  107. package/agent-omp-sdk/dist/discovery.d.ts +57 -0
  108. package/agent-omp-sdk/dist/discovery.js +116 -0
  109. package/agent-omp-sdk/dist/inbox.d.ts +28 -0
  110. package/agent-omp-sdk/dist/inbox.js +53 -0
  111. package/agent-omp-sdk/dist/index.d.ts +60 -0
  112. package/agent-omp-sdk/dist/index.js +632 -0
  113. package/agent-omp-sdk/dist/knobs.d.ts +34 -0
  114. package/agent-omp-sdk/dist/knobs.js +50 -0
  115. package/agent-omp-sdk/dist/lazy-rpc.d.ts +98 -0
  116. package/agent-omp-sdk/dist/lazy-rpc.js +178 -0
  117. package/agent-omp-sdk/dist/mobile/zoom-guard.d.ts +187 -0
  118. package/agent-omp-sdk/dist/mobile/zoom-guard.js +294 -0
  119. package/agent-omp-sdk/dist/mobile-boot.d.ts +49 -0
  120. package/agent-omp-sdk/dist/mobile-boot.js +38 -0
  121. package/agent-omp-sdk/dist/models.d.ts +39 -0
  122. package/agent-omp-sdk/dist/models.js +299 -0
  123. package/agent-omp-sdk/dist/omp-cli.d.ts +67 -0
  124. package/agent-omp-sdk/dist/omp-cli.js +164 -0
  125. package/agent-omp-sdk/dist/omp-disk-discovery.d.ts +27 -0
  126. package/agent-omp-sdk/dist/omp-disk-discovery.js +178 -0
  127. package/agent-omp-sdk/dist/omp-executable.d.ts +2 -0
  128. package/agent-omp-sdk/dist/omp-executable.js +30 -0
  129. package/agent-omp-sdk/dist/omp-store.d.ts +134 -0
  130. package/agent-omp-sdk/dist/omp-store.js +526 -0
  131. package/agent-omp-sdk/dist/pairing.d.ts +7 -0
  132. package/agent-omp-sdk/dist/pairing.js +45 -0
  133. package/agent-omp-sdk/dist/permission.d.ts +138 -0
  134. package/agent-omp-sdk/dist/permission.js +161 -0
  135. package/agent-omp-sdk/dist/protocol.d.ts +189 -0
  136. package/agent-omp-sdk/dist/rpc-types.d.ts +77 -0
  137. package/agent-omp-sdk/dist/rpc-types.js +1 -0
  138. package/agent-omp-sdk/dist/sdk-client.d.ts +160 -0
  139. package/{agent-omp → agent-omp-sdk}/dist/sdk-client.js +108 -56
  140. package/agent-omp-sdk/dist/sdk-runtime.d.ts +211 -0
  141. package/agent-omp-sdk/dist/sdk-runtime.js +418 -0
  142. package/agent-omp-sdk/dist/sidecar-client.d.ts +87 -0
  143. package/agent-omp-sdk/dist/sidecar-client.js +197 -0
  144. package/agent-omp-sdk/dist/store/adapter.d.ts +17 -0
  145. package/agent-omp-sdk/dist/store/adapter.js +47 -0
  146. package/agent-omp-sdk/dist/store/db.d.ts +70 -0
  147. package/agent-omp-sdk/dist/store/db.js +210 -0
  148. package/agent-omp-sdk/dist/store/index.d.ts +23 -0
  149. package/agent-omp-sdk/dist/store/index.js +59 -0
  150. package/agent-omp-sdk/dist/store/reconcile.d.ts +15 -0
  151. package/agent-omp-sdk/dist/store/reconcile.js +51 -0
  152. package/agent-omp-sdk/dist/store/schema.d.ts +8 -0
  153. package/agent-omp-sdk/dist/store/schema.js +40 -0
  154. package/agent-omp-sdk/dist/transport.d.ts +49 -0
  155. package/agent-omp-sdk/dist/transport.js +35 -0
  156. package/agent-omp-sdk/dist/world-home.d.ts +4 -0
  157. package/agent-omp-sdk/dist/world-home.js +23 -0
  158. package/agent-omp-sdk/dist/world-plugin.d.ts +10 -0
  159. package/agent-omp-sdk/dist/world-plugin.js +137 -0
  160. package/agent-omp-sdk/package.json +87 -0
  161. package/client/index.js +1204 -93
  162. package/cordis.patch.yml +64 -0
  163. package/package.json +17 -8
  164. package/superd-hub/dist/client/agent-settings.d.ts +210 -0
  165. package/superd-hub/dist/client/agent-settings.js +300 -0
  166. package/superd-hub/dist/client/roster.js +6 -0
  167. package/superd-hub/dist/client/runtime-settings-api.d.ts +76 -0
  168. package/superd-hub/dist/client/runtime-settings-api.js +137 -0
  169. package/superd-hub/dist/client/switch.d.ts +165 -0
  170. package/superd-hub/dist/client/switch.js +295 -0
  171. package/superd-hub/lib/client/index.js +1204 -93
  172. package/agent-codex/dist/models.js +0 -152
  173. package/agent-omp/dist/sidecar-client.js +0 -195
  174. /package/{agent-agy → agent-agy-sdk}/bridge/agy_bridge.py +0 -0
  175. /package/{agent-agy → agent-agy-sdk}/bridge/gemini_openai_server.mjs +0 -0
  176. /package/{agent-codex/dist/codex-events.js → agent-codex-sdk/dist/codex-sdk-events.js} +0 -0
  177. /package/{agent-omp → agent-omp-sdk}/dist/protocol.js +0 -0
  178. /package/{agent-omp → agent-omp-sdk}/sidecar/main.ts +0 -0
package/client/index.js CHANGED
@@ -25,10 +25,10 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
25
25
  //#endregion
26
26
  let react = require("react");
27
27
  react = __toESM(react);
28
- let react_jsx_runtime = require("react/jsx-runtime");
29
- react_jsx_runtime = __toESM(react_jsx_runtime);
30
28
  let __deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
31
29
  __deepseek_ai_dsh_client_ui_primitives = __toESM(__deepseek_ai_dsh_client_ui_primitives);
30
+ let react_jsx_runtime = require("react/jsx-runtime");
31
+ react_jsx_runtime = __toESM(react_jsx_runtime);
32
32
 
33
33
  //#region src/client/glyph.tsx
34
34
  /**
@@ -241,9 +241,10 @@ function imgMark(uri, size) {
241
241
  */
242
242
  function RuntimeMark({ agentKey, size }) {
243
243
  if (agentKey === "native") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DshFish, { size });
244
- const uri = RUNTIME_ICON_URIS[agentKey];
244
+ const base = agentKey.endsWith("-sdk") ? agentKey.slice(0, -4) : agentKey;
245
+ const uri = RUNTIME_ICON_URIS[base];
245
246
  if (uri === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SubagentGlyph, { size });
246
- const tile = RUNTIME_TILE_BG[agentKey];
247
+ const tile = RUNTIME_TILE_BG[base];
247
248
  if (tile === void 0) return imgMark(uri, size);
248
249
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
249
250
  "aria-hidden": "true",
@@ -280,7 +281,10 @@ const FALLBACK_LABELS = {
280
281
  claude: "Claude",
281
282
  pi: "Pi",
282
283
  hermes: "Hermes",
283
- agy: "Antigravity"
284
+ agy: "Antigravity",
285
+ "omp-sdk": "OMP (SDK)",
286
+ "agy-sdk": "Antigravity (SDK)",
287
+ "codex-sdk": "Codex (SDK)"
284
288
  };
285
289
  /** Coerce a wire `agents` array into links; undefined when unusable/empty. */
286
290
  function coerceAgents(raw) {
@@ -1446,14 +1450,43 @@ function reportMachineDown(label, row) {
1446
1450
  clearTimeout: (handle) => globalThis.clearTimeout(handle)
1447
1451
  });
1448
1452
  }
1453
+ /**
1454
+ * Build the `SwitchHost.openDefaultWorkspace` action.
1455
+ *
1456
+ * dsh 0.2.0-rc.2 removed the request object from the Host's
1457
+ * `workspaces.initializeDefault` Remote: the first-use directory and stored
1458
+ * title are fixed and language-neutral (`default-workspace` under
1459
+ * `<Documents>/deepseek-harness`), so the caller passes only its own lifetime
1460
+ * signal. This module shipped the pre-0.2 `{ directoryName, title }` shape for
1461
+ * a release cycle; the Host then received a plain object where it feeds
1462
+ * `AbortSignal.any(...)`, EVERY empty-plane landing rejected, and the browser
1463
+ * surfaced "Unable to create default workspace". Root-caused 2026-10-02 on
1464
+ * the Hermes world — the only world whose registry was still empty after the
1465
+ * 0.2.0-rc.2 upgrade, hence the only one that took the default-workspace
1466
+ * branch. Exported so the call shape is unit-testable without the
1467
+ * module-level single-bind guard.
1468
+ *
1469
+ * @param deps - the workspace projection and the navigation face.
1470
+ * @param toast - the feedback sink; a refusal is feedback, never a revert.
1471
+ * @returns the host action.
1472
+ */
1473
+ function openDefaultWorkspaceFor(deps, toast) {
1474
+ return () => {
1475
+ deps.workspaces.initializeDefault().then((prepared) => {
1476
+ if (prepared !== void 0) deps.uiWorkspace.openWorkspace(prepared.workspaceId);
1477
+ }).catch(() => {
1478
+ toast("Super D: unable to create the default workspace.");
1479
+ });
1480
+ };
1481
+ }
1449
1482
  /** The bound `switchAgent`, or `undefined` until the entry binds it. */
1450
1483
  let bound;
1451
1484
  /**
1452
1485
  * Bind the page's switch agent from the client services. Idempotent; a no-op
1453
1486
  * without the page atom (a non-fusion page never has one).
1454
- * @param deps - the `connection`, `uiWorkspace`, `workspaces`, `sessions`
1455
- * and `locale` services (structural; the stock client tree always has all
1456
- * five — upstream's ui-workspace injects the same set).
1487
+ * @param deps - the `connection`, `uiWorkspace`, `workspaces` and `sessions`
1488
+ * services (structural; the stock client tree always has all four —
1489
+ * upstream's ui-workspace injects the same set).
1457
1490
  */
1458
1491
  function bindSwitchAgent(deps) {
1459
1492
  if (bound !== void 0) return;
@@ -1474,19 +1507,6 @@ function bindSwitchAgent(deps) {
1474
1507
  }
1475
1508
  };
1476
1509
  };
1477
- const openDefaultWorkspace = () => {
1478
- const language = deps.locale.getSnapshot().active.toLowerCase().split("-")[0];
1479
- const title = language === "zh" ? "默认工作区" : "Default workspace";
1480
- const directoryName = language === "zh" || language === "en" ? title : "default-workspace";
1481
- deps.workspaces.initializeDefault({
1482
- directoryName,
1483
- title
1484
- }).then((prepared) => {
1485
- if (prepared !== void 0) deps.uiWorkspace.openWorkspace(prepared.workspaceId);
1486
- }).catch(() => {
1487
- showToast("Super D: unable to create the default workspace.");
1488
- });
1489
- };
1490
1510
  const controller = createSwitchController({
1491
1511
  setAgent(label) {
1492
1512
  state.setAgent(label);
@@ -1501,7 +1521,7 @@ function bindSwitchAgent(deps) {
1501
1521
  startSession(workspaceId) {
1502
1522
  deps.uiWorkspace.startSession(workspaceId);
1503
1523
  },
1504
- openDefaultWorkspace,
1524
+ openDefaultWorkspace: openDefaultWorkspaceFor(deps, showToast),
1505
1525
  readRestoreCaches,
1506
1526
  onCachesRefreshed(listener, gate) {
1507
1527
  return observeCachesRefreshed(deps.workspaces.list, {
@@ -1619,16 +1639,365 @@ function attemptSwitch(label) {
1619
1639
  return true;
1620
1640
  }
1621
1641
 
1642
+ //#endregion
1643
+ //#region src/client/agent-settings.ts
1644
+ const SETTINGS_PANELS = {
1645
+ agy: {
1646
+ namespace: "agyAuth",
1647
+ kind: "auth"
1648
+ },
1649
+ codex: {
1650
+ namespace: "codexProviders",
1651
+ kind: "providers"
1652
+ },
1653
+ "codex-sdk": {
1654
+ namespace: "codexProviders",
1655
+ kind: "providers"
1656
+ },
1657
+ claude: {
1658
+ namespace: "claudeProviders",
1659
+ kind: "providers"
1660
+ }
1661
+ };
1662
+ /** The panel spec for a runtime, or `undefined` when it has none yet. */
1663
+ function settingsPanelFor(agentKey) {
1664
+ return SETTINGS_PANELS[agentKey];
1665
+ }
1666
+ /**
1667
+ * Accordion transition: at most one row open. Clicking the open row closes it;
1668
+ * clicking another moves the panel there in one click.
1669
+ */
1670
+ function toggleExpanded(current$1, key) {
1671
+ return current$1 === key ? null : key;
1672
+ }
1673
+ function str(value) {
1674
+ return typeof value === "string" && value !== "" ? value : void 0;
1675
+ }
1676
+ function methodOption(raw) {
1677
+ if (typeof raw !== "object" || raw === null) return void 0;
1678
+ const record = raw;
1679
+ const key = str(record.key);
1680
+ const label = str(record.label);
1681
+ if (key === void 0 || label === void 0) return void 0;
1682
+ return {
1683
+ key,
1684
+ label,
1685
+ available: record.available === true,
1686
+ placeholder: record.placeholder === true,
1687
+ hint: str(record.hint) ?? ""
1688
+ };
1689
+ }
1690
+ /**
1691
+ * Defensive parse of the RPC answer. Returns `undefined` for anything that is
1692
+ * not a usable view (no methods, wrong shape, adapter absent) so the panel can
1693
+ * render an explicit failure instead of an empty list that looks like success.
1694
+ */
1695
+ function parseAuthView(raw) {
1696
+ if (typeof raw !== "object" || raw === null) return void 0;
1697
+ const record = raw;
1698
+ if (!Array.isArray(record.methods)) return void 0;
1699
+ const methods = record.methods.map(methodOption).filter((m) => m !== void 0);
1700
+ if (methods.length === 0) return void 0;
1701
+ const settingsPath = str(record.settingsPath);
1702
+ const note = str(record.note);
1703
+ return {
1704
+ methods,
1705
+ selected: str(record.selected) ?? null,
1706
+ effective: str(record.effective) ?? null,
1707
+ ...settingsPath === void 0 ? {} : { settingsPath },
1708
+ ...note === void 0 ? {} : { note }
1709
+ };
1710
+ }
1711
+ /**
1712
+ * The option the panel marks as in force: the effective one when the adapter
1713
+ * named it, else the explicit selection. `undefined` when neither is known —
1714
+ * the panel then marks nothing rather than guessing.
1715
+ */
1716
+ function activeMethod(view) {
1717
+ const wanted = view.effective ?? view.selected;
1718
+ if (wanted === null) return void 0;
1719
+ return view.methods.find((method) => method.key === wanted);
1720
+ }
1721
+ /** Whether an option may be picked (placeholders are rendered, never selected). */
1722
+ function selectable(option) {
1723
+ return !option.placeholder;
1724
+ }
1725
+ function nonEmpty(value) {
1726
+ return typeof value === "string" && value !== "" ? value : void 0;
1727
+ }
1728
+ function modelOption(raw) {
1729
+ if (typeof raw !== "object" || raw === null) return void 0;
1730
+ const record = raw;
1731
+ const id = nonEmpty(record.id);
1732
+ if (id === void 0) return void 0;
1733
+ const contextWindow = record.contextWindow;
1734
+ return {
1735
+ id,
1736
+ label: nonEmpty(record.displayName) ?? id,
1737
+ ...typeof contextWindow === "number" && Number.isSafeInteger(contextWindow) && contextWindow > 0 ? { contextWindow } : {}
1738
+ };
1739
+ }
1740
+ function providerAuthKind(raw) {
1741
+ return raw === "env" ? "env" : raw === "authToken" ? "authToken" : "apiKey";
1742
+ }
1743
+ function providerOption(raw) {
1744
+ if (typeof raw !== "object" || raw === null) return void 0;
1745
+ const record = raw;
1746
+ const id = nonEmpty(record.id);
1747
+ const label = nonEmpty(record.label);
1748
+ const baseUrl = nonEmpty(record.baseUrl);
1749
+ if (id === void 0 || label === void 0 || baseUrl === void 0) return void 0;
1750
+ const models = Array.isArray(record.models) ? record.models.map(modelOption).filter((entry) => entry !== void 0) : [];
1751
+ const envName = nonEmpty(record.envName);
1752
+ return {
1753
+ id,
1754
+ label,
1755
+ baseUrl,
1756
+ protocol: record.protocol === "chat" ? "chat" : "responses",
1757
+ models,
1758
+ authKind: providerAuthKind(record.authKind),
1759
+ ...envName === void 0 ? {} : { envName },
1760
+ hasKey: record.hasKey === true
1761
+ };
1762
+ }
1763
+ function protocolOption(raw) {
1764
+ if (typeof raw !== "object" || raw === null) return void 0;
1765
+ const record = raw;
1766
+ const value = nonEmpty(record.value);
1767
+ const label = nonEmpty(record.label);
1768
+ if (value === void 0 || label === void 0) return void 0;
1769
+ return {
1770
+ value,
1771
+ label,
1772
+ available: record.available === true,
1773
+ hint: nonEmpty(record.hint) ?? ""
1774
+ };
1775
+ }
1776
+ function providerPaths(raw) {
1777
+ if (typeof raw !== "object" || raw === null) return void 0;
1778
+ const record = raw;
1779
+ const store = nonEmpty(record.store);
1780
+ if (store === void 0) return void 0;
1781
+ const config = nonEmpty(record.config);
1782
+ const auth = nonEmpty(record.auth);
1783
+ const catalogs = nonEmpty(record.catalogs);
1784
+ const settings = nonEmpty(record.settings);
1785
+ return {
1786
+ store,
1787
+ ...config === void 0 ? {} : { config },
1788
+ ...auth === void 0 ? {} : { auth },
1789
+ ...catalogs === void 0 ? {} : { catalogs },
1790
+ ...settings === void 0 ? {} : { settings }
1791
+ };
1792
+ }
1793
+ /**
1794
+ * Defensive parse of the adapter's providers answer.
1795
+ *
1796
+ * `undefined` for anything that is not a usable view — but an empty `providers`
1797
+ * array is valid (a fresh install), and so is a provider whose `models` array
1798
+ * is empty. `protocols` is the codex face's field: an ABSENT field (the claude
1799
+ * face) reads as "no protocol choice" and never fails the parse, while a
1800
+ * present-but-unusable list is a malformed codex answer and rejects as before.
1801
+ */
1802
+ function parseProvidersView(raw) {
1803
+ if (typeof raw !== "object" || raw === null) return void 0;
1804
+ const record = raw;
1805
+ if (!Array.isArray(record.providers)) return void 0;
1806
+ let protocols = [];
1807
+ if (record.protocols !== void 0) {
1808
+ if (!Array.isArray(record.protocols)) return void 0;
1809
+ protocols = record.protocols.map(protocolOption).filter((entry) => entry !== void 0);
1810
+ if (protocols.length === 0) return void 0;
1811
+ }
1812
+ const paths = providerPaths(record.paths);
1813
+ if (paths === void 0) return void 0;
1814
+ const providers = record.providers.map(providerOption).filter((entry) => entry !== void 0);
1815
+ const syncedAt = record.syncedAt;
1816
+ const note = nonEmpty(record.note);
1817
+ return {
1818
+ providers,
1819
+ native: nativeConfig(record.native),
1820
+ protocols,
1821
+ paths,
1822
+ ...typeof syncedAt === "number" && Number.isFinite(syncedAt) ? { syncedAt } : {},
1823
+ ...note === void 0 ? {} : { note }
1824
+ };
1825
+ }
1826
+ /**
1827
+ * The user's own native config as the adapter last read it. `null` when the
1828
+ * adapter said nothing; otherwise an object carrying whichever halves were
1829
+ * named — codex names a provider id, claude an endpoint URL — and an empty
1830
+ * one is still news (the panel says the file names nothing).
1831
+ */
1832
+ function nativeConfig(raw) {
1833
+ if (typeof raw !== "object" || raw === null) return null;
1834
+ const record = raw;
1835
+ const providerId = nonEmpty(record.providerId);
1836
+ const baseUrl = nonEmpty(record.baseUrl);
1837
+ const model = nonEmpty(record.model);
1838
+ return {
1839
+ ...providerId === void 0 ? {} : { providerId },
1840
+ ...baseUrl === void 0 ? {} : { baseUrl },
1841
+ ...model === void 0 ? {} : { model }
1842
+ };
1843
+ }
1844
+ /** The codex face: a stored key, or the name of a variable holding it. */
1845
+ const KEY_OR_ENV = [{
1846
+ value: "apiKey",
1847
+ label: "API key",
1848
+ hint: "Stored with the provider; injected at launch."
1849
+ }, {
1850
+ value: "env",
1851
+ label: "Environment variable",
1852
+ hint: "The child reads the key from a named variable."
1853
+ }];
1854
+ /** The claude face: both kinds are literal keys, differing only in the header. */
1855
+ const TWO_HEADER_SHAPES = [{
1856
+ value: "apiKey",
1857
+ label: "API key (x-api-key)",
1858
+ hint: "Sent as the x-api-key header."
1859
+ }, {
1860
+ value: "authToken",
1861
+ label: "Bearer token",
1862
+ hint: "Sent as the Authorization header."
1863
+ }];
1864
+ /**
1865
+ * The auth shapes the add/edit form may pick for a provider, in display order.
1866
+ *
1867
+ * The view carries no explicit vocabulary field, so the panel reads it off the
1868
+ * one field that distinguishes the faces today: `protocols`. The codex face
1869
+ * advertises wire protocols and offers key-or-env-var; the claude face carries
1870
+ * neither and offers its two header shapes instead. The list is per-view,
1871
+ * never a fixed union — each adapter rejects the other's kinds on save, so a
1872
+ * union dropdown would miscode silently. A third face extends the table.
1873
+ */
1874
+ function authKindOptions(view) {
1875
+ return view.protocols.length > 0 ? KEY_OR_ENV : TWO_HEADER_SHAPES;
1876
+ }
1877
+ /**
1878
+ * `provider / model` (codex) or `endpoint / model` (claude) — whichever halves
1879
+ * the user's own native config names, joined for the one-line fact the panel
1880
+ * states and never acts on.
1881
+ */
1882
+ function nativeDescription(native) {
1883
+ const parts = [native?.providerId ?? native?.baseUrl, native?.model].filter((part) => part !== void 0);
1884
+ return parts.length === 0 ? "no provider" : parts.join(" / ");
1885
+ }
1886
+ /**
1887
+ * The non-store paths joined for the panel's second footer line, in a fixed
1888
+ * slot order. `undefined` when the face named none — the line is omitted
1889
+ * entirely rather than rendered with blank slots.
1890
+ */
1891
+ function nativePathsLine(paths) {
1892
+ const parts = [
1893
+ paths.config,
1894
+ paths.auth,
1895
+ paths.catalogs,
1896
+ paths.settings
1897
+ ].filter((part) => part !== void 0);
1898
+ return parts.length === 0 ? void 0 : parts.join(" · ");
1899
+ }
1900
+
1901
+ //#endregion
1902
+ //#region src/client/runtime-settings-api.ts
1903
+ /** One rpcId for request/response correlation. `randomUUID` needs a secure context. */
1904
+ function mintRpcId() {
1905
+ const webCrypto = globalThis.crypto;
1906
+ if (webCrypto !== void 0 && typeof webCrypto.randomUUID === "function") return webCrypto.randomUUID();
1907
+ if (webCrypto !== void 0 && typeof webCrypto.getRandomValues === "function") return [...webCrypto.getRandomValues(new Uint8Array(16))].map((b) => b.toString(16).padStart(2, "0")).join("");
1908
+ return `rpc-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
1909
+ }
1910
+ function settingsEnvelope(namespace, op, args) {
1911
+ return {
1912
+ type: "client-request",
1913
+ rpcId: mintRpcId(),
1914
+ method: `${namespace}/${op}`,
1915
+ payload: { args }
1916
+ };
1917
+ }
1918
+ /** What a caller can do with a failed panel call — kept structural for the tests. */
1919
+ function messageOf(error) {
1920
+ if (error !== null && typeof error === "object" && typeof error.message === "string") return error.message;
1921
+ return String(error);
1922
+ }
1923
+ /**
1924
+ * One unary call at a runtime's world, unwrapped to the RPC's value.
1925
+ *
1926
+ * Every failure mode is reported as a thrown `Error` carrying the adapter's own
1927
+ * message where one exists: a transport failure, a malformed answer, and the
1928
+ * gateway's business error (`{ok:false,error}`) must not look alike to the panel.
1929
+ *
1930
+ * @param agentKey - roster key addressing the world (`?agent=`).
1931
+ * @param namespace - the adapter's Remote namespace (`agyAuth`).
1932
+ * @param op - the Remote method.
1933
+ * @param args - exact named wire arguments.
1934
+ * @param doFetch - fetch implementation (tests inject).
1935
+ * @returns the raw RPC value.
1936
+ * @throws {Error} on transport, framing, or gateway failure.
1937
+ */
1938
+ async function callRuntimeSettings(agentKey, namespace, op, args, doFetch) {
1939
+ const send = doFetch ?? ((input, init) => fetch(input, init));
1940
+ const envelope = settingsEnvelope(namespace, op, args);
1941
+ const response = await send(`/hub/api?agent=${encodeURIComponent(agentKey)}`, {
1942
+ method: "POST",
1943
+ credentials: "same-origin",
1944
+ headers: { "content-type": "application/json" },
1945
+ body: JSON.stringify(envelope)
1946
+ });
1947
+ const text = await response.text();
1948
+ let body;
1949
+ try {
1950
+ body = JSON.parse(text);
1951
+ } catch {
1952
+ throw new Error(`settings ${envelope.method}: unreadable answer (HTTP ${response.status})`);
1953
+ }
1954
+ const result = body?.result;
1955
+ if (result === null || typeof result !== "object") throw new Error(`settings ${envelope.method}: malformed answer (HTTP ${response.status})`);
1956
+ const record = result;
1957
+ if (record.ok === true) return record.value;
1958
+ const error = record.error;
1959
+ if (error !== void 0 && typeof error.message === "string") throw new Error(error.message);
1960
+ throw new Error(`settings ${envelope.method}: failed (HTTP ${response.status})`);
1961
+ }
1962
+ /** Read one runtime's panel state, parsed. */
1963
+ async function fetchAuthView(agentKey, namespace, doFetch) {
1964
+ const view = parseAuthView(await callRuntimeSettings(agentKey, namespace, "get", {}, doFetch));
1965
+ if (view === void 0) throw new Error("settings: the runtime returned no usable auth options");
1966
+ return view;
1967
+ }
1968
+ /** Adopt one auth form (`null` clears the explicit choice). */
1969
+ async function setAuthMode(agentKey, namespace, mode, doFetch) {
1970
+ const view = parseAuthView(await callRuntimeSettings(agentKey, namespace, "set", { request: { mode } }, doFetch));
1971
+ if (view === void 0) throw new Error("settings: the runtime returned no usable auth options");
1972
+ return view;
1973
+ }
1974
+ function requireProvidersView(value) {
1975
+ const view = parseProvidersView(value);
1976
+ if (view === void 0) throw new Error("settings: the runtime returned no usable provider list");
1977
+ return view;
1978
+ }
1979
+ /** Read the multi-provider state of one runtime. */
1980
+ async function fetchProvidersView(agentKey, namespace, doFetch) {
1981
+ return requireProvidersView(await callRuntimeSettings(agentKey, namespace, "get", {}, doFetch));
1982
+ }
1983
+ /**
1984
+ * Add or edit one provider in the adapter's list. Which provider a session runs
1985
+ * on is DSH's own per-session model selection, so this touches no active half.
1986
+ */
1987
+ async function saveProvider(agentKey, namespace, provider, doFetch) {
1988
+ return requireProvidersView(await callRuntimeSettings(agentKey, namespace, "save", { request: { provider } }, doFetch));
1989
+ }
1990
+ /** Drop one provider from the list. Does not rewrite the runtime's own config. */
1991
+ async function removeProvider(agentKey, namespace, id, doFetch) {
1992
+ return requireProvidersView(await callRuntimeSettings(agentKey, namespace, "remove", { request: { id } }, doFetch));
1993
+ }
1994
+
1622
1995
  //#endregion
1623
1996
  //#region src/client/HubPage.tsx
1624
1997
  const EMPTY_DRAFT = {
1625
1998
  name: "",
1626
1999
  tokenUrl: ""
1627
2000
  };
1628
- /** Chip text: drop the trailing slash for concision ('/claude/' → '/claude'). */
1629
- function displayPath(path) {
1630
- return path.length > 1 && path.endsWith("/") ? path.slice(0, -1) : path;
1631
- }
1632
2001
  /** Shared chip look (status pills, path tags). */
1633
2002
  const chip = {
1634
2003
  fontSize: 10,
@@ -1679,17 +2048,29 @@ const originStyle = {
1679
2048
  fontSize: 12,
1680
2049
  color: "var(--dsw-alias-label-secondary)"
1681
2050
  };
1682
- /** The quiet text action (reprobe) — the switch link's look. */
1683
- const textActionStyle = {
2051
+ /**
2052
+ * The row's switch control (P3: the `switch` word is a real BUTTON now — the
2053
+ * row body toggles the settings panel instead, so the switch needs its own
2054
+ * hit target that stops propagation).
2055
+ */
2056
+ const switchButtonStyle = {
1684
2057
  border: "none",
1685
2058
  background: "transparent",
1686
2059
  padding: 0,
1687
2060
  font: "inherit",
1688
2061
  fontSize: 12.5,
1689
2062
  color: "var(--dsw-alias-label-secondary)",
1690
- cursor: "pointer",
1691
2063
  whiteSpace: "nowrap"
1692
2064
  };
2065
+ /** The inline settings panel a row body expands into (P3 accordion). */
2066
+ const panelStyle = {
2067
+ listStyle: "none",
2068
+ margin: "0 -8px 4px",
2069
+ padding: "10px 12px 12px 42px",
2070
+ borderRadius: 12,
2071
+ background: "var(--dsw-alias-interactive-bg-hover)",
2072
+ fontSize: 12.5
2073
+ };
1693
2074
  /** The icon action (remove ✕) seat. */
1694
2075
  const iconActionStyle = {
1695
2076
  display: "inline-flex",
@@ -1704,6 +2085,39 @@ const iconActionStyle = {
1704
2085
  font: "inherit",
1705
2086
  cursor: "pointer"
1706
2087
  };
2088
+ /** The quiet text action (reprobe) — the switch/row-action look. */
2089
+ const textActionStyle = {
2090
+ border: "none",
2091
+ background: "transparent",
2092
+ padding: 0,
2093
+ font: "inherit",
2094
+ fontSize: 12.5,
2095
+ color: "var(--dsw-alias-label-secondary)",
2096
+ cursor: "pointer",
2097
+ whiteSpace: "nowrap"
2098
+ };
2099
+ /** The row's settings panel heading. */
2100
+ const panelHeadStyle = {
2101
+ margin: "0 0 6px",
2102
+ fontSize: 12,
2103
+ fontWeight: 500,
2104
+ color: "var(--dsw-alias-label-secondary)"
2105
+ };
2106
+ /** One auth-method option's row in the panel. */
2107
+ const optionStyle = {
2108
+ display: "flex",
2109
+ alignItems: "baseline",
2110
+ gap: 8,
2111
+ width: "100%",
2112
+ textAlign: "left",
2113
+ border: "none",
2114
+ background: "transparent",
2115
+ padding: "5px 6px",
2116
+ borderRadius: 8,
2117
+ font: "inherit",
2118
+ fontSize: 12.5,
2119
+ color: "var(--dsw-alias-label-primary)"
2120
+ };
1707
2121
  /** The status dot's color: reachable green, pending grey, unreachable amber, auth-expired red. */
1708
2122
  function statusDotColor(status) {
1709
2123
  if (status === "reachable") return "var(--dsw-alias-state-success-primary)";
@@ -1775,11 +2189,18 @@ const sectionHeadStyle = {
1775
2189
  whiteSpace: "nowrap",
1776
2190
  listStyle: "none"
1777
2191
  };
1778
- /** A row that lights its surface on hover (inline styles have no :hover). */
1779
- function HoverRow({ children, onClick, clickable }) {
2192
+ /**
2193
+ * A row that lights its surface on hover (inline styles have no :hover).
2194
+ *
2195
+ * P3: the row BODY is the settings-panel toggle, not the switch — `expanded`
2196
+ * keeps the surface lit while its panel is open, so the open row stays
2197
+ * identifiable after the pointer leaves. The switch lives in the row's button.
2198
+ */
2199
+ function HoverRow({ children, onClick, clickable, expanded }) {
1780
2200
  const [hover, setHover] = react.useState(false);
1781
2201
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
1782
2202
  onClick,
2203
+ "aria-expanded": expanded,
1783
2204
  onMouseEnter: () => {
1784
2205
  setHover(true);
1785
2206
  },
@@ -1794,7 +2215,7 @@ function HoverRow({ children, onClick, clickable }) {
1794
2215
  padding: "9px 8px",
1795
2216
  borderRadius: 12,
1796
2217
  listStyle: "none",
1797
- background: hover ? "var(--dsw-alias-interactive-bg-hover)" : "transparent",
2218
+ background: hover || expanded ? "var(--dsw-alias-interactive-bg-hover)" : "transparent",
1798
2219
  cursor: clickable ? "pointer" : void 0
1799
2220
  },
1800
2221
  children
@@ -1831,6 +2252,687 @@ function GroupHead({ title, count, action }) {
1831
2252
  ]
1832
2253
  });
1833
2254
  }
2255
+ /**
2256
+ * One runtime's settings, rendered inside its expanded row (P3/P4).
2257
+ *
2258
+ * The adapter owns the config, so this component owns no config knowledge: each
2259
+ * body asks the adapter's Remote and renders what comes back. The round trip
2260
+ * after a change is authoritative — the adapter re-reads the machine and
2261
+ * answers with the new state, which is why nothing is optimistically patched
2262
+ * here (a selection can fall through a ladder, or a switch can leave a provider
2263
+ * the runtime is still running).
2264
+ *
2265
+ * Mounted only while its row is open, so expanding always re-reads the
2266
+ * machine rather than replaying a stale first read.
2267
+ */
2268
+ function RuntimeSettingsPanel({ agent }) {
2269
+ const panel = settingsPanelFor(agent.key);
2270
+ if (panel === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2271
+ style: {
2272
+ margin: 0,
2273
+ color: "var(--dsw-alias-label-tertiary)"
2274
+ },
2275
+ children: "No settings for this runtime yet."
2276
+ });
2277
+ return panel.kind === "providers" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProvidersPanel, {
2278
+ agent,
2279
+ namespace: panel.namespace
2280
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AuthSettingsPanel, {
2281
+ agent,
2282
+ namespace: panel.namespace
2283
+ });
2284
+ }
2285
+ /**
2286
+ * The sign-in-form body (Antigravity): one form picked from the adapter's
2287
+ * ladder. `namespace` always resolves — the row would not mount this body
2288
+ * otherwise.
2289
+ */
2290
+ function AuthSettingsPanel({ agent, namespace }) {
2291
+ const [view, setView] = react.useState(void 0);
2292
+ const [error, setError] = react.useState(null);
2293
+ const [busy, setBusy] = react.useState(null);
2294
+ const [loading, setLoading] = react.useState(true);
2295
+ react.useEffect(() => {
2296
+ let live = true;
2297
+ setLoading(true);
2298
+ fetchAuthView(agent.key, namespace).then((next) => {
2299
+ if (live) {
2300
+ setView(next);
2301
+ setError(null);
2302
+ }
2303
+ }).catch((err) => {
2304
+ if (live) {
2305
+ setView(void 0);
2306
+ setError(messageOf(err));
2307
+ }
2308
+ }).finally(() => {
2309
+ if (live) setLoading(false);
2310
+ });
2311
+ return () => {
2312
+ live = false;
2313
+ };
2314
+ }, [agent.key, namespace]);
2315
+ const choose = (mode) => {
2316
+ if (busy !== null) return;
2317
+ setBusy(mode);
2318
+ setAuthMode(agent.key, namespace, mode).then((next) => {
2319
+ setView(next);
2320
+ setError(null);
2321
+ }).catch((err) => {
2322
+ setError(messageOf(err));
2323
+ }).finally(() => {
2324
+ setBusy(null);
2325
+ });
2326
+ };
2327
+ if (loading) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2328
+ style: {
2329
+ margin: 0,
2330
+ color: "var(--dsw-alias-label-tertiary)"
2331
+ },
2332
+ children: "Loading settings…"
2333
+ });
2334
+ if (view === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2335
+ style: {
2336
+ margin: 0,
2337
+ color: "var(--dsw-alias-state-warn-label, var(--dsw-alias-label-secondary))"
2338
+ },
2339
+ children: error ?? "Settings are unavailable for this runtime."
2340
+ });
2341
+ const current$1 = activeMethod(view);
2342
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2343
+ style: {
2344
+ display: "flex",
2345
+ flexDirection: "column",
2346
+ gap: 4
2347
+ },
2348
+ children: [
2349
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2350
+ style: panelHeadStyle,
2351
+ children: "Sign-in method"
2352
+ }),
2353
+ view.methods.map((option) => {
2354
+ const isCurrent = current$1?.key === option.key;
2355
+ const disabled = !selectable(option) || busy !== null;
2356
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2357
+ type: "button",
2358
+ role: "radio",
2359
+ "aria-checked": isCurrent,
2360
+ disabled,
2361
+ onClick: () => {
2362
+ choose(option.key);
2363
+ },
2364
+ style: {
2365
+ ...optionStyle,
2366
+ color: disabled ? "var(--dsw-alias-label-tertiary)" : "var(--dsw-alias-label-primary)",
2367
+ cursor: disabled ? "default" : "pointer",
2368
+ background: isCurrent ? "var(--dsw-alias-bg-elevated, transparent)" : "transparent"
2369
+ },
2370
+ children: [
2371
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2372
+ "aria-hidden": true,
2373
+ style: {
2374
+ width: 12,
2375
+ flex: "0 0 12px"
2376
+ },
2377
+ children: isCurrent ? "●" : "○"
2378
+ }),
2379
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2380
+ style: {
2381
+ fontWeight: 500,
2382
+ whiteSpace: "nowrap"
2383
+ },
2384
+ children: option.label
2385
+ }),
2386
+ isCurrent && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2387
+ style: chipSelf,
2388
+ children: "in force"
2389
+ }),
2390
+ !option.available && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2391
+ style: chip,
2392
+ children: "not detected"
2393
+ }),
2394
+ option.placeholder && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2395
+ style: chip,
2396
+ children: "placeholder"
2397
+ }),
2398
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2399
+ style: {
2400
+ flex: 1,
2401
+ minWidth: 0,
2402
+ color: "var(--dsw-alias-label-tertiary)"
2403
+ },
2404
+ children: option.hint
2405
+ }),
2406
+ busy === option.key && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2407
+ style: chip,
2408
+ children: "applying…"
2409
+ })
2410
+ ]
2411
+ }, option.key);
2412
+ }),
2413
+ view.note !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2414
+ style: {
2415
+ margin: "2px 0 0",
2416
+ color: "var(--dsw-alias-label-tertiary)"
2417
+ },
2418
+ children: view.note
2419
+ }),
2420
+ error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2421
+ style: {
2422
+ margin: "2px 0 0",
2423
+ color: "var(--dsw-alias-state-error-primary)"
2424
+ },
2425
+ children: error
2426
+ }),
2427
+ view.settingsPath !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
2428
+ style: {
2429
+ margin: "4px 0 0",
2430
+ color: "var(--dsw-alias-label-caption)",
2431
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
2432
+ fontSize: 11
2433
+ },
2434
+ children: ["writes ", view.settingsPath]
2435
+ })
2436
+ ]
2437
+ });
2438
+ }
2439
+ /** Field and control styling for the providers body — same visual language as the auth list. */
2440
+ const fieldStyle = {
2441
+ display: "flex",
2442
+ flexDirection: "column",
2443
+ gap: 3,
2444
+ fontSize: 12.5,
2445
+ color: "var(--dsw-alias-label-secondary)"
2446
+ };
2447
+ const buttonStyle = {
2448
+ font: "inherit",
2449
+ fontSize: 12.5,
2450
+ padding: "5px 10px",
2451
+ borderRadius: 6,
2452
+ border: "1px solid var(--dsw-alias-border-secondary, rgba(127,127,127,0.3))",
2453
+ background: "transparent",
2454
+ color: "var(--dsw-alias-label-primary)",
2455
+ cursor: "pointer"
2456
+ };
2457
+ const primaryButtonStyle = {
2458
+ ...buttonStyle,
2459
+ border: "none",
2460
+ background: "var(--dsw-alias-label-primary)",
2461
+ color: "var(--dsw-alias-bg-base, #fff)",
2462
+ fontWeight: 500
2463
+ };
2464
+ /** The blank draft the Add form starts from. */
2465
+ const EMPTY_PROVIDER_DRAFT = {
2466
+ id: "",
2467
+ label: "",
2468
+ baseUrl: "",
2469
+ protocol: "responses",
2470
+ authKind: "apiKey",
2471
+ apiKey: "",
2472
+ envName: "",
2473
+ modelIds: "",
2474
+ contextWindow: ""
2475
+ };
2476
+ /** `a, b c` → `['a','b','c']` — the model field takes whatever separator is natural. */
2477
+ function parseModelIds(text) {
2478
+ return text.split(/[\s,]+/).map((part) => part.trim()).filter((part) => part !== "");
2479
+ }
2480
+ /**
2481
+ * The multi-provider body (Codex 2026-10-03 ruling; Claude 2026-10-04 rides
2482
+ * the same body with its own view shape — no `protocols` field, two header
2483
+ * auth kinds, an endpoint-URL native).
2484
+ *
2485
+ * Super D hosts the list cc-switch otherwise holds: add, edit and remove only.
2486
+ * The active/select half is gone — which provider a Codex session runs on is
2487
+ * DSH's own per-session model selection, so no row is marked "in force". What
2488
+ * the user's OWN config.toml names is rendered as a passive fact (the adapter
2489
+ * reconciles the list against that file), and the files it touches are named so
2490
+ * an edit is never a surprise.
2491
+ *
2492
+ * Keys are write-only by construction: the adapter answers `hasKey`, never a
2493
+ * value, so the field is blank on every open and blank means "leave whatever is
2494
+ * already there".
2495
+ */
2496
+ function ProvidersPanel({ agent, namespace }) {
2497
+ const [view, setView] = react.useState(void 0);
2498
+ const [error, setError] = react.useState(null);
2499
+ const [busy, setBusy] = react.useState(null);
2500
+ const [loading, setLoading] = react.useState(true);
2501
+ /** The id being edited, `NEW_PROVIDER` for the add form, `null` when closed. */
2502
+ const [editing, setEditing] = react.useState(null);
2503
+ const [draft, setDraft] = react.useState(EMPTY_PROVIDER_DRAFT);
2504
+ react.useEffect(() => {
2505
+ let live = true;
2506
+ setLoading(true);
2507
+ fetchProvidersView(agent.key, namespace).then((next) => {
2508
+ if (live) {
2509
+ setView(next);
2510
+ setError(null);
2511
+ }
2512
+ }).catch((err) => {
2513
+ if (live) {
2514
+ setView(void 0);
2515
+ setError(messageOf(err));
2516
+ }
2517
+ }).finally(() => {
2518
+ if (live) setLoading(false);
2519
+ });
2520
+ return () => {
2521
+ live = false;
2522
+ };
2523
+ }, [agent.key, namespace]);
2524
+ /** One mutation, one authoritative re-read; `key` drives the busy chip. */
2525
+ const run = (key, call) => {
2526
+ if (busy !== null) return;
2527
+ setBusy(key);
2528
+ call().then((next) => {
2529
+ setView(next);
2530
+ setError(null);
2531
+ setEditing(null);
2532
+ setDraft(EMPTY_PROVIDER_DRAFT);
2533
+ }).catch((err) => {
2534
+ setError(messageOf(err));
2535
+ }).finally(() => {
2536
+ setBusy(null);
2537
+ });
2538
+ };
2539
+ const openNew = () => {
2540
+ setEditing(NEW_PROVIDER);
2541
+ setDraft(EMPTY_PROVIDER_DRAFT);
2542
+ setError(null);
2543
+ };
2544
+ const openEdit = (provider) => {
2545
+ setEditing(provider.id);
2546
+ setDraft({
2547
+ id: provider.id,
2548
+ label: provider.label,
2549
+ baseUrl: provider.baseUrl,
2550
+ protocol: provider.protocol,
2551
+ authKind: provider.authKind,
2552
+ apiKey: "",
2553
+ envName: provider.envName ?? "",
2554
+ modelIds: provider.models.map((model) => model.id).join(", "),
2555
+ contextWindow: provider.models[0]?.contextWindow === void 0 ? "" : String(provider.models[0].contextWindow)
2556
+ });
2557
+ setError(null);
2558
+ };
2559
+ const submit = () => {
2560
+ const ids = parseModelIds(draft.modelIds);
2561
+ const window$1 = draft.contextWindow.trim() === "" ? void 0 : Number(draft.contextWindow);
2562
+ const provider = {
2563
+ id: draft.id.trim(),
2564
+ label: draft.label.trim(),
2565
+ baseUrl: draft.baseUrl.trim(),
2566
+ protocol: draft.protocol,
2567
+ auth: draft.authKind === "env" ? {
2568
+ kind: "env",
2569
+ name: draft.envName.trim()
2570
+ } : {
2571
+ kind: draft.authKind === "authToken" ? "authToken" : "apiKey",
2572
+ value: draft.apiKey
2573
+ },
2574
+ models: ids.map((id) => ({
2575
+ id,
2576
+ ...window$1 !== void 0 && Number.isSafeInteger(window$1) && window$1 > 0 ? { contextWindow: window$1 } : {}
2577
+ }))
2578
+ };
2579
+ run("save", () => saveProvider(agent.key, namespace, provider));
2580
+ };
2581
+ if (loading) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2582
+ style: {
2583
+ margin: 0,
2584
+ color: "var(--dsw-alias-label-tertiary)"
2585
+ },
2586
+ children: "Loading providers…"
2587
+ });
2588
+ if (view === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2589
+ style: {
2590
+ margin: 0,
2591
+ color: "var(--dsw-alias-state-warn-label, var(--dsw-alias-label-secondary))"
2592
+ },
2593
+ children: error ?? "Providers are unavailable for this runtime."
2594
+ });
2595
+ const nativePaths = nativePathsLine(view.paths);
2596
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2597
+ style: {
2598
+ display: "flex",
2599
+ flexDirection: "column",
2600
+ gap: 6
2601
+ },
2602
+ children: [
2603
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2604
+ style: panelHeadStyle,
2605
+ children: "Model provider"
2606
+ }),
2607
+ view.native !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
2608
+ style: {
2609
+ margin: 0,
2610
+ color: "var(--dsw-alias-label-tertiary)"
2611
+ },
2612
+ children: [
2613
+ "Your own ",
2614
+ agent.label,
2615
+ " config currently runs ",
2616
+ nativeDescription(view.native),
2617
+ "."
2618
+ ]
2619
+ }),
2620
+ view.syncedAt !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
2621
+ style: {
2622
+ margin: 0,
2623
+ color: "var(--dsw-alias-label-caption)",
2624
+ fontSize: 11
2625
+ },
2626
+ children: ["synced from your native config ", new Date(view.syncedAt).toLocaleString()]
2627
+ }),
2628
+ view.providers.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2629
+ style: {
2630
+ margin: 0,
2631
+ color: "var(--dsw-alias-label-tertiary)"
2632
+ },
2633
+ children: "No providers yet — add one to pin a model and endpoint."
2634
+ }),
2635
+ view.providers.map((provider) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2636
+ style: optionStyle,
2637
+ children: [
2638
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2639
+ style: {
2640
+ fontWeight: 500,
2641
+ whiteSpace: "nowrap"
2642
+ },
2643
+ children: provider.label
2644
+ }),
2645
+ provider.authKind === "env" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2646
+ style: chip,
2647
+ children: ["env ", provider.envName]
2648
+ }),
2649
+ provider.authKind === "authToken" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2650
+ style: chip,
2651
+ children: "token"
2652
+ }),
2653
+ !provider.hasKey && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2654
+ style: chip,
2655
+ children: "no key"
2656
+ }),
2657
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2658
+ style: {
2659
+ flex: 1,
2660
+ minWidth: 0,
2661
+ overflow: "hidden",
2662
+ textOverflow: "ellipsis",
2663
+ whiteSpace: "nowrap",
2664
+ fontSize: 12,
2665
+ color: "var(--dsw-alias-label-tertiary)"
2666
+ },
2667
+ children: [provider.baseUrl, provider.models.length === 0 ? "" : ` · ${provider.models.map((model) => model.id).join(", ")}`]
2668
+ }),
2669
+ provider.models.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2670
+ style: chipWarn,
2671
+ children: "no models yet — add one"
2672
+ }),
2673
+ busy === `remove:${provider.id}` && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2674
+ style: chip,
2675
+ children: "removing…"
2676
+ }),
2677
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2678
+ type: "button",
2679
+ style: buttonStyle,
2680
+ disabled: busy !== null,
2681
+ onClick: () => {
2682
+ openEdit(provider);
2683
+ },
2684
+ children: "Edit"
2685
+ }),
2686
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2687
+ type: "button",
2688
+ style: {
2689
+ ...buttonStyle,
2690
+ color: "var(--dsw-alias-state-error-primary, #c66)"
2691
+ },
2692
+ disabled: busy !== null,
2693
+ "aria-label": `Delete ${provider.label}`,
2694
+ onClick: () => {
2695
+ run(`remove:${provider.id}`, () => removeProvider(agent.key, namespace, provider.id));
2696
+ },
2697
+ children: "Delete"
2698
+ })
2699
+ ]
2700
+ }, provider.id)),
2701
+ editing === null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2702
+ type: "button",
2703
+ style: buttonStyle,
2704
+ disabled: busy !== null,
2705
+ onClick: openNew,
2706
+ children: "+ Add provider"
2707
+ }) }),
2708
+ editing !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2709
+ style: {
2710
+ display: "flex",
2711
+ flexDirection: "column",
2712
+ gap: 8,
2713
+ maxWidth: 520
2714
+ },
2715
+ children: [
2716
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2717
+ style: panelHeadStyle,
2718
+ children: editing === NEW_PROVIDER ? "Add provider" : `Edit ${editing}`
2719
+ }),
2720
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2721
+ style: fieldStyle,
2722
+ children: ["Provider id", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2723
+ style: inputStyle,
2724
+ value: draft.id,
2725
+ disabled: editing !== NEW_PROVIDER,
2726
+ placeholder: "zai-plan",
2727
+ onChange: (event) => {
2728
+ setDraft({
2729
+ ...draft,
2730
+ id: event.target.value
2731
+ });
2732
+ }
2733
+ })]
2734
+ }),
2735
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2736
+ style: fieldStyle,
2737
+ children: ["Display name", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2738
+ style: inputStyle,
2739
+ value: draft.label,
2740
+ placeholder: "Z.ai Plan",
2741
+ onChange: (event) => {
2742
+ setDraft({
2743
+ ...draft,
2744
+ label: event.target.value
2745
+ });
2746
+ }
2747
+ })]
2748
+ }),
2749
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2750
+ style: fieldStyle,
2751
+ children: ["Base URL", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2752
+ style: inputStyle,
2753
+ value: draft.baseUrl,
2754
+ placeholder: "https://api.z.ai/api/coding/paas/v4",
2755
+ onChange: (event) => {
2756
+ setDraft({
2757
+ ...draft,
2758
+ baseUrl: event.target.value
2759
+ });
2760
+ }
2761
+ })]
2762
+ }),
2763
+ view.protocols.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2764
+ style: fieldStyle,
2765
+ children: ["API protocol", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
2766
+ style: inputStyle,
2767
+ value: draft.protocol,
2768
+ onChange: (event) => {
2769
+ setDraft({
2770
+ ...draft,
2771
+ protocol: event.target.value
2772
+ });
2773
+ },
2774
+ children: view.protocols.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
2775
+ value: option.value,
2776
+ disabled: !option.available,
2777
+ title: option.hint,
2778
+ children: [option.label, option.available ? "" : " — unsupported"]
2779
+ }, option.value))
2780
+ })]
2781
+ }), (() => {
2782
+ const option = view.protocols.find((candidate) => candidate.value === draft.protocol);
2783
+ return option === void 0 || option.available ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2784
+ style: {
2785
+ margin: 0,
2786
+ color: "var(--dsw-alias-state-error-primary, #c66)",
2787
+ fontSize: 12
2788
+ },
2789
+ children: option.hint
2790
+ });
2791
+ })()] }),
2792
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2793
+ style: fieldStyle,
2794
+ children: ["Model IDs", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2795
+ style: inputStyle,
2796
+ value: draft.modelIds,
2797
+ placeholder: "glm-5.2, glm-5.2-air",
2798
+ onChange: (event) => {
2799
+ setDraft({
2800
+ ...draft,
2801
+ modelIds: event.target.value
2802
+ });
2803
+ }
2804
+ })]
2805
+ }),
2806
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2807
+ style: fieldStyle,
2808
+ children: ["Context window (tokens, optional)", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2809
+ style: inputStyle,
2810
+ value: draft.contextWindow,
2811
+ inputMode: "numeric",
2812
+ placeholder: "1000000",
2813
+ onChange: (event) => {
2814
+ setDraft({
2815
+ ...draft,
2816
+ contextWindow: event.target.value
2817
+ });
2818
+ }
2819
+ })]
2820
+ }),
2821
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2822
+ style: fieldStyle,
2823
+ children: ["Key shape", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
2824
+ style: inputStyle,
2825
+ value: draft.authKind,
2826
+ onChange: (event) => {
2827
+ setDraft({
2828
+ ...draft,
2829
+ authKind: event.target.value
2830
+ });
2831
+ },
2832
+ children: authKindOptions(view).map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2833
+ value: option.value,
2834
+ title: option.hint,
2835
+ children: option.label
2836
+ }, option.value))
2837
+ })]
2838
+ }),
2839
+ draft.authKind === "env" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2840
+ style: fieldStyle,
2841
+ children: ["Environment variable", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2842
+ style: inputStyle,
2843
+ value: draft.envName,
2844
+ placeholder: "DEEPSEEK_API_KEY",
2845
+ onChange: (event) => {
2846
+ setDraft({
2847
+ ...draft,
2848
+ envName: event.target.value
2849
+ });
2850
+ }
2851
+ })]
2852
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2853
+ style: fieldStyle,
2854
+ children: [
2855
+ draft.authKind === "authToken" ? "Bearer token" : "API key",
2856
+ " ",
2857
+ editing !== NEW_PROVIDER && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2858
+ style: { color: "var(--dsw-alias-label-caption)" },
2859
+ children: "(leave blank to keep the saved one)"
2860
+ }),
2861
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2862
+ style: inputStyle,
2863
+ type: "password",
2864
+ value: draft.apiKey,
2865
+ autoComplete: "off",
2866
+ onChange: (event) => {
2867
+ setDraft({
2868
+ ...draft,
2869
+ apiKey: event.target.value
2870
+ });
2871
+ }
2872
+ })
2873
+ ]
2874
+ }),
2875
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2876
+ style: {
2877
+ display: "flex",
2878
+ gap: 6
2879
+ },
2880
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2881
+ type: "button",
2882
+ style: primaryButtonStyle,
2883
+ disabled: busy !== null,
2884
+ onClick: submit,
2885
+ children: busy === "save" ? "Saving…" : "Save"
2886
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2887
+ type: "button",
2888
+ style: buttonStyle,
2889
+ disabled: busy !== null,
2890
+ onClick: () => {
2891
+ setEditing(null);
2892
+ setDraft(EMPTY_PROVIDER_DRAFT);
2893
+ },
2894
+ children: "Cancel"
2895
+ })]
2896
+ })
2897
+ ]
2898
+ }),
2899
+ view.note !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2900
+ style: {
2901
+ margin: "2px 0 0",
2902
+ color: "var(--dsw-alias-label-tertiary)"
2903
+ },
2904
+ children: view.note
2905
+ }),
2906
+ error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2907
+ style: {
2908
+ margin: "2px 0 0",
2909
+ color: "var(--dsw-alias-state-error-primary)"
2910
+ },
2911
+ children: error
2912
+ }),
2913
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
2914
+ style: {
2915
+ margin: "4px 0 0",
2916
+ color: "var(--dsw-alias-label-caption)",
2917
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
2918
+ fontSize: 11
2919
+ },
2920
+ children: ["list ", view.paths.store]
2921
+ }),
2922
+ nativePaths !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
2923
+ style: {
2924
+ margin: 0,
2925
+ color: "var(--dsw-alias-label-caption)",
2926
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
2927
+ fontSize: 11
2928
+ },
2929
+ children: ["native ", nativePaths]
2930
+ })
2931
+ ]
2932
+ });
2933
+ }
2934
+ /** Sentinel for "the add form", distinct from any real provider id. */
2935
+ const NEW_PROVIDER = "\0new";
1834
2936
  function HubPage() {
1835
2937
  const [mesh, setMesh] = react.useState({ machines: [] });
1836
2938
  const [meshLoaded, setMeshLoaded] = react.useState(false);
@@ -1845,6 +2947,11 @@ function HubPage() {
1845
2947
  const fusion = fusionPage();
1846
2948
  const switchUi = switchAffordance(fusion);
1847
2949
  const [activeKey, setActiveKey] = react.useState(() => fusion ? uiState()?.activeAgent : void 0);
2950
+ /**
2951
+ * The open settings panel (P3 accordion): one runtime key at a time, or
2952
+ * `null` when every row is collapsed. `toggleExpanded` owns the transition.
2953
+ */
2954
+ const [expandedKey, setExpandedKey] = react.useState(null);
1848
2955
  react.useEffect(() => {
1849
2956
  let cancelled = false;
1850
2957
  fetchRoster().then((list) => {
@@ -1917,66 +3024,71 @@ function HubPage() {
1917
3024
  /**
1918
3025
  * One Agent Runtimes row — the SAME row in both shapes; `showMachine`
1919
3026
  * keeps the flat fallback's per-row machine text (the grouped shape moves
1920
- * it onto the section head). The click contract is untouched: every row
1921
- * still runs the `seatClickDecision` table.
3027
+ * it onto the section head).
3028
+ *
3029
+ * P3 interaction (2026-10-02 user ruling): the ROW BODY expands this
3030
+ * runtime's OWN settings inline (accordion — one panel at a time), and the
3031
+ * right-hand `switch` is the row's ONLY switch, now a real button. The two
3032
+ * are separate controls on purpose: expanding is navigation of config, the
3033
+ * button performs `seatClickDecision` (intercept + in-place switchAgent).
1922
3034
  */
1923
- const renderRow = (agent, showMachine) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(HoverRow, {
1924
- clickable: switchUi.available,
1925
- onClick: switchUi.available ? (event) => {
1926
- rowDecision(agent, event);
1927
- } : void 0,
1928
- children: [
1929
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RuntimeMark, {
1930
- agentKey: agent.key,
1931
- size: 20
1932
- }),
1933
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1934
- style: {
1935
- fontWeight: 500,
1936
- whiteSpace: "nowrap"
1937
- },
1938
- children: agent.label
1939
- }),
1940
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1941
- style: {
1942
- flex: 1,
1943
- minWidth: 0,
1944
- overflow: "hidden",
1945
- textOverflow: "ellipsis",
1946
- whiteSpace: "nowrap",
1947
- fontSize: 12.5,
1948
- color: "var(--dsw-alias-label-tertiary)"
1949
- },
1950
- children: showMachine ? agent.machine : null
1951
- }),
1952
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1953
- style: {
1954
- ...chip,
1955
- fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
1956
- fontSize: 10.5
1957
- },
1958
- children: displayPath(agent.path)
1959
- }),
1960
- isActive(agent) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1961
- style: chipSelf,
1962
- children: "current"
1963
- }),
1964
- switchUi.available && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
1965
- "aria-current": isActive(agent) ? "page" : void 0,
1966
- style: {
1967
- fontSize: 12.5,
1968
- color: "var(--dsh-alias-label-secondary)",
1969
- textDecoration: "none",
1970
- cursor: "pointer"
1971
- },
1972
- onClick: (event) => {
1973
- event.stopPropagation();
1974
- rowDecision(agent, event);
1975
- },
1976
- children: "switch"
1977
- })
1978
- ]
1979
- }, agent.key);
3035
+ const renderRow = (agent, showMachine) => {
3036
+ const expanded = expandedKey === agent.key;
3037
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(HoverRow, {
3038
+ clickable: true,
3039
+ expanded,
3040
+ onClick: () => {
3041
+ setExpandedKey((current$1) => toggleExpanded(current$1, agent.key));
3042
+ },
3043
+ children: [
3044
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RuntimeMark, {
3045
+ agentKey: agent.key,
3046
+ size: 20
3047
+ }),
3048
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3049
+ style: {
3050
+ fontWeight: 500,
3051
+ whiteSpace: "nowrap"
3052
+ },
3053
+ children: agent.label
3054
+ }),
3055
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3056
+ style: {
3057
+ flex: 1,
3058
+ minWidth: 0,
3059
+ overflow: "hidden",
3060
+ textOverflow: "ellipsis",
3061
+ whiteSpace: "nowrap",
3062
+ fontSize: 12.5,
3063
+ color: "var(--dsw-alias-label-tertiary)"
3064
+ },
3065
+ children: showMachine ? agent.machine : null
3066
+ }),
3067
+ isActive(agent) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3068
+ style: chipSelf,
3069
+ children: "current"
3070
+ }),
3071
+ switchUi.available && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3072
+ type: "button",
3073
+ "aria-current": isActive(agent) ? "page" : void 0,
3074
+ "aria-label": isActive(agent) ? `${agent.label} is current` : `Switch to ${agent.label}`,
3075
+ style: {
3076
+ ...switchButtonStyle,
3077
+ color: isActive(agent) ? "var(--dsw-alias-label-tertiary)" : "var(--dsw-alias-label-secondary)",
3078
+ cursor: "pointer"
3079
+ },
3080
+ onClick: (event) => {
3081
+ event.stopPropagation();
3082
+ rowDecision(agent, event);
3083
+ },
3084
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.IconPlayOutlineRegular, { size: 16 })
3085
+ })
3086
+ ]
3087
+ }), expanded && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
3088
+ style: panelStyle,
3089
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RuntimeSettingsPanel, { agent })
3090
+ })] }, agent.key);
3091
+ };
1980
3092
  /** Bump the reload sequence — every mutating op refreshes through the API. */
1981
3093
  const reloadMachines = () => {
1982
3094
  setMeshReloads((n) => n + 1);
@@ -2738,8 +3850,7 @@ function apply(ctx) {
2738
3850
  "connection",
2739
3851
  "uiWorkspace",
2740
3852
  "workspaces",
2741
- "sessions",
2742
- "locale"
3853
+ "sessions"
2743
3854
  ], (raw) => {
2744
3855
  bindSwitchAgent(raw);
2745
3856
  });