@klarkxy/dsh-current-title 0.2.5 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -298,6 +298,17 @@ ${scope} {
298
298
  /* --- Layout scaffolding. --- */
299
299
  & .dsh-ui-stack { display: flex; flex-direction: column; gap: 12px; }
300
300
  & .dsh-ui-stack-lg { display: flex; flex-direction: column; gap: 20px; }
301
+ /* Plugin detail pages share one reading column. These are layout recipes;
302
+ * controls keep their own primitive styles. Form spacing follows the host's
303
+ * settings-form/fields.module.css and SettingsForm.module.css. */
304
+ & .dsh-ui-page { display: flex; flex-direction: column; gap: 16px; width: 100%; max-width: 760px; min-width: 0; }
305
+ & .dsh-ui-form { display: flex; flex-direction: column; gap: 0; min-width: 0; }
306
+ &.dsh-ui-form > .dsh-ui-field, & .dsh-ui-form > .dsh-ui-field { padding: 12px 0; }
307
+ & .dsh-ui-form-footer { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; padding-top: 16px; min-width: 0; }
308
+ & .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; }
309
+ @media (max-width: 560px) {
310
+ & .dsh-ui-fields-grid { grid-template-columns: minmax(0, 1fr); }
311
+ }
301
312
  & .dsh-ui-row { display: flex; align-items: center; gap: 8px; min-width: 0; }
302
313
  & .dsh-ui-row-wrap { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
303
314
  & .dsh-ui-spacer { flex: 1; min-width: 0; }
@@ -325,6 +336,9 @@ ${scope} {
325
336
  * official primitive, so this only owns the vertical rhythm. --- */
326
337
  & .dsh-ui-field { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
327
338
  & .dsh-ui-field + .dsh-ui-field { border-top: 0.5px solid var(--dsw-alias-border-l2); padding-top: 12px; }
339
+ /* Grid fields share a row, so the stacked-field divider must not win later in
340
+ * the cascade. Reset it here, after the field recipe. */
341
+ & .dsh-ui-fields-grid > .dsh-ui-field { padding: 0; border: 0; }
328
342
  & .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); }
329
343
  & .dsh-ui-label-row { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
330
344
  & .dsh-ui-help { font-size: 12px; line-height: 1.6; color: var(--dsw-alias-label-secondary); }
@@ -515,29 +529,22 @@ ${scope} {
515
529
  & *, & *::before, & *::after { transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; }
516
530
  }
517
531
  }
518
- `.replace(/^& (\.dsh-ui-[a-z0-9-]+)(?![+>~])/gm, (_match, className) => `&${className}, & ${className}`);
532
+ `.replace(/^& (\.dsh-ui-[a-z0-9-]+)(?=\s*\{)/gm, (_match, className) => `&${className}, & ${className}`);
519
533
  }
520
534
  //#endregion
521
535
  //#region ../dsh-plugin-kit/src/contracts.ts
522
- /**
523
- * Remote contribution for model-settings change notifications.
524
- * Provided by `@klarkxy/dsh-model-hub` (`ModelSettingsChangesController`);
525
- * subscribers degrade silently when that package is not installed.
526
- * The stream carries invalidations only — `ready` is never a settings snapshot.
527
- */
528
- const MODEL_SETTINGS_CHANGES_REMOTE = {
529
- package: "@klarkxy/dsh-model-hub",
530
- descriptors: [{
531
- id: "modelSettingsChanges/watch",
532
- service: "modelSettingsChangesController",
533
- namespace: "modelSettingsChanges",
534
- method: "watch",
535
- mode: "stream",
536
- invocation: { kind: "direct" },
537
- parameters: [],
538
- cancellation: { parameter: "signal" }
539
- }]
540
- };
536
+ /** `@klarkxy/dsh-git-commit` becomes `KlarkxyDshGitCommitModelSettingsChanges`. */
537
+ function modelSettingsChangesIdentity(packageName) {
538
+ const scoped = /^@([^/]+)\/(.+)$/.exec(packageName);
539
+ const parts = (scoped ? `${scoped[1]}/${scoped[2]}` : packageName).split(/[^A-Za-z0-9]+/).filter(Boolean);
540
+ if (!parts.length) throw new Error("Model settings package name has no RPC segment");
541
+ const segment = `${parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("")}ModelSettingsChanges`;
542
+ if (!/^[A-Z][A-Za-z0-9]*$/.test(segment)) throw new Error(`Model settings sync name is not an RPC segment: ${segment}`);
543
+ return {
544
+ service: segment,
545
+ namespace: segment
546
+ };
547
+ }
541
548
  //#endregion
542
549
  //#region ../dsh-plugin-kit/src/model-settings-client.ts
543
550
  /** This stream carries invalidations only; ready is never a settings snapshot. */
@@ -552,17 +559,44 @@ function parseModelSettingsChangeFrame(value) {
552
559
  };
553
560
  throw new Error("Invalid model settings change frame");
554
561
  }
555
- MODEL_SETTINGS_CHANGES_REMOTE.package, MODEL_SETTINGS_CHANGES_REMOTE.descriptors.map((descriptor) => ({
556
- ...descriptor,
557
- result: {
558
- mode: "strict",
559
- typeSymbol: "ModelSettings.ChangeFrame",
560
- create: () => ({ parse: parseModelSettingsChangeFrame })
561
- }
562
- }));
562
+ /** One owner's contribution. Each package mounts only its own endpoint. */
563
+ function modelSettingsChangesRemote(packageName, identity = modelSettingsChangesIdentity(packageName)) {
564
+ return {
565
+ package: packageName,
566
+ descriptors: [{
567
+ id: `${identity.namespace}/watch`,
568
+ service: identity.service,
569
+ namespace: identity.namespace,
570
+ method: "watch",
571
+ mode: "stream",
572
+ invocation: { kind: "direct" },
573
+ parameters: [],
574
+ cancellation: { parameter: "signal" },
575
+ result: {
576
+ mode: "strict",
577
+ typeSymbol: "ModelSettings.ChangeFrame",
578
+ create: () => ({ parse: parseModelSettingsChangeFrame })
579
+ }
580
+ }]
581
+ };
582
+ }
583
+ /** Mount from a synchronous apply. Disposal waits for an in-flight mount and
584
+ * then withdraws it, so the contribution cannot outlive the plugin. */
585
+ function mountModelSettingsChanges(ctx, packageName) {
586
+ const contribution = modelSettingsChangesRemote(packageName);
587
+ ctx.inject(["remote"], (injected) => {
588
+ const scope = injected;
589
+ const mounting = scope.remote.$mount(contribution);
590
+ mounting.catch(() => {});
591
+ scope.effect(() => async () => {
592
+ await (await mounting.catch(() => void 0))?.();
593
+ });
594
+ });
595
+ }
563
596
  /** Native RemoteStream owns carrier reconnects; no timers or browser transport.
564
597
  * A factory shares one logical stream while at least one subscriber remains. */
565
- function createModelSettingsSubscriber(ctx, namespace) {
598
+ function createModelSettingsSubscriber(ctx, packageName, identity = modelSettingsChangesIdentity(packageName), accept = (ns) => ns === packageName) {
599
+ const endpoint = identity;
566
600
  const listeners = /* @__PURE__ */ new Set();
567
601
  let injection;
568
602
  let current;
@@ -588,12 +622,18 @@ function createModelSettingsSubscriber(ctx, namespace) {
588
622
  };
589
623
  const start = () => {
590
624
  const life = ++lifecycle;
591
- injection = ctx.inject(["remote", "remote.modelSettingsChanges"], (injected) => {
625
+ injection = ctx.inject(["remote", `remote.${endpoint.namespace}`], (injected) => {
592
626
  if (life !== lifecycle || !listeners.size) return;
593
627
  const scope = injected;
628
+ const service = scope.remote[endpoint.namespace];
629
+ if (!service || !("watch" in service) || !service.watch) {
630
+ report("Model settings sync service unavailable");
631
+ return;
632
+ }
633
+ const watch = service.watch;
594
634
  const stream = scope.remote.$stream({
595
635
  name: "Model settings sync",
596
- open: (signal) => scope.remote.modelSettingsChanges.watch(signal),
636
+ open: (signal) => watch(signal),
597
637
  ended: (accepted) => /* @__PURE__ */ new Error(`Model settings sync stream ended ${accepted ? "after" : "before"} ready; refresh or reopen the page`)
598
638
  });
599
639
  const session = {
@@ -628,7 +668,7 @@ function createModelSettingsSubscriber(ctx, namespace) {
628
668
  invalidate();
629
669
  } else {
630
670
  if (!ready) throw new Error("Model settings sync change arrived before ready");
631
- if (namespace === "*" || frame.ns === namespace) invalidate();
671
+ if (accept(frame.ns)) invalidate();
632
672
  }
633
673
  }
634
674
  } catch (error) {
@@ -933,7 +973,14 @@ function useModelCatalog(load, options = {}) {
933
973
  refresh
934
974
  };
935
975
  }
936
- /** Also accepts a mapped state from a caller that owns broader async orchestration. */
976
+ /**
977
+ * Also accepts a mapped state from a caller that owns broader async orchestration.
978
+ *
979
+ * The dsh-ui-* classes carry the styling and resolve through the consuming
980
+ * plugin's own officialUiCss scope (every consumer mounts its root class, so
981
+ * e.g. `.dsh-model-hub .dsh-ui-loading` paints this); dsh-model-catalog-status
982
+ * stays as a structural hook for tests and layout, never a colour source.
983
+ */
937
984
  function ModelCatalogStatus({ state, locale }) {
938
985
  const zh = locale?.toLowerCase().startsWith("zh") === true;
939
986
  const copy = zh ? {
@@ -949,15 +996,17 @@ function ModelCatalogStatus({ state, locale }) {
949
996
  };
950
997
  if (!state.loading && !state.error && state.failures.length === 0) return null;
951
998
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
952
- className: "dsh-model-catalog-status",
999
+ className: "dsh-model-catalog-status dsh-ui-stack",
953
1000
  lang: zh ? "zh" : "en",
954
1001
  children: [
955
1002
  state.loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
956
1003
  role: "status",
1004
+ className: "dsh-ui-loading",
957
1005
  children: copy.loading
958
1006
  }) : null,
959
1007
  state.error ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
960
1008
  role: "alert",
1009
+ className: "dsh-ui-error dsh-ui-wrap",
961
1010
  children: [
962
1011
  copy.error,
963
1012
  ": ",
@@ -966,20 +1015,27 @@ function ModelCatalogStatus({ state, locale }) {
966
1015
  }) : null,
967
1016
  state.failures.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
968
1017
  role: "status",
969
- 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: [
970
- failure.name || failure.id,
971
- ": ",
972
- failure.message
973
- ] }, `${failure.id}:${index}`)) })]
1018
+ className: "dsh-ui-meta",
1019
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: copy.partial }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
1020
+ className: "dsh-ui-list",
1021
+ children: state.failures.map((failure, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
1022
+ failure.name || failure.id,
1023
+ ": ",
1024
+ failure.message
1025
+ ] }, `${failure.id}:${index}`))
1026
+ })]
974
1027
  }) : null,
975
- state.error || state.failures.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
976
- type: "button",
977
- size: "sm",
978
- disabled: state.loading,
979
- onClick: () => {
980
- state.refresh();
981
- },
982
- children: copy.retry
1028
+ state.error || state.failures.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1029
+ className: "dsh-ui-actions",
1030
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1031
+ type: "button",
1032
+ size: "sm",
1033
+ disabled: state.loading,
1034
+ onClick: () => {
1035
+ state.refresh();
1036
+ },
1037
+ children: copy.retry
1038
+ })
983
1039
  }) : null
984
1040
  ]
985
1041
  });
@@ -999,7 +1055,8 @@ function ModelCatalogStatus({ state, locale }) {
999
1055
  * Sticky group headings, subsequence ranking, highlight walking and focus
1000
1056
  * hand-back all match the host. SYNC POINT: when the host's ModelSelect or
1001
1057
  * ModelSelect.module.css changes, this copy must be re-transcribed; styles
1002
- * below may only reference --dsw-* theme tokens, never literal colors.
1058
+ * below may only reference allowlisted theme tokens (the --dsw-* set plus the
1059
+ * host's --ds-* motion tokens), never literal colors.
1003
1060
  *
1004
1061
  * It statically imports the primitives, so only browser clients should load
1005
1062
  * this entry; host code and node tests use the core entry (`./index.ts`).
@@ -1507,7 +1564,8 @@ function ModelMenu(props) {
1507
1564
  * sticky group headings (MenuGroup's own CSS), and the l1-stroke prominent
1508
1565
  * elevation at portal z-index 1100. The trigger mirrors the composer's 28px
1509
1566
  * model pill, chevron rotating open. MenuSurface paints fill and backdrop.
1510
- * Only --dsw-* theme tokens may appear here — a literal color is a bug.
1567
+ * Only allowlisted --dsw- and --ds- theme tokens may appear here — a literal
1568
+ * color is a bug.
1511
1569
  */
1512
1570
  const modelMenuCss = `
1513
1571
  .dsh-model-menu-root { min-width: 0; position: relative; display: inline-flex; }
@@ -1530,12 +1588,14 @@ const modelMenuCss = `
1530
1588
  display: flex;
1531
1589
  }
1532
1590
  .dsh-model-menu-trigger:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); }
1533
- .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)); }
1591
+ .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)); }
1534
1592
  .dsh-model-menu-trigger:disabled { color: var(--dsw-alias-label-dimmed); cursor: default; }
1535
1593
  .dsh-model-menu-triggerLabel { text-overflow: ellipsis; white-space: nowrap; min-width: 0; overflow: hidden; }
1536
1594
  .dsh-model-menu-triggerEffort { text-overflow: ellipsis; white-space: nowrap; min-width: 0; color: var(--dsw-alias-label-caption); flex-shrink: 1000; overflow: hidden; }
1537
- .dsh-model-menu-chevron { color: var(--dsw-alias-label-caption); flex: none; transition: transform 0.12s; }
1595
+ .dsh-model-menu-chevron { color: var(--dsw-alias-label-caption); flex: none; transition: transform var(--ds-transition-duration) var(--ds-ease-in-out); }
1538
1596
  .dsh-model-menu-chevronOpen { transform: rotate(180deg); }
1597
+ /* Portal overlay: the host publishes no z-index token, so the card keeps the
1598
+ * native menu layer's 1100 verbatim. */
1539
1599
  .dsh-model-menu-menu {
1540
1600
  z-index: 1100;
1541
1601
  --dsw-elevation-stroke-color: var(--dsw-alias-border-l1);
@@ -1561,6 +1621,8 @@ const modelMenuCss = `
1561
1621
  .dsh-model-menu-searchWithQuery { padding-right: 34px; }
1562
1622
  .dsh-model-menu-searchRow .dsh-model-menu-search input { padding: 0; font-size: 12px; line-height: normal; }
1563
1623
  .dsh-model-menu-searchRow .dsh-model-menu-search input::placeholder { color: var(--dsw-alias-label-caption); }
1624
+ /* The clear button is a circle by geometry (border-radius: 50%), a shape the
1625
+ * radius token scale does not describe. */
1564
1626
  .dsh-model-menu-searchClear {
1565
1627
  width: 24px;
1566
1628
  height: 24px;
@@ -2055,7 +2117,7 @@ function TitleSettings(props) {
2055
2117
  });
2056
2118
  }
2057
2119
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2058
- className: "current-title-settings ct-root dsh-ui-panel",
2120
+ className: "current-title-settings ct-root dsh-ui-page",
2059
2121
  lang: seat.locale,
2060
2122
  "data-testid": "current-title-settings",
2061
2123
  children: [
@@ -2321,13 +2383,16 @@ function TitleSettings(props) {
2321
2383
  ]
2322
2384
  });
2323
2385
  }
2324
- const styles = `${officialUiCss("ct-root")}${modelMenuCss}
2325
- .ct-root { max-width: 760px; }
2326
- .ct-prompt { min-height: 96px; }
2386
+ const currentTitleOwnCss = `
2387
+ .ct-root .ct-prompt { min-height: 96px; }
2327
2388
  `;
2389
+ const currentTitleStyles = `${officialUiCss("ct-root")}${modelMenuCss}${currentTitleOwnCss}`;
2390
+ const styles = currentTitleStyles;
2328
2391
  function apply(ctx) {
2329
2392
  const client = ctx;
2330
- const settingsSubscriber = createModelSettingsSubscriber(ctx, "@klarkxy/dsh-current-title");
2393
+ const packageName = "@klarkxy/dsh-current-title";
2394
+ mountModelSettingsChanges(ctx, packageName);
2395
+ const settingsSubscriber = createModelSettingsSubscriber(ctx, packageName, modelSettingsChangesIdentity(packageName));
2331
2396
  const subscribeSettings = (change, error) => settingsSubscriber.subscribe(change, error);
2332
2397
  const marker = createTitleClientMarker();
2333
2398
  ctx.provide(TITLE_CLIENT_SERVICE, marker);
@@ -2367,6 +2432,8 @@ exports.TitleSettings = TitleSettings;
2367
2432
  exports.apply = apply;
2368
2433
  exports.applyMarkerFromStatus = applyMarkerFromStatus;
2369
2434
  exports.createTitleClientMarker = createTitleClientMarker;
2435
+ exports.currentTitleOwnCss = currentTitleOwnCss;
2436
+ exports.currentTitleStyles = currentTitleStyles;
2370
2437
  exports.disposeTitleClientMarker = disposeTitleClientMarker;
2371
2438
  exports.inject = inject;
2372
2439
  exports.name = name;
@@ -1,5 +1,5 @@
1
- import { ModelRoute, defaultModelRoute, normalizeModelRoute } from "@klarkxy/dsh-model-route";
2
1
  import { RpcResult } from "@klarkxy/dsh-plugin-kit/contracts";
2
+ import { ModelRoute, defaultModelRoute, normalizeModelRoute } from "@klarkxy/dsh-model-route";
3
3
  //#region src/contracts.d.ts
4
4
  declare const PLUGIN_NAME = "@klarkxy/dsh-current-title";
5
5
  declare const FEATURE_ID = "current-title";
package/lib/index.d.ts CHANGED
@@ -123,6 +123,7 @@ declare class CurrentTitleService {
123
123
  private settingsPending;
124
124
  private disposed;
125
125
  private readonly inFlight;
126
+ private readonly generations;
126
127
  readonly support: TitleSupport;
127
128
  constructor(options: {
128
129
  plugin: string;
@@ -155,6 +156,7 @@ declare class CurrentTitleService {
155
156
  regenerate(sessionId: string, signal: AbortSignal): Promise<TitleSnapshot | undefined>;
156
157
  read(session: SessionLike): TitleSnapshot | undefined;
157
158
  private nativeGenerate;
159
+ private runNativeGenerate;
158
160
  }
159
161
  //#endregion
160
162
  //#region src/input.d.ts
package/lib/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { DEFAULT_TIMEOUT_MS, PLUGIN_NAME, PROVIDER_ID, RPC_CHANNEL, SETTINGS_KEY, defaultGenerateConfig, defaultModelRoute, isTitleLocaleMode, normalizeCadence, normalizeModelRoute, normalizePrompt } from "./contracts.js";
2
2
  import { registerHostRpc } from "@klarkxy/dsh-plugin-kit/host-rpc";
3
+ import { isSubagentSession, modelSettingsChangesIdentity } from "@klarkxy/dsh-plugin-kit/contracts";
4
+ import { createModelSettingsChanges } from "@klarkxy/dsh-plugin-kit/model-settings-changes";
3
5
  import { notifyModelSettingsChanged, registerModelSettings } from "@klarkxy/dsh-plugin-kit/model-settings";
4
- import "@klarkxy/dsh-model-route";
5
- import { isSubagentSession } from "@klarkxy/dsh-plugin-kit/contracts";
6
6
  import { callLlmText, resolveFeatureModel } from "@klarkxy/dsh-plugin-kit";
7
7
  import { defineDomain, domainTable } from "@deepseek-ai/dsh-storage-domain";
8
8
  import { z } from "zod";
@@ -251,32 +251,32 @@ async function generateCurrentTitle(options) {
251
251
  const timeoutMs = Number.isFinite(options.config.timeoutMs) && options.config.timeoutMs > 0 ? options.config.timeoutMs : DEFAULT_TIMEOUT_MS;
252
252
  const timer = setTimeout(() => deadline.abort(new DOMException("Title generation timed out.", "TimeoutError")), timeoutMs);
253
253
  const signal = AbortSignal.any([options.signal, deadline.signal]);
254
- let abort = () => {};
255
- const cancelled = new Promise((_resolve, reject) => {
256
- abort = () => reject(signal.reason);
257
- if (signal.aborted) abort();
258
- else signal.addEventListener("abort", abort, { once: true });
259
- });
260
254
  let result;
261
255
  try {
262
- result = await Promise.race([callLlmText(options.llm, {
256
+ result = await callLlmText(options.llm, {
263
257
  plugin: PLUGIN_NAME,
264
258
  route: callRoute,
265
259
  system: systemPrompt(options.config, options.prompt),
266
260
  text: framedInput,
267
261
  maxTokens: options.config.maxOutputTokens,
268
262
  purpose: "session-title",
263
+ configure: (controls, model) => {
264
+ const effort = model.reasoning?.efforts[0]?.id;
265
+ return {
266
+ ...controls,
267
+ ...effort === void 0 ? {} : { reasoningEffort: effort }
268
+ };
269
+ },
269
270
  signal,
270
271
  sessionId: options.sessionId,
271
272
  isCurrent: options.isCurrent
272
- }), cancelled]);
273
+ });
273
274
  signal.throwIfAborted();
274
275
  } catch (error) {
275
276
  signal.throwIfAborted();
276
277
  throw error;
277
278
  } finally {
278
279
  clearTimeout(timer);
279
- signal.removeEventListener("abort", abort);
280
280
  }
281
281
  options.signal.throwIfAborted();
282
282
  if (!options.isCurrent()) throw Object.assign(/* @__PURE__ */ new Error("dsh-current-title: generation is no longer current"), { code: "superseded" });
@@ -487,6 +487,7 @@ var CurrentTitleService = class {
487
487
  settingsPending = Promise.resolve();
488
488
  disposed = false;
489
489
  inFlight = /* @__PURE__ */ new Map();
490
+ generations = /* @__PURE__ */ new Set();
490
491
  support;
491
492
  constructor(options) {
492
493
  this.options = options;
@@ -527,6 +528,7 @@ var CurrentTitleService = class {
527
528
  this.generation += 1;
528
529
  for (const controller of this.inFlight.values()) controller.abort(/* @__PURE__ */ new Error("current-title disabled"));
529
530
  this.inFlight.clear();
531
+ await Promise.allSettled([...this.generations]);
530
532
  await this.settingsPending;
531
533
  const snapshot = this.nativeSlot?.displacement;
532
534
  const outcome = await releaseTitleSlot(this.nativeSlot ?? {
@@ -634,7 +636,14 @@ var CurrentTitleService = class {
634
636
  updatedAt: snapshot.updatedAt ?? 0
635
637
  };
636
638
  }
637
- async nativeGenerate(request) {
639
+ nativeGenerate(request) {
640
+ if (this.disposed) return Promise.reject(/* @__PURE__ */ new Error("dsh-current-title: plugin disabled"));
641
+ const pending = this.runNativeGenerate(request);
642
+ this.generations.add(pending);
643
+ pending.then(() => this.generations.delete(pending), () => this.generations.delete(pending));
644
+ return pending;
645
+ }
646
+ async runNativeGenerate(request) {
638
647
  const llm = this.options.llm;
639
648
  if (!llm) throw new Error("dsh-current-title: llm is not available");
640
649
  const generation = this.generation;
@@ -801,6 +810,7 @@ const inject = [
801
810
  "agentDefaultModel"
802
811
  ];
803
812
  async function apply(ctx, config = {}) {
813
+ createModelSettingsChanges(ctx, modelSettingsChangesIdentity(PLUGIN_NAME), PLUGIN_NAME);
804
814
  const host = ctx;
805
815
  const domain = await host.storageDomain.open(titleDomain);
806
816
  const table = domain.table("settings");