@klarkxy/dsh-model-hub 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -5,30 +5,7 @@ window.__ModuleLoader__.load({
5
5
  var module = { exports: {} };
6
6
  var exports = module.exports;
7
7
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
8
- //#region \0rolldown/runtime.js
9
- var __create = Object.create;
10
- var __defProp = Object.defineProperty;
11
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
12
- var __getOwnPropNames = Object.getOwnPropertyNames;
13
- var __getProtoOf = Object.getPrototypeOf;
14
- var __hasOwnProp = Object.prototype.hasOwnProperty;
15
- var __copyProps = (to, from, except, desc) => {
16
- if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
17
- key = keys[i];
18
- if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
19
- get: ((k) => from[k]).bind(null, key),
20
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
21
- });
22
- }
23
- return to;
24
- };
25
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
26
- value: mod,
27
- enumerable: true
28
- }) : target, mod));
29
- //#endregion
30
8
  let react = require("react");
31
- react = __toESM(react, 1);
32
9
  let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
33
10
  let react_dom = require("react-dom");
34
11
  let react_jsx_runtime = require("react/jsx-runtime");
@@ -226,7 +203,14 @@ function groupModelMenuChoices(choices, query) {
226
203
  }
227
204
  //#endregion
228
205
  //#region ../dsh-model-route/src/catalog-ui.tsx
229
- /** Also accepts a mapped state from a caller that owns broader async orchestration. */
206
+ /**
207
+ * Also accepts a mapped state from a caller that owns broader async orchestration.
208
+ *
209
+ * The dsh-ui-* classes carry the styling and resolve through the consuming
210
+ * plugin's own officialUiCss scope (every consumer mounts its root class, so
211
+ * e.g. `.dsh-model-hub .dsh-ui-loading` paints this); dsh-model-catalog-status
212
+ * stays as a structural hook for tests and layout, never a colour source.
213
+ */
230
214
  function ModelCatalogStatus({ state, locale }) {
231
215
  const zh = locale?.toLowerCase().startsWith("zh") === true;
232
216
  const copy = zh ? {
@@ -242,15 +226,17 @@ function ModelCatalogStatus({ state, locale }) {
242
226
  };
243
227
  if (!state.loading && !state.error && state.failures.length === 0) return null;
244
228
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
245
- className: "dsh-model-catalog-status",
229
+ className: "dsh-model-catalog-status dsh-ui-stack",
246
230
  lang: zh ? "zh" : "en",
247
231
  children: [
248
232
  state.loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
249
233
  role: "status",
234
+ className: "dsh-ui-loading",
250
235
  children: copy.loading
251
236
  }) : null,
252
237
  state.error ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
253
238
  role: "alert",
239
+ className: "dsh-ui-error dsh-ui-wrap",
254
240
  children: [
255
241
  copy.error,
256
242
  ": ",
@@ -259,20 +245,27 @@ function ModelCatalogStatus({ state, locale }) {
259
245
  }) : null,
260
246
  state.failures.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
261
247
  role: "status",
262
- 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: [
263
- failure.name || failure.id,
264
- ": ",
265
- failure.message
266
- ] }, `${failure.id}:${index}`)) })]
248
+ className: "dsh-ui-meta",
249
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: copy.partial }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
250
+ className: "dsh-ui-list",
251
+ children: state.failures.map((failure, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
252
+ failure.name || failure.id,
253
+ ": ",
254
+ failure.message
255
+ ] }, `${failure.id}:${index}`))
256
+ })]
267
257
  }) : null,
268
- state.error || state.failures.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
269
- type: "button",
270
- size: "sm",
271
- disabled: state.loading,
272
- onClick: () => {
273
- state.refresh();
274
- },
275
- children: copy.retry
258
+ state.error || state.failures.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
259
+ className: "dsh-ui-actions",
260
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
261
+ type: "button",
262
+ size: "sm",
263
+ disabled: state.loading,
264
+ onClick: () => {
265
+ state.refresh();
266
+ },
267
+ children: copy.retry
268
+ })
276
269
  }) : null
277
270
  ]
278
271
  });
@@ -292,7 +285,8 @@ function ModelCatalogStatus({ state, locale }) {
292
285
  * Sticky group headings, subsequence ranking, highlight walking and focus
293
286
  * hand-back all match the host. SYNC POINT: when the host's ModelSelect or
294
287
  * ModelSelect.module.css changes, this copy must be re-transcribed; styles
295
- * below may only reference --dsw-* theme tokens, never literal colors.
288
+ * below may only reference allowlisted theme tokens (the --dsw-* set plus the
289
+ * host's --ds-* motion tokens), never literal colors.
296
290
  *
297
291
  * It statically imports the primitives, so only browser clients should load
298
292
  * this entry; host code and node tests use the core entry (`./index.ts`).
@@ -800,7 +794,8 @@ function ModelMenu(props) {
800
794
  * sticky group headings (MenuGroup's own CSS), and the l1-stroke prominent
801
795
  * elevation at portal z-index 1100. The trigger mirrors the composer's 28px
802
796
  * model pill, chevron rotating open. MenuSurface paints fill and backdrop.
803
- * Only --dsw-* theme tokens may appear here — a literal color is a bug.
797
+ * Only allowlisted --dsw- and --ds- theme tokens may appear here — a literal
798
+ * color is a bug.
804
799
  */
805
800
  const modelMenuCss = `
806
801
  .dsh-model-menu-root { min-width: 0; position: relative; display: inline-flex; }
@@ -823,12 +818,14 @@ const modelMenuCss = `
823
818
  display: flex;
824
819
  }
825
820
  .dsh-model-menu-trigger:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); }
826
- .dsh-model-menu-trigger:focus-visible:not([data-selection-focus]) { box-shadow: 0 0 0 2px var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary)); }
821
+ .dsh-model-menu-trigger:focus-visible:not([data-selection-focus]) { box-shadow: 0 0 0 var(--dsw-focus-ring-width) var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary)); }
827
822
  .dsh-model-menu-trigger:disabled { color: var(--dsw-alias-label-dimmed); cursor: default; }
828
823
  .dsh-model-menu-triggerLabel { text-overflow: ellipsis; white-space: nowrap; min-width: 0; overflow: hidden; }
829
824
  .dsh-model-menu-triggerEffort { text-overflow: ellipsis; white-space: nowrap; min-width: 0; color: var(--dsw-alias-label-caption); flex-shrink: 1000; overflow: hidden; }
830
- .dsh-model-menu-chevron { color: var(--dsw-alias-label-caption); flex: none; transition: transform 0.12s; }
825
+ .dsh-model-menu-chevron { color: var(--dsw-alias-label-caption); flex: none; transition: transform var(--ds-transition-duration) var(--ds-ease-in-out); }
831
826
  .dsh-model-menu-chevronOpen { transform: rotate(180deg); }
827
+ /* Portal overlay: the host publishes no z-index token, so the card keeps the
828
+ * native menu layer's 1100 verbatim. */
832
829
  .dsh-model-menu-menu {
833
830
  z-index: 1100;
834
831
  --dsw-elevation-stroke-color: var(--dsw-alias-border-l1);
@@ -854,6 +851,8 @@ const modelMenuCss = `
854
851
  .dsh-model-menu-searchWithQuery { padding-right: 34px; }
855
852
  .dsh-model-menu-searchRow .dsh-model-menu-search input { padding: 0; font-size: 12px; line-height: normal; }
856
853
  .dsh-model-menu-searchRow .dsh-model-menu-search input::placeholder { color: var(--dsw-alias-label-caption); }
854
+ /* The clear button is a circle by geometry (border-radius: 50%), a shape the
855
+ * radius token scale does not describe. */
857
856
  .dsh-model-menu-searchClear {
858
857
  width: 24px;
859
858
  height: 24px;
@@ -1101,6 +1100,17 @@ ${scope} {
1101
1100
  /* --- Layout scaffolding. --- */
1102
1101
  & .dsh-ui-stack { display: flex; flex-direction: column; gap: 12px; }
1103
1102
  & .dsh-ui-stack-lg { display: flex; flex-direction: column; gap: 20px; }
1103
+ /* Plugin detail pages share one reading column. These are layout recipes;
1104
+ * controls keep their own primitive styles. Form spacing follows the host's
1105
+ * settings-form/fields.module.css and SettingsForm.module.css. */
1106
+ & .dsh-ui-page { display: flex; flex-direction: column; gap: 16px; width: 100%; max-width: 760px; min-width: 0; }
1107
+ & .dsh-ui-form { display: flex; flex-direction: column; gap: 0; min-width: 0; }
1108
+ &.dsh-ui-form > .dsh-ui-field, & .dsh-ui-form > .dsh-ui-field { padding: 12px 0; }
1109
+ & .dsh-ui-form-footer { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; padding-top: 16px; min-width: 0; }
1110
+ & .dsh-ui-fields-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px 20px; min-width: 0; margin: 0; padding: 0; border: 0; }
1111
+ @media (max-width: 560px) {
1112
+ & .dsh-ui-fields-grid { grid-template-columns: minmax(0, 1fr); }
1113
+ }
1104
1114
  & .dsh-ui-row { display: flex; align-items: center; gap: 8px; min-width: 0; }
1105
1115
  & .dsh-ui-row-wrap { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
1106
1116
  & .dsh-ui-spacer { flex: 1; min-width: 0; }
@@ -1128,6 +1138,9 @@ ${scope} {
1128
1138
  * official primitive, so this only owns the vertical rhythm. --- */
1129
1139
  & .dsh-ui-field { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
1130
1140
  & .dsh-ui-field + .dsh-ui-field { border-top: 0.5px solid var(--dsw-alias-border-l2); padding-top: 12px; }
1141
+ /* Grid fields share a row, so the stacked-field divider must not win later in
1142
+ * the cascade. Reset it here, after the field recipe. */
1143
+ & .dsh-ui-fields-grid > .dsh-ui-field { padding: 0; border: 0; }
1131
1144
  & .dsh-ui-label { display: flex; align-items: center; gap: 6px; font-size: 13px; line-height: 20px; font-weight: 500; color: var(--dsw-alias-label-primary); }
1132
1145
  & .dsh-ui-label-row { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
1133
1146
  & .dsh-ui-help { font-size: 12px; line-height: 1.6; color: var(--dsw-alias-label-secondary); }
@@ -1318,29 +1331,22 @@ ${scope} {
1318
1331
  & *, & *::before, & *::after { transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; }
1319
1332
  }
1320
1333
  }
1321
- `.replace(/^& (\.dsh-ui-[a-z0-9-]+)(?![+>~])/gm, (_match, className) => `&${className}, & ${className}`);
1334
+ `.replace(/^& (\.dsh-ui-[a-z0-9-]+)(?=\s*\{)/gm, (_match, className) => `&${className}, & ${className}`);
1322
1335
  }
1323
1336
  //#endregion
1324
1337
  //#region ../dsh-plugin-kit/src/contracts.ts
1325
- /**
1326
- * Remote contribution for model-settings change notifications.
1327
- * Provided by `@klarkxy/dsh-model-hub` (`ModelSettingsChangesController`);
1328
- * subscribers degrade silently when that package is not installed.
1329
- * The stream carries invalidations only — `ready` is never a settings snapshot.
1330
- */
1331
- const MODEL_SETTINGS_CHANGES_REMOTE = {
1332
- package: "@klarkxy/dsh-model-hub",
1333
- descriptors: [{
1334
- id: "modelSettingsChanges/watch",
1335
- service: "modelSettingsChangesController",
1336
- namespace: "modelSettingsChanges",
1337
- method: "watch",
1338
- mode: "stream",
1339
- invocation: { kind: "direct" },
1340
- parameters: [],
1341
- cancellation: { parameter: "signal" }
1342
- }]
1343
- };
1338
+ /** `@klarkxy/dsh-git-commit` becomes `KlarkxyDshGitCommitModelSettingsChanges`. */
1339
+ function modelSettingsChangesIdentity(packageName) {
1340
+ const scoped = /^@([^/]+)\/(.+)$/.exec(packageName);
1341
+ const parts = (scoped ? `${scoped[1]}/${scoped[2]}` : packageName).split(/[^A-Za-z0-9]+/).filter(Boolean);
1342
+ if (!parts.length) throw new Error("Model settings package name has no RPC segment");
1343
+ const segment = `${parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("")}ModelSettingsChanges`;
1344
+ if (!/^[A-Z][A-Za-z0-9]*$/.test(segment)) throw new Error(`Model settings sync name is not an RPC segment: ${segment}`);
1345
+ return {
1346
+ service: segment,
1347
+ namespace: segment
1348
+ };
1349
+ }
1344
1350
  //#endregion
1345
1351
  //#region ../dsh-plugin-kit/src/model-settings-client.ts
1346
1352
  /** This stream carries invalidations only; ready is never a settings snapshot. */
@@ -1355,21 +1361,44 @@ function parseModelSettingsChangeFrame(value) {
1355
1361
  };
1356
1362
  throw new Error("Invalid model settings change frame");
1357
1363
  }
1358
- /** One contribution mounted by Model Hub; consumers only inject its service. */
1359
- const modelSettingsChangesRemote = {
1360
- package: MODEL_SETTINGS_CHANGES_REMOTE.package,
1361
- descriptors: MODEL_SETTINGS_CHANGES_REMOTE.descriptors.map((descriptor) => ({
1362
- ...descriptor,
1363
- result: {
1364
- mode: "strict",
1365
- typeSymbol: "ModelSettings.ChangeFrame",
1366
- create: () => ({ parse: parseModelSettingsChangeFrame })
1367
- }
1368
- }))
1369
- };
1364
+ /** One owner's contribution. Each package mounts only its own endpoint. */
1365
+ function modelSettingsChangesRemote(packageName, identity = modelSettingsChangesIdentity(packageName)) {
1366
+ return {
1367
+ package: packageName,
1368
+ descriptors: [{
1369
+ id: `${identity.namespace}/watch`,
1370
+ service: identity.service,
1371
+ namespace: identity.namespace,
1372
+ method: "watch",
1373
+ mode: "stream",
1374
+ invocation: { kind: "direct" },
1375
+ parameters: [],
1376
+ cancellation: { parameter: "signal" },
1377
+ result: {
1378
+ mode: "strict",
1379
+ typeSymbol: "ModelSettings.ChangeFrame",
1380
+ create: () => ({ parse: parseModelSettingsChangeFrame })
1381
+ }
1382
+ }]
1383
+ };
1384
+ }
1385
+ /** Mount from a synchronous apply. Disposal waits for an in-flight mount and
1386
+ * then withdraws it, so the contribution cannot outlive the plugin. */
1387
+ function mountModelSettingsChanges(ctx, packageName) {
1388
+ const contribution = modelSettingsChangesRemote(packageName);
1389
+ ctx.inject(["remote"], (injected) => {
1390
+ const scope = injected;
1391
+ const mounting = scope.remote.$mount(contribution);
1392
+ mounting.catch(() => {});
1393
+ scope.effect(() => async () => {
1394
+ await (await mounting.catch(() => void 0))?.();
1395
+ });
1396
+ });
1397
+ }
1370
1398
  /** Native RemoteStream owns carrier reconnects; no timers or browser transport.
1371
1399
  * A factory shares one logical stream while at least one subscriber remains. */
1372
- function createModelSettingsSubscriber(ctx, namespace) {
1400
+ function createModelSettingsSubscriber(ctx, packageName, identity = modelSettingsChangesIdentity(packageName), accept = (ns) => ns === packageName) {
1401
+ const endpoint = identity;
1373
1402
  const listeners = /* @__PURE__ */ new Set();
1374
1403
  let injection;
1375
1404
  let current;
@@ -1395,12 +1424,18 @@ function createModelSettingsSubscriber(ctx, namespace) {
1395
1424
  };
1396
1425
  const start = () => {
1397
1426
  const life = ++lifecycle;
1398
- injection = ctx.inject(["remote", "remote.modelSettingsChanges"], (injected) => {
1427
+ injection = ctx.inject(["remote", `remote.${endpoint.namespace}`], (injected) => {
1399
1428
  if (life !== lifecycle || !listeners.size) return;
1400
1429
  const scope = injected;
1430
+ const service = scope.remote[endpoint.namespace];
1431
+ if (!service || !("watch" in service) || !service.watch) {
1432
+ report("Model settings sync service unavailable");
1433
+ return;
1434
+ }
1435
+ const watch = service.watch;
1401
1436
  const stream = scope.remote.$stream({
1402
1437
  name: "Model settings sync",
1403
- open: (signal) => scope.remote.modelSettingsChanges.watch(signal),
1438
+ open: (signal) => watch(signal),
1404
1439
  ended: (accepted) => /* @__PURE__ */ new Error(`Model settings sync stream ended ${accepted ? "after" : "before"} ready; refresh or reopen the page`)
1405
1440
  });
1406
1441
  const session = {
@@ -1435,7 +1470,7 @@ function createModelSettingsSubscriber(ctx, namespace) {
1435
1470
  invalidate();
1436
1471
  } else {
1437
1472
  if (!ready) throw new Error("Model settings sync change arrived before ready");
1438
- if (namespace === "*" || frame.ns === namespace) invalidate();
1473
+ if (accept(frame.ns)) invalidate();
1439
1474
  }
1440
1475
  }
1441
1476
  } catch (error) {
@@ -1998,7 +2033,7 @@ function CastPage({ api, locale }) {
1998
2033
  refresh: refreshNow
1999
2034
  };
2000
2035
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2001
- className: "cast-root dsh-ui-stack",
2036
+ className: "cast-root dsh-model-hub dsh-ui-stack",
2002
2037
  "aria-label": t("title"),
2003
2038
  "aria-busy": saving !== "" || refreshing,
2004
2039
  children: [
@@ -2428,7 +2463,7 @@ function HubPage({ view, api, locale, initialEntries = null, initialCatalog = nu
2428
2463
  refresh: refreshNow
2429
2464
  };
2430
2465
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2431
- className: "dsh-model-hub dsh-ui-stack",
2466
+ className: "dsh-model-hub dsh-ui-page",
2432
2467
  lang: t.language,
2433
2468
  "aria-label": t("title"),
2434
2469
  "aria-busy": writing !== "" || refreshing,
@@ -2594,11 +2629,10 @@ function HubPage({ view, api, locale, initialEntries = null, initialCatalog = nu
2594
2629
  ]
2595
2630
  });
2596
2631
  }
2597
- async function apply(ctx) {
2632
+ function apply(ctx) {
2598
2633
  const client = ctx;
2599
- const disposeRemote = await client.remote.$mount(modelSettingsChangesRemote);
2600
- client.effect(() => disposeRemote);
2601
- const changes = createModelSettingsSubscriber(ctx, "*");
2634
+ mountModelSettingsChanges(ctx, BUNDLE);
2635
+ const changes = createModelSettingsSubscriber(ctx, BUNDLE, modelSettingsChangesIdentity(BUNDLE), () => true);
2602
2636
  const call = async (endpoint, payload) => {
2603
2637
  const result = await client.connection.rpc.call(CHANNEL, endpoint, payload);
2604
2638
  if (!result?.ok) throw new Error(result?.error?.message || "Request failed");
package/lib/index.js CHANGED
@@ -1,13 +1,45 @@
1
- import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
1
+ import { mkdir, writeFile } from "node:fs/promises";
2
2
  import { dirname, join } from "node:path";
3
3
  import Schema from "@deepseek-ai/schemastery";
4
4
  import { registerHostRpc } from "@klarkxy/dsh-plugin-kit/host-rpc";
5
5
  import { modelRouteRoleSchemastery, modelRouteSchemastery } from "@klarkxy/dsh-model-route/schemastery";
6
6
  import { detectModelFields, modelRouteOverride, normalizeModelRoute } from "@klarkxy/dsh-model-route";
7
- import { MODEL_SETTINGS_CHANGED_EVENT, collectModelSettings, notifyModelSettingsChanged } from "@klarkxy/dsh-plugin-kit/model-settings";
8
- import { Remote, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
9
- import { MODEL_SETTINGS_CHANGES_REMOTE, isSubagentSession } from "@klarkxy/dsh-plugin-kit/contracts";
10
- import { parseModelSettingsChangeFrame } from "@klarkxy/dsh-plugin-kit/model-settings-client";
7
+ import { collectModelSettings, notifyModelSettingsChanged } from "@klarkxy/dsh-plugin-kit/model-settings";
8
+ import { isSubagentSession, modelSettingsChangesIdentity } from "@klarkxy/dsh-plugin-kit/contracts";
9
+ import { createModelSettingsChanges } from "@klarkxy/dsh-plugin-kit/model-settings-changes";
10
+ import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
11
+ //#region src/diagnostics.ts
12
+ /** Invalidation is constant-time. Only startup and explicit reads request a
13
+ * diagnostic; a burst shares one deferred run. Work never reschedules itself. */
14
+ function createProbeDiagnostics(run, onError) {
15
+ let dirty = true;
16
+ let stopped = false;
17
+ let timer;
18
+ let running;
19
+ return {
20
+ invalidate() {
21
+ if (!stopped) dirty = true;
22
+ },
23
+ request() {
24
+ if (stopped || !dirty || timer !== void 0 || running) return;
25
+ timer = setTimeout(() => {
26
+ timer = void 0;
27
+ if (stopped || !dirty) return;
28
+ dirty = false;
29
+ running = Promise.resolve().then(run).catch(onError).finally(() => {
30
+ running = void 0;
31
+ });
32
+ }, 0);
33
+ },
34
+ async dispose() {
35
+ stopped = true;
36
+ if (timer !== void 0) clearTimeout(timer);
37
+ timer = void 0;
38
+ await running;
39
+ }
40
+ };
41
+ }
42
+ //#endregion
11
43
  //#region src/probe.ts
12
44
  /**
13
45
  * Pure probe-report builder for the model hub. Takes the host's
@@ -209,170 +241,10 @@ function createHubSettings(events, settings, ownNamespace = "@klarkxy/dsh-model-
209
241
  }
210
242
  //#endregion
211
243
  //#region src/changes.ts
212
- var __create = Object.create;
213
- var __defProp = Object.defineProperty;
214
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
215
- var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
216
- var __typeError = (msg) => {
217
- throw TypeError(msg);
218
- };
219
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, {
220
- enumerable: true,
221
- configurable: true,
222
- writable: true,
223
- value
224
- }) : obj[key] = value;
225
- var __name = (target, value) => __defProp(target, "name", {
226
- value,
227
- configurable: true
228
- });
229
- var __decoratorStart = (base) => [
230
- ,
231
- ,
232
- ,
233
- __create(base?.[__knownSymbol("metadata")] ?? null)
234
- ];
235
- var __decoratorStrings = [
236
- "class",
237
- "method",
238
- "getter",
239
- "setter",
240
- "accessor",
241
- "field",
242
- "value",
243
- "get",
244
- "set"
245
- ];
246
- var __expectFn = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError("Function expected") : fn;
247
- var __decoratorContext = (kind, name, done, metadata, fns) => ({
248
- kind: __decoratorStrings[kind],
249
- name,
250
- metadata,
251
- addInitializer: (fn) => done._ ? __typeError("Already initialized") : fns.push(__expectFn(fn || null))
252
- });
253
- var __decoratorMetadata = (array, target) => __defNormalProp(target, __knownSymbol("metadata"), array[3]);
254
- var __runInitializers = (array, flags, self, value) => {
255
- for (var i = 0, fns = array[flags >> 1], n = fns && fns.length; i < n; i++) flags & 1 ? fns[i].call(self) : value = fns[i].call(self, value);
256
- return value;
257
- };
258
- var __decorateElement = (array, flags, name, decorators, target, extra) => {
259
- var fn, it, done, ctx, access, k = flags & 7, s = !!(flags & 8), p = !!(flags & 16);
260
- var j = k > 3 ? array.length + 1 : k ? s ? 1 : 2 : 0, key = __decoratorStrings[k + 5];
261
- var initializers = k > 3 && (array[j - 1] = []), extraInitializers = array[j] || (array[j] = []);
262
- var desc = k && (!p && !s && (target = target.prototype), k < 5 && (k > 3 || !p) && __getOwnPropDesc(k < 4 ? target : {
263
- get [name]() {
264
- return __privateGet(this, extra);
265
- },
266
- set [name](x) {
267
- return __privateSet(this, extra, x);
268
- }
269
- }, name));
270
- k ? p && k < 4 && __name(extra, (k > 2 ? "set " : k > 1 ? "get " : "") + name) : __name(target, name);
271
- for (var i = decorators.length - 1; i >= 0; i--) {
272
- ctx = __decoratorContext(k, name, done = {}, array[3], extraInitializers);
273
- if (k) {
274
- ctx.static = s, ctx.private = p, access = ctx.access = { has: p ? (x) => __privateIn(target, x) : (x) => name in x };
275
- if (k ^ 3) access.get = p ? (x) => (k ^ 1 ? __privateGet : __privateMethod)(x, target, k ^ 4 ? extra : desc.get) : (x) => x[name];
276
- if (k > 2) access.set = p ? (x, y) => __privateSet(x, target, y, k ^ 4 ? extra : desc.set) : (x, y) => x[name] = y;
277
- }
278
- it = (0, decorators[i])(k ? k < 4 ? p ? extra : desc[key] : k > 4 ? void 0 : {
279
- get: desc.get,
280
- set: desc.set
281
- } : target, ctx), done._ = 1;
282
- if (k ^ 4 || it === void 0) __expectFn(it) && (k > 4 ? initializers.unshift(it) : k ? p ? extra = it : desc[key] = it : target = it);
283
- else if (typeof it !== "object" || it === null) __typeError("Object expected");
284
- else __expectFn(fn = it.get) && (desc.get = fn), __expectFn(fn = it.set) && (desc.set = fn), __expectFn(fn = it.init) && initializers.unshift(fn);
285
- }
286
- return k || __decoratorMetadata(array, target), desc && __defProp(target, name, desc), p ? k ^ 4 ? extra : desc : target;
287
- };
288
- var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
289
- var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
290
- var __privateIn = (member, obj) => Object(obj) !== obj ? __typeError("Cannot use the \"in\" operator on this value") : member.has(obj);
291
- var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
292
- var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
293
- var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
294
- var _watch_dec;
295
- var _a;
296
- var _init;
297
- async function* watchModelSettingsChanges(events, signal) {
298
- signal.throwIfAborted();
299
- const pending = /* @__PURE__ */ new Map();
300
- let full = false;
301
- let wake;
302
- const changed = (ns, revision) => {
303
- let frame;
304
- try {
305
- frame = parseModelSettingsChangeFrame({
306
- kind: "changed",
307
- ns,
308
- revision
309
- });
310
- } catch {
311
- return;
312
- }
313
- if (!full) {
314
- if (!pending.has(ns) && pending.size >= 256) {
315
- pending.clear();
316
- full = true;
317
- } else pending.set(ns, frame);
318
- }
319
- wake?.();
320
- };
321
- const disposers = [];
322
- const cleanup = () => {
323
- for (const off of disposers.splice(0).reverse()) off();
324
- pending.clear();
325
- };
326
- const aborted = () => {
327
- cleanup();
328
- wake?.();
329
- };
330
- signal.addEventListener("abort", aborted, { once: true });
331
- try {
332
- disposers.push(events.on(MODEL_SETTINGS_CHANGED_EVENT, ({ ns, revision }) => changed(ns, revision)));
333
- disposers.push(events.on("settings/document-updated", changed));
334
- signal.throwIfAborted();
335
- yield { kind: "ready" };
336
- while (true) {
337
- signal.throwIfAborted();
338
- if (full) {
339
- full = false;
340
- yield { kind: "ready" };
341
- continue;
342
- }
343
- if (pending.size) {
344
- const [ns, frame] = pending.entries().next().value;
345
- pending.delete(ns);
346
- yield frame;
347
- continue;
348
- }
349
- await new Promise((resolve) => {
350
- wake = resolve;
351
- if (signal.aborted || full || pending.size) resolve();
352
- });
353
- wake = void 0;
354
- }
355
- } finally {
356
- signal.removeEventListener("abort", aborted);
357
- cleanup();
358
- }
244
+ /** Model Hub forwards every settings namespace; feature plugins filter their own. */
245
+ function registerHubModelSettingsChanges(ctx) {
246
+ createModelSettingsChanges(ctx, modelSettingsChangesIdentity("@klarkxy/dsh-model-hub"));
359
247
  }
360
- var ModelSettingsChangesController = class extends (_a = TypertRemoteService, _watch_dec = [Remote({ mode: "stream" })], _a) {
361
- constructor(ctx) {
362
- super(ctx, MODEL_SETTINGS_CHANGES_REMOTE.descriptors[0].service, { namespace: MODEL_SETTINGS_CHANGES_REMOTE.descriptors[0].namespace });
363
- __runInitializers(_init, 5, this);
364
- __publicField(this, "lifetime", new AbortController());
365
- ctx.effect(() => () => {
366
- this.lifetime.abort(/* @__PURE__ */ new Error("Model settings sync owner unloaded"));
367
- });
368
- }
369
- watch(signal) {
370
- return watchModelSettingsChanges(this.ctx, AbortSignal.any([this.lifetime.signal, signal]));
371
- }
372
- };
373
- _init = __decoratorStart(_a);
374
- __decorateElement(_init, 1, "watch", _watch_dec, ModelSettingsChangesController);
375
- __decoratorMetadata(_init, ModelSettingsChangesController);
376
248
  //#endregion
377
249
  //#region src/cast.ts
378
250
  /**
@@ -863,7 +735,7 @@ const Config = Schema.object({
863
735
  }).volatile()
864
736
  });
865
737
  function apply(ctx) {
866
- new ModelSettingsChangesController(ctx);
738
+ registerHubModelSettingsChanges(ctx);
867
739
  const profile = ctx.get("profileContext");
868
740
  const settings = ctx.settings;
869
741
  const describe = () => settings.describe({ redactSecrets: true });
@@ -891,7 +763,8 @@ function apply(ctx) {
891
763
  interpolate: false,
892
764
  text: () => cast.promptText(() => ctx.get("subagentModelSelection")?.current())
893
765
  });
894
- const run = (reason) => {
766
+ const run = async () => {
767
+ const reason = "requested";
895
768
  let report;
896
769
  try {
897
770
  report = buildProbeReport(describe());
@@ -902,20 +775,33 @@ function apply(ctx) {
902
775
  ctx.logger.info("model-hub: probe (%s): %d namespaces, %d with model fields; self-test extra-key=%s role=%s", reason, report.totalNamespaces, report.namespacesWithModelFields, report.selfTest.extraKey, report.selfTest.role);
903
776
  if (profile?.dir) try {
904
777
  const dir = join(profile.dir, "data", "model-hub");
905
- mkdirSync(dir, { recursive: true });
906
- writeFileSync(join(dir, "probe-report.json"), JSON.stringify(report, null, 2));
778
+ await mkdir(dir, { recursive: true });
779
+ await writeFile(join(dir, "probe-report.json"), JSON.stringify(report, null, 2));
907
780
  } catch (error) {
908
781
  ctx.logger.warn("model-hub: could not write probe report: %s", error instanceof Error ? error.message : String(error));
909
782
  }
910
783
  };
911
- run("boot");
912
- ctx.on("settings/document-updated", () => run("document-updated"));
784
+ const diagnostics = createProbeDiagnostics(run, (error) => {
785
+ ctx.logger.warn("model-hub: diagnostics failed: %s", String(error));
786
+ });
787
+ ctx.effect(() => {
788
+ const off = ctx.on("settings/document-updated", () => diagnostics.invalidate());
789
+ diagnostics.request();
790
+ return async () => {
791
+ off();
792
+ await diagnostics.dispose();
793
+ };
794
+ });
913
795
  ctx.effect(() => registerHostRpc(ctx, CHANNEL, async (endpoint, payload, signal) => {
914
796
  try {
915
- if (endpoint === "fields.list") return {
916
- ok: true,
917
- value: await hub.list(signal)
918
- };
797
+ if (endpoint === "fields.list") {
798
+ const value = await hub.list(signal);
799
+ diagnostics.request();
800
+ return {
801
+ ok: true,
802
+ value
803
+ };
804
+ }
919
805
  if (endpoint === "fields.set") return {
920
806
  ok: true,
921
807
  value: await hub.set(payload, signal)