dsh-llm-fallbacks 0.3.4 → 0.4.0-alpha.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.
@@ -9,7 +9,7 @@ window.__ModuleLoader__.load({
9
9
  //#endregion
10
10
  let react = require("react");
11
11
  let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
12
- let _deepseek_ai_dsh_client_runtime_client = require("@deepseek-ai/dsh-client-runtime/client");
12
+ let _deepseek_ai_dsh_client_store = require("@deepseek-ai/dsh-client-store");
13
13
  let react_jsx_runtime = require("react/jsx-runtime");
14
14
  //#region node_modules/use-sync-external-store/cjs/use-sync-external-store-shim.production.js
15
15
  /**
@@ -486,7 +486,8 @@ window.__ModuleLoader__.load({
486
486
  presets: "bundled",
487
487
  roleAutoMatch: true,
488
488
  timeSlots: [],
489
- tz: "Asia/Shanghai"
489
+ tz: "Asia/Shanghai",
490
+ recovery: "timer"
490
491
  };
491
492
  /**
492
493
  * Reserved role id: legal as a rule target (`roles.rules[].role`) and as
@@ -537,6 +538,56 @@ window.__ModuleLoader__.load({
537
538
  return typeof entry.id === "string" && typeof entry.overridden === "boolean";
538
539
  });
539
540
  }
541
+ function parsePolicyRoute(value) {
542
+ if (!isRecord(value)) return void 0;
543
+ if (typeof value.provider !== "string" || value.provider.length === 0) return void 0;
544
+ if (typeof value.model !== "string" || value.model.length === 0) return void 0;
545
+ return {
546
+ provider: value.provider,
547
+ model: value.model
548
+ };
549
+ }
550
+ /**
551
+ * Shape-guard the additive `subagentPolicy` wire field (T5). Malformed
552
+ * payloads resolve to `undefined` (hide the status area) — the store never
553
+ * throws on an old or misshapen field (old-payload tolerance).
554
+ */
555
+ function parseSubagentPolicyWire(value) {
556
+ if (!isRecord(value) || typeof value.state !== "string") return void 0;
557
+ if (value.state === "disabled" || value.state === "unprovable") return { state: value.state };
558
+ if (value.state !== "enabled") return void 0;
559
+ if (!Array.isArray(value.allowedModels)) return void 0;
560
+ const allowedModels = [];
561
+ for (const entry of value.allowedModels) {
562
+ const route = parsePolicyRoute(entry);
563
+ if (route === void 0) return void 0;
564
+ allowedModels.push(route);
565
+ }
566
+ const view = {
567
+ state: "enabled",
568
+ allowedModels
569
+ };
570
+ if (isRecord(value.head) && (value.head.source === "authorized" || value.head.source === "injected")) {
571
+ const route = parsePolicyRoute(value.head.route);
572
+ if (route !== void 0) view.head = {
573
+ route,
574
+ source: value.head.source
575
+ };
576
+ }
577
+ if (isRecord(value.blockedAttempt) && typeof value.blockedAttempt.at === "number") {
578
+ const route = parsePolicyRoute(value.blockedAttempt.route);
579
+ if (route !== void 0) view.blockedAttempt = {
580
+ at: value.blockedAttempt.at,
581
+ route,
582
+ reason: typeof value.blockedAttempt.reason === "string" ? value.blockedAttempt.reason : ""
583
+ };
584
+ }
585
+ return view;
586
+ }
587
+ function subagentPolicyFromWire(value, fallback) {
588
+ if (!isRecord(value) || !("subagentPolicy" in value)) return fallback;
589
+ return parseSubagentPolicyWire(value.subagentPolicy);
590
+ }
540
591
  /** Seed-default persona from a revert-seed wire body (issue #59). */
541
592
  function revertOutcomePersona(value) {
542
593
  if (value === null || typeof value !== "object" || !("outcome" in value)) return void 0;
@@ -643,6 +694,8 @@ window.__ModuleLoader__.load({
643
694
  }))) throw new TypeError("fallbacks descriptor timeSlots must be an array of slot rows (kind/preset/start/end strings, chain string array, days number array)");
644
695
  const tz = value.tz;
645
696
  if (tz !== void 0 && typeof tz !== "string") throw new TypeError("fallbacks descriptor tz must be a string");
697
+ const recovery = value.recovery;
698
+ if (recovery !== void 0 && recovery !== "timer" && recovery !== "half-open") throw new TypeError("fallbacks descriptor recovery must be timer|half-open");
646
699
  return {
647
700
  enabled: enabled ?? defaultFallbacksConfig.enabled,
648
701
  triggerCodes: triggerCodes ?? [...defaultFallbacksConfig.triggerCodes],
@@ -658,7 +711,8 @@ window.__ModuleLoader__.load({
658
711
  presets: presets ?? defaultFallbacksConfig.presets,
659
712
  roleAutoMatch: roleAutoMatch ?? defaultFallbacksConfig.roleAutoMatch,
660
713
  timeSlots: timeSlots ?? [...defaultFallbacksConfig.timeSlots ?? []],
661
- tz: tz ?? defaultFallbacksConfig.tz
714
+ tz: tz ?? defaultFallbacksConfig.tz,
715
+ recovery: recovery ?? defaultFallbacksConfig.recovery
662
716
  };
663
717
  }
664
718
  /** The raw selector string a selection serializes to ('' when empty). */
@@ -698,13 +752,14 @@ window.__ModuleLoader__.load({
698
752
  }
699
753
  /**
700
754
  * Extract the most recent `fallbacks/switch` events from one history page
701
- * (spec §2.5 D-5): filter by event type, order by `seq` descending, take at
702
- * most `limit`. Single-page read — fewer than `limit` events show as-is; no
703
- * multi-page backfill (Non-Goal).
755
+ * (spec §2.5 D-5): drop packed chunk runs, filter by event type, order by
756
+ * `seq` descending, take at most `limit`. Single-page read — fewer than
757
+ * `limit` events show as-is; no multi-page backfill (Non-Goal).
704
758
  */
705
759
  function extractRecentSwitches(entries, limit = 5) {
706
760
  const switches = [];
707
761
  for (const entry of entries) {
762
+ if (entry.type !== "event") continue;
708
763
  const event = entry.event;
709
764
  if (event.type !== "fallbacks/switch") continue;
710
765
  switches.push({
@@ -874,7 +929,7 @@ window.__ModuleLoader__.load({
874
929
  api;
875
930
  rpc;
876
931
  /** Snapshot consumed by the section through `useSyncExternalStore`. */
877
- store = (0, _deepseek_ai_dsh_client_runtime_client.createSnapshotStore)({
932
+ store = (0, _deepseek_ai_dsh_client_store.createSnapshotStore)({
878
933
  status: "idle",
879
934
  error: null,
880
935
  writable: false,
@@ -890,7 +945,8 @@ window.__ModuleLoader__.load({
890
945
  catalogEpoch: 0,
891
946
  switchesStatus: "idle",
892
947
  switchesError: null,
893
- switches: []
948
+ switches: [],
949
+ subagentPolicy: void 0
894
950
  });
895
951
  /** Read guard: a newer load() supersedes an older one's publish. */
896
952
  readGeneration = 0;
@@ -906,8 +962,18 @@ window.__ModuleLoader__.load({
906
962
  namespaces = /* @__PURE__ */ new Map();
907
963
  currentSession;
908
964
  /**
909
- * @param api - Settings / Llm / Sessions wire faces (describe `writable` +
910
- * namespace directory, provider/model catalog, session history).
965
+ * The store's dispose signal (qc3 F-003): created alongside the store
966
+ * lifecycle, threaded into every `session.follow` call, aborted once in
967
+ * {@link dispose} — a follow opening still mid-await at plugin unload
968
+ * (HMR / fiber dispose, or a wedged gateway stream) is cancelled promptly
969
+ * instead of staying open until the mount abort or socket failure.
970
+ */
971
+ followAbort = new AbortController();
972
+ /**
973
+ * @param api - the Remote namespace faces the store reads (settings
974
+ * describe `writable` + namespace directory, llm provider directory,
975
+ * session model catalog + follow tail page) — `ctx.remote` cast once at
976
+ * the mount boundary.
911
977
  * @param rpc - the connection's generic RPC caller for the host gateway
912
978
  * channel (`/api`), injected from the connection handle.
913
979
  */
@@ -937,12 +1003,12 @@ window.__ModuleLoader__.load({
937
1003
  state.error = null;
938
1004
  });
939
1005
  try {
940
- const [describeResult, getResult] = await Promise.all([this.api.settings.describe({}), this.rpc.call("/api", "fallbacks/get", { args: {} }).catch(() => void 0)]);
1006
+ const [describeResult, getResult] = await Promise.all([this.api.settings.describe(), this.rpc.call("/api", "fallbacks/get", { args: {} }).catch(() => void 0)]);
941
1007
  if (generation !== this.readGeneration) return;
942
1008
  if (writeGenerationAtStart !== this.writeGeneration) return;
943
- if (!describeResult.result.ok) throw describeResult.result.error;
944
- this.namespaces = new Map(describeResult.result.value.namespaces.map((entry) => [entry.ns, entry]));
945
- const writable = describeResult.result.value.writable;
1009
+ if (!describeResult.ok) throw describeResult.error;
1010
+ this.namespaces = new Map(describeResult.value.namespaces.map((entry) => [entry.ns, entry]));
1011
+ const writable = describeResult.value.writable;
946
1012
  let config;
947
1013
  let legacyKeys = [];
948
1014
  let seeds = [];
@@ -954,7 +1020,8 @@ window.__ModuleLoader__.load({
954
1020
  }
955
1021
  if ("seeds" in getResult.value) seeds = parseSeedsWire(getResult.value.seeds);
956
1022
  }
957
- this.accept(config, writable, legacyKeys, seeds);
1023
+ const subagentPolicy = getResult !== void 0 && getResult.ok ? subagentPolicyFromWire(getResult.value, void 0) : void 0;
1024
+ this.accept(config, writable, legacyKeys, seeds, subagentPolicy);
958
1025
  } catch (error) {
959
1026
  if (generation !== this.readGeneration) return;
960
1027
  if (writeGenerationAtStart !== this.writeGeneration) return;
@@ -962,7 +1029,8 @@ window.__ModuleLoader__.load({
962
1029
  }
963
1030
  }
964
1031
  /**
965
- * Refresh the provider/model catalog (`llm.providers` + `llm.models`), an
1032
+ * Refresh the provider/model catalog (`llm/listConfigurableProviders` +
1033
+ * `session/modelCatalog`), an
966
1034
  * independent read path with its own generation guard so it can run
967
1035
  * parallel to {@link load} without clobbering it (spec §2.5 D-4).
968
1036
  * Per-provider lookup failures ride `catalogError` as a diagnostic without
@@ -977,13 +1045,13 @@ window.__ModuleLoader__.load({
977
1045
  state.catalogError = null;
978
1046
  });
979
1047
  try {
980
- const [providersResponse, modelsResponse] = await Promise.all([this.api.llm.providers({}), this.api.llm.models({})]);
1048
+ const [providersResponse, catalogResponse] = await Promise.all([this.api.llm.listConfigurableProviders(), this.api.session.modelCatalog()]);
981
1049
  if (generation !== this.catalogGeneration) return;
982
- if (!providersResponse.result.ok) throw providersResponse.result.error;
983
- if (!modelsResponse.result.ok) throw modelsResponse.result.error;
984
- const providers = providersResponse.result.value.providers;
985
- const groups = modelsResponse.result.value.groups;
986
- const failures = modelsResponse.result.value.failures;
1050
+ if (!providersResponse.ok) throw providersResponse.error;
1051
+ if (!catalogResponse.ok) throw catalogResponse.error;
1052
+ const providers = providersResponse.value;
1053
+ const groups = catalogResponse.value.groups;
1054
+ const failures = catalogResponse.value.failures;
987
1055
  this.store.update((state) => {
988
1056
  state.catalogStatus = "ready";
989
1057
  state.catalogError = failures.length > 0 ? failures.map((failure) => `${failure.name}: ${failure.message}`).join("; ") : null;
@@ -1016,7 +1084,10 @@ window.__ModuleLoader__.load({
1016
1084
  }
1017
1085
  /**
1018
1086
  * Read the recent-switch summary for the current session (spec §2.5 D-5):
1019
- * one `sessions.history` page (`maxMessages` = {@link SWITCHES_HISTORY_PAGE}),
1087
+ * one tail page (`maxMessages` = {@link SWITCHES_HISTORY_PAGE}) read from
1088
+ * the `session/follow` opening snapshot — 0.1.2 has no unary tail-page
1089
+ * read (`session/page` needs the follow opening frame's cursor), so the
1090
+ * stream is cancelled right after its first frame —
1020
1091
  * `fallbacks/switch` events extracted newest-first capped at
1021
1092
  * {@link RECENT_SWITCH_LIMIT}. No current session → honest empty ready
1022
1093
  * state (no RPC); a read failure lands `switchesStatus: 'error'` and never
@@ -1039,13 +1110,22 @@ window.__ModuleLoader__.load({
1039
1110
  state.switchesError = null;
1040
1111
  });
1041
1112
  try {
1042
- const response = await this.api.sessions.history({
1043
- sessionId,
1113
+ const iterator = this.api.session.follow({
1114
+ address: {
1115
+ kind: "session",
1116
+ sessionId
1117
+ },
1044
1118
  maxMessages: 50
1045
- });
1119
+ }, this.followAbort.signal)[Symbol.asyncIterator]();
1120
+ let first;
1121
+ try {
1122
+ first = await iterator.next();
1123
+ } finally {
1124
+ await iterator.return?.();
1125
+ }
1046
1126
  if (generation !== this.switchesGeneration) return;
1047
- if (!response.result.ok) throw response.result.error;
1048
- const switches = extractRecentSwitches(response.result.value.events);
1127
+ if (first.done || first.value.type !== "snapshot") throw new Error("session follow closed before its opening snapshot");
1128
+ const switches = extractRecentSwitches(first.value.records);
1049
1129
  this.store.update((state) => {
1050
1130
  state.switchesStatus = "ready";
1051
1131
  state.switchesError = null;
@@ -1095,7 +1175,8 @@ window.__ModuleLoader__.load({
1095
1175
  const wireSeeds = value.seeds;
1096
1176
  if (Array.isArray(wireSeeds)) seeds = parseSeedsWire(wireSeeds);
1097
1177
  }
1098
- this.accept(config, true, legacyKeys, seeds);
1178
+ const subagentPolicy = subagentPolicyFromWire(value, this.store.getSnapshot().subagentPolicy);
1179
+ this.accept(config, true, legacyKeys, seeds, subagentPolicy);
1099
1180
  } catch (error) {
1100
1181
  if (generation !== this.writeGeneration) return;
1101
1182
  this.fail(error);
@@ -1131,7 +1212,8 @@ window.__ModuleLoader__.load({
1131
1212
  const wireSeeds = value.seeds;
1132
1213
  if (Array.isArray(wireSeeds)) seeds = parseSeedsWire(wireSeeds);
1133
1214
  }
1134
- this.accept(config, true, legacyKeys, seeds);
1215
+ const subagentPolicy = subagentPolicyFromWire(value, this.store.getSnapshot().subagentPolicy);
1216
+ this.accept(config, true, legacyKeys, seeds, subagentPolicy);
1135
1217
  } catch (error) {
1136
1218
  if (generation !== this.writeGeneration) return;
1137
1219
  this.fail(error);
@@ -1179,7 +1261,8 @@ window.__ModuleLoader__.load({
1179
1261
  const wireSeeds = value.seeds;
1180
1262
  if (Array.isArray(wireSeeds)) seeds = parseSeedsWire(wireSeeds);
1181
1263
  }
1182
- this.accept(config, true, legacyKeys, seeds);
1264
+ const subagentPolicy = subagentPolicyFromWire(value, this.store.getSnapshot().subagentPolicy);
1265
+ this.accept(config, true, legacyKeys, seeds, subagentPolicy);
1183
1266
  return revertOutcomePersona(value);
1184
1267
  } catch (error) {
1185
1268
  if (generation !== this.writeGeneration) return void 0;
@@ -1194,6 +1277,7 @@ window.__ModuleLoader__.load({
1194
1277
  this.catalogGeneration += 1;
1195
1278
  this.switchesGeneration += 1;
1196
1279
  this.namespaces = /* @__PURE__ */ new Map();
1280
+ this.followAbort.abort();
1197
1281
  }
1198
1282
  /**
1199
1283
  * Publish a settled load: `status` ready, `writable` from describe, and —
@@ -1211,7 +1295,7 @@ window.__ModuleLoader__.load({
1211
1295
  * only when a real config resolved — a transient channel-down keeps the
1212
1296
  * last accepted badge state.
1213
1297
  */
1214
- accept(config, writable, legacyKeys, seeds) {
1298
+ accept(config, writable, legacyKeys, seeds, subagentPolicy) {
1215
1299
  const parsed = config === void 0 ? void 0 : parseFallbacksConfig(config);
1216
1300
  this.store.update((state) => {
1217
1301
  state.status = "ready";
@@ -1220,6 +1304,7 @@ window.__ModuleLoader__.load({
1220
1304
  state.present = parsed !== void 0;
1221
1305
  state.legacyKeys = parsed === void 0 ? state.legacyKeys : legacyKeys;
1222
1306
  state.seeds = parsed === void 0 ? state.seeds : seeds;
1307
+ state.subagentPolicy = parsed === void 0 ? state.subagentPolicy : subagentPolicy;
1223
1308
  if (parsed !== void 0) state.config = parsed;
1224
1309
  state.configuredProviders = configuredProvidersOf(state.providers, this.namespaces);
1225
1310
  });
@@ -1323,6 +1408,13 @@ window.__ModuleLoader__.load({
1323
1408
  "chains.selector.noModelsStrict": "该 provider 暂无可用模型(目录查询失败),请改选其他 provider。",
1324
1409
  "mainAgent.label": "主代理",
1325
1410
  "subagents.label": "子代理",
1411
+ "subagents.policy.label": "宿主子代理模型策略",
1412
+ "subagents.policy.allowlist": "允许的模型",
1413
+ "subagents.policy.head": "当前链头",
1414
+ "subagents.policy.source.authorized": "宿主授权",
1415
+ "subagents.policy.source.injected": "插件注入",
1416
+ "subagents.policy.blocked": "降级候选均不在允许列表中 — 未切换",
1417
+ "subagents.policy.unprovable": "策略存在但无法读取 — 插件切换已禁用",
1326
1418
  "timeSlots.label": "分时槽设置",
1327
1419
  "timeSlots.hint": "自上而下第一条命中生效;全时段行固定最后",
1328
1420
  "timeSlots.tz.label": "时区(UTC±)",
@@ -1508,6 +1600,13 @@ window.__ModuleLoader__.load({
1508
1600
  "chains.selector.noModelsStrict": "No models available for this provider (catalog lookup failed); pick another provider.",
1509
1601
  "mainAgent.label": "Main agent",
1510
1602
  "subagents.label": "Subagents",
1603
+ "subagents.policy.label": "Host subagent model policy",
1604
+ "subagents.policy.allowlist": "Allowed models",
1605
+ "subagents.policy.head": "Effective chain head",
1606
+ "subagents.policy.source.authorized": "authorized",
1607
+ "subagents.policy.source.injected": "injected",
1608
+ "subagents.policy.blocked": "No in-allowlist fallback candidate — switch skipped",
1609
+ "subagents.policy.unprovable": "Policy present but unreadable — plugin switches disabled",
1511
1610
  "timeSlots.label": "Time slots",
1512
1611
  "timeSlots.hint": "First match from top to bottom wins; the all-day row is always last",
1513
1612
  "timeSlots.tz.label": "Timezone (UTC±)",
@@ -1680,7 +1779,7 @@ window.__ModuleLoader__.load({
1680
1779
  }
1681
1780
  //#endregion
1682
1781
  //#region \0dsh-css:/home/runner/work/dsh-llm-fallbacks/dsh-llm-fallbacks/src/client/FallbacksCard.module.css.mjs
1683
- const css$2 = "\n\n\n._8827595f_card {\n list-style: none;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n background: var(--dsw-alias-bg-layer-3);\n transition: border-color .16s, background .16s;\n}\n\n._8827595f_card:hover {\n border-color: var(--dsw-alias-label-dimmed);\n}\n\n\n._06704203_cardOpen {\n background: var(--dsw-alias-bg-layer-2);\n border-color: var(--dsw-alias-label-dimmed);\n}\n\n\n._e488d460_header {\n width: 100%;\n appearance: none;\n border: 0;\n background: none;\n font: inherit;\n color: inherit;\n text-align: left;\n cursor: pointer;\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 14px 16px;\n border-radius: 12px;\n}\n\n._e488d460_header:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: -2px;\n}\n\n\n._f5dfe084_headText {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n._8d39bde6_name {\n font-size: 15px;\n font-weight: 600;\n line-height: 1.4;\n color: var(--dsw-alias-label-primary);\n}\n\n._346f3b69_description {\n font-size: 13px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n\n._631094a0_chevron {\n flex: none;\n color: var(--dsw-alias-label-tertiary);\n transition: transform .16s;\n}\n\n._44836ce8_chevronOpen {\n transform: rotate(180deg);\n}\n\n\n._dbaa7975_body {\n border-top: 1px solid var(--dsw-alias-border-l2);\n margin: 0 16px;\n padding-bottom: 8px;\n}\n\n._4e39cf17_readOnly {\n margin: 12px 0 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n\n\n._99bf7f3c_pending {\n flex: none;\n border-radius: 999px;\n padding: 1px 8px;\n font-size: 11px;\n line-height: 17px;\n font-weight: 500;\n white-space: nowrap;\n background: var(--dsw-alias-bg-module-platform);\n color: var(--dsw-alias-label-secondary);\n}\n\n\n\n\n._9860a5b1_notice {\n margin: 12px 0 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-state-business-primary);\n}\n\n\n._5006d43e_legacyNotice {\n margin: 12px 0 0;\n padding: 10px 12px;\n border-radius: 8px;\n background: var(--dsw-alias-bg-module-platform);\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-state-business-primary);\n}\n\n._21918751_error {\n margin: 12px 0 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-state-error-primary);\n}\n\n\n._7cea4b2f_noticeRow {\n display: flex;\n align-items: center;\n gap: 8px;\n margin: 12px 0 0;\n}\n\n._7cea4b2f_noticeRow ._21918751_error {\n flex: 1;\n min-width: 0;\n margin: 0;\n}\n\n\n._4058c747_form {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 12px 0 0;\n}\n\n\n._9998065c_checkboxRow {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n._02c2f6df_checkLabel {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 4px;\n font-size: 14px;\n line-height: 22px;\n font-weight: 400;\n color: var(--dsw-alias-label-primary);\n cursor: pointer;\n}\n\n._f2d47237_checkLabelTitle {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-weight: 400;\n}\n\n._c1bed8ea_checkLabelDesc {\n font-size: 12px;\n line-height: 18px;\n font-weight: 400;\n color: var(--dsw-alias-label-tertiary);\n}\n\n\n._06389b18_checkbox {\n flex: none;\n width: 16px;\n height: 16px;\n margin: 0;\n accent-color: var(--dsw-alias-brand-primary);\n cursor: pointer;\n}\n\n._06389b18_checkbox:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n\n._94c91bfd_fieldset {\n margin: 0;\n padding: 0;\n border: none;\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n\n\n._67826267_field {\n display: flex;\n flex-direction: column;\n gap: 6px;\n margin: 0;\n padding: 0;\n border: none;\n min-width: 0;\n}\n\n._13e68c3f_fieldLabel {\n \n display: inline-flex;\n align-items: center;\n gap: 10px;\n padding: 0;\n font-size: 12px;\n line-height: 18px;\n font-weight: 500;\n color: var(--dsw-alias-label-secondary);\n}\n\n\n._ba240d82_sectionHeading {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 4px 0;\n font-size: 12px;\n line-height: 18px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary);\n}\n\n._ddbc1b49_sectionHeadingText {\n flex: 1;\n min-width: 0;\n}\n\n\n._c41f9db7_sectionActions {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-left: auto;\n}\n\n\n._b116c758_sectionToggle {\n width: 100%;\n appearance: none;\n border: 0;\n background: none;\n font: inherit;\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 4px 0;\n cursor: pointer;\n font-size: 12px;\n line-height: 18px;\n font-weight: 500;\n color: var(--dsw-alias-label-secondary);\n}\n\n._b116c758_sectionToggle:hover:not(:disabled) {\n color: var(--dsw-alias-label-primary);\n}\n\n._b116c758_sectionToggle:disabled {\n cursor: default;\n}\n\n._b116c758_sectionToggle:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 2px;\n border-radius: 6px;\n}\n\n._a5313fdf_sectionToggleText {\n flex: 1;\n min-width: 0;\n text-align: left;\n}\n\n._4bc809b8_hint {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n flex-wrap: wrap;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n._579f813a_defaultNote {\n font-size: 12px;\n line-height: 18px;\n font-weight: 400;\n color: var(--dsw-alias-label-tertiary);\n}\n\n\n._b1962759_slotPresetName {\n font-size: 14px;\n line-height: 22px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n\n._a6244318_infoHint {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n width: 16px;\n height: 16px;\n border-radius: 50%;\n font-size: 14px;\n line-height: 1;\n color: var(--dsw-alias-label-tertiary);\n background: var(--dsw-alias-interactive-bg-hover);\n cursor: help;\n user-select: none;\n}\n\n._a6244318_infoHint:focus-visible {\n outline: none;\n box-shadow: 0 0 0 2px var(--dsw-alias-border-l3);\n}\n\n\n._e655c840_infoHintDisabled {\n opacity: 0.4;\n cursor: default;\n}\n\n\n._a2521bb0_optionRow {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 6px 8px;\n border-radius: 6px;\n font-size: 14px;\n line-height: 22px;\n color: var(--dsw-alias-label-primary);\n cursor: pointer;\n}\n\n._a2521bb0_optionRow:hover:has(input:not(:disabled)) {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n\n._06389b18_checkbox,\n._a2521bb0_optionRow input {\n flex: none;\n width: 16px;\n height: 16px;\n margin: 0;\n accent-color: var(--dsw-alias-brand-primary);\n cursor: pointer;\n}\n\n._06389b18_checkbox:focus-visible,\n._a2521bb0_optionRow input:focus-visible {\n outline: none;\n box-shadow: 0 0 0 2px var(--dsw-alias-border-l3);\n}\n\n._a2521bb0_optionRow input:disabled {\n cursor: default;\n}\n\n\n._c4ecbdf3_dayRow {\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n}\n\n._0330c1ed_dayCell {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 2px 6px;\n border-radius: 6px;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-secondary);\n cursor: pointer;\n}\n\n._0330c1ed_dayCell:hover:has(input:not(:disabled)) {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n._0330c1ed_dayCell input {\n flex: none;\n width: 16px;\n height: 16px;\n margin: 0;\n accent-color: var(--dsw-alias-brand-primary);\n cursor: pointer;\n}\n\n._0330c1ed_dayCell input:disabled {\n cursor: default;\n}\n\n._0330c1ed_dayCell input:focus-visible {\n outline: none;\n box-shadow: 0 0 0 2px var(--dsw-alias-border-l3);\n}\n\n\n._f9d86f7b_input {\n box-sizing: border-box;\n width: 100%;\n height: 32px;\n padding: 0 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n font: inherit;\n font-size: 14px;\n line-height: 22px;\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-primary);\n}\n\n._f9d86f7b_input:focus {\n outline: none;\n border-color: var(--dsw-alias-brand-primary);\n}\n\n._f9d86f7b_input::placeholder {\n color: var(--dsw-alias-label-dimmed);\n}\n\n._f9d86f7b_input:disabled {\n opacity: 0.6;\n cursor: default;\n}\n\n\n._3a9b42fd_inputTextarea {\n height: auto;\n min-height: 72px;\n padding: 6px 10px;\n resize: vertical;\n line-height: 20px;\n}\n\n\n._7b60337a_inputInvalid,\n._7b60337a_inputInvalid:focus {\n border-color: var(--dsw-alias-state-error-primary);\n}\n\n\nselect._f9d86f7b_input {\n max-width: 240px;\n cursor: pointer;\n}\n\n\n._16eb7153_selectInput {\n appearance: none;\n padding-right: 32px;\n \n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12' fill='none'%3E%3Cpath d='M3 4.5L6 7.5L9 4.5' stroke='%2381858C' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E\");\n background-repeat: no-repeat;\n background-position: right 12px center;\n background-size: 12px 12px;\n}\n\n\n._3e95886d_numberFields {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));\n gap: 8px;\n}\n\n._0cfb5881_list {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 4px;\n}\n\n\n._f5c620d6_editorCard {\n display: flex;\n flex-direction: column;\n gap: 14px;\n padding: 14px 16px;\n border-radius: 12px;\n background: var(--dsw-alias-bg-module-platform);\n}\n\n._224b1387_cardFoot {\n display: flex;\n justify-content: flex-end;\n}\n\n._8e220e07_ruleGrid {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n}\n\n._24a12a2f_ruleCell {\n display: flex;\n flex-direction: column;\n gap: 4px;\n min-width: 120px;\n flex: 1;\n}\n\n\n._86bc52d2_collapseRow {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n}\n\n._a73efcc6_collapseToggle {\n appearance: none;\n border: 0;\n background: none;\n padding: 0;\n cursor: pointer;\n color: var(--dsw-alias-label-secondary);\n display: flex;\n align-items: center;\n gap: 8px;\n flex: 1;\n min-width: 0;\n font: inherit;\n text-align: left;\n}\n\n._a73efcc6_collapseToggle:disabled {\n cursor: default;\n}\n\n._af017718_collapseTitle {\n \n flex: none;\n max-width: 55%;\n min-width: 0;\n font-size: 12px;\n line-height: 18px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n._2cabe603_collapseMeta {\n \n flex: none;\n margin-left: auto;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-secondary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n\n._7b04f727_slotTag {\n flex: none;\n display: inline-flex;\n align-items: center;\n border-radius: 999px;\n padding: 0 6px;\n font-size: 11px;\n line-height: 17px;\n font-weight: 500;\n white-space: nowrap;\n border: 1px solid;\n}\n\n._c381e792_slotTagHighCost {\n color: var(--dsw-alias-state-error-primary);\n border-color: var(--dsw-alias-state-error-primary);\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 10%, transparent);\n}\n\n._23bdb6fc_slotTagMultiplier {\n color: var(--dsw-alias-state-warn-primary);\n border-color: var(--dsw-alias-state-warn-primary);\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 10%, transparent);\n}\n\n._f000eed9_slotTagActive {\n color: var(--dsw-alias-state-success-primary);\n border-color: var(--dsw-alias-state-success-primary);\n background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 10%, transparent);\n}\n\n\n._cd905ce5_dragHandle {\n appearance: none;\n border: 0;\n background: none;\n padding: 0;\n cursor: grab;\n color: var(--dsw-alias-label-tertiary);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 20px;\n border-radius: 6px;\n flex: none;\n}\n\n._cd905ce5_dragHandle:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-primary);\n}\n\n._cd905ce5_dragHandle:active {\n cursor: grabbing;\n}\n\n._cd905ce5_dragHandle:disabled {\n cursor: default;\n opacity: 0.4;\n}\n\n._cd905ce5_dragHandle:focus-visible {\n outline: none;\n box-shadow: 0 0 0 2px var(--dsw-alias-border-l3);\n}\n\n._49f0c4f0_dragHandleIcon {\n transform: rotate(90deg);\n}\n\n\n._fee63b7e_slotCardDragging {\n opacity: 0.45;\n}\n\n._6f108491_slotCardOver {\n outline: 1px dashed var(--dsw-alias-brand-primary);\n outline-offset: 2px;\n}\n\n._1014c097_ruleCellLabel {\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-secondary);\n}\n\n\n._15a12620_chainSelectors {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n._240cbbf8_selectorRow {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n\n._70effa64_iconButton {\n position: relative;\n box-sizing: border-box;\n appearance: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n padding: 0;\n border: 0;\n border-radius: 6px;\n background: none;\n color: var(--dsw-alias-label-tertiary);\n cursor: pointer;\n}\n\n._70effa64_iconButton:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n._70effa64_iconButton:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-primary);\n}\n\n._70effa64_iconButton:focus-visible {\n outline: none;\n box-shadow: 0 0 0 2px var(--dsw-alias-border-l3);\n}\n\n._70effa64_iconButton::after {\n content: attr(data-tip);\n position: absolute;\n bottom: calc(100% + 6px);\n left: 50%;\n transform: translateX(-50%);\n padding: 3px 8px;\n border-radius: 6px;\n background: var(--dsw-alias-label-primary);\n color: var(--dsw-alias-bg-layer-3);\n font-size: 11px;\n line-height: 17px;\n white-space: nowrap;\n opacity: 0;\n pointer-events: none;\n transition: opacity .12s;\n}\n\n._70effa64_iconButton:hover::after,\n._70effa64_iconButton:focus-visible::after {\n opacity: 1;\n}\n\n\n._a0968257_iconButtonDanger:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover-danger);\n color: var(--dsw-alias-state-error-primary);\n}\n\n\n._28202c90_addButton {\n align-self: flex-start;\n}\n\n\n._6a2bd54c_slotAddRow {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-top: 4px;\n flex-wrap: wrap;\n}\n\n\n._4e77523e_rowActions {\n display: flex;\n align-items: center;\n gap: 4px;\n}\n\n\n._747fd56d_primaryButton,\n._7e855445_secondaryButton {\n box-sizing: border-box;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 4px;\n height: 36px;\n padding: 0 14px;\n border: none;\n border-radius: 18px;\n font: inherit;\n font-size: 14px;\n line-height: 22px;\n cursor: pointer;\n}\n\n._747fd56d_primaryButton {\n background: var(--dsw-alias-button-primary-fill);\n color: var(--dsw-alias-label-primary-foreground);\n}\n\n._747fd56d_primaryButton:hover:not(:disabled) {\n background: var(--dsw-alias-button-primary-hover);\n}\n\n._7e855445_secondaryButton {\n border: 1px solid var(--dsw-alias-border-l2);\n background: transparent;\n color: var(--dsw-alias-label-primary);\n}\n\n._7e855445_secondaryButton:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover-solid);\n}\n\n._747fd56d_primaryButton:disabled,\n._7e855445_secondaryButton:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n._747fd56d_primaryButton:focus-visible,\n._7e855445_secondaryButton:focus-visible {\n outline: none;\n box-shadow: 0 0 0 2px var(--dsw-alias-border-l3);\n}\n\n\n._238d19be_sectionAction {\n height: 28px;\n padding: 0 12px;\n border-radius: 14px;\n font-size: 12px;\n line-height: 18px;\n}\n\n\n._fbd045e0_statusBlock {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 12px 14px;\n margin-top: 12px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n}\n\n._d4f70367_statusTitle {\n font-size: 12px;\n line-height: 18px;\n font-weight: 500;\n color: var(--dsw-alias-label-secondary);\n}\n\n\n._8ab300ed_statusLine {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-secondary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n._e8cedbf5_statusLineLabel {\n font-weight: 500;\n color: var(--dsw-alias-label-secondary);\n}\n\n\n._b48dfcba_offNotice {\n margin: 0;\n padding: 12px;\n border: 1px dashed var(--dsw-alias-border-l3);\n border-radius: 8px;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n text-align: center;\n}\n\n@media (prefers-reduced-motion: reduce) {\n ._70effa64_iconButton::after {\n transition: none;\n }\n}\n";
1782
+ const css$2 = "\n\n\n._8827595f_card {\n list-style: none;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n background: var(--dsw-alias-bg-layer-3);\n transition: border-color .16s, background .16s;\n}\n\n._8827595f_card:hover {\n border-color: var(--dsw-alias-label-dimmed);\n}\n\n\n._06704203_cardOpen {\n background: var(--dsw-alias-bg-layer-2);\n border-color: var(--dsw-alias-label-dimmed);\n}\n\n\n._e488d460_header {\n width: 100%;\n appearance: none;\n border: 0;\n background: none;\n font: inherit;\n color: inherit;\n text-align: left;\n cursor: pointer;\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 14px 16px;\n border-radius: 12px;\n}\n\n._e488d460_header:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: -2px;\n}\n\n\n._f5dfe084_headText {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n._8d39bde6_name {\n font-size: 15px;\n font-weight: 600;\n line-height: 1.4;\n color: var(--dsw-alias-label-primary);\n}\n\n._346f3b69_description {\n font-size: 13px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n\n._631094a0_chevron {\n flex: none;\n color: var(--dsw-alias-label-tertiary);\n transition: transform .16s;\n}\n\n._44836ce8_chevronOpen {\n transform: rotate(180deg);\n}\n\n\n._dbaa7975_body {\n border-top: 1px solid var(--dsw-alias-border-l2);\n margin: 0 16px;\n padding-bottom: 8px;\n}\n\n._4e39cf17_readOnly {\n margin: 12px 0 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n\n\n._99bf7f3c_pending {\n flex: none;\n border-radius: 999px;\n padding: 1px 8px;\n font-size: 11px;\n line-height: 17px;\n font-weight: 500;\n white-space: nowrap;\n background: var(--dsw-alias-bg-module-platform);\n color: var(--dsw-alias-label-secondary);\n}\n\n\n\n\n._9860a5b1_notice {\n margin: 12px 0 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-state-business-primary);\n}\n\n\n._5006d43e_legacyNotice {\n margin: 12px 0 0;\n padding: 10px 12px;\n border-radius: 8px;\n background: var(--dsw-alias-bg-module-platform);\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-state-business-primary);\n}\n\n._21918751_error {\n margin: 12px 0 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-state-error-primary);\n}\n\n\n._7cea4b2f_noticeRow {\n display: flex;\n align-items: center;\n gap: 8px;\n margin: 12px 0 0;\n}\n\n._7cea4b2f_noticeRow ._21918751_error {\n flex: 1;\n min-width: 0;\n margin: 0;\n}\n\n\n._4058c747_form {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 12px 0 0;\n}\n\n\n._9998065c_checkboxRow {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n._02c2f6df_checkLabel {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 4px;\n font-size: 14px;\n line-height: 22px;\n font-weight: 400;\n color: var(--dsw-alias-label-primary);\n cursor: pointer;\n}\n\n._f2d47237_checkLabelTitle {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-weight: 400;\n}\n\n._c1bed8ea_checkLabelDesc {\n font-size: 12px;\n line-height: 18px;\n font-weight: 400;\n color: var(--dsw-alias-label-tertiary);\n}\n\n\n._06389b18_checkbox {\n flex: none;\n width: 16px;\n height: 16px;\n margin: 0;\n accent-color: var(--dsw-alias-brand-primary);\n cursor: pointer;\n}\n\n._06389b18_checkbox:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n\n._94c91bfd_fieldset {\n margin: 0;\n padding: 0;\n border: none;\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n\n\n._67826267_field {\n display: flex;\n flex-direction: column;\n gap: 6px;\n margin: 0;\n padding: 0;\n border: none;\n min-width: 0;\n}\n\n._13e68c3f_fieldLabel {\n \n display: inline-flex;\n align-items: center;\n gap: 10px;\n padding: 0;\n font-size: 12px;\n line-height: 18px;\n font-weight: 500;\n color: var(--dsw-alias-label-secondary);\n}\n\n\n._ba240d82_sectionHeading {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 4px 0;\n font-size: 12px;\n line-height: 18px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary);\n}\n\n._ddbc1b49_sectionHeadingText {\n flex: 1;\n min-width: 0;\n}\n\n\n._c41f9db7_sectionActions {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-left: auto;\n}\n\n\n._b116c758_sectionToggle {\n width: 100%;\n appearance: none;\n border: 0;\n background: none;\n font: inherit;\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 4px 0;\n cursor: pointer;\n font-size: 12px;\n line-height: 18px;\n font-weight: 500;\n color: var(--dsw-alias-label-secondary);\n}\n\n._b116c758_sectionToggle:hover:not(:disabled) {\n color: var(--dsw-alias-label-primary);\n}\n\n._b116c758_sectionToggle:disabled {\n cursor: default;\n}\n\n._b116c758_sectionToggle:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 2px;\n border-radius: 6px;\n}\n\n._a5313fdf_sectionToggleText {\n flex: 1;\n min-width: 0;\n text-align: left;\n}\n\n._4bc809b8_hint {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n flex-wrap: wrap;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n._579f813a_defaultNote {\n font-size: 12px;\n line-height: 18px;\n font-weight: 400;\n color: var(--dsw-alias-label-tertiary);\n}\n\n\n._b1962759_slotPresetName {\n font-size: 14px;\n line-height: 22px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n\n._a6244318_infoHint {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n width: 16px;\n height: 16px;\n border-radius: 50%;\n font-size: 14px;\n line-height: 1;\n color: var(--dsw-alias-label-tertiary);\n background: var(--dsw-alias-interactive-bg-hover);\n cursor: help;\n user-select: none;\n}\n\n._a6244318_infoHint:focus-visible {\n outline: none;\n box-shadow: 0 0 0 2px var(--dsw-alias-border-l3);\n}\n\n\n._e655c840_infoHintDisabled {\n opacity: 0.4;\n cursor: default;\n}\n\n\n._a2521bb0_optionRow {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 6px 8px;\n border-radius: 6px;\n font-size: 14px;\n line-height: 22px;\n color: var(--dsw-alias-label-primary);\n cursor: pointer;\n}\n\n._a2521bb0_optionRow:hover:has(input:not(:disabled)) {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n\n._06389b18_checkbox,\n._a2521bb0_optionRow input {\n flex: none;\n width: 16px;\n height: 16px;\n margin: 0;\n accent-color: var(--dsw-alias-brand-primary);\n cursor: pointer;\n}\n\n._06389b18_checkbox:focus-visible,\n._a2521bb0_optionRow input:focus-visible {\n outline: none;\n box-shadow: 0 0 0 2px var(--dsw-alias-border-l3);\n}\n\n._a2521bb0_optionRow input:disabled {\n cursor: default;\n}\n\n\n._c4ecbdf3_dayRow {\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n}\n\n._0330c1ed_dayCell {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 2px 6px;\n border-radius: 6px;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-secondary);\n cursor: pointer;\n}\n\n._0330c1ed_dayCell:hover:has(input:not(:disabled)) {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n._0330c1ed_dayCell input {\n flex: none;\n width: 16px;\n height: 16px;\n margin: 0;\n accent-color: var(--dsw-alias-brand-primary);\n cursor: pointer;\n}\n\n._0330c1ed_dayCell input:disabled {\n cursor: default;\n}\n\n._0330c1ed_dayCell input:focus-visible {\n outline: none;\n box-shadow: 0 0 0 2px var(--dsw-alias-border-l3);\n}\n\n\n._f9d86f7b_input {\n box-sizing: border-box;\n width: 100%;\n height: 32px;\n padding: 0 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n font: inherit;\n font-size: 14px;\n line-height: 22px;\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-primary);\n}\n\n._f9d86f7b_input:focus {\n outline: none;\n border-color: var(--dsw-alias-brand-primary);\n}\n\n._f9d86f7b_input::placeholder {\n color: var(--dsw-alias-label-dimmed);\n}\n\n._f9d86f7b_input:disabled {\n opacity: 0.6;\n cursor: default;\n}\n\n\n._3a9b42fd_inputTextarea {\n height: auto;\n min-height: 72px;\n padding: 6px 10px;\n resize: vertical;\n line-height: 20px;\n}\n\n\n._7b60337a_inputInvalid,\n._7b60337a_inputInvalid:focus {\n border-color: var(--dsw-alias-state-error-primary);\n}\n\n\nselect._f9d86f7b_input {\n max-width: 240px;\n cursor: pointer;\n}\n\n\n._16eb7153_selectInput {\n appearance: none;\n padding-right: 32px;\n \n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12' fill='none'%3E%3Cpath d='M3 4.5L6 7.5L9 4.5' stroke='%2381858C' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E\");\n background-repeat: no-repeat;\n background-position: right 12px center;\n background-size: 12px 12px;\n}\n\n\n._3e95886d_numberFields {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));\n gap: 8px;\n}\n\n._0cfb5881_list {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 4px;\n}\n\n\n._f5c620d6_editorCard {\n display: flex;\n flex-direction: column;\n gap: 14px;\n padding: 14px 16px;\n border-radius: 12px;\n background: var(--dsw-alias-bg-module-platform);\n}\n\n._224b1387_cardFoot {\n display: flex;\n justify-content: flex-end;\n}\n\n._8e220e07_ruleGrid {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n}\n\n._24a12a2f_ruleCell {\n display: flex;\n flex-direction: column;\n gap: 4px;\n min-width: 120px;\n flex: 1;\n}\n\n\n._86bc52d2_collapseRow {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n}\n\n._a73efcc6_collapseToggle {\n appearance: none;\n border: 0;\n background: none;\n padding: 0;\n cursor: pointer;\n color: var(--dsw-alias-label-secondary);\n display: flex;\n align-items: center;\n gap: 8px;\n flex: 1;\n min-width: 0;\n font: inherit;\n text-align: left;\n}\n\n._a73efcc6_collapseToggle:disabled {\n cursor: default;\n}\n\n._af017718_collapseTitle {\n \n flex: none;\n max-width: 55%;\n min-width: 0;\n font-size: 12px;\n line-height: 18px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n._2cabe603_collapseMeta {\n \n flex: none;\n margin-left: auto;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-secondary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n\n._7b04f727_slotTag {\n flex: none;\n display: inline-flex;\n align-items: center;\n border-radius: 999px;\n padding: 0 6px;\n font-size: 11px;\n line-height: 17px;\n font-weight: 500;\n white-space: nowrap;\n border: 1px solid;\n}\n\n._c381e792_slotTagHighCost {\n color: var(--dsw-alias-state-error-primary);\n border-color: var(--dsw-alias-state-error-primary);\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 10%, transparent);\n}\n\n._23bdb6fc_slotTagMultiplier {\n color: var(--dsw-alias-state-warn-primary);\n border-color: var(--dsw-alias-state-warn-primary);\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 10%, transparent);\n}\n\n._f000eed9_slotTagActive {\n color: var(--dsw-alias-state-success-primary);\n border-color: var(--dsw-alias-state-success-primary);\n background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 10%, transparent);\n}\n\n\n._cd905ce5_dragHandle {\n appearance: none;\n border: 0;\n background: none;\n padding: 0;\n cursor: grab;\n color: var(--dsw-alias-label-tertiary);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 20px;\n border-radius: 6px;\n flex: none;\n}\n\n._cd905ce5_dragHandle:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-primary);\n}\n\n._cd905ce5_dragHandle:active {\n cursor: grabbing;\n}\n\n._cd905ce5_dragHandle:disabled {\n cursor: default;\n opacity: 0.4;\n}\n\n._cd905ce5_dragHandle:focus-visible {\n outline: none;\n box-shadow: 0 0 0 2px var(--dsw-alias-border-l3);\n}\n\n._49f0c4f0_dragHandleIcon {\n transform: rotate(90deg);\n}\n\n\n._fee63b7e_slotCardDragging {\n opacity: 0.45;\n}\n\n._6f108491_slotCardOver {\n outline: 1px dashed var(--dsw-alias-brand-primary);\n outline-offset: 2px;\n}\n\n._1014c097_ruleCellLabel {\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-secondary);\n}\n\n\n._15a12620_chainSelectors {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n._240cbbf8_selectorRow {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n\n._70effa64_iconButton {\n position: relative;\n box-sizing: border-box;\n appearance: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n padding: 0;\n border: 0;\n border-radius: 6px;\n background: none;\n color: var(--dsw-alias-label-tertiary);\n cursor: pointer;\n}\n\n._70effa64_iconButton:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n._70effa64_iconButton:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-primary);\n}\n\n._70effa64_iconButton:focus-visible {\n outline: none;\n box-shadow: 0 0 0 2px var(--dsw-alias-border-l3);\n}\n\n._70effa64_iconButton::after {\n content: attr(data-tip);\n position: absolute;\n bottom: calc(100% + 6px);\n left: 50%;\n transform: translateX(-50%);\n padding: 3px 8px;\n border-radius: 6px;\n background: var(--dsw-alias-label-primary);\n color: var(--dsw-alias-bg-layer-3);\n font-size: 11px;\n line-height: 17px;\n white-space: nowrap;\n opacity: 0;\n pointer-events: none;\n transition: opacity .12s;\n}\n\n._70effa64_iconButton:hover::after,\n._70effa64_iconButton:focus-visible::after {\n opacity: 1;\n}\n\n\n._a0968257_iconButtonDanger:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover-danger);\n color: var(--dsw-alias-state-error-primary);\n}\n\n\n._28202c90_addButton {\n align-self: flex-start;\n}\n\n\n._6a2bd54c_slotAddRow {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-top: 4px;\n flex-wrap: wrap;\n}\n\n\n._4e77523e_rowActions {\n display: flex;\n align-items: center;\n gap: 4px;\n}\n\n\n._747fd56d_primaryButton,\n._7e855445_secondaryButton {\n box-sizing: border-box;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 4px;\n height: 36px;\n padding: 0 14px;\n border: none;\n border-radius: 18px;\n font: inherit;\n font-size: 14px;\n line-height: 22px;\n cursor: pointer;\n}\n\n._747fd56d_primaryButton {\n background: var(--dsw-alias-button-primary-fill);\n color: var(--dsw-alias-label-primary-foreground);\n}\n\n._747fd56d_primaryButton:hover:not(:disabled) {\n background: var(--dsw-alias-button-primary-hover);\n}\n\n._7e855445_secondaryButton {\n border: 1px solid var(--dsw-alias-border-l2);\n background: transparent;\n color: var(--dsw-alias-label-primary);\n}\n\n._7e855445_secondaryButton:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover-solid);\n}\n\n._747fd56d_primaryButton:disabled,\n._7e855445_secondaryButton:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n._747fd56d_primaryButton:focus-visible,\n._7e855445_secondaryButton:focus-visible {\n outline: none;\n box-shadow: 0 0 0 2px var(--dsw-alias-border-l3);\n}\n\n\n._238d19be_sectionAction {\n height: 28px;\n padding: 0 12px;\n border-radius: 14px;\n font-size: 12px;\n line-height: 18px;\n}\n\n\n._fbd045e0_statusBlock {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 12px 14px;\n margin-top: 12px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n}\n\n._d4f70367_statusTitle {\n font-size: 12px;\n line-height: 18px;\n font-weight: 500;\n color: var(--dsw-alias-label-secondary);\n}\n\n\n._8ab300ed_statusLine {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-secondary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n._e8cedbf5_statusLineLabel {\n font-weight: 500;\n color: var(--dsw-alias-label-secondary);\n}\n\n\n._b48dfcba_offNotice {\n margin: 0;\n padding: 12px;\n border: 1px dashed var(--dsw-alias-border-l3);\n border-radius: 8px;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n text-align: center;\n}\n\n\n._700006c9_policyStatus {\n margin: 12px 0 0;\n padding: 10px 12px;\n border-radius: 8px;\n background: var(--dsw-alias-bg-module-platform);\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n._d4b67a81_policyStatusLabel {\n font-size: 12px;\n font-weight: 600;\n line-height: 18px;\n color: var(--dsw-alias-label-secondary);\n}\n\n._278bb4b3_policyStatusLine {\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-primary);\n}\n\n._da6387e5_policyStatusWarn {\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-state-warn-primary);\n}\n\n@media (prefers-reduced-motion: reduce) {\n ._70effa64_iconButton::after {\n transition: none;\n }\n}\n";
1684
1783
  const tagId$2 = "dsh-llm-fallbacks/FallbacksCard.module.css";
1685
1784
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
1686
1785
  const tag = document.createElement("style");
@@ -1764,7 +1863,11 @@ window.__ModuleLoader__.load({
1764
1863
  "statusTitle": "_d4f70367_statusTitle",
1765
1864
  "statusLine": "_8ab300ed_statusLine",
1766
1865
  "statusLineLabel": "_e8cedbf5_statusLineLabel",
1767
- "offNotice": "_b48dfcba_offNotice"
1866
+ "offNotice": "_b48dfcba_offNotice",
1867
+ "policyStatus": "_700006c9_policyStatus",
1868
+ "policyStatusLabel": "_d4b67a81_policyStatusLabel",
1869
+ "policyStatusLine": "_278bb4b3_policyStatusLine",
1870
+ "policyStatusWarn": "_da6387e5_policyStatusWarn"
1768
1871
  };
1769
1872
  //#endregion
1770
1873
  //#region src/client/FallbacksCard.tsx
@@ -3378,6 +3481,38 @@ window.__ModuleLoader__.load({
3378
3481
  role: "alert",
3379
3482
  children: t("error.generic", { message: state.error })
3380
3483
  }),
3484
+ state.subagentPolicy?.state === "enabled" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3485
+ className: FallbacksCard_module_css_default.policyStatus,
3486
+ role: "status",
3487
+ children: [
3488
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3489
+ className: FallbacksCard_module_css_default.policyStatusLabel,
3490
+ children: t("subagents.policy.label")
3491
+ }),
3492
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3493
+ className: FallbacksCard_module_css_default.policyStatusLine,
3494
+ children: `${t("subagents.policy.allowlist")}: ${state.subagentPolicy.allowedModels.map((route) => `${route.provider}/${route.model}`).join(", ")}`
3495
+ }),
3496
+ state.subagentPolicy.head !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3497
+ className: FallbacksCard_module_css_default.policyStatusLine,
3498
+ children: `${t("subagents.policy.head")}: ${state.subagentPolicy.head.route.provider}/${state.subagentPolicy.head.route.model} (${t(state.subagentPolicy.head.source === "authorized" ? "subagents.policy.source.authorized" : "subagents.policy.source.injected")})`
3499
+ }),
3500
+ state.subagentPolicy.blockedAttempt !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3501
+ className: FallbacksCard_module_css_default.policyStatusWarn,
3502
+ role: "alert",
3503
+ children: t("subagents.policy.blocked")
3504
+ })
3505
+ ]
3506
+ }),
3507
+ state.subagentPolicy?.state === "unprovable" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3508
+ className: FallbacksCard_module_css_default.policyStatus,
3509
+ role: "status",
3510
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3511
+ className: FallbacksCard_module_css_default.policyStatusWarn,
3512
+ role: "alert",
3513
+ children: t("subagents.policy.unprovable")
3514
+ })
3515
+ }),
3381
3516
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3382
3517
  className: FallbacksCard_module_css_default.field,
3383
3518
  role: "group",
@@ -4343,12 +4478,12 @@ window.__ModuleLoader__.load({
4343
4478
  //#region src/client/index.ts
4344
4479
  /**
4345
4480
  * Required services (cordis fiber inject); registrations wait on the slot
4346
- * declaration. `conversationEvents` is declared because the D1 Definition
4347
- * registration reads the service directly (`ctx.conversationEvents.register`
4481
+ * declaration. `uiConversation` is declared because the D1 Definition
4482
+ * registration reads the service directly (`ctx.uiConversation.events.register`
4348
4483
  * at the bottom of `apply` — explicit fiber-ordering parity with the
4349
4484
  * ui-workflow-run precedent, whose inject list includes it for the same
4350
- * direct read). The runtime would still provide the service synchronously
4351
- * on apply, but the declaration makes the dependency honest. `sessions` is
4485
+ * direct read). Cordis would still provide the service synchronously on
4486
+ * apply, but the declaration makes the dependency honest. `sessions` is
4352
4487
  * deliberately NOT injected (S-g): a non-web host without the dsh-session
4353
4488
  * client service must not hang the fiber waiting for it — the wiring reads
4354
4489
  * it reflectively and degrades to the switches empty state when absent
@@ -4360,7 +4495,7 @@ window.__ModuleLoader__.load({
4360
4495
  "locale",
4361
4496
  "connection",
4362
4497
  "remote",
4363
- "conversationEvents"
4498
+ "uiConversation"
4364
4499
  ];
4365
4500
  /**
4366
4501
  * Register the `fallbacks` dictionaries and the plugin-config card once the
@@ -4374,7 +4509,7 @@ window.__ModuleLoader__.load({
4374
4509
  }), "llm-fallbacks: dictionaries");
4375
4510
  const connection = ctx.get("connection");
4376
4511
  const sessions = ctx.get("sessions");
4377
- const controller = new FallbacksSettingsController(connection.api, connection.rpc);
4512
+ const controller = new FallbacksSettingsController(ctx.remote, connection.rpc);
4378
4513
  const useSnapshot = bindSnapshotSelector(controller.store);
4379
4514
  ctx.effect(() => {
4380
4515
  const syncSession = () => {
@@ -4438,7 +4573,7 @@ window.__ModuleLoader__.load({
4438
4573
  })
4439
4574
  }, GeneralFallbacksRow);
4440
4575
  });
4441
- ctx.effect(() => ctx.conversationEvents.register(fallbackSwitchDefinition), "llm-fallbacks: conversation node definition");
4576
+ ctx.effect(() => ctx.uiConversation.events.register(fallbackSwitchDefinition), "llm-fallbacks: conversation node definition");
4442
4577
  ctx.slots.inject("conversation.chat.node", function* () {
4443
4578
  yield ctx.slots.register({
4444
4579
  name: "conversation.chat.node",
@@ -50,6 +50,13 @@ export declare const zh: {
50
50
  'chains.selector.noModelsStrict': string;
51
51
  'mainAgent.label': string;
52
52
  'subagents.label': string;
53
+ 'subagents.policy.label': string;
54
+ 'subagents.policy.allowlist': string;
55
+ 'subagents.policy.head': string;
56
+ 'subagents.policy.source.authorized': string;
57
+ 'subagents.policy.source.injected': string;
58
+ 'subagents.policy.blocked': string;
59
+ 'subagents.policy.unprovable': string;
53
60
  'timeSlots.label': string;
54
61
  'timeSlots.hint': string;
55
62
  'timeSlots.tz.label': string;
@@ -237,6 +244,13 @@ export declare const en: {
237
244
  'chains.selector.noModelsStrict': string;
238
245
  'mainAgent.label': string;
239
246
  'subagents.label': string;
247
+ 'subagents.policy.label': string;
248
+ 'subagents.policy.allowlist': string;
249
+ 'subagents.policy.head': string;
250
+ 'subagents.policy.source.authorized': string;
251
+ 'subagents.policy.source.injected': string;
252
+ 'subagents.policy.blocked': string;
253
+ 'subagents.policy.unprovable': string;
240
254
  'timeSlots.label': string;
241
255
  'timeSlots.hint': string;
242
256
  'timeSlots.tz.label': string;
@@ -57,6 +57,8 @@ export interface FallbacksCooldownEntry {
57
57
  readonly key: string;
58
58
  /** Expiry epoch ms; `Infinity` for `revertPolicy: 'never'`. */
59
59
  readonly untilEpochMs: number;
60
+ /** True for a half-open marker row (an expired cooldown awaiting a recovery probe). */
61
+ readonly halfOpen?: boolean;
60
62
  }
61
63
  /** The read-only diagnostic snapshot the `/fallbacks` handler renders. */
62
64
  export interface FallbacksCommandSnapshot {
@@ -192,6 +194,7 @@ export declare const FALLBACKS_COMMAND_LOCALES: {
192
194
  readonly cooldownNone: "无活跃冷却";
193
195
  readonly cooldownLine: "{key} 冷却至 {time}";
194
196
  readonly cooldownNever: "{key} 会话内不再回主";
197
+ readonly cooldownHalfOpen: "{key} half-open(等待恢复探针)";
195
198
  readonly reason: {
196
199
  readonly 'trigger-code': "触发码";
197
200
  readonly 'always-cap': "always 上限";
@@ -246,6 +249,7 @@ export declare const FALLBACKS_COMMAND_LOCALES: {
246
249
  readonly cooldownNone: "none active";
247
250
  readonly cooldownLine: "{key} suppressed until {time}";
248
251
  readonly cooldownNever: "{key} not reverting this session";
252
+ readonly cooldownHalfOpen: "{key} half-open (awaiting recovery probe)";
249
253
  readonly reason: {
250
254
  readonly 'trigger-code': "trigger-code";
251
255
  readonly 'always-cap': "always-cap";
package/dist/config.d.ts CHANGED
@@ -28,6 +28,8 @@
28
28
  * @module dsh-llm-fallbacks/config
29
29
  */
30
30
  import type { SlotRowConfig } from './time-slots.ts';
31
+ /** How an expired cooldown recovers the route (plan fallbacks-half-open-recovery Task 1). */
32
+ export type RecoveryPolicy = 'timer' | 'half-open';
31
33
  /** How a cooled-down model comes back (spec §4). */
32
34
  export type RevertPolicy = 'cooldown-expiry' | 'never';
33
35
  /** A single role rule: match on provider/model patterns (spec §3). */
@@ -125,6 +127,18 @@ export interface FallbacksConfig {
125
127
  * UTC+8). Not per-slot.
126
128
  */
127
129
  tz?: string;
130
+ /**
131
+ * Cooldown-expiry recovery mode (plan fallbacks-half-open-recovery Task
132
+ * 1): `'timer'` restores the preferred candidate when the cooldown
133
+ * expires (today's behavior); `'half-open'` leaves the route half-open
134
+ * for one logged probe instead. Optional on purpose, mirroring
135
+ * `presets` — a required field would break library consumers that
136
+ * construct `FallbacksConfig` literals with the existing keys
137
+ * (additive, non-breaking). The value domain is guarded by the schema
138
+ * (`Config` in `src/schema.ts`), NOT by `validateFallbacksConfig`, and
139
+ * every resolved config carries a value via the schema default.
140
+ */
141
+ recovery?: RecoveryPolicy;
128
142
  }
129
143
  /**
130
144
  * Spec §4 defaults — `Config({})` must equal this (no-op install).
@@ -24,6 +24,15 @@ export declare class CooldownStore {
24
24
  suppress(key: string, untilEpochMs: number): void;
25
25
  /** True while `key` is suppressed at time `now` (default `Date.now()`); expired entries are removed. */
26
26
  isSuppressed(key: string, now?: number): boolean;
27
+ /**
28
+ * Raw expiry read — no lazy drop (plan fallbacks-half-open-recovery P2
29
+ * rule 3). `undefined` for unknown keys; `Infinity` for
30
+ * `revertPolicy: 'never'` entries. The half-open read path uses this to
31
+ * observe the lapsed `until` before the decision-path drop.
32
+ */
33
+ peek(key: string): number | undefined;
34
+ /** Unfiltered map keys, in insertion order (plan P4 display sync). */
35
+ keys(): IterableIterator<string>;
27
36
  /**
28
37
  * Read-only diagnostic accessor: the active (non-expired) entries, in
29
38
  * insertion order. This is a pure read — expired entries are FILTERED OUT