@klarkxy/dsh-model-hub 0.1.2 → 0.1.4

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.
@@ -1,28 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- //#region \0rolldown/runtime.js
3
- var __create = Object.create;
4
- var __defProp = Object.defineProperty;
5
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
- var __getOwnPropNames = Object.getOwnPropertyNames;
7
- var __getProtoOf = Object.getPrototypeOf;
8
- var __hasOwnProp = Object.prototype.hasOwnProperty;
9
- var __copyProps = (to, from, except, desc) => {
10
- if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
- key = keys[i];
12
- if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
13
- get: ((k) => from[k]).bind(null, key),
14
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
15
- });
16
- }
17
- return to;
18
- };
19
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
20
- value: mod,
21
- enumerable: true
22
- }) : target, mod));
23
- //#endregion
24
2
  let react = require("react");
25
- react = __toESM(react, 1);
26
3
  let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
27
4
  let react_dom = require("react-dom");
28
5
  let react_jsx_runtime = require("react/jsx-runtime");
@@ -61,12 +38,68 @@ function normalizeModelRoute(value) {
61
38
  }
62
39
  //#endregion
63
40
  //#region ../dsh-model-route/src/catalog.ts
41
+ /** Both services are required: Cordis checks the nested namespace separately. */
42
+ const MODEL_CATALOG_INJECT = ["remote", "remote.session"];
64
43
  function asRecord(value) {
65
44
  return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
66
45
  }
67
46
  function asText(value) {
68
47
  return typeof value === "string" && value.trim() ? value.trim() : void 0;
69
48
  }
49
+ /** A useful diagnostic even when a transport rejects with a plain object. */
50
+ function modelCatalogErrorMessage(value) {
51
+ if (value instanceof Error && value.message) return value.message;
52
+ return asText(asRecord(value)?.message) ?? asText(value) ?? "Unable to load the model catalog.";
53
+ }
54
+ /**
55
+ * Validate and unwrap a successful catalog without discarding provider failures
56
+ * or newer host metadata. Names and metadata remain optional for older hosts.
57
+ * A broken response is never presented as a successfully loaded empty menu.
58
+ */
59
+ function checkedModelCatalog(value) {
60
+ const envelope = asRecord(value);
61
+ if (envelope?.ok === false) throw new Error(modelCatalogErrorMessage(envelope.error ?? envelope));
62
+ const root = envelope?.ok === true ? asRecord(envelope.value) : envelope;
63
+ const malformed = (field) => {
64
+ throw new Error(`Invalid model catalog: ${field}.`);
65
+ };
66
+ if (!root || !Array.isArray(root.groups)) return malformed("groups must be an array");
67
+ for (const value of root.groups) {
68
+ const group = asRecord(value);
69
+ if (!group || !asText(group.id) || !Array.isArray(group.models)) return malformed("each group needs an id and models array");
70
+ for (const value of group.models) {
71
+ const model = asRecord(value);
72
+ if (!model || !asText(model.id)) return malformed("each model needs an id");
73
+ if (model.reasoning !== void 0) {
74
+ const reasoning = asRecord(model.reasoning);
75
+ if (!reasoning || !Array.isArray(reasoning.efforts) || reasoning.efforts.some((value) => !parseEffort(value))) return malformed("reasoning efforts must be an array of identified efforts");
76
+ }
77
+ }
78
+ }
79
+ if (root.failures !== void 0 && (!Array.isArray(root.failures) || root.failures.some((value) => {
80
+ const failure = asRecord(value);
81
+ return !failure || !asText(failure.id) || typeof failure.name !== "string" || typeof failure.message !== "string";
82
+ }))) return malformed("failures must be an array of provider failures");
83
+ return root;
84
+ }
85
+ /** Read through the injected session namespace; absence and rejection are errors. */
86
+ async function readModelCatalog(client) {
87
+ const session = client.remote?.session;
88
+ if (typeof session?.modelCatalog !== "function") throw new Error("Model catalog unavailable: inject both remote and remote.session.");
89
+ return checkedModelCatalog(await session.modelCatalog());
90
+ }
91
+ /** Provider failures are partial results, not a transport failure. */
92
+ function modelCatalogFailures(value) {
93
+ try {
94
+ return checkedModelCatalog(value).failures ?? [];
95
+ } catch {
96
+ return [];
97
+ }
98
+ }
99
+ /** Text adapter for existing model-hub status messages. */
100
+ function catalogFailures(value) {
101
+ return modelCatalogFailures(value).map((failure) => `${failure.name || failure.id}: ${failure.message}`);
102
+ }
70
103
  function parseEffort(value) {
71
104
  const row = asRecord(value);
72
105
  const id = row ? asText(row.id) : void 0;
@@ -163,6 +196,59 @@ function groupModelMenuChoices(choices, query) {
163
196
  return groups;
164
197
  }
165
198
  //#endregion
199
+ //#region ../dsh-model-route/src/catalog-ui.tsx
200
+ /** Also accepts a mapped state from a caller that owns broader async orchestration. */
201
+ function ModelCatalogStatus({ state, locale }) {
202
+ const zh = locale?.toLowerCase().startsWith("zh") === true;
203
+ const copy = zh ? {
204
+ loading: "正在加载模型…",
205
+ error: "模型列表加载失败",
206
+ partial: "部分提供商的模型未能加载",
207
+ retry: "重试"
208
+ } : {
209
+ loading: "Loading models…",
210
+ error: "Unable to load models",
211
+ partial: "Some providers could not load their models",
212
+ retry: "Retry"
213
+ };
214
+ if (!state.loading && !state.error && state.failures.length === 0) return null;
215
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
216
+ className: "dsh-model-catalog-status",
217
+ lang: zh ? "zh" : "en",
218
+ children: [
219
+ state.loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
220
+ role: "status",
221
+ children: copy.loading
222
+ }) : null,
223
+ state.error ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
224
+ role: "alert",
225
+ children: [
226
+ copy.error,
227
+ ": ",
228
+ state.error
229
+ ]
230
+ }) : null,
231
+ state.failures.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
232
+ role: "status",
233
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: copy.partial }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", { children: state.failures.map((failure, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
234
+ failure.name || failure.id,
235
+ ": ",
236
+ failure.message
237
+ ] }, `${failure.id}:${index}`)) })]
238
+ }) : null,
239
+ state.error || state.failures.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
240
+ type: "button",
241
+ size: "sm",
242
+ disabled: state.loading,
243
+ onClick: () => {
244
+ state.refresh();
245
+ },
246
+ children: copy.retry
247
+ }) : null
248
+ ]
249
+ });
250
+ }
251
+ //#endregion
166
252
  //#region ../dsh-model-route/src/ui.tsx
167
253
  /**
168
254
  * ModelMenu: the standard editor of one model-route field — the visible half
@@ -1172,6 +1258,29 @@ ${scope} {
1172
1258
  & .dsh-ui-select:disabled { color: var(--dsw-alias-label-tertiary); cursor: default; }
1173
1259
  & .dsh-ui-select[aria-invalid='true'] { border-color: var(--dsw-alias-state-error-primary); }
1174
1260
 
1261
+ /* --- Native textarea. The primitives ship no textarea either, so a settings
1262
+ * prompt keeps the platform element and the same field shell as the select:
1263
+ * 0.5px l4, radius-md, layer-3, 13/1.5 type. It grows vertically instead of
1264
+ * locking to the select's 34px row. --- */
1265
+ & .dsh-ui-textarea {
1266
+ box-sizing: border-box;
1267
+ width: 100%;
1268
+ min-width: 0;
1269
+ resize: vertical;
1270
+ padding: 8px 12px;
1271
+ border: 0.5px solid var(--dsw-alias-border-l4);
1272
+ border-radius: var(--dsw-radius-md);
1273
+ background: var(--dsw-alias-bg-layer-3);
1274
+ font: inherit;
1275
+ font-size: 13px;
1276
+ line-height: 1.5;
1277
+ color: var(--dsw-alias-label-primary);
1278
+ }
1279
+ & .dsh-ui-textarea:focus-visible { outline: none; border-color: var(--dsw-alias-state-business-primary); }
1280
+ & .dsh-ui-textarea:disabled { color: var(--dsw-alias-label-tertiary); cursor: default; }
1281
+ & .dsh-ui-textarea::placeholder { color: var(--dsw-alias-label-dimmed); }
1282
+ & .dsh-ui-textarea[aria-invalid='true'] { border-color: var(--dsw-alias-state-error-primary); }
1283
+
1175
1284
  /* Long panels scroll inside themselves instead of growing the host page. */
1176
1285
  & .dsh-ui-panel { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
1177
1286
  & .dsh-ui-panel-wide { max-width: 100%; }
@@ -1184,25 +1293,18 @@ ${scope} {
1184
1293
  }
1185
1294
  //#endregion
1186
1295
  //#region ../dsh-plugin-kit/src/contracts.ts
1187
- /**
1188
- * Remote contribution for model-settings change notifications.
1189
- * Provided by `@klarkxy/dsh-model-hub` (`ModelSettingsChangesController`);
1190
- * subscribers degrade silently when that package is not installed.
1191
- * The stream carries invalidations only — `ready` is never a settings snapshot.
1192
- */
1193
- const MODEL_SETTINGS_CHANGES_REMOTE = {
1194
- package: "@klarkxy/dsh-model-hub",
1195
- descriptors: [{
1196
- id: "modelSettingsChanges/watch",
1197
- service: "modelSettingsChangesController",
1198
- namespace: "modelSettingsChanges",
1199
- method: "watch",
1200
- mode: "stream",
1201
- invocation: { kind: "direct" },
1202
- parameters: [],
1203
- cancellation: { parameter: "signal" }
1204
- }]
1205
- };
1296
+ /** `@klarkxy/dsh-git-commit` becomes `KlarkxyDshGitCommitModelSettingsChanges`. */
1297
+ function modelSettingsChangesIdentity(packageName) {
1298
+ const scoped = /^@([^/]+)\/(.+)$/.exec(packageName);
1299
+ const parts = (scoped ? `${scoped[1]}/${scoped[2]}` : packageName).split(/[^A-Za-z0-9]+/).filter(Boolean);
1300
+ if (!parts.length) throw new Error("Model settings package name has no RPC segment");
1301
+ const segment = `${parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("")}ModelSettingsChanges`;
1302
+ if (!/^[A-Z][A-Za-z0-9]*$/.test(segment)) throw new Error(`Model settings sync name is not an RPC segment: ${segment}`);
1303
+ return {
1304
+ service: segment,
1305
+ namespace: segment
1306
+ };
1307
+ }
1206
1308
  //#endregion
1207
1309
  //#region ../dsh-plugin-kit/src/model-settings-client.ts
1208
1310
  /** This stream carries invalidations only; ready is never a settings snapshot. */
@@ -1217,21 +1319,44 @@ function parseModelSettingsChangeFrame(value) {
1217
1319
  };
1218
1320
  throw new Error("Invalid model settings change frame");
1219
1321
  }
1220
- /** One contribution mounted by Model Hub; consumers only inject its service. */
1221
- const modelSettingsChangesRemote = {
1222
- package: MODEL_SETTINGS_CHANGES_REMOTE.package,
1223
- descriptors: MODEL_SETTINGS_CHANGES_REMOTE.descriptors.map((descriptor) => ({
1224
- ...descriptor,
1225
- result: {
1226
- mode: "strict",
1227
- typeSymbol: "ModelSettings.ChangeFrame",
1228
- create: () => ({ parse: parseModelSettingsChangeFrame })
1229
- }
1230
- }))
1231
- };
1322
+ /** One owner's contribution. Each package mounts only its own endpoint. */
1323
+ function modelSettingsChangesRemote(packageName, identity = modelSettingsChangesIdentity(packageName)) {
1324
+ return {
1325
+ package: packageName,
1326
+ descriptors: [{
1327
+ id: `${identity.namespace}/watch`,
1328
+ service: identity.service,
1329
+ namespace: identity.namespace,
1330
+ method: "watch",
1331
+ mode: "stream",
1332
+ invocation: { kind: "direct" },
1333
+ parameters: [],
1334
+ cancellation: { parameter: "signal" },
1335
+ result: {
1336
+ mode: "strict",
1337
+ typeSymbol: "ModelSettings.ChangeFrame",
1338
+ create: () => ({ parse: parseModelSettingsChangeFrame })
1339
+ }
1340
+ }]
1341
+ };
1342
+ }
1343
+ /** Mount from a synchronous apply. Disposal waits for an in-flight mount and
1344
+ * then withdraws it, so the contribution cannot outlive the plugin. */
1345
+ function mountModelSettingsChanges(ctx, packageName) {
1346
+ const contribution = modelSettingsChangesRemote(packageName);
1347
+ ctx.inject(["remote"], (injected) => {
1348
+ const scope = injected;
1349
+ const mounting = scope.remote.$mount(contribution);
1350
+ mounting.catch(() => {});
1351
+ scope.effect(() => async () => {
1352
+ await (await mounting.catch(() => void 0))?.();
1353
+ });
1354
+ });
1355
+ }
1232
1356
  /** Native RemoteStream owns carrier reconnects; no timers or browser transport.
1233
1357
  * A factory shares one logical stream while at least one subscriber remains. */
1234
- function createModelSettingsSubscriber(ctx, namespace) {
1358
+ function createModelSettingsSubscriber(ctx, packageName, identity = modelSettingsChangesIdentity(packageName), accept = (ns) => ns === packageName) {
1359
+ const endpoint = identity;
1235
1360
  const listeners = /* @__PURE__ */ new Set();
1236
1361
  let injection;
1237
1362
  let current;
@@ -1257,12 +1382,18 @@ function createModelSettingsSubscriber(ctx, namespace) {
1257
1382
  };
1258
1383
  const start = () => {
1259
1384
  const life = ++lifecycle;
1260
- injection = ctx.inject(["remote", "remote.modelSettingsChanges"], (injected) => {
1385
+ injection = ctx.inject(["remote", `remote.${endpoint.namespace}`], (injected) => {
1261
1386
  if (life !== lifecycle || !listeners.size) return;
1262
1387
  const scope = injected;
1388
+ const service = scope.remote[endpoint.namespace];
1389
+ if (!service || !("watch" in service) || !service.watch) {
1390
+ report("Model settings sync service unavailable");
1391
+ return;
1392
+ }
1393
+ const watch = service.watch;
1263
1394
  const stream = scope.remote.$stream({
1264
1395
  name: "Model settings sync",
1265
- open: (signal) => scope.remote.modelSettingsChanges.watch(signal),
1396
+ open: (signal) => watch(signal),
1266
1397
  ended: (accepted) => /* @__PURE__ */ new Error(`Model settings sync stream ended ${accepted ? "after" : "before"} ready; refresh or reopen the page`)
1267
1398
  });
1268
1399
  const session = {
@@ -1297,7 +1428,7 @@ function createModelSettingsSubscriber(ctx, namespace) {
1297
1428
  invalidate();
1298
1429
  } else {
1299
1430
  if (!ready) throw new Error("Model settings sync change arrived before ready");
1300
- if (namespace === "*" || frame.ns === namespace) invalidate();
1431
+ if (accept(frame.ns)) invalidate();
1301
1432
  }
1302
1433
  }
1303
1434
  } catch (error) {
@@ -1429,20 +1560,6 @@ function messageOf(error) {
1429
1560
  if (error && typeof error === "object" && "message" in error) return String(error.message);
1430
1561
  return String(error);
1431
1562
  }
1432
- function checkedCatalog(value) {
1433
- if (value && typeof value === "object" && "ok" in value && value.ok === false) throw new Error("error" in value ? messageOf(value.error) : "Request failed");
1434
- return value;
1435
- }
1436
- function catalogFailures(value) {
1437
- if (!value || typeof value !== "object") return [];
1438
- const root = "ok" in value && value.ok === true && "value" in value ? value.value : value;
1439
- if (!root || typeof root !== "object" || !("failures" in root) || !Array.isArray(root.failures)) return [];
1440
- return root.failures.map((failure) => {
1441
- if (!failure || typeof failure !== "object") return messageOf(failure);
1442
- const provider = "name" in failure ? failure.name : "id" in failure ? failure.id : "provider" in failure ? failure.provider : "";
1443
- return provider ? `${provider}: ${messageOf(failure)}` : messageOf(failure);
1444
- });
1445
- }
1446
1563
  //#endregion
1447
1564
  //#region src/client-copy.ts
1448
1565
  function hubText(value, language) {
@@ -1547,7 +1664,6 @@ const TEXT$1 = {
1547
1664
  loading: "正在读取…",
1548
1665
  failed: (message) => `读取失败:${message}`,
1549
1666
  syncFailed: (message) => `自动同步不可用:${message}`,
1550
- catalogFailed: (message) => `模型列表读取失败:${message}`,
1551
1667
  writeFailed: (message) => `未保存:${message}。请刷新后重试。`,
1552
1668
  saved: "已保存,正在刷新…",
1553
1669
  savedRefreshFailed: (message) => `已保存;阵容刷新失败:${message}。请刷新以读取最新设置。`,
@@ -1570,7 +1686,13 @@ const TEXT$1 = {
1570
1686
  effortDefault: "默认",
1571
1687
  searchModels: "搜索模型…",
1572
1688
  noModels: "没有匹配的模型",
1573
- clearSearch: "清除搜索"
1689
+ clearSearch: "清除搜索",
1690
+ runningTitle: "运行中的子代理",
1691
+ runningEmpty: "当前没有运行中的子代理。",
1692
+ runningPending: "尚未发起模型请求",
1693
+ runningRemote: "远程子代理,无法读取路由",
1694
+ runningFailed: (message) => `运行状态读取失败:${message}`,
1695
+ effortAdapterDefault: (effort) => `${effort}(适配器默认)`
1574
1696
  },
1575
1697
  en: {
1576
1698
  title: "Subagent Cast",
@@ -1579,7 +1701,6 @@ const TEXT$1 = {
1579
1701
  loading: "Loading…",
1580
1702
  failed: (message) => `Load failed: ${message}`,
1581
1703
  syncFailed: (message) => `Live sync unavailable: ${message}`,
1582
- catalogFailed: (message) => `Model list failed: ${message}`,
1583
1704
  writeFailed: (message) => `Not saved: ${message}. Refresh, then try again.`,
1584
1705
  saved: "Saved; refreshing…",
1585
1706
  savedRefreshFailed: (message) => `Saved; cast refresh failed: ${message}. Refresh to read the latest settings.`,
@@ -1602,7 +1723,13 @@ const TEXT$1 = {
1602
1723
  effortDefault: "Default",
1603
1724
  searchModels: "Search models…",
1604
1725
  noModels: "No matching models",
1605
- clearSearch: "Clear search"
1726
+ clearSearch: "Clear search",
1727
+ runningTitle: "Running subagents",
1728
+ runningEmpty: "No subagents are running.",
1729
+ runningPending: "No model request yet",
1730
+ runningRemote: "Remote subagent; route unavailable",
1731
+ runningFailed: (message) => `Running state failed: ${message}`,
1732
+ effortAdapterDefault: (effort) => `${effort} (adapter default)`
1606
1733
  }
1607
1734
  };
1608
1735
  function useText$1(locale) {
@@ -1625,6 +1752,11 @@ const castCss = `
1625
1752
  .dsh-model-hub .cast-banner{display:flex;gap:12px;align-items:center;justify-content:space-between;flex-wrap:wrap}
1626
1753
  .dsh-model-hub .cast-add{display:flex}
1627
1754
  .dsh-model-hub .cast-add .dsh-model-menu-root{max-width:100%}
1755
+ .dsh-model-hub .cast-running{margin-top:16px}
1756
+ .dsh-model-hub .cast-running-title{font-size:14px;line-height:22px;font-weight:600;margin:0 0 4px;color:var(--dsw-alias-label-primary)}
1757
+ .dsh-model-hub .cast-running-row{display:flex;gap:12px;align-items:baseline;flex-wrap:wrap;padding:6px 0;border-bottom:.5px solid var(--dsw-alias-border-l1)}
1758
+ .dsh-model-hub .cast-running-label{font-size:13px;line-height:20px;color:var(--dsw-alias-label-primary);overflow-wrap:anywhere;min-width:0}
1759
+ .dsh-model-hub .cast-running-route{font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere;min-width:0}
1628
1760
  @media(max-width:520px){.dsh-model-hub .cast-row{grid-template-columns:minmax(0,1fr);gap:8px}}
1629
1761
  `;
1630
1762
  /** CAS rejections all carry a refresh hint in either language. */
@@ -1648,6 +1780,14 @@ function routeInput(route) {
1648
1780
  model: normalized.model
1649
1781
  };
1650
1782
  }
1783
+ /** One live subagent row: label (or short id) plus its effective route. */
1784
+ function runningRouteText(entry, t) {
1785
+ if (!entry.local) return t("runningRemote");
1786
+ const route = entry.route;
1787
+ if (!route) return t("runningPending");
1788
+ const effort = route.reasoningEffort ? entry.effortIsAdapterDefault ? t("effortAdapterDefault", route.reasoningEffort) : route.reasoningEffort : "";
1789
+ return `${route.provider} / ${route.model}${effort ? ` · ${effort}` : ""}`;
1790
+ }
1651
1791
  /** One cast entry: model picker, tendency input saved on blur, remove button. */
1652
1792
  function CastRow({ t, catalog, route, tendencyRevision, draftReview, disabled, tendencyDisabled, menuDisabled, onSetRoute, onSetTendency, onRemove }) {
1653
1793
  const label = `${route.provider} / ${route.model}`;
@@ -1731,6 +1871,8 @@ function CastPage({ api, locale }) {
1731
1871
  const t = useText$1(locale);
1732
1872
  const [cast, setCast] = (0, react.useState)(null);
1733
1873
  const [catalog, setCatalog] = (0, react.useState)(null);
1874
+ const [running, setRunning] = (0, react.useState)(null);
1875
+ const [runningError, setRunningError] = (0, react.useState)("");
1734
1876
  const [loadError, setLoadError] = (0, react.useState)("");
1735
1877
  const [catalogError, setCatalogError] = (0, react.useState)("");
1736
1878
  const [syncError, setSyncError] = (0, react.useState)("");
@@ -1750,7 +1892,11 @@ function CastPage({ api, locale }) {
1750
1892
  scope.queued = false;
1751
1893
  scope.busy = true;
1752
1894
  setRefreshing(true);
1753
- const [castResult, models] = await Promise.allSettled([Promise.resolve().then(() => scope.api.getCast()), Promise.resolve().then(() => scope.api.modelCatalog()).then(checkedCatalog)]);
1895
+ const [castResult, models, live] = await Promise.allSettled([
1896
+ Promise.resolve().then(() => scope.api.getCast()),
1897
+ Promise.resolve().then(() => scope.api.modelCatalog()).then(checkedModelCatalog),
1898
+ Promise.resolve().then(() => scope.api.listRunning())
1899
+ ]);
1754
1900
  if (!scope.active) return;
1755
1901
  if (castResult.status === "fulfilled") {
1756
1902
  setCast(castResult.value);
@@ -1764,6 +1910,10 @@ function CastPage({ api, locale }) {
1764
1910
  setCatalog(models.value);
1765
1911
  setCatalogError("");
1766
1912
  } else setCatalogError(messageOf(models.reason));
1913
+ if (live.status === "fulfilled") {
1914
+ setRunning(live.value);
1915
+ setRunningError("");
1916
+ } else setRunningError(messageOf(live.reason));
1767
1917
  scope.busy = false;
1768
1918
  setRefreshing(false);
1769
1919
  if (scope.queued) refresh(scope);
@@ -1785,6 +1935,8 @@ function CastPage({ api, locale }) {
1785
1935
  setSyncError("");
1786
1936
  setCast(null);
1787
1937
  setCatalog(null);
1938
+ setRunning(null);
1939
+ setRunningError("");
1788
1940
  refresh(scope);
1789
1941
  const unsubscribe = api.subscribeChanges?.(() => {
1790
1942
  if (!scope.active) return;
@@ -1827,12 +1979,17 @@ function CastPage({ api, locale }) {
1827
1979
  }
1828
1980
  });
1829
1981
  };
1830
- const refreshNow = () => {
1982
+ const refreshNow = async () => {
1831
1983
  const scope = scopeRef.current;
1832
- if (scope?.api === api) refresh(scope, true);
1984
+ if (scope?.api === api && !scope.busy) await refresh(scope, true);
1833
1985
  };
1834
1986
  const disabled = saving !== "" || refreshing || writeError !== "" || saved && loadError !== "";
1835
- const failures = catalogFailures(catalog);
1987
+ const catalogStatus = {
1988
+ loading: refreshing,
1989
+ error: catalogError ? hubMessage(catalogError, t.language) : "",
1990
+ failures: modelCatalogFailures(catalog),
1991
+ refresh: refreshNow
1992
+ };
1836
1993
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1837
1994
  className: "cast-root dsh-ui-stack",
1838
1995
  "aria-label": t("title"),
@@ -1877,16 +2034,10 @@ function CastPage({ api, locale }) {
1877
2034
  className: "dsh-ui-error dsh-ui-wrap",
1878
2035
  children: t("syncFailed", hubMessage(syncError, t.language))
1879
2036
  }) : null,
1880
- catalogError ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1881
- role: "alert",
1882
- className: "dsh-ui-error dsh-ui-wrap",
1883
- children: t("catalogFailed", hubMessage(catalogError, t.language))
1884
- }) : null,
1885
- failures.map((failure, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1886
- role: "alert",
1887
- className: "dsh-ui-error dsh-ui-wrap",
1888
- children: t("catalogFailed", hubMessage(failure, t.language))
1889
- }, index)),
2037
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelCatalogStatus, {
2038
+ state: catalogStatus,
2039
+ locale: t.language
2040
+ }),
1890
2041
  writeError ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1891
2042
  className: "dsh-ui-banner dsh-ui-banner--danger cast-banner",
1892
2043
  role: "alert",
@@ -1963,7 +2114,40 @@ function CastPage({ api, locale }) {
1963
2114
  expectedRevision: cast.revision
1964
2115
  }))
1965
2116
  })
1966
- }) : null
2117
+ }) : null,
2118
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2119
+ className: "cast-running",
2120
+ "aria-label": t("runningTitle"),
2121
+ children: [
2122
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
2123
+ className: "cast-running-title",
2124
+ children: t("runningTitle")
2125
+ }),
2126
+ runningError ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2127
+ role: "alert",
2128
+ className: "dsh-ui-error dsh-ui-wrap",
2129
+ children: t("runningFailed", hubMessage(runningError, t.language))
2130
+ }) : null,
2131
+ running === null && !runningError ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2132
+ className: "dsh-ui-loading",
2133
+ children: t("loading")
2134
+ }) : null,
2135
+ running !== null && running.entries.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2136
+ className: "dsh-ui-help",
2137
+ children: t("runningEmpty")
2138
+ }) : null,
2139
+ running?.entries.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2140
+ className: "cast-running-row",
2141
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2142
+ className: "cast-running-label",
2143
+ children: entry.label || entry.id
2144
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2145
+ className: "cast-running-route",
2146
+ children: runningRouteText(entry, t)
2147
+ })]
2148
+ }, entry.id))
2149
+ ]
2150
+ })
1967
2151
  ]
1968
2152
  });
1969
2153
  }
@@ -1981,8 +2165,7 @@ const name = "dsh-model-hub-client";
1981
2165
  const inject = [
1982
2166
  "slots",
1983
2167
  "connection",
1984
- "remote",
1985
- "remote.session",
2168
+ ...MODEL_CATALOG_INJECT,
1986
2169
  "locale"
1987
2170
  ];
1988
2171
  const CHANNEL = "/dsh-model-hub";
@@ -2114,7 +2297,7 @@ function HubPage({ view, api, locale, initialEntries = null, initialCatalog = nu
2114
2297
  scope.queued = false;
2115
2298
  scope.busy = true;
2116
2299
  setRefreshing(true);
2117
- const [fields, models] = await Promise.allSettled([Promise.resolve().then(() => scope.api.list()), Promise.resolve().then(() => scope.api.modelCatalog()).then(checkedCatalog)]);
2300
+ const [fields, models] = await Promise.allSettled([Promise.resolve().then(() => scope.api.list()), Promise.resolve().then(() => scope.api.modelCatalog()).then(checkedModelCatalog)]);
2118
2301
  if (!scope.active) return;
2119
2302
  if (fields.status === "fulfilled") {
2120
2303
  setEntries(fields.value);
@@ -2227,7 +2410,16 @@ function HubPage({ view, api, locale, initialEntries = null, initialCatalog = nu
2227
2410
  })
2228
2411
  })).filter((entry) => entry.fields.length > 0 || (entry.error || entry.catalogError) && (!needle || [entry.ns, pluginTitle(entry, t)].some((text) => text.toLocaleLowerCase().includes(needle))));
2229
2412
  const disabled = writing !== "" || refreshing || writeError !== "" || saved && listError !== "";
2230
- const failures = catalogFailures(catalog);
2413
+ const refreshNow = async () => {
2414
+ const scope = scopeRef.current;
2415
+ if (scope?.api === api && !scope.busy) await refresh(scope);
2416
+ };
2417
+ const catalogStatus = {
2418
+ loading: refreshing,
2419
+ error: catalogError ? hubMessage(catalogError, t.language) : "",
2420
+ failures: modelCatalogFailures(catalog),
2421
+ refresh: refreshNow
2422
+ };
2231
2423
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2232
2424
  className: "dsh-model-hub dsh-ui-stack",
2233
2425
  lang: t.language,
@@ -2271,10 +2463,7 @@ function HubPage({ view, api, locale, initialEntries = null, initialCatalog = nu
2271
2463
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
2272
2464
  variant: "ghost",
2273
2465
  disabled: writing !== "" || refreshing,
2274
- onClick: () => {
2275
- const scope = scopeRef.current;
2276
- if (scope?.api === api) refresh(scope);
2277
- },
2466
+ onClick: refreshNow,
2278
2467
  children: refreshing ? t("refreshing") : t("refresh")
2279
2468
  })]
2280
2469
  }),
@@ -2288,16 +2477,10 @@ function HubPage({ view, api, locale, initialEntries = null, initialCatalog = nu
2288
2477
  className: "dsh-ui-error dsh-ui-wrap",
2289
2478
  children: t("syncFailed", hubMessage(syncError, t.language))
2290
2479
  }) : null,
2291
- catalogError ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2292
- role: "alert",
2293
- className: "dsh-ui-error dsh-ui-wrap",
2294
- children: t("catalogFailed", hubMessage(catalogError, t.language))
2295
- }) : null,
2296
- failures.map((failure, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2297
- role: "alert",
2298
- className: "dsh-ui-error dsh-ui-wrap",
2299
- children: t("catalogFailed", hubMessage(failure, t.language))
2300
- }, index)),
2480
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelCatalogStatus, {
2481
+ state: catalogStatus,
2482
+ locale: t.language
2483
+ }),
2301
2484
  writeError ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2302
2485
  role: "alert",
2303
2486
  className: "dsh-ui-error dsh-ui-wrap",
@@ -2404,11 +2587,10 @@ function HubPage({ view, api, locale, initialEntries = null, initialCatalog = nu
2404
2587
  ]
2405
2588
  });
2406
2589
  }
2407
- async function apply(ctx) {
2590
+ function apply(ctx) {
2408
2591
  const client = ctx;
2409
- const disposeRemote = await client.remote.$mount(modelSettingsChangesRemote);
2410
- client.effect(() => disposeRemote);
2411
- const changes = createModelSettingsSubscriber(ctx, "*");
2592
+ mountModelSettingsChanges(ctx, BUNDLE);
2593
+ const changes = createModelSettingsSubscriber(ctx, BUNDLE, modelSettingsChangesIdentity(BUNDLE), () => true);
2412
2594
  const call = async (endpoint, payload) => {
2413
2595
  const result = await client.connection.rpc.call(CHANNEL, endpoint, payload);
2414
2596
  if (!result?.ok) throw new Error(result?.error?.message || "Request failed");
@@ -2417,14 +2599,15 @@ async function apply(ctx) {
2417
2599
  const api = Object.freeze({
2418
2600
  list: () => call("fields.list", {}),
2419
2601
  setField: (payload) => call("fields.set", payload),
2420
- modelCatalog: () => client.remote.session.modelCatalog(),
2602
+ modelCatalog: () => readModelCatalog(client),
2421
2603
  subscribeChanges: changes.subscribe,
2422
2604
  getCast: () => call("cast.get", {}),
2423
2605
  setCastEnabled: (payload) => call("cast.setEnabled", payload),
2424
2606
  addCastRoute: (payload) => call("cast.addRoute", payload),
2425
2607
  setCastRoute: (payload) => call("cast.setRoute", payload),
2426
2608
  removeCastRoute: (payload) => call("cast.removeRoute", payload),
2427
- setCastTendency: (payload) => call("cast.setTendency", payload)
2609
+ setCastTendency: (payload) => call("cast.setTendency", payload),
2610
+ listRunning: () => call("cast.running", {})
2428
2611
  });
2429
2612
  client.slots.inject("plugins.bundle.config", () => client.slots.register({
2430
2613
  name: "plugins.bundle.config",