dsh-autotier 0.2.3 → 0.2.6

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 (51) hide show
  1. package/AGENTS.md +50 -21
  2. package/CHANGELOG.md +43 -0
  3. package/README-es.md +95 -13
  4. package/README-hi.md +94 -14
  5. package/README-pt.md +97 -16
  6. package/README-zh.md +84 -11
  7. package/README.md +111 -18
  8. package/cordis.patch.yml +2 -2
  9. package/lib/client.js +72 -40
  10. package/lib/client.js.map +1 -1
  11. package/lib/index.js +233 -66
  12. package/lib/typert.host.js +1 -1
  13. package/lib/types/client/TierSettingsCard.d.ts +14 -12
  14. package/lib/types/client/TierSettingsCard.d.ts.map +1 -1
  15. package/lib/types/client/index.d.ts +5 -4
  16. package/lib/types/client/index.d.ts.map +1 -1
  17. package/lib/types/client/remote.d.ts +12 -12
  18. package/lib/types/config.d.ts +20 -9
  19. package/lib/types/config.d.ts.map +1 -1
  20. package/lib/types/index.d.ts +11 -8
  21. package/lib/types/index.d.ts.map +1 -1
  22. package/lib/types/judge.d.ts +17 -0
  23. package/lib/types/judge.d.ts.map +1 -1
  24. package/lib/types/preflight.d.ts +21 -1
  25. package/lib/types/preflight.d.ts.map +1 -1
  26. package/lib/types/routing.d.ts.map +1 -1
  27. package/lib/types/schema.d.ts +44 -2
  28. package/lib/types/schema.d.ts.map +1 -1
  29. package/lib/types/selection.d.ts +36 -10
  30. package/lib/types/selection.d.ts.map +1 -1
  31. package/lib/types/service.d.ts +16 -9
  32. package/lib/types/service.d.ts.map +1 -1
  33. package/lib/types/settings.d.ts +44 -0
  34. package/lib/types/settings.d.ts.map +1 -0
  35. package/lib/types/typert.host.d.ts +12 -12
  36. package/lib/types/wire.d.ts +24 -24
  37. package/lib/types/wire.d.ts.map +1 -1
  38. package/lib/{wire-BpKSOHgo.js → wire-BxsY8BiN.js} +22 -20
  39. package/package.json +58 -52
  40. package/src/client/TierSettingsCard.tsx +25 -12
  41. package/src/client/index.ts +29 -16
  42. package/src/config.ts +70 -12
  43. package/src/index.ts +23 -19
  44. package/src/judge.ts +30 -2
  45. package/src/preflight.ts +68 -1
  46. package/src/routing.ts +11 -0
  47. package/src/schema.ts +48 -9
  48. package/src/selection.ts +54 -20
  49. package/src/service.ts +20 -18
  50. package/src/settings.ts +80 -0
  51. package/src/wire.ts +19 -20
package/lib/index.js CHANGED
@@ -1,6 +1,7 @@
1
- import { a as TIER_INVOCATIONS, c as TIER_SESSION_VIEW_SCHEMA, d as TIER_STATUS_SCHEMA, f as COST_MODES, g as TIER_IDS, h as SCENARIOS, i as TIER_CATALOG_SCHEMA, l as TIER_SET_MODE_DESCRIPTOR, m as ROUTING_MODES, n as TIER_AGENT_ID_SCHEMA, o as TIER_MODE_SCHEMA, p as EFFORT_IDS, r as TIER_CATALOG_DESCRIPTOR, s as TIER_NAMESPACE, t as ROUTE_SOURCES, u as TIER_STATUS_DESCRIPTOR } from "./wire-BpKSOHgo.js";
1
+ import { a as TIER_INVOCATIONS, c as TIER_SESSION_VIEW_SCHEMA, d as TIER_STATUS_SCHEMA, f as COST_MODES, g as TIER_IDS, h as SCENARIOS, i as TIER_CATALOG_SCHEMA, l as TIER_SET_MODE_DESCRIPTOR, m as ROUTING_MODES, n as TIER_AGENT_ID_SCHEMA, o as TIER_MODE_SCHEMA, p as EFFORT_IDS, r as TIER_CATALOG_DESCRIPTOR, s as TIER_NAMESPACE, t as ROUTE_SOURCES, u as TIER_STATUS_DESCRIPTOR } from "./wire-BxsY8BiN.js";
2
+ import { isVolatile } from "@deepseek-ai/cosmokit";
2
3
  import z from "@deepseek-ai/schemastery";
3
- import { BlockAssembler, createUserMessage } from "@deepseek-ai/dsh-llm";
4
+ import { BlockAssembler, boundContextSummary, createUserMessage } from "@deepseek-ai/dsh-llm";
4
5
  import { Service } from "@deepseek-ai/cordis";
5
6
  import { TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
6
7
  import { defineTool } from "@deepseek-ai/dsh-tools";
@@ -403,15 +404,6 @@ function registerTierCommand(ctx, service, states) {
403
404
  }
404
405
  //#endregion
405
406
  //#region src/schema.ts
406
- /**
407
- * The raw (possibly partial) configuration surface of dsh-autotier: the
408
- * Schemastery schema the Loader validates and the settings UI renders, plus the
409
- * interfaces it resolves to. The judgement that turns a raw config into a
410
- * resolved one lives in `config.ts`, so this module stays free of executable
411
- * logic (a schema module must not mix function values into its declarations).
412
- *
413
- * @module dsh-autotier/schema
414
- */
415
407
  /** The default strong tier: the catalog's quality-critical model at high effort. */
416
408
  const DEFAULT_STRONG = {
417
409
  provider: "deepseek-official",
@@ -422,14 +414,14 @@ const DEFAULT_STRONG = {
422
414
  /** The default cheap tier: the catalog's routine/parallel model at low effort. */
423
415
  const DEFAULT_CHEAP = {
424
416
  provider: "deepseek-official",
425
- model: "deepseek-v4-flash",
417
+ model: "deepseek-flash",
426
418
  effort: "low",
427
419
  followSession: true
428
420
  };
429
421
  /** The default vision landing: the catalog's only image-capable model. */
430
422
  const DEFAULT_VISION = {
431
423
  provider: "deepseek-official",
432
- model: "deepseek-v4-flash-vision-exp"
424
+ model: "deepseek-flash"
433
425
  };
434
426
  /**
435
427
  * One tier schema per tier, so a partially-specified tier gets the same
@@ -457,7 +449,15 @@ const cheapTier = z.object({
457
449
  model: z.string().default("")
458
450
  })).default([])
459
451
  });
460
- /** Schemastery schema: the loader validates and fills defaults before `apply`. */
452
+ /**
453
+ * Schemastery schema: the loader validates and fills defaults before `apply`.
454
+ *
455
+ * Each top-level section is `.volatile()` so a host settings-form write reaches
456
+ * a running plugin through `loader/volatile-update`. A section that owns no
457
+ * live-switchable field (`routingMode`) stays plain: `.volatile()` is an opt-in
458
+ * that adds a form-editable surface, and inventing one for a field the plugin
459
+ * already switches per session would give the same behaviour two owners.
460
+ */
461
461
  const Config = z.object({
462
462
  tiers: z.object({
463
463
  strong: strongTier.default({
@@ -470,7 +470,7 @@ const Config = z.object({
470
470
  }),
471
471
  vision: z.object({
472
472
  provider: z.string().default("deepseek-official"),
473
- model: z.string().default("deepseek-v4-flash-vision-exp")
473
+ model: z.string().default("deepseek-flash")
474
474
  }).default({ ...DEFAULT_VISION })
475
475
  }).default({
476
476
  strong: {
@@ -482,7 +482,7 @@ const Config = z.object({
482
482
  fallback: []
483
483
  },
484
484
  vision: { ...DEFAULT_VISION }
485
- }),
485
+ }).volatile(),
486
486
  intent: z.object({
487
487
  ruleThreshold: z.number().min(1e-6).max(1).default(.7),
488
488
  attemptBand: z.object({
@@ -581,7 +581,7 @@ const Config = z.object({
581
581
  multimodal: true
582
582
  },
583
583
  costMode: "balanced"
584
- }),
584
+ }).volatile(),
585
585
  guard: z.object({
586
586
  enabled: z.boolean().default(true),
587
587
  tiers: z.array(z.union(["cheap"])).default(["cheap"]),
@@ -604,7 +604,7 @@ const Config = z.object({
604
604
  ".github/workflows"
605
605
  ],
606
606
  interopDefend: "auto"
607
- }),
607
+ }).volatile(),
608
608
  escalation: z.object({
609
609
  threshold: z.number().step(1).min(1).max(100).default(2),
610
610
  windowMs: z.number().min(1).max(864e5).default(6e4),
@@ -617,7 +617,7 @@ const Config = z.object({
617
617
  ttlMs: 18e4,
618
618
  fallbackTtlMs: 3e5,
619
619
  signature: true
620
- }),
620
+ }).volatile(),
621
621
  routingMode: z.union([...ROUTING_MODES]).default("auto")
622
622
  });
623
623
  //#endregion
@@ -630,6 +630,47 @@ const Config = z.object({
630
630
  *
631
631
  * @module dsh-autotier/config
632
632
  */
633
+ /**
634
+ * Read one config field that may arrive as a live reference or as plain data.
635
+ * The Loader hands `apply` a `VolatileConfig` (every section is a reference);
636
+ * a programmatic mount, a test, or `scripts/` hands plain data. Both faces feed
637
+ * the same resolver, so neither can drift from the other.
638
+ *
639
+ * `Volatile.get()` answers a deeply-readonly snapshot. The resolvers below read
640
+ * these values and then produce their own frozen output, so the read-only view
641
+ * is exactly the contract wanted; the cast re-widens it to the mutable field
642
+ * type so those signatures do not have to carry `readonly` everywhere.
643
+ *
644
+ * @param value - the field as handed to the plugin.
645
+ * @returns the current snapshot value.
646
+ */
647
+ function unwrap(value) {
648
+ return isVolatile(value) ? value.get() : value;
649
+ }
650
+ /**
651
+ * Read the plain-data config out of either accepted face.
652
+ *
653
+ * `exactOptionalPropertyTypes` is on, so an absent section is omitted rather
654
+ * than set to `undefined`: the resolvers below treat "key absent" and "key
655
+ * undefined" alike, but only the omitted form is assignable to `Config`.
656
+ *
657
+ * @param raw - the plugin configuration as received.
658
+ * @returns the plain-data config every resolver below reads.
659
+ */
660
+ function plain(raw) {
661
+ if (raw === void 0) return {};
662
+ const tiers = unwrap(raw.tiers);
663
+ const intent = unwrap(raw.intent);
664
+ const guard = unwrap(raw.guard);
665
+ const escalation = unwrap(raw.escalation);
666
+ return {
667
+ ...tiers === void 0 ? {} : { tiers },
668
+ ...intent === void 0 ? {} : { intent },
669
+ ...guard === void 0 ? {} : { guard },
670
+ ...escalation === void 0 ? {} : { escalation },
671
+ ...raw.routingMode === void 0 ? {} : { routingMode: raw.routingMode }
672
+ };
673
+ }
633
674
  /** Throw the standard fail-loud config error for one invalid field. */
634
675
  function invalid(field, detail) {
635
676
  throw new Error(`dsh-autotier: config.${field} ${detail}`);
@@ -815,12 +856,23 @@ function effectiveLanding(tier) {
815
856
  * Resolve raw config to the frozen runtime policy, re-judging every default,
816
857
  * bound and cross-field requirement.
817
858
  *
818
- * @param raw - raw loader config; `undefined` for a bare row.
859
+ * The cross-field judgement is the whole reason this module exists, and it is
860
+ * NOT expressed in the Schemastery schema: the host validates a form write
861
+ * against the schema alone, so a violation this function catches (a strong and
862
+ * a cheap tier landing on the same route, `hysteresis.toCheap >= toStrong`, a
863
+ * rule with neither a pattern nor a tool) would otherwise persist and only fail
864
+ * later. Every actor that can change the configuration therefore routes through
865
+ * here — the Loader at mount, and `settings.ts` on each `loader/volatile-update`
866
+ * — and refuses the change instead of storing something unroutable.
867
+ *
868
+ * @param raw - raw loader config; `undefined` for a bare row. Accepts both the
869
+ * Loader's `VolatileConfig` face and the plain-data `Config` face.
819
870
  * @returns the frozen resolved config.
820
871
  * @throws {Error} when a value is out of bounds or a cross-field requirement fails.
821
872
  */
822
873
  function resolveConfig(raw) {
823
- const tiers = raw?.tiers ?? {};
874
+ const source = plain(raw);
875
+ const tiers = source.tiers ?? {};
824
876
  const strong = resolveTier("strong", tiers.strong, DEFAULT_STRONG);
825
877
  const cheap = resolveTier("cheap", tiers.cheap, DEFAULT_CHEAP);
826
878
  const strongTriple = effectiveLanding(strong);
@@ -834,20 +886,21 @@ function resolveConfig(raw) {
834
886
  model: text("tiers.vision.model", tiers.vision?.model, DEFAULT_VISION.model)
835
887
  }
836
888
  },
837
- intent: resolveIntent(raw?.intent),
838
- guard: resolveGuard(raw?.guard),
839
- escalation: resolveEscalation(raw?.escalation),
840
- routingMode: member("routingMode", raw?.routingMode, "auto", ROUTING_MODES)
889
+ intent: resolveIntent(source.intent),
890
+ guard: resolveGuard(source.guard),
891
+ escalation: resolveEscalation(source.escalation),
892
+ routingMode: member("routingMode", source.routingMode, "auto", ROUTING_MODES)
841
893
  });
842
894
  }
843
895
  /**
844
- * Judge a configuration without keeping the resolved value. This is the
845
- * save-time hook the `autotier` settings namespace registers, so a user write
846
- * that violates a cross-field requirement is refused at the write instead of
847
- * silently disabling the plugin.
896
+ * Judge a configuration without keeping the resolved value.
848
897
  *
849
898
  * @param value - the configuration to judge.
850
899
  * @throws {Error} when the configuration is invalid.
900
+ * @deprecated The host no longer offers a `settings.register` validate hook, so
901
+ * this is no longer wired as a save-time gate. {@link resolveConfig} is the
902
+ * single judge and is called directly at mount and on every live update; this
903
+ * alias remains exported because it is part of the plugin's published surface.
851
904
  */
852
905
  function validateConfig(value) {
853
906
  resolveConfig(value);
@@ -1987,8 +2040,9 @@ async function runJudge(ctx, config, text, signal) {
1987
2040
  text: prompt
1988
2041
  }],
1989
2042
  source: {
1990
- kind: "plugin",
1991
- plugin: "dsh-autotier"
2043
+ kind: "dsh-autotier",
2044
+ form: "notice",
2045
+ summary: boundContextSummary(`autotier judge: classify intent (${route.provider}/${route.model})`)
1992
2046
  }
1993
2047
  })],
1994
2048
  temperature: config.intent.judge.temperature,
@@ -2103,6 +2157,57 @@ var RoutePreflight = class {
2103
2157
  return safe;
2104
2158
  }
2105
2159
  };
2160
+ /**
2161
+ * Mount-time default-model catalogue membership check.
2162
+ *
2163
+ * The 0.1.6 line removed the deepseek v4-flash* catalogue ids and introduced
2164
+ * `deepseek-flash` (image-capable). On that catalogue generation a default
2165
+ * tier id that is not in the host catalogue must fail the mount loudly —
2166
+ * silently degrading to a text-only passthrough is exactly the N7 regression
2167
+ * this check guards against.
2168
+ *
2169
+ * Generation gate: hosts whose catalogue predates the new vocabulary (e.g.
2170
+ * `0.1.2-rc.1`, where none of the default ids exist) keep the accepted M2
2171
+ * degradation (unlisted id ⇒ text-only) and skip the check with one
2172
+ * documented log line, so mounting on the old compat rows is preserved.
2173
+ *
2174
+ * @param ctx - the plugin context.
2175
+ * @param tiers - the resolved tier landings to verify.
2176
+ * @throws {Error} when the catalogue is the new generation and a default
2177
+ * model id is not present in it.
2178
+ */
2179
+ async function assertTierDefaultsInCatalog(ctx, tiers) {
2180
+ const provider = "deepseek-official";
2181
+ const llm = ctx.llm;
2182
+ if (llm === void 0 || typeof llm.listProviders !== "function" || typeof llm.resolveModelInfo !== "function") {
2183
+ ctx.logger.info("dsh-autotier: the llm catalogue seam is unavailable; skipping the default-model catalogue check");
2184
+ return;
2185
+ }
2186
+ if (!llm.listProviders().some((entry) => entry.id === provider)) {
2187
+ ctx.logger.info("dsh-autotier: provider \"%s\" is not registered; skipping the default-model catalogue check", provider);
2188
+ return;
2189
+ }
2190
+ const defaults = [
2191
+ ["strong", tiers.strong.model],
2192
+ ["cheap", tiers.cheap.model],
2193
+ ["vision", tiers.vision.model]
2194
+ ];
2195
+ let newGeneration = false;
2196
+ for (const [, model] of defaults) try {
2197
+ await llm.resolveModelInfo(provider, model);
2198
+ newGeneration = true;
2199
+ break;
2200
+ } catch {}
2201
+ if (!newGeneration) {
2202
+ ctx.logger.info("dsh-autotier: the host catalogue predates the deepseek-flash vocabulary; skipping the default-model catalogue check (unlisted ids degrade to text-only on this host)");
2203
+ return;
2204
+ }
2205
+ for (const [tier, model] of defaults) try {
2206
+ await llm.resolveModelInfo(provider, model);
2207
+ } catch {
2208
+ throw new Error(`dsh-autotier: the default ${tier} model "${provider}/${model}" is not in the host catalogue; refusing to mount with a silent text-only landing`);
2209
+ }
2210
+ }
2106
2211
  //#endregion
2107
2212
  //#region src/selection.ts
2108
2213
  /** Key of one selection, for comparing our own writes with external ones. */
@@ -2124,28 +2229,47 @@ var SelectionSync = class {
2124
2229
  constructor(options) {
2125
2230
  this.ctx = options.ctx;
2126
2231
  this.states = options.states;
2127
- this.ctx.on("settings/updated", (ns, next) => {
2128
- if (String(ns) !== "agent-default-model") return;
2129
- const key = selectionKey(next);
2130
- if (this.selfWrite === key) {
2131
- this.selfWrite = void 0;
2132
- return;
2133
- }
2134
- this.delegateLiveSessions(key);
2135
- });
2136
2232
  }
2137
2233
  /** The optional default-model service. */
2138
2234
  get defaultModel() {
2139
2235
  return this.ctx.get("agentDefaultModel");
2140
2236
  }
2141
2237
  /**
2142
- * Mirror one applied landing into the default-model document. Repeated
2143
- * landings with the same value are skipped, so a long cheap run writes once.
2238
+ * Read the documented selection and delegate every live session when it is
2239
+ * no longer the value this module wrote.
2240
+ *
2241
+ * Before this module's first write nothing is delegated, whatever the
2242
+ * document holds: at that point "not ours" describes the composition default
2243
+ * and any selection the user made before the plugin ever routed, and neither
2244
+ * is an override of a routing decision. Once this module owns a write, any
2245
+ * divergence is by construction somebody else's edit.
2246
+ */
2247
+ observeExternalSelection() {
2248
+ if (this.selfWrite === void 0) return;
2249
+ const service = this.defaultModel;
2250
+ if (service === void 0) return;
2251
+ let current;
2252
+ try {
2253
+ current = service.currentSelection();
2254
+ } catch (error) {
2255
+ this.ctx.logger.debug("dsh-autotier: could not read the default-model selection: %o", error);
2256
+ return;
2257
+ }
2258
+ const key = selectionKey(current);
2259
+ if (key === this.selfWrite) return;
2260
+ this.delegateLiveSessions(key);
2261
+ }
2262
+ /**
2263
+ * Mirror one applied landing into the default-model document. An external
2264
+ * change to that document is honoured first, so routing never overwrites the
2265
+ * model the user picked. Repeated landings with the same value are skipped,
2266
+ * so a long cheap run writes once.
2144
2267
  * @param route - the landing actually applied.
2145
2268
  */
2146
2269
  noteRoute(route) {
2147
2270
  const service = this.defaultModel;
2148
2271
  if (service === void 0) return;
2272
+ this.observeExternalSelection();
2149
2273
  const selection = {
2150
2274
  provider: route.provider,
2151
2275
  model: route.model,
@@ -2229,6 +2353,13 @@ var AutotierRouter = class {
2229
2353
  this.ctx.on("agent/error", (payload) => this.onAgentError(payload.agent, payload.error));
2230
2354
  this.ctx.on("agent/request-error", (payload, next) => this.onRequestError(payload.agent, payload.provider, payload.failure, next));
2231
2355
  this.ctx.on("session/event", (session, event) => this.onSessionEvent(session, event));
2356
+ this.ctx.on("agent/created", (payload) => {
2357
+ try {
2358
+ this.states.for(payload.agent);
2359
+ } catch (error) {
2360
+ this.ctx.logger.warn("dsh-autotier: could not backfill routing state for the created agent (%o)", error);
2361
+ }
2362
+ });
2232
2363
  this.ctx.effect(() => () => {
2233
2364
  this.disposed = true;
2234
2365
  this.lifetime.abort();
@@ -2626,25 +2757,33 @@ function routeOf(tier) {
2626
2757
  * plugin unloading removes the service with every listener it owns.
2627
2758
  */
2628
2759
  var AutotierService = class extends Service {
2629
- scope;
2630
2760
  posteriorTable = new PosteriorTable();
2631
2761
  resolved;
2632
2762
  compiled;
2633
2763
  /**
2634
- * Register the service as `ctx.autotier` and start following the settings
2635
- * namespace.
2764
+ * Register the service as `ctx.autotier`.
2636
2765
  * @param ctx - the owning plugin context.
2637
- * @param options - the live settings scope and the mount-time configuration.
2766
+ * @param options - the mount-time configuration.
2638
2767
  */
2639
2768
  constructor(ctx, options) {
2640
2769
  super(ctx, "autotier");
2641
- this.scope = options.scope;
2642
2770
  this.resolved = options.config;
2643
2771
  this.compiled = compileRules(options.config.intent.rules);
2644
- ctx.effect(() => this.scope.watch((next) => {
2645
- this.resolved = resolveConfig(next);
2646
- this.compiled = compileRules(this.resolved.intent.rules);
2647
- }));
2772
+ }
2773
+ /**
2774
+ * Adopt a configuration a live settings write produced. The caller has
2775
+ * already judged it through {@link resolveConfig}, so this only swaps the
2776
+ * policy and recompiles the rule table.
2777
+ *
2778
+ * The whole policy is replaced, never merged: a settings form submits the
2779
+ * complete Config, so a field the user cleared must fall back to its schema
2780
+ * default rather than keep the previous live value.
2781
+ *
2782
+ * @param config - the newly resolved configuration.
2783
+ */
2784
+ reconfigure(config) {
2785
+ this.resolved = config;
2786
+ this.compiled = compileRules(config.intent.rules);
2648
2787
  }
2649
2788
  /** The live resolved configuration. */
2650
2789
  config() {
@@ -2712,6 +2851,34 @@ var AutotierService = class extends Service {
2712
2851
  }
2713
2852
  };
2714
2853
  //#endregion
2854
+ //#region src/settings.ts
2855
+ /**
2856
+ * Claim the settings page policy and follow live configuration edits.
2857
+ *
2858
+ * The page-policy claim is best-effort: `settings` is a hard inject, but a host
2859
+ * line that composes the plugin without the settings service must still route,
2860
+ * so a missing or differently-shaped service degrades to "no page" instead of
2861
+ * failing the mount.
2862
+ *
2863
+ * @param options - the plugin context, the volatile row config and the service.
2864
+ */
2865
+ function bindSettings(options) {
2866
+ const { ctx, config, service } = options;
2867
+ const settings = ctx.get("settings");
2868
+ if (settings !== void 0 && typeof settings.configure === "function") ctx.effect(() => settings.configure({ auto: false }, ctx.fiber));
2869
+ ctx.on("loader/volatile-update", () => {
2870
+ let next;
2871
+ try {
2872
+ next = resolveConfig(config);
2873
+ } catch (error) {
2874
+ ctx.logger.warn("dsh-autotier: keeping the last good routing policy; the updated configuration was refused: %s", error instanceof Error ? error.message : String(error));
2875
+ return;
2876
+ }
2877
+ service.reconfigure(next);
2878
+ ctx.logger.info("dsh-autotier: routing policy reloaded from a live configuration update");
2879
+ });
2880
+ }
2881
+ //#endregion
2715
2882
  //#region src/state.ts
2716
2883
  /** The projection key autotier owns. */
2717
2884
  const TIER_PROJECTION_KEY = "autotier";
@@ -3006,25 +3173,25 @@ const inject = [
3006
3173
  "sessions"
3007
3174
  ];
3008
3175
  /**
3009
- * Mount the plugin: judge the configuration, register the `autotier` settings
3010
- * namespace, publish the `ctx.autotier` service, and wire the routing listeners,
3011
- * the `/tier` command and the two read-only tools.
3176
+ * Mount the plugin: judge the configuration, claim the settings page policy,
3177
+ * publish the `ctx.autotier` service, and wire the routing listeners, the
3178
+ * `/tier` command and the two read-only tools.
3012
3179
  *
3013
3180
  * @param ctx - the plugin context.
3014
- * @param config - the raw row configuration; every field is optional.
3015
- * @throws {Error} when the configuration fails the cross-field judgement.
3181
+ * @param config - the Loader's volatile row configuration. Also accepts the
3182
+ * plain-data `AutotierConfig` face, so a programmatic mount or a test can
3183
+ * pass literal values.
3184
+ * @throws {Error} when the configuration fails the cross-field judgement, or
3185
+ * when a default tier model id is absent from a new-generation host catalogue.
3016
3186
  */
3017
- function apply(ctx, config = {}) {
3187
+ async function apply(ctx, config = {}) {
3018
3188
  const resolved = resolveConfig(config);
3019
- const service = new AutotierService(ctx, {
3020
- scope: ctx.settings.register("autotier", Config, {
3021
- base: config,
3022
- applies: "live",
3023
- validate: (value) => {
3024
- validateConfig(value);
3025
- }
3026
- }),
3027
- config: resolved
3189
+ await assertTierDefaultsInCatalog(ctx, resolved.tiers);
3190
+ const service = new AutotierService(ctx, { config: resolved });
3191
+ bindSettings({
3192
+ ctx,
3193
+ config,
3194
+ service
3028
3195
  });
3029
3196
  registerTierProjection(ctx);
3030
3197
  const states = new AgentStateStore();
@@ -1,4 +1,4 @@
1
- import { a as TIER_INVOCATIONS } from "./wire-BpKSOHgo.js";
1
+ import { a as TIER_INVOCATIONS } from "./wire-BxsY8BiN.js";
2
2
  //#region src/typert.host.ts
3
3
  /**
4
4
  * The hand-written Typert HOST manifest for `dsh-autotier`, exported as
@@ -1,13 +1,15 @@
1
1
  /**
2
- * The autotier Settings card (Plugins section): the session routing-mode
3
- * selector, the read-only tier landings, the live session state, and the model
4
- * catalog dropdown fed by `tier/catalog`.
2
+ * The autotier card on the Plugins page (`plugins.item`, id `autotier`): the
3
+ * session routing-mode selector, the read-only tier landings, the live session
4
+ * state, and the model catalog dropdown fed by `tier/catalog`.
5
5
  *
6
- * The selector writes the *session* override for the session currently open in
7
- * the shell (resolved by the client entry through the sessions store face), so
8
- * it stays non-persistent exactly like the pill and `/tier`. When no session is
9
- * open the host answers with an empty session view and the selector is disabled
10
- * with an explanation instead of failing silently.
6
+ * The Plugins page renders it in two views: `summary` (the one-liner under the
7
+ * card title) and `page` (the full panel). The selector writes the *session*
8
+ * override for the session currently open in the shell (resolved by the client
9
+ * entry through the sessions store face), so it stays non-persistent exactly
10
+ * like the pill and `/tier`. When no session is open the host answers with an
11
+ * empty session view and the selector is disabled with an explanation instead
12
+ * of failing silently.
11
13
  *
12
14
  * @module dsh-autotier/client/TierSettingsCard
13
15
  */
@@ -24,8 +26,8 @@ export interface TierSettingsInjected {
24
26
  /** Pin or clear the current session's routing mode. */
25
27
  setMode: (mode: RoutingMode) => Promise<TierStatus>;
26
28
  }
27
- /** Full component props assembled by the Plugins-tab renderer. */
28
- export type TierSettingsCardProps = PropsRuntime<'settings.plugins.tab'> & PropsLocale<'settings.autotier'> & InjectFace<TierSettingsInjected>;
29
- /** The Settings card body. */
30
- export declare function TierSettingsCard({ status, catalog, setMode, t }: TierSettingsCardProps): ReactNode;
29
+ /** Full component props assembled by the Plugins-page renderer. */
30
+ export type TierSettingsCardProps = PropsRuntime<'plugins.item'> & PropsLocale<'settings.autotier'> & InjectFace<TierSettingsInjected>;
31
+ /** The Plugins-page card body. */
32
+ export declare function TierSettingsCard({ status, catalog, setMode, t, view }: TierSettingsCardProps): ReactNode;
31
33
  //# sourceMappingURL=TierSettingsCard.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"TierSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/client/TierSettingsCard.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAuB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAC3D,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AAC7F,OAAO,EAAiB,KAAK,WAAW,EAAE,MAAM,aAAa,CAAA;AAC7D,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAGzD,kFAAkF;AAClF,MAAM,WAAW,oBAAoB;IACnC,2CAA2C;IAC3C,MAAM,EAAE,MAAM,OAAO,CAAC,UAAU,CAAC,CAAA;IACjC,4CAA4C;IAC5C,OAAO,EAAE,MAAM,OAAO,CAAC,WAAW,CAAC,CAAA;IACnC,uDAAuD;IACvD,OAAO,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,OAAO,CAAC,UAAU,CAAC,CAAA;CACpD;AAED,kEAAkE;AAClE,MAAM,MAAM,qBAAqB,GAC/B,YAAY,CAAC,sBAAsB,CAAC,GAClC,WAAW,CAAC,mBAAmB,CAAC,GAChC,UAAU,CAAC,oBAAoB,CAAC,CAAA;AAOpC,8BAA8B;AAC9B,wBAAgB,gBAAgB,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,EAAE,qBAAqB,GAAG,SAAS,CAoIlG"}
1
+ {"version":3,"file":"TierSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/client/TierSettingsCard.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAuB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAC3D,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AAC7F,OAAO,EAAiB,KAAK,WAAW,EAAE,MAAM,aAAa,CAAA;AAC7D,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAGzD,kFAAkF;AAClF,MAAM,WAAW,oBAAoB;IACnC,2CAA2C;IAC3C,MAAM,EAAE,MAAM,OAAO,CAAC,UAAU,CAAC,CAAA;IACjC,4CAA4C;IAC5C,OAAO,EAAE,MAAM,OAAO,CAAC,WAAW,CAAC,CAAA;IACnC,uDAAuD;IACvD,OAAO,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,OAAO,CAAC,UAAU,CAAC,CAAA;CACpD;AAED,mEAAmE;AACnE,MAAM,MAAM,qBAAqB,GAC/B,YAAY,CAAC,cAAc,CAAC,GAC1B,WAAW,CAAC,mBAAmB,CAAC,GAChC,UAAU,CAAC,oBAAoB,CAAC,CAAA;AAOpC,kCAAkC;AAClC,wBAAgB,gBAAgB,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,qBAAqB,GAAG,SAAS,CA+IxG"}
@@ -1,10 +1,11 @@
1
1
  /**
2
2
  * `dsh-autotier`, browser half: mounts the `tier` Remote contribution, then
3
3
  * registers the composer tier pill into `conversation.input.left` and the
4
- * autotier card into the Plugins settings section (`settings.plugins.tab`, id
5
- * `autotier`). All data arrives through the `remote.tier` namespace — the pill
6
- * binds the session id the slot factory hands it, the Settings card resolves the
7
- * session currently open in the shell through the sessions store face.
4
+ * autotier card on the Plugins page (`plugins.item`, id `autotier`; two views:
5
+ * a `summary` one-liner and the `page` form). All data arrives through the
6
+ * `remote.tier` namespace — the pill binds the session id the slot factory
7
+ * hands it, the card resolves the session currently open in the shell through
8
+ * the sessions store face.
8
9
  *
9
10
  * @module dsh-autotier/client
10
11
  */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAiBnE,YAAY,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AACrE,YAAY,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAA;AACzF,YAAY,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA;AACrD,OAAO,EACL,cAAc,EACd,WAAW,EACX,QAAQ,EACR,YAAY,EACZ,OAAO,EACP,QAAQ,EACR,UAAU,EACV,QAAQ,EACR,YAAY,EACZ,OAAO,GACR,MAAM,cAAc,CAAA;AACrB,YAAY,EAAE,gBAAgB,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAe5E,iDAAiD;AACjD,eAAO,MAAM,EAAE,sBAAsB,CAAA;AAErC,kFAAkF;AAClF,eAAO,MAAM,IAAI,iBAAiB,CAAA;AAElC,uGAAuG;AACvG,eAAO,MAAM,MAAM,UAAgC,CAAA;AAEnD;;;;;GAKG;AACH,wBAAsB,KAAK,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAqD7D"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAiBnE,YAAY,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AACrE,YAAY,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAA;AACzF,YAAY,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA;AACrD,OAAO,EACL,cAAc,EACd,WAAW,EACX,QAAQ,EACR,YAAY,EACZ,OAAO,EACP,QAAQ,EACR,UAAU,EACV,QAAQ,EACR,YAAY,EACZ,OAAO,GACR,MAAM,cAAc,CAAA;AACrB,YAAY,EAAE,gBAAgB,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAe5E,iDAAiD;AACjD,eAAO,MAAM,EAAE,sBAAsB,CAAA;AAErC,kFAAkF;AAClF,eAAO,MAAM,IAAI,iBAAiB,CAAA;AAElC,uGAAuG;AACvG,eAAO,MAAM,MAAM,UAAgC,CAAA;AAEnD;;;;;GAKG;AACH,wBAAsB,KAAK,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAqD7D"}
@@ -45,15 +45,15 @@ export declare const AUTOTIER_REMOTE: Readonly<{
45
45
  source: "json";
46
46
  codec: Readonly<{
47
47
  mode: "strict";
48
- typeSymbol: "dsh-autotier/wire#TierAgentId";
49
- schema: import("zod").ZodOptional<import("zod").ZodString>;
48
+ typeSymbol: string;
49
+ create: () => import("zod").ZodOptional<import("zod").ZodString>;
50
50
  }>;
51
51
  acceptsUndefined: true;
52
52
  }>[];
53
53
  readonly result: Readonly<{
54
54
  mode: "strict";
55
55
  typeSymbol: string;
56
- schema: import("zod").ZodObject<{
56
+ create: () => import("zod").ZodObject<{
57
57
  mode: import("zod").ZodEnum<{
58
58
  strong: "strong";
59
59
  cheap: "cheap";
@@ -158,10 +158,10 @@ export declare const AUTOTIER_REMOTE: Readonly<{
158
158
  kind: "direct";
159
159
  }>;
160
160
  readonly parameters: readonly never[];
161
- readonly result: Readonly<{
161
+ readonly result: Readonly<Readonly<{
162
162
  mode: "strict";
163
- typeSymbol: "dsh-autotier/wire#TierCatalog";
164
- schema: import("zod").ZodArray<import("zod").ZodObject<{
163
+ typeSymbol: string;
164
+ create: () => import("zod").ZodArray<import("zod").ZodObject<{
165
165
  provider: import("zod").ZodString;
166
166
  models: import("zod").ZodArray<import("zod").ZodObject<{
167
167
  id: import("zod").ZodString;
@@ -169,7 +169,7 @@ export declare const AUTOTIER_REMOTE: Readonly<{
169
169
  inputModalities: import("zod").ZodArray<import("zod").ZodString>;
170
170
  }, import("zod/v4/core").$strip>>;
171
171
  }, import("zod/v4/core").$strip>>;
172
- }>;
172
+ }>>;
173
173
  readonly sourceLocation: Readonly<{
174
174
  file: "src/wire.ts";
175
175
  line: 1;
@@ -189,8 +189,8 @@ export declare const AUTOTIER_REMOTE: Readonly<{
189
189
  source: "json";
190
190
  codec: Readonly<{
191
191
  mode: "strict";
192
- typeSymbol: "dsh-autotier/wire#TierAgentId";
193
- schema: import("zod").ZodOptional<import("zod").ZodString>;
192
+ typeSymbol: string;
193
+ create: () => import("zod").ZodOptional<import("zod").ZodString>;
194
194
  }>;
195
195
  acceptsUndefined: true;
196
196
  }> | Readonly<{
@@ -199,8 +199,8 @@ export declare const AUTOTIER_REMOTE: Readonly<{
199
199
  source: "json";
200
200
  codec: Readonly<{
201
201
  mode: "strict";
202
- typeSymbol: "dsh-autotier/types#RoutingMode";
203
- schema: import("zod").ZodEnum<{
202
+ typeSymbol: string;
203
+ create: () => import("zod").ZodEnum<{
204
204
  strong: "strong";
205
205
  cheap: "cheap";
206
206
  off: "off";
@@ -212,7 +212,7 @@ export declare const AUTOTIER_REMOTE: Readonly<{
212
212
  readonly result: Readonly<{
213
213
  mode: "strict";
214
214
  typeSymbol: string;
215
- schema: import("zod").ZodObject<{
215
+ create: () => import("zod").ZodObject<{
216
216
  mode: import("zod").ZodEnum<{
217
217
  strong: "strong";
218
218
  cheap: "cheap";