dsh-llm-fallbacks 0.6.8 → 0.6.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -50,7 +50,7 @@ GLM Peak and GLM Valley are offered in the card picker only when `zai-coding-cn`
50
50
 
51
51
  A subagent's role is declared in its dispatch prompt — the Assignment header `**Execute as**: <id>` — and it is resolved against the role ids declared in `roles.list`. Only that **declared header** delivers the child's identity: the role's `persona` is installed on the child as a system-prompt section, the child's own session gets exactly one `[role: <id>]` notice row, and its session header shows a role badge.
52
52
 
53
- Role **routing** is resolved separately, in four ordered stages, first hit winning — explicit `agentPreset` → **dispatch-declared** `**Execute as**: <id>` header → rules → LLM auto-match. If the child's dispatch prompt declares `**Execute as**: <id>`, that role's persona, `[role: <id>]` notice and badge are delivered **regardless of which routing stage wins** — an earlier explicit `agentPreset` can route the child as another role while the identity carriers stay the declared header's. A child with **no declared dispatch header** gets no persona, notice row or badge, even when rules or auto-match resolve a role for routing. Making the resolved role's chain head the child's model is plugin-originated and conditional: with the host `subagent-model-selection` policy enabled, an explicit authorized route is preserved (inject skipped) and an injected head must be inside the effective allowlist — an empty intersection injects nothing ([host policy](docs/features.md#host-subagent-model-selection-policy-dsh-012)).
53
+ Role **routing** is resolved separately, in four ordered stages, first hit winning — explicit `agentPreset` → **dispatch-declared** `**Execute as**: <id>` header → rules → LLM auto-match. If the child's dispatch prompt declares `**Execute as**: <id>`, that role's persona, `[role: <id>]` notice and badge are delivered **regardless of which routing stage wins** — an earlier explicit `agentPreset` can route the child as another role while the identity carriers stay the declared header's. A child with **no declared dispatch header** gets no persona, no notice row and no role half of the badge (the model half still renders when the child has a recorded route), even when rules or auto-match resolve a role for routing. Making the resolved role's chain head the child's model is plugin-originated and conditional: with the host `subagent-model-selection` policy enabled, an explicit authorized route is preserved (inject skipped) and an injected head must be inside the effective allowlist — an empty intersection injects nothing ([host policy](docs/features.md#host-subagent-model-selection-policy-dsh-012)).
54
54
 
55
55
  This covers an **ordinary subagent dispatch and an Agent Team teammate** (`spawn_teammate`) alike: both are children dispatched with the same header, which the model writes — the same dispatch seam, no mechanism-specific configuration.
56
56
 
@@ -1,48 +1,53 @@
1
1
  /**
2
- * Session-header subagent role badge (plan subagent-role-badge T3; durable
3
- * channel plan role-based-subagent-adoption Task 3b): a compact read-only pill
4
- * in `conversation.session.header.actions` showing which Subagent role the
5
- * viewed session's dispatch resolved, hovering as `role → latest request route`.
2
+ * Session-header subagent role/model badge (plan subagent-role-badge T3 +
3
+ * plan subagent-role-model): a compact read-only pill in
4
+ * `conversation.session.header.actions` showing the viewed CHILD session's
5
+ * dispatch role and the model route it runs on, hovering as
6
+ * `role → provider/model (request route)`.
6
7
  *
7
- * Contract notes (verified 2026-09-09 against the harness checkout):
8
+ * Contract notes (re-verified 2026-10-23 against the installed dsh 0.2.1-rc.2):
8
9
  * - The slot is `kind: 'list'`, `scope: 'session'`, owner props an empty marker
9
10
  * (`ConversationHeaderActionOwnerProps`, `children?: never`) — the header
10
11
  * renders occupants with `renderSlot(..., {})`, so ALL data flows through the
11
12
  * session standard kit.
12
- * - Every `scope: 'session'` occupant receives the session standard kit
13
- * (`useSession` + `useProjection`; ui-session `src/client/index.ts:112-119`,
14
- * merged into the runtime share by ui-slots' `PropsRuntime`). The compiled
15
- * peer types do not carry that merge into this plugin's typecheck program
16
- * (ui-session is deliberately not a plugin peer), so both seats are read
17
- * STRUCTURALLY and guarded — a version-skewed host without them renders
18
- * nothing, never throws.
19
- * - Data: the host's `fallbacksSubagentRole` projection (Task 3b), folded
13
+ * - Every `scope: 'session'` occupant receives the session standard kit, whose
14
+ * members this component uses are `sessionId`, `useSessions` (the shared
15
+ * Session store) and `useProjection` (the viewed session's projection cut).
16
+ * The compiled peer types do not carry that merge into this plugin's
17
+ * typecheck program (ui-session is deliberately not a plugin peer), so the
18
+ * seats are read STRUCTURALLY and guarded — a version-skewed host without them
19
+ * renders nothing, never throws.
20
+ * - The badge belongs to a CHILD session: `byId[sessionId].origin === 'subagent'`
21
+ * is the host's own child-session idiom (`dsh-client-ui-subagent`
22
+ * `SubagentCatalogAction`). Root rows omit `origin`, so a root session renders
23
+ * nothing even though its own `modelSelection` carries a route — the composer
24
+ * owns that control.
25
+ * - Role data: the host's `fallbacksSubagentRole` projection (Task 3b), folded
20
26
  * server-side out of the child's own durable log — the ONE role source. The
21
- * session kit binds the hook to the VIEWED session (`binding.session.
22
- * projections.faceOf(key)`), so no `sessionId` plumbing, no effect, no state,
23
- * and no polling: a settled child's follow opening snapshot already carries
24
- * the value, and a running child's change frame updates the pill. An absent
25
- * key (host without the unit, or no notice row) reads as "no pill".
26
- * - Hook order is render-independent (Task 3b L2 review C-1): the seat is a real
27
- * hook, so BOTH projection reads are unconditional and only their derived
28
- * values are guarded — a conditional second read would change the hook count
29
- * on the very re-render that publishes a running child's role.
30
- * - The hover route comes from the host's EXISTING `modelSelection` projection
31
- * (`lastUsed` — the route of the LATEST recorded request), NOT from the
32
- * dispatch-time route: it is a separate, richer fact, and the label says so.
27
+ * session kit binds the hook to the VIEWED session, so no `sessionId`
28
+ * plumbing, no effect, no state, and no polling. An absent key (host without
29
+ * the unit, or no notice row) omits the role half; `inherit` omits it too.
30
+ * - Route data: the host's EXISTING `modelSelection` projection, read as
31
+ * `next ?? lastUsed` — the route the child's next request will use, falling
32
+ * back to the one its latest recorded request ran on. The two halves are
33
+ * INDEPENDENT: either alone still renders, and both missing renders nothing.
34
+ * - Hook order is render-independent (Task 3b L2 review C-1): the seats are real
35
+ * hooks, so the three reads are unconditional and only their derived values
36
+ * are guarded — a conditional read would change the hook count on the very
37
+ * re-render that publishes a running child's role.
33
38
  * - Render-only discipline (C4 pattern, same as `ConversationFallbackSwitch`):
34
39
  * the badge contributes a view; no message construction, no model-context
35
- * injection. Degrade-never-crash: a missing/foreign projection value or an
36
- * `inherit` role renders `null` — the actions row collapses cleanly.
40
+ * injection. Degrade-never-crash: a missing/foreign projection value renders
41
+ * `null` — the actions row collapses cleanly.
37
42
  */
38
43
  import type { ReactNode } from 'react';
39
44
  import type { PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
40
45
  /** Props delivered by the actions slot outlet: runtime share + locale seat. */
41
46
  export type SubagentRoleBadgeProps = PropsRuntime<'conversation.session.header.actions'> & PropsLocale<'fallbacks'>;
42
47
  /**
43
- * Render the viewed session's dispatch-resolved role badge.
44
- * @param props - composed slot props (the session-kit `useProjection` seat is
45
- * read structurally — see the module docblock).
48
+ * Render the viewed SUBAGENT session's role and model.
49
+ * @param props - composed slot props (the session-kit `useProjection` /
50
+ * `useSessions` seats are read structurally — see the module docblock).
46
51
  * @returns the badge element, or `null` when there is nothing to show.
47
52
  */
48
53
  export declare function SubagentRoleBadge(props: SubagentRoleBadgeProps): ReactNode;
@@ -501,6 +501,19 @@ export declare class FallbacksSettingsController {
501
501
  * @param next - the complete edited configuration.
502
502
  */
503
503
  save(next: FallbacksConfig): Promise<void>;
504
+ /**
505
+ * Explicit "sync to host allowlist" write (plan
506
+ * role-chain-allowlist-visibility T2): the gateway endpoint merges the
507
+ * CURRENT allowlist with this plugin's exact chain routes plus the virtual
508
+ * `FallbacksChain/Auto` route and writes only `allowedModels`. The button
509
+ * click is the ONLY trigger — the store never syncs on its own.
510
+ * @returns true when the write landed (the card then reads the fresh
511
+ * snapshot from the store); false on a refused/failed call, leaving the
512
+ * last accepted state untouched. A response discarded by the write
513
+ * generation guard is still a landed write — dispose() is the only
514
+ * invalidator, so it must not report failure.
515
+ */
516
+ syncAllowlist(): Promise<boolean>;
504
517
  /**
505
518
  * Reset to composition defaults through the gateway channel
506
519
  * (`/api/fallbacks/reset` — the fallbacks-specific third method; the host
@@ -510,6 +523,13 @@ export declare class FallbacksSettingsController {
510
523
  resetToDefaults(): Promise<void>;
511
524
  /** Stop in-flight responses from publishing after plugin disposal. */
512
525
  dispose(): void;
526
+ /**
527
+ * Unpack one write response (set / reset / sync-allowlist) into the store
528
+ * snapshot. The additive fields (legacyKeys §9, seeds §9.4, subagentPolicy
529
+ * D4) follow the W-1/F-1 rule: absent on the wire keeps the last accepted
530
+ * value — only a `get` may settle server truth.
531
+ */
532
+ private acceptWire;
513
533
  /**
514
534
  * Publish a settled load: `status` ready, `writable` from describe, and —
515
535
  * only when the gateway returned a REAL config — `present` true and
@@ -518,6 +518,15 @@ window.__ModuleLoader__.load({
518
518
  /** Role id format (aligned with yet-another-subagent `isValidProfileId`). */
519
519
  const ROLE_ID_PATTERN = /^[a-z0-9-]{1,32}$/;
520
520
  //#endregion
521
+ //#region src/route-allowlist.ts
522
+ /**
523
+ * Whether the route is on the allowlist under exact route identity: both the
524
+ * provider and the model strings must equal an allowed route exactly.
525
+ */
526
+ function routeOnAllowlist(route, allowedModels) {
527
+ return allowedModels.some((allowed) => allowed.provider === route.provider && allowed.model === route.model);
528
+ }
529
+ //#endregion
521
530
  //#region src/client/fallbacks-store.ts
522
531
  /** The plugin's settings namespace on the host wire (settings/document-updated ns filter). */
523
532
  const FALLBACKS_SETTINGS_NS = "fallbacks";
@@ -1187,26 +1196,37 @@ window.__ModuleLoader__.load({
1187
1196
  const result = await this.rpc.call("/api", "fallbacks/set", { args: { patch: next } });
1188
1197
  if (generation !== this.writeGeneration) return;
1189
1198
  if (!result.ok) throw result.error;
1190
- const value = result.value;
1191
- const config = value !== null && typeof value === "object" && "config" in value ? value.config : void 0;
1192
- let legacyKeys = this.store.getSnapshot().legacyKeys;
1193
- if (value !== null && typeof value === "object" && "legacyKeys" in value) {
1194
- const wireLegacyKeys = value.legacyKeys;
1195
- if (Array.isArray(wireLegacyKeys)) legacyKeys = wireLegacyKeys.filter((key) => typeof key === "string");
1196
- }
1197
- let seeds = this.store.getSnapshot().seeds;
1198
- if (value !== null && typeof value === "object" && "seeds" in value) {
1199
- const wireSeeds = value.seeds;
1200
- if (Array.isArray(wireSeeds)) seeds = parseSeedsWire(wireSeeds);
1201
- }
1202
- const subagentPolicy = subagentPolicyFromWire(value, this.store.getSnapshot().subagentPolicy);
1203
- this.accept(config, true, legacyKeys, seeds, subagentPolicy);
1199
+ this.acceptWire(result.value);
1204
1200
  } catch (error) {
1205
1201
  if (generation !== this.writeGeneration) return;
1206
1202
  this.fail(error);
1207
1203
  }
1208
1204
  }
1209
1205
  /**
1206
+ * Explicit "sync to host allowlist" write (plan
1207
+ * role-chain-allowlist-visibility T2): the gateway endpoint merges the
1208
+ * CURRENT allowlist with this plugin's exact chain routes plus the virtual
1209
+ * `FallbacksChain/Auto` route and writes only `allowedModels`. The button
1210
+ * click is the ONLY trigger — the store never syncs on its own.
1211
+ * @returns true when the write landed (the card then reads the fresh
1212
+ * snapshot from the store); false on a refused/failed call, leaving the
1213
+ * last accepted state untouched. A response discarded by the write
1214
+ * generation guard is still a landed write — dispose() is the only
1215
+ * invalidator, so it must not report failure.
1216
+ */
1217
+ async syncAllowlist() {
1218
+ if (!this.store.getSnapshot().writable) return false;
1219
+ const generation = ++this.writeGeneration;
1220
+ try {
1221
+ const result = await this.rpc.call("/api", "fallbacks/sync-allowlist", { args: {} });
1222
+ if (!result.ok) return false;
1223
+ if (generation === this.writeGeneration) this.acceptWire(result.value);
1224
+ return true;
1225
+ } catch {
1226
+ return false;
1227
+ }
1228
+ }
1229
+ /**
1210
1230
  * Reset to composition defaults through the gateway channel
1211
1231
  * (`/api/fallbacks/reset` — the fallbacks-specific third method; the host
1212
1232
  * clears the user layer via `settings.replace(ns, {})`, the removal path a
@@ -1224,20 +1244,7 @@ window.__ModuleLoader__.load({
1224
1244
  const result = await this.rpc.call("/api", "fallbacks/reset", { args: {} });
1225
1245
  if (generation !== this.writeGeneration) return;
1226
1246
  if (!result.ok) throw result.error;
1227
- const value = result.value;
1228
- const config = value !== null && typeof value === "object" && "config" in value ? value.config : void 0;
1229
- let legacyKeys = this.store.getSnapshot().legacyKeys;
1230
- if (value !== null && typeof value === "object" && "legacyKeys" in value) {
1231
- const wireLegacyKeys = value.legacyKeys;
1232
- if (Array.isArray(wireLegacyKeys)) legacyKeys = wireLegacyKeys.filter((key) => typeof key === "string");
1233
- }
1234
- let seeds = this.store.getSnapshot().seeds;
1235
- if (value !== null && typeof value === "object" && "seeds" in value) {
1236
- const wireSeeds = value.seeds;
1237
- if (Array.isArray(wireSeeds)) seeds = parseSeedsWire(wireSeeds);
1238
- }
1239
- const subagentPolicy = subagentPolicyFromWire(value, this.store.getSnapshot().subagentPolicy);
1240
- this.accept(config, true, legacyKeys, seeds, subagentPolicy);
1247
+ this.acceptWire(result.value);
1241
1248
  } catch (error) {
1242
1249
  if (generation !== this.writeGeneration) return;
1243
1250
  this.fail(error);
@@ -1253,6 +1260,27 @@ window.__ModuleLoader__.load({
1253
1260
  this.followAbort.abort();
1254
1261
  }
1255
1262
  /**
1263
+ * Unpack one write response (set / reset / sync-allowlist) into the store
1264
+ * snapshot. The additive fields (legacyKeys §9, seeds §9.4, subagentPolicy
1265
+ * D4) follow the W-1/F-1 rule: absent on the wire keeps the last accepted
1266
+ * value — only a `get` may settle server truth.
1267
+ */
1268
+ acceptWire(value) {
1269
+ const config = value !== null && typeof value === "object" && "config" in value ? value.config : void 0;
1270
+ let legacyKeys = this.store.getSnapshot().legacyKeys;
1271
+ if (value !== null && typeof value === "object" && "legacyKeys" in value) {
1272
+ const wireLegacyKeys = value.legacyKeys;
1273
+ if (Array.isArray(wireLegacyKeys)) legacyKeys = wireLegacyKeys.filter((key) => typeof key === "string");
1274
+ }
1275
+ let seeds = this.store.getSnapshot().seeds;
1276
+ if (value !== null && typeof value === "object" && "seeds" in value) {
1277
+ const wireSeeds = value.seeds;
1278
+ if (Array.isArray(wireSeeds)) seeds = parseSeedsWire(wireSeeds);
1279
+ }
1280
+ const subagentPolicy = subagentPolicyFromWire(value, this.store.getSnapshot().subagentPolicy);
1281
+ this.accept(config, true, legacyKeys, seeds, subagentPolicy);
1282
+ }
1283
+ /**
1256
1284
  * Publish a settled load: `status` ready, `writable` from describe, and —
1257
1285
  * only when the gateway returned a REAL config — `present` true and
1258
1286
  * `state.config` replaced with the parsed value. A get that did not
@@ -1368,6 +1396,7 @@ window.__ModuleLoader__.load({
1368
1396
  "chains.selector.remove": "删除第 {index} 个选择器",
1369
1397
  "chains.selector.providerAria": "第 {index} 个选择器的 provider",
1370
1398
  "chains.selector.modelAria": "第 {index} 个选择器的 model",
1399
+ "chains.selector.reorder": "拖拽排序第 {index} 个选择器,或聚焦后按 ↑/↓ 调整",
1371
1400
  "chains.selector.providerPlaceholder": "选择 provider",
1372
1401
  "chains.selector.modelPlaceholder": "选择 model",
1373
1402
  "chains.selector.wildcardLegacy": "该条目为通配(provider/*):选择具体模型后将转为精确条目",
@@ -1381,6 +1410,12 @@ window.__ModuleLoader__.load({
1381
1410
  "subagents.policy.source.injected": "插件注入",
1382
1411
  "subagents.policy.blocked": "降级候选均不在允许列表中 — 未切换",
1383
1412
  "subagents.policy.unprovable": "策略存在但无法读取 — 插件切换已禁用",
1413
+ "subagents.policy.offAllowlist": "角色链中有 {count} 个精确条目不在允许列表 — 这些条目不会生效",
1414
+ "subagents.policy.recommend": "建议将 FallbacksChain/Auto 加入允许列表(该虚拟路由不受宿主策略过滤)",
1415
+ "subagents.policy.sync": "同步到宿主允许列表",
1416
+ "subagents.policy.sync.done": "已同步:精确条目(角色链、默认降级链与 FallbacksChain/Auto)已写入允许列表;通配条目不展开、不同步",
1417
+ "subagents.policy.sync.failed": "同步失败:宿主设置未写入(策略未启用、条目不可配置或版本冲突等宿主拒绝),请检查宿主设置",
1418
+ "subagents.policy.sync.dirty": "角色链或默认降级链有未保存的修改 — 请先保存再同步(同步只写入已保存的配置)",
1384
1419
  "timeSlots.label": "分时槽设置",
1385
1420
  "timeSlots.hint": "自上而下第一条命中生效;全时段行固定最后",
1386
1421
  "timeSlots.tz.label": "时区(UTC±)",
@@ -1509,7 +1544,7 @@ window.__ModuleLoader__.load({
1509
1544
  "chat.switch.summary": "{from} → {to}({reason})",
1510
1545
  "chat.switch.summary.roleInject": "({reason})",
1511
1546
  "chat.switch.roleMap": "{role} → {model}",
1512
- "subagentRole.hover": "{role} → {model}(最近一次请求的路由)",
1547
+ "subagentRole.hover": "{role} → {model}(请求路由)",
1513
1548
  "defaults.prefix": "默认值",
1514
1549
  "save": "保存",
1515
1550
  "save.saving": "保存中…",
@@ -1557,6 +1592,7 @@ window.__ModuleLoader__.load({
1557
1592
  "chains.selector.remove": "Remove selector {index}",
1558
1593
  "chains.selector.providerAria": "Selector {index} provider",
1559
1594
  "chains.selector.modelAria": "Selector {index} model",
1595
+ "chains.selector.reorder": "Drag to reorder selector {index}, or focus and press ↑/↓",
1560
1596
  "chains.selector.providerPlaceholder": "Select provider",
1561
1597
  "chains.selector.modelPlaceholder": "Select model",
1562
1598
  "chains.selector.wildcardLegacy": "This entry is a wildcard (provider/*): picking a model converts it to an exact entry",
@@ -1570,6 +1606,12 @@ window.__ModuleLoader__.load({
1570
1606
  "subagents.policy.source.injected": "injected",
1571
1607
  "subagents.policy.blocked": "No in-allowlist fallback candidate — switch skipped",
1572
1608
  "subagents.policy.unprovable": "Policy present but unreadable — plugin switches disabled",
1609
+ "subagents.policy.offAllowlist": "{count} exact role-chain entries are outside the allowlist — they will not run",
1610
+ "subagents.policy.recommend": "Add FallbacksChain/Auto to the allowlist (this virtual route is not filtered by the host policy)",
1611
+ "subagents.policy.sync": "Sync to host allowlist",
1612
+ "subagents.policy.sync.done": "Synced: exact entries (role chains, default fallback chain and FallbacksChain/Auto) are now allowed; wildcard entries are not expanded or synced",
1613
+ "subagents.policy.sync.failed": "Sync failed: the host settings were not written (policy off, entry not configurable, revision conflict or another host refusal) — check the host settings",
1614
+ "subagents.policy.sync.dirty": "Unsaved role-chain or default-chain edits — save first, then sync (the sync writes the saved configuration only)",
1573
1615
  "timeSlots.label": "Time slots",
1574
1616
  "timeSlots.hint": "First match from top to bottom wins; the all-day row is always last",
1575
1617
  "timeSlots.tz.label": "Timezone (UTC±)",
@@ -1698,7 +1740,7 @@ window.__ModuleLoader__.load({
1698
1740
  "chat.switch.summary": "{from} → {to} ({reason})",
1699
1741
  "chat.switch.summary.roleInject": "({reason})",
1700
1742
  "chat.switch.roleMap": "{role} → {model}",
1701
- "subagentRole.hover": "{role} → {model} (latest request route)",
1743
+ "subagentRole.hover": "{role} → {model} (request route)",
1702
1744
  "defaults.prefix": "Default",
1703
1745
  "save": "Save",
1704
1746
  "save.saving": "Saving…",
@@ -1746,7 +1788,7 @@ window.__ModuleLoader__.load({
1746
1788
  }
1747
1789
  //#endregion
1748
1790
  //#region \0dsh-css:/home/runner/work/dsh-llm-fallbacks/dsh-llm-fallbacks/src/client/FallbacksCard.module.css.mjs
1749
- const css$3 = "\n\n\n._20fd0e45_root {\n min-width: 0;\n}\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._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._94c91bfd_fieldset {\n margin: 0;\n padding: 0;\n border: none;\n display: flex;\n flex-direction: column;\n min-width: 0;\n}\n\n\n._ba240d82_sectionHeading {\n display: flex;\n align-items: center;\n gap: 8px;\n margin: 0;\n padding: 4px 0;\n font-size: 16px;\n line-height: 24px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary);\n}\n\n._ddbc1b49_sectionHeadingText {\n display: block;\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._c41f9db7_sectionActions ._f0864181_discard,\n._c41f9db7_sectionActions ._ccff7e48_save {\n padding: 3px 12px;\n font-size: 12px;\n line-height: 18px;\n}\n\n\n._b116c758_sectionToggle {\n appearance: none;\n border: 0;\n background: none;\n display: flex;\n align-items: center;\n gap: 4px;\n margin: 0;\n padding: 0;\n font: inherit;\n color: inherit;\n cursor: pointer;\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: 1px;\n}\n\n\n._80101203_advancedBody {\n display: flex;\n flex-direction: column;\n min-width: 0;\n}\n\n._94c91bfd_fieldset > ._80101203_advancedBody > ._67826267_field,\n._94c91bfd_fieldset > ._80101203_advancedBody > ._9998065c_checkboxRow,\n._94c91bfd_fieldset > ._80101203_advancedBody > ._3e95886d_numberFields {\n padding: 12px 0;\n}\n\n._94c91bfd_fieldset > ._80101203_advancedBody > ._67826267_field + ._67826267_field,\n._94c91bfd_fieldset > ._80101203_advancedBody > ._9998065c_checkboxRow + ._67826267_field,\n._94c91bfd_fieldset > ._80101203_advancedBody > ._67826267_field + ._3e95886d_numberFields {\n border-top: 0.5px solid var(--dsw-alias-border-l2);\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\n._94c91bfd_fieldset > ._67826267_field,\n._94c91bfd_fieldset > ._9998065c_checkboxRow,\n._94c91bfd_fieldset > ._3e95886d_numberFields {\n padding: 12px 0;\n}\n\n._94c91bfd_fieldset > ._67826267_field + ._67826267_field,\n._94c91bfd_fieldset > ._67826267_field + ._3e95886d_numberFields,\n._94c91bfd_fieldset > ._9998065c_checkboxRow + ._67826267_field {\n border-top: 0.5px solid var(--dsw-alias-border-l2);\n}\n\n\n._9998065c_checkboxRow {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n}\n\n\n._9998065c_checkboxRow ._06389b18_checkbox {\n margin-top: 3px;\n}\n\n._13e68c3f_fieldLabel {\n \n display: inline-flex;\n align-items: center;\n gap: 10px;\n padding: 0;\n font-size: 13px;\n line-height: 1.5;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\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\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._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 position: relative;\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._a6244318_infoHint::after {\n content: attr(data-tip);\n position: absolute;\n top: 50%;\n left: calc(100% + 8px);\n transform: translateY(-50%);\n z-index: 10;\n box-sizing: border-box;\n width: max-content;\n max-width: 280px;\n padding: 5px 10px;\n border-radius: 8px;\n background: var(--dsw-alias-label-primary);\n color: var(--dsw-alias-bg-layer-3);\n font-size: 11px;\n line-height: 17px;\n font-weight: 400;\n white-space: normal;\n text-align: left;\n pointer-events: none;\n opacity: 0;\n transition: opacity .12s;\n}\n\n._a6244318_infoHint:hover::after,\n._a6244318_infoHint:focus-visible::after {\n opacity: 1;\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: 34px;\n padding: 0 12px;\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: var(--dsw-radius-md);\n font: inherit;\n font-size: 13px;\n line-height: 1.5;\n background: var(--dsw-alias-bg-layer-3);\n color: var(--dsw-alias-label-primary);\n}\n\n._f9d86f7b_input:focus-visible {\n outline: none;\n border-color: var(--dsw-alias-state-business-primary);\n}\n\n._f9d86f7b_input::placeholder {\n color: var(--dsw-alias-label-dimmed);\n}\n\n._f9d86f7b_input:disabled {\n color: var(--dsw-alias-label-tertiary);\n cursor: default;\n}\n\n\n._3a9b42fd_inputTextarea {\n height: auto;\n min-height: 72px;\n padding: 8px 12px;\n resize: vertical;\n}\n\n\n._1285c19b_personaBriefRow {\n display: flex;\n align-items: center;\n gap: 4px;\n min-width: 0;\n}\n\n._4b8f7345_personaBrief {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 14px;\n line-height: 22px;\n color: var(--dsw-alias-label-primary);\n}\n\n\n._2686400e_personaBriefEmpty {\n color: var(--dsw-alias-label-tertiary);\n}\n\n\n._4e4a07be_personaFull {\n margin: 0;\n font-size: 14px;\n line-height: 22px;\n color: var(--dsw-alias-label-secondary);\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n}\n\n\n._7b60337a_inputInvalid,\n._7b60337a_inputInvalid:focus-visible {\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: 12px;\n}\n\n._3e95886d_numberFields ._67826267_field {\n padding: 0;\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._1014c097_ruleCellLabel {\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-secondary);\n}\n\n\n._631094a0_chevron {\n flex: none;\n color: var(--dsw-alias-label-tertiary);\n transform: rotate(-90deg);\n transition: transform .16s;\n}\n\n._44836ce8_chevronOpen {\n transform: none;\n}\n\n\n._cf3961bb_chevronFlip {\n transform: rotate(180deg);\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._52d6df87_seedBadge {\n flex: none;\n max-width: 40%;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\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-layer-1);\n color: var(--dsw-alias-label-secondary);\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\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\n._28237b53_chainBlock {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n\n._15a12620_chainSelectors {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n\n._5694c1de_selectorLine {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: nowrap;\n \n min-height: 32px;\n}\n\n\n._2f156f38_selectorIndex {\n flex: none;\n width: 1.5em;\n text-align: right;\n font-size: 11px;\n line-height: 16px;\n color: var(--dsw-alias-label-tertiary);\n font-variant-numeric: tabular-nums;\n}\n\n\n._4843ae21_selectorProviderCell {\n flex: 0 1 clamp(140px, 20%, 220px);\n min-width: 0;\n}\n\n._df828181_selectorModelCell {\n flex: 1 1 auto;\n min-width: 0;\n}\n\n\n._df828181_selectorModelCell > select._f9d86f7b_input {\n max-width: 100%;\n}\n\n\n._fe6d05a5_selectorHint {\n display: flex;\n align-items: center;\n flex: 0 1 auto;\n min-width: 0;\n}\n\n\n._d04ee9fe_selectorHintText {\n flex: 0 1 auto;\n min-width: 0;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n font-size: 11px;\n line-height: 16px;\n color: var(--dsw-alias-label-secondary);\n}\n\n\n._fe6d05a5_selectorHint > ._a6244318_infoHint {\n flex: none;\n margin-left: 4px;\n}\n\n\n._ec7c2fac_selectorRemove {\n flex: none;\n opacity: 0;\n transition: opacity .12s;\n}\n\n\n._ec7c2fac_selectorRemove:disabled {\n display: none;\n}\n\n\n._240cbbf8_selectorRow:hover ._ec7c2fac_selectorRemove:not(:disabled),\n._240cbbf8_selectorRow:focus-within ._ec7c2fac_selectorRemove:not(:disabled) {\n opacity: 1;\n}\n\n@media (hover: none) {\n ._ec7c2fac_selectorRemove:not(:disabled) {\n opacity: 0.7;\n }\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._711248a9_outlineButton {\n appearance: none;\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 4px 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: var(--dsw-radius-md);\n background: none;\n font: inherit;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-secondary);\n cursor: pointer;\n}\n\n._711248a9_outlineButton:hover:not(:disabled) {\n color: var(--dsw-alias-label-primary);\n border-color: var(--dsw-alias-label-dimmed);\n}\n\n._711248a9_outlineButton:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n._711248a9_outlineButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n\n._28202c90_addButton {\n align-self: flex-start;\n}\n\n\n._28237b53_chainBlock > ._28202c90_addButton {\n box-sizing: border-box;\n align-self: flex-start;\n height: 26px;\n padding: 0 8px;\n border: 1px solid transparent;\n gap: 4px;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-secondary);\n}\n\n._28237b53_chainBlock > ._28202c90_addButton:hover:not(:disabled) {\n border-color: transparent;\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-primary);\n}\n\n\n._28237b53_chainBlock > ._28202c90_addButton:focus-visible {\n outline: none;\n box-shadow: 0 0 0 2px var(--dsw-alias-border-l3);\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._f0864181_discard,\n._ccff7e48_save {\n appearance: none;\n border: 1px solid transparent;\n border-radius: var(--dsw-radius-md);\n padding: 5px 14px;\n font: inherit;\n font-size: 13px;\n line-height: 1.5;\n cursor: pointer;\n}\n\n._f0864181_discard {\n border-color: var(--dsw-alias-border-l2);\n background: none;\n color: var(--dsw-alias-label-secondary);\n}\n\n._f0864181_discard:hover:not(:disabled) {\n color: var(--dsw-alias-label-primary);\n border-color: var(--dsw-alias-label-dimmed);\n}\n\n._ccff7e48_save {\n background: var(--dsw-alias-label-primary);\n color: var(--dsw-alias-bg-layer-3);\n}\n\n._f0864181_discard:disabled,\n._ccff7e48_save:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n._f0864181_discard:focus-visible,\n._ccff7e48_save:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\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._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 ._a6244318_infoHint::after {\n transition: none;\n }\n\n ._631094a0_chevron {\n transition: none;\n }\n}\n";
1791
+ const css$3 = "\n\n\n._20fd0e45_root {\n min-width: 0;\n}\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._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._94c91bfd_fieldset {\n margin: 0;\n padding: 0;\n border: none;\n display: flex;\n flex-direction: column;\n min-width: 0;\n}\n\n\n._ba240d82_sectionHeading {\n display: flex;\n align-items: center;\n gap: 8px;\n margin: 0;\n padding: 4px 0;\n font-size: 16px;\n line-height: 24px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary);\n}\n\n._ddbc1b49_sectionHeadingText {\n display: block;\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._c41f9db7_sectionActions ._f0864181_discard,\n._c41f9db7_sectionActions ._ccff7e48_save {\n padding: 3px 12px;\n font-size: 12px;\n line-height: 18px;\n}\n\n\n._b116c758_sectionToggle {\n appearance: none;\n border: 0;\n background: none;\n display: flex;\n align-items: center;\n gap: 4px;\n margin: 0;\n padding: 0;\n font: inherit;\n color: inherit;\n cursor: pointer;\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: 1px;\n}\n\n\n._80101203_advancedBody {\n display: flex;\n flex-direction: column;\n min-width: 0;\n}\n\n._94c91bfd_fieldset > ._80101203_advancedBody > ._67826267_field,\n._94c91bfd_fieldset > ._80101203_advancedBody > ._9998065c_checkboxRow,\n._94c91bfd_fieldset > ._80101203_advancedBody > ._3e95886d_numberFields {\n padding: 12px 0;\n}\n\n._94c91bfd_fieldset > ._80101203_advancedBody > ._67826267_field + ._67826267_field,\n._94c91bfd_fieldset > ._80101203_advancedBody > ._9998065c_checkboxRow + ._67826267_field,\n._94c91bfd_fieldset > ._80101203_advancedBody > ._67826267_field + ._3e95886d_numberFields {\n border-top: 0.5px solid var(--dsw-alias-border-l2);\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\n._94c91bfd_fieldset > ._67826267_field,\n._94c91bfd_fieldset > ._9998065c_checkboxRow,\n._94c91bfd_fieldset > ._3e95886d_numberFields {\n padding: 12px 0;\n}\n\n._94c91bfd_fieldset > ._67826267_field + ._67826267_field,\n._94c91bfd_fieldset > ._67826267_field + ._3e95886d_numberFields,\n._94c91bfd_fieldset > ._9998065c_checkboxRow + ._67826267_field {\n border-top: 0.5px solid var(--dsw-alias-border-l2);\n}\n\n\n._9998065c_checkboxRow {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n}\n\n\n._9998065c_checkboxRow ._06389b18_checkbox {\n margin-top: 3px;\n}\n\n._13e68c3f_fieldLabel {\n \n display: inline-flex;\n align-items: center;\n gap: 10px;\n padding: 0;\n font-size: 13px;\n line-height: 1.5;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\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\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._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 position: relative;\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._a6244318_infoHint::after {\n content: attr(data-tip);\n position: absolute;\n top: 50%;\n left: calc(100% + 8px);\n transform: translateY(-50%);\n z-index: 10;\n box-sizing: border-box;\n width: max-content;\n max-width: 280px;\n padding: 5px 10px;\n border-radius: 8px;\n background: var(--dsw-alias-label-primary);\n color: var(--dsw-alias-bg-layer-3);\n font-size: 11px;\n line-height: 17px;\n font-weight: 400;\n white-space: normal;\n text-align: left;\n pointer-events: none;\n opacity: 0;\n transition: opacity .12s;\n}\n\n._a6244318_infoHint:hover::after,\n._a6244318_infoHint:focus-visible::after {\n opacity: 1;\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: 34px;\n padding: 0 12px;\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: var(--dsw-radius-md);\n font: inherit;\n font-size: 13px;\n line-height: 1.5;\n background: var(--dsw-alias-bg-layer-3);\n color: var(--dsw-alias-label-primary);\n}\n\n._f9d86f7b_input:focus-visible {\n outline: none;\n border-color: var(--dsw-alias-state-business-primary);\n}\n\n._f9d86f7b_input::placeholder {\n color: var(--dsw-alias-label-dimmed);\n}\n\n._f9d86f7b_input:disabled {\n color: var(--dsw-alias-label-tertiary);\n cursor: default;\n}\n\n\n._3a9b42fd_inputTextarea {\n height: auto;\n min-height: 72px;\n padding: 8px 12px;\n resize: vertical;\n}\n\n\n._1285c19b_personaBriefRow {\n display: flex;\n align-items: center;\n gap: 4px;\n min-width: 0;\n}\n\n._4b8f7345_personaBrief {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 14px;\n line-height: 22px;\n color: var(--dsw-alias-label-primary);\n}\n\n\n._2686400e_personaBriefEmpty {\n color: var(--dsw-alias-label-tertiary);\n}\n\n\n._4e4a07be_personaFull {\n margin: 0;\n font-size: 14px;\n line-height: 22px;\n color: var(--dsw-alias-label-secondary);\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n}\n\n\n._7b60337a_inputInvalid,\n._7b60337a_inputInvalid:focus-visible {\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: 12px;\n}\n\n._3e95886d_numberFields ._67826267_field {\n padding: 0;\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._1014c097_ruleCellLabel {\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-secondary);\n}\n\n\n._631094a0_chevron {\n flex: none;\n color: var(--dsw-alias-label-tertiary);\n transform: rotate(-90deg);\n transition: transform .16s;\n}\n\n._44836ce8_chevronOpen {\n transform: none;\n}\n\n\n._cf3961bb_chevronFlip {\n transform: rotate(180deg);\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: 0 1 auto;\n min-width: 0;\n max-width: 40%;\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._52d6df87_seedBadge {\n flex: none;\n max-width: 40%;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\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-layer-1);\n color: var(--dsw-alias-label-secondary);\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\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\n._1a0fa2e1_selectorRowDragging {\n opacity: 0.45;\n}\n\n._688095f2_selectorRowOver {\n outline: 1px dashed var(--dsw-alias-brand-primary);\n outline-offset: 2px;\n border-radius: 6px;\n}\n\n\n._7489fed0_selectorGrip {\n flex: none;\n}\n\n._7489fed0_selectorGrip:disabled {\n display: none;\n}\n\n\n._28237b53_chainBlock {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n\n._15a12620_chainSelectors {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n\n._5694c1de_selectorLine {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: nowrap;\n \n min-height: 32px;\n}\n\n\n._2f156f38_selectorIndex {\n flex: none;\n width: 1.5em;\n text-align: right;\n font-size: 11px;\n line-height: 16px;\n color: var(--dsw-alias-label-tertiary);\n font-variant-numeric: tabular-nums;\n}\n\n\n._4843ae21_selectorProviderCell {\n flex: 0 1 clamp(140px, 20%, 220px);\n min-width: 0;\n}\n\n._df828181_selectorModelCell {\n flex: 1 1 auto;\n min-width: 0;\n}\n\n\n._df828181_selectorModelCell > select._f9d86f7b_input {\n max-width: 100%;\n}\n\n\n._fe6d05a5_selectorHint {\n display: flex;\n align-items: center;\n flex: 0 1 auto;\n min-width: 0;\n}\n\n\n._d04ee9fe_selectorHintText {\n flex: 0 1 auto;\n min-width: 0;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n font-size: 11px;\n line-height: 16px;\n color: var(--dsw-alias-label-secondary);\n}\n\n\n._fe6d05a5_selectorHint > ._a6244318_infoHint {\n flex: none;\n margin-left: 4px;\n}\n\n\n._ec7c2fac_selectorRemove {\n flex: none;\n opacity: 0;\n transition: opacity .12s;\n}\n\n\n._ec7c2fac_selectorRemove:disabled {\n display: none;\n}\n\n\n._240cbbf8_selectorRow:hover ._ec7c2fac_selectorRemove:not(:disabled),\n._240cbbf8_selectorRow:focus-within ._ec7c2fac_selectorRemove:not(:disabled) {\n opacity: 1;\n}\n\n@media (hover: none) {\n ._ec7c2fac_selectorRemove:not(:disabled) {\n opacity: 0.7;\n }\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._711248a9_outlineButton {\n appearance: none;\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 4px 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: var(--dsw-radius-md);\n background: none;\n font: inherit;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-secondary);\n cursor: pointer;\n}\n\n._711248a9_outlineButton:hover:not(:disabled) {\n color: var(--dsw-alias-label-primary);\n border-color: var(--dsw-alias-label-dimmed);\n}\n\n._711248a9_outlineButton:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n._711248a9_outlineButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n\n._28202c90_addButton {\n align-self: flex-start;\n}\n\n\n._28237b53_chainBlock > ._28202c90_addButton {\n box-sizing: border-box;\n align-self: flex-start;\n height: 26px;\n padding: 0 8px;\n border: 1px solid transparent;\n gap: 4px;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-secondary);\n}\n\n._28237b53_chainBlock > ._28202c90_addButton:hover:not(:disabled) {\n border-color: transparent;\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-primary);\n}\n\n\n._28237b53_chainBlock > ._28202c90_addButton:focus-visible {\n outline: none;\n box-shadow: 0 0 0 2px var(--dsw-alias-border-l3);\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._f0864181_discard,\n._ccff7e48_save {\n appearance: none;\n border: 1px solid transparent;\n border-radius: var(--dsw-radius-md);\n padding: 5px 14px;\n font: inherit;\n font-size: 13px;\n line-height: 1.5;\n cursor: pointer;\n}\n\n._f0864181_discard {\n border-color: var(--dsw-alias-border-l2);\n background: none;\n color: var(--dsw-alias-label-secondary);\n}\n\n._f0864181_discard:hover:not(:disabled) {\n color: var(--dsw-alias-label-primary);\n border-color: var(--dsw-alias-label-dimmed);\n}\n\n._ccff7e48_save {\n background: var(--dsw-alias-label-primary);\n color: var(--dsw-alias-bg-layer-3);\n}\n\n._f0864181_discard:disabled,\n._ccff7e48_save:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n._f0864181_discard:focus-visible,\n._ccff7e48_save:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\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._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\n._aa3fae4a_policyStatusText {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n._d90f6286_policyStatusSync {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 6px;\n}\n\n\n._579f87fb_policyStatusLive:empty {\n position: absolute;\n width: 1px;\n height: 1px;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\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 ._a6244318_infoHint::after {\n transition: none;\n }\n\n ._631094a0_chevron {\n transition: none;\n }\n}\n";
1750
1792
  const tagId$3 = "dsh-llm-fallbacks/FallbacksCard.module.css";
1751
1793
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$3) + "]") === null) {
1752
1794
  const tag = document.createElement("style");
@@ -1814,6 +1856,9 @@ window.__ModuleLoader__.load({
1814
1856
  "dragHandle": "_cd905ce5_dragHandle",
1815
1857
  "slotCardDragging": "_fee63b7e_slotCardDragging",
1816
1858
  "slotCardOver": "_6f108491_slotCardOver",
1859
+ "selectorRowDragging": "_1a0fa2e1_selectorRowDragging",
1860
+ "selectorRowOver": "_688095f2_selectorRowOver",
1861
+ "selectorGrip": "_7489fed0_selectorGrip",
1817
1862
  "chainBlock": "_28237b53_chainBlock",
1818
1863
  "chainSelectors": "_15a12620_chainSelectors",
1819
1864
  "selectorLine": "_5694c1de_selectorLine",
@@ -1835,6 +1880,9 @@ window.__ModuleLoader__.load({
1835
1880
  "statusLine": "_8ab300ed_statusLine",
1836
1881
  "statusLineLabel": "_e8cedbf5_statusLineLabel",
1837
1882
  "policyStatus": "_700006c9_policyStatus",
1883
+ "policyStatusText": "_aa3fae4a_policyStatusText",
1884
+ "policyStatusSync": "_d90f6286_policyStatusSync",
1885
+ "policyStatusLive": "_579f87fb_policyStatusLive",
1838
1886
  "policyStatusLabel": "_d4b67a81_policyStatusLabel",
1839
1887
  "policyStatusLine": "_278bb4b3_policyStatusLine",
1840
1888
  "policyStatusWarn": "_da6387e5_policyStatusWarn"
@@ -2322,6 +2370,73 @@ window.__ModuleLoader__.load({
2322
2370
  });
2323
2371
  }
2324
2372
  /**
2373
+ * The virtual all-fallbacks route the host carve-out recognizes (features.md):
2374
+ * a subagent spawned on it is not allowlist-filtered, so it is the
2375
+ * recommendation when the allowlist does not already carry it.
2376
+ */
2377
+ const VIRTUAL_ALL_ROUTE = {
2378
+ provider: "FallbacksChain",
2379
+ model: "Auto"
2380
+ };
2381
+ /**
2382
+ * Parse one raw selector into an exact route; wildcard and unparseable
2383
+ * entries resolve to `undefined` (they are not exact routes — never expanded,
2384
+ * never counted). The selector parser is the single grammar authority.
2385
+ */
2386
+ function exactRouteOf(raw) {
2387
+ let parsed;
2388
+ try {
2389
+ parsed = parseSelector(raw);
2390
+ } catch {
2391
+ return;
2392
+ }
2393
+ return parsed.model === void 0 ? void 0 : {
2394
+ provider: parsed.provider,
2395
+ model: parsed.model
2396
+ };
2397
+ }
2398
+ /**
2399
+ * The whole chain as one line (plan fallbacks-chain-reorder T2, user request):
2400
+ * every non-blank entry in walk order joined with `>`, or `undefined` for an
2401
+ * empty/blank chain so the caller keeps its own summary (a role's raw strategy
2402
+ * token). The card ellipsizes long summaries; the full string rides `title`.
2403
+ *
2404
+ * With `allowedModels` (host policy enabled) exact entries the policy cannot
2405
+ * use are dropped, so the title tells the truth about what will run; wildcard
2406
+ * entries stay (their resolved routes are unknown client-side) and an
2407
+ * unparseable entry is kept rather than hidden.
2408
+ */
2409
+ function chainSummary(selectors, allowedModels) {
2410
+ const entries = selectors.map(selectorRowToRaw).filter((entry) => entry !== "");
2411
+ const visible = allowedModels === void 0 ? entries : entries.filter((entry) => {
2412
+ const route = exactRouteOf(entry);
2413
+ return route === void 0 || routeOnAllowlist(route, allowedModels);
2414
+ });
2415
+ return visible.length === 0 ? void 0 : visible.join(" > ");
2416
+ }
2417
+ /** How many selected role-chain exact entries the host allowlist does not carry. */
2418
+ function offAllowlistEntryCount(roles, allowedModels) {
2419
+ let count = 0;
2420
+ for (const role of roles) for (const entry of role.chain ?? []) {
2421
+ const route = exactRouteOf(entry);
2422
+ if (route !== void 0 && !routeOnAllowlist(route, allowedModels)) count += 1;
2423
+ }
2424
+ return count;
2425
+ }
2426
+ /**
2427
+ * Pure chain reorder (plan fallbacks-chain-reorder T1): a copy of `list` with
2428
+ * the entry at `from` re-inserted at `to`; an out-of-range or same-position
2429
+ * move returns `list` untouched (the end-clamping the keyboard and the drop
2430
+ * handler both rely on).
2431
+ */
2432
+ function moveItem(list, from, to) {
2433
+ if (from === to || from < 0 || to < 0 || from >= list.length || to >= list.length) return list;
2434
+ const next = [...list];
2435
+ const [moved] = next.splice(from, 1);
2436
+ next.splice(to, 0, moved);
2437
+ return next;
2438
+ }
2439
+ /**
2325
2440
  * One chain entry selector row: provider select + model select (cascade).
2326
2441
  * The row is ONE compact line (plan fallbacks-chain-row-compact T1): the
2327
2442
  * 1-based order index, the provider select, the model select, any row-level
@@ -2345,7 +2460,7 @@ window.__ModuleLoader__.load({
2345
2460
  * existing value is never hidden or dropped. New rows only offer configured
2346
2461
  * options.
2347
2462
  */
2348
- function ChainSelectorEditor({ selector, index, catalog, configuredProviders, disabled, t, onChange, onRemove }) {
2463
+ function ChainSelectorEditor({ selector, index, catalog, configuredProviders, disabled, t, onChange, onRemove, reorder }) {
2349
2464
  const providerRaw = selectionToRaw(selector.provider);
2350
2465
  const providerOutside = selector.provider?.kind === "outside";
2351
2466
  const providerUnconfigured = !providerOutside && providerRaw !== "" && (catalog?.providers.some((entry) => entry.provider === providerRaw) ?? false) && !configuredProviders.some((entry) => entry.provider === providerRaw);
@@ -2357,8 +2472,18 @@ window.__ModuleLoader__.load({
2357
2472
  const hintInstanceId = (0, react.useId)().replace(/:/g, "");
2358
2473
  const strictHintId = groupMissing ? `fallbacks-selector-hint-${index}-${hintInstanceId}` : void 0;
2359
2474
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2360
- className: FallbacksCard_module_css_default.selectorRow,
2475
+ className: `${FallbacksCard_module_css_default.selectorRow}${reorder.dragging ? ` ${FallbacksCard_module_css_default.selectorRowDragging}` : ""}${reorder.dropTarget ? ` ${FallbacksCard_module_css_default.selectorRowOver}` : ""}`,
2361
2476
  "data-selector-row": "",
2477
+ onDragOver: (event) => {
2478
+ if (disabled || !reorder.active) return;
2479
+ event.preventDefault();
2480
+ reorder.onDragOver();
2481
+ },
2482
+ onDrop: (event) => {
2483
+ if (disabled || !reorder.active) return;
2484
+ event.preventDefault();
2485
+ reorder.onDrop();
2486
+ },
2362
2487
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2363
2488
  className: FallbacksCard_module_css_default.selectorLine,
2364
2489
  children: [
@@ -2468,12 +2593,101 @@ window.__ModuleLoader__.load({
2468
2593
  disabled,
2469
2594
  onClick: onRemove,
2470
2595
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TrashIcon, {})
2596
+ }),
2597
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2598
+ type: "button",
2599
+ ref: reorder.gripRef,
2600
+ className: `${FallbacksCard_module_css_default.dragHandle} ${FallbacksCard_module_css_default.selectorGrip}`,
2601
+ draggable: !disabled,
2602
+ "data-tip": t("chains.selector.reorder", { index: String(index) }),
2603
+ "aria-label": t("chains.selector.reorder", { index: String(index) }),
2604
+ disabled,
2605
+ onDragStart: () => {
2606
+ if (disabled) return;
2607
+ reorder.onDragStart();
2608
+ },
2609
+ onDragEnd: reorder.onDragEnd,
2610
+ onKeyDown: (event) => {
2611
+ if (disabled) return;
2612
+ const step = event.key === "ArrowUp" ? -1 : event.key === "ArrowDown" ? 1 : 0;
2613
+ if (step === 0) return;
2614
+ event.preventDefault();
2615
+ reorder.onKeyMove(index - 1, index - 1 + step);
2616
+ },
2617
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GripIcon, {})
2471
2618
  })
2472
2619
  ]
2473
2620
  })
2474
2621
  });
2475
2622
  }
2476
2623
  /**
2624
+ * One chain's selector rows plus their drag state (plan
2625
+ * fallbacks-chain-reorder T1). The list owns the dragged/over index pair —
2626
+ * card-local state, no DataTransfer payload (the jsdom-testable slot-card
2627
+ * pattern), one instance per rendered chain, so hooks are never called inside
2628
+ * a loop and no two lists share a drag. All three chain editors (default
2629
+ * chain, time-slot chains, role chains) render through this component.
2630
+ */
2631
+ function ChainSelectorList({ selectors, catalog, configuredProviders, disabled, t, onChange, onRemove, onMove }) {
2632
+ const [draggedIndex, setDraggedIndex] = (0, react.useState)(null);
2633
+ const [overIndex, setOverIndex] = (0, react.useState)(null);
2634
+ const settle = () => {
2635
+ setDraggedIndex(null);
2636
+ setOverIndex(null);
2637
+ };
2638
+ const gripRefs = (0, react.useRef)([]);
2639
+ const [pendingFocus, setPendingFocus] = (0, react.useState)(null);
2640
+ (0, react.useEffect)(() => {
2641
+ if (pendingFocus === null) return;
2642
+ gripRefs.current[pendingFocus]?.focus();
2643
+ setPendingFocus(null);
2644
+ }, [pendingFocus]);
2645
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2646
+ className: FallbacksCard_module_css_default.chainSelectors,
2647
+ children: selectors.map((selector, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChainSelectorEditor, {
2648
+ index: index + 1,
2649
+ selector,
2650
+ catalog,
2651
+ configuredProviders,
2652
+ disabled,
2653
+ t,
2654
+ onChange: (patch) => {
2655
+ onChange(index, patch);
2656
+ },
2657
+ onRemove: () => {
2658
+ onRemove(index);
2659
+ },
2660
+ reorder: {
2661
+ dragging: draggedIndex === index,
2662
+ dropTarget: draggedIndex !== null && draggedIndex !== index && overIndex === index,
2663
+ active: draggedIndex !== null,
2664
+ gripRef: (node) => {
2665
+ gripRefs.current[index] = node;
2666
+ },
2667
+ onDragStart: () => {
2668
+ setDraggedIndex(index);
2669
+ setOverIndex(index);
2670
+ },
2671
+ onDragOver: () => {
2672
+ if (draggedIndex === null || overIndex === index) return;
2673
+ setOverIndex(index);
2674
+ },
2675
+ onDrop: () => {
2676
+ const from = draggedIndex;
2677
+ settle();
2678
+ if (from !== null && from !== index) onMove(from, index);
2679
+ },
2680
+ onDragEnd: settle,
2681
+ onKeyMove: (from, to) => {
2682
+ if (from === to || from < 0 || to < 0 || from >= selectors.length || to >= selectors.length) return;
2683
+ setPendingFocus(to);
2684
+ onMove(from, to);
2685
+ }
2686
+ }
2687
+ }, index))
2688
+ });
2689
+ }
2690
+ /**
2477
2691
  * Render the Fallbacks settings card inside the plugin-config section —
2478
2692
  * flat: the notices, the form, and the section action pairs directly under
2479
2693
  * the page's plugin title/description, with no chrome of our own.
@@ -2517,6 +2731,19 @@ window.__ModuleLoader__.load({
2517
2731
  state.config.alwaysModeRetryCap,
2518
2732
  state.config.roleAutoMatch
2519
2733
  ]);
2734
+ const enabledPolicy = state.subagentPolicy?.state === "enabled" ? state.subagentPolicy : void 0;
2735
+ const allowedModels = enabledPolicy?.allowedModels;
2736
+ const offAllowlistCount = enabledPolicy === void 0 ? 0 : offAllowlistEntryCount(draft.roles.list, enabledPolicy.allowedModels);
2737
+ const virtualRouteAllowed = enabledPolicy !== void 0 && routeOnAllowlist(VIRTUAL_ALL_ROUTE, enabledPolicy.allowedModels);
2738
+ const [syncStatus, setSyncStatus] = (0, react.useState)("idle");
2739
+ const syncDirtyHintId = `fallbacks-policy-sync-dirty-${(0, react.useId)().replace(/:/g, "")}`;
2740
+ const syncAllowlist = () => {
2741
+ if (syncStatus === "pending") return;
2742
+ setSyncStatus("pending");
2743
+ controller.syncAllowlist().then((ok) => {
2744
+ setSyncStatus(ok ? "ok" : "error");
2745
+ });
2746
+ };
2520
2747
  const firstSeedDone = (0, react.useRef)(false);
2521
2748
  (0, react.useEffect)(() => {
2522
2749
  if (state.status !== "ready") return;
@@ -2681,6 +2908,24 @@ window.__ModuleLoader__.load({
2681
2908
  selectors: row.selectors.filter((_, index) => index !== selectorIndex)
2682
2909
  }));
2683
2910
  };
2911
+ const reorderAllDayChainSelector = (from, to) => {
2912
+ setAllDayChainRow((row) => ({
2913
+ ...row,
2914
+ selectors: moveItem(row.selectors, from, to)
2915
+ }));
2916
+ };
2917
+ const reorderTimeSlotSelector = (slotIndex, from, to) => {
2918
+ setTimeSlotRows((rows) => rows.map((row, index) => index === slotIndex ? {
2919
+ ...row,
2920
+ selectors: moveItem(row.selectors, from, to)
2921
+ } : row));
2922
+ };
2923
+ const reorderRoleSelector = (roleIndex, from, to) => {
2924
+ setRoleRows((rows) => rows.map((row, index) => index === roleIndex ? {
2925
+ ...row,
2926
+ selectors: moveItem(row.selectors, from, to)
2927
+ } : row));
2928
+ };
2684
2929
  const updateRoleRow = (index, patch) => {
2685
2930
  setRoleRows((rows) => {
2686
2931
  const next = rows.map((row) => ({ ...row }));
@@ -3173,22 +3418,21 @@ window.__ModuleLoader__.load({
3173
3418
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3174
3419
  className: FallbacksCard_module_css_default.chainBlock,
3175
3420
  "data-chain-block": "",
3176
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3177
- className: FallbacksCard_module_css_default.chainSelectors,
3178
- children: row.selectors.map((selector, selectorIndex) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChainSelectorEditor, {
3179
- index: selectorIndex + 1,
3180
- selector,
3181
- catalog: catalogOf(state),
3182
- configuredProviders: state.configuredProviders,
3183
- disabled: !writable,
3184
- t,
3185
- onChange: (patch) => {
3186
- updateTimeSlotSelector(index, selectorIndex, patch);
3187
- },
3188
- onRemove: () => {
3189
- removeTimeSlotSelector(index, selectorIndex);
3190
- }
3191
- }, selectorIndex))
3421
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChainSelectorList, {
3422
+ selectors: row.selectors,
3423
+ catalog: catalogOf(state),
3424
+ configuredProviders: state.configuredProviders,
3425
+ disabled: !writable,
3426
+ t,
3427
+ onChange: (selectorIndex, patch) => {
3428
+ updateTimeSlotSelector(index, selectorIndex, patch);
3429
+ },
3430
+ onRemove: (selectorIndex) => {
3431
+ removeTimeSlotSelector(index, selectorIndex);
3432
+ },
3433
+ onMove: (from, to) => {
3434
+ reorderTimeSlotSelector(index, from, to);
3435
+ }
3192
3436
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3193
3437
  type: "button",
3194
3438
  className: `${FallbacksCard_module_css_default.outlineButton} ${FallbacksCard_module_css_default.addButton}`,
@@ -3318,22 +3562,19 @@ window.__ModuleLoader__.load({
3318
3562
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3319
3563
  className: FallbacksCard_module_css_default.chainBlock,
3320
3564
  "data-chain-block": "",
3321
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3322
- className: FallbacksCard_module_css_default.chainSelectors,
3323
- children: allDayChainRow.selectors.map((selector, selectorIndex) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChainSelectorEditor, {
3324
- index: selectorIndex + 1,
3325
- selector,
3326
- catalog: catalogOf(state),
3327
- configuredProviders: state.configuredProviders,
3328
- disabled: !writable,
3329
- t,
3330
- onChange: (patch) => {
3331
- updateAllDayChainSelector(selectorIndex, patch);
3332
- },
3333
- onRemove: () => {
3334
- removeAllDayChainSelector(selectorIndex);
3335
- }
3336
- }, selectorIndex))
3565
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChainSelectorList, {
3566
+ selectors: allDayChainRow.selectors,
3567
+ catalog: catalogOf(state),
3568
+ configuredProviders: state.configuredProviders,
3569
+ disabled: !writable,
3570
+ t,
3571
+ onChange: (selectorIndex, patch) => {
3572
+ updateAllDayChainSelector(selectorIndex, patch);
3573
+ },
3574
+ onRemove: (selectorIndex) => {
3575
+ removeAllDayChainSelector(selectorIndex);
3576
+ },
3577
+ onMove: reorderAllDayChainSelector
3337
3578
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3338
3579
  type: "button",
3339
3580
  className: `${FallbacksCard_module_css_default.outlineButton} ${FallbacksCard_module_css_default.addButton}`,
@@ -3425,26 +3666,65 @@ window.__ModuleLoader__.load({
3425
3666
  }),
3426
3667
  state.subagentPolicy?.state === "enabled" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3427
3668
  className: FallbacksCard_module_css_default.policyStatus,
3428
- role: "status",
3429
- children: [
3430
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3431
- className: FallbacksCard_module_css_default.policyStatusLabel,
3432
- children: t("subagents.policy.label")
3433
- }),
3434
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3435
- className: FallbacksCard_module_css_default.policyStatusLine,
3436
- children: `${t("subagents.policy.allowlist")}: ${state.subagentPolicy.allowedModels.map((route) => `${route.provider}/${route.model}`).join(", ")}`
3437
- }),
3438
- state.subagentPolicy.head !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3439
- className: FallbacksCard_module_css_default.policyStatusLine,
3440
- 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")})`
3441
- }),
3442
- state.subagentPolicy.blockedAttempt !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3443
- className: FallbacksCard_module_css_default.policyStatusWarn,
3444
- role: "alert",
3445
- children: t("subagents.policy.blocked")
3446
- })
3447
- ]
3669
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3670
+ className: FallbacksCard_module_css_default.policyStatusText,
3671
+ role: "status",
3672
+ children: [
3673
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3674
+ className: FallbacksCard_module_css_default.policyStatusLabel,
3675
+ children: t("subagents.policy.label")
3676
+ }),
3677
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3678
+ className: FallbacksCard_module_css_default.policyStatusLine,
3679
+ children: `${t("subagents.policy.allowlist")}: ${state.subagentPolicy.allowedModels.map((route) => `${route.provider}/${route.model}`).join(", ")}`
3680
+ }),
3681
+ state.subagentPolicy.head !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3682
+ className: FallbacksCard_module_css_default.policyStatusLine,
3683
+ 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")})`
3684
+ }),
3685
+ state.subagentPolicy.blockedAttempt !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3686
+ className: FallbacksCard_module_css_default.policyStatusWarn,
3687
+ role: "alert",
3688
+ children: t("subagents.policy.blocked")
3689
+ }),
3690
+ offAllowlistCount > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3691
+ className: FallbacksCard_module_css_default.policyStatusWarn,
3692
+ children: t("subagents.policy.offAllowlist", { count: String(offAllowlistCount) })
3693
+ }),
3694
+ !virtualRouteAllowed && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3695
+ className: FallbacksCard_module_css_default.policyStatusLine,
3696
+ children: t("subagents.policy.recommend")
3697
+ })
3698
+ ]
3699
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3700
+ className: FallbacksCard_module_css_default.policyStatusSync,
3701
+ role: "group",
3702
+ "aria-label": t("subagents.policy.sync"),
3703
+ children: [
3704
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3705
+ type: "button",
3706
+ className: FallbacksCard_module_css_default.outlineButton,
3707
+ disabled: !writable || subDirty || mainDirty,
3708
+ "aria-busy": syncStatus === "pending" || void 0,
3709
+ "aria-describedby": subDirty || mainDirty ? syncDirtyHintId : void 0,
3710
+ onClick: syncAllowlist,
3711
+ children: t("subagents.policy.sync")
3712
+ }),
3713
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3714
+ id: syncDirtyHintId,
3715
+ role: "status",
3716
+ "data-sync-hint": "dirty",
3717
+ className: `${FallbacksCard_module_css_default.policyStatusWarn} ${FallbacksCard_module_css_default.policyStatusLive}`,
3718
+ children: subDirty || mainDirty ? t("subagents.policy.sync.dirty") : ""
3719
+ }),
3720
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3721
+ role: "status",
3722
+ "data-sync-hint": "outcome",
3723
+ className: `${syncStatus === "error" ? FallbacksCard_module_css_default.policyStatusWarn : FallbacksCard_module_css_default.policyStatusLine} ${FallbacksCard_module_css_default.policyStatusLive}`,
3724
+ children: syncStatus === "ok" ? t("subagents.policy.sync.done") : syncStatus === "error" ? t("subagents.policy.sync.failed") : ""
3725
+ })
3726
+ ]
3727
+ })]
3448
3728
  }),
3449
3729
  state.subagentPolicy?.state === "unprovable" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3450
3730
  className: FallbacksCard_module_css_default.policyStatus,
@@ -3481,7 +3761,8 @@ window.__ModuleLoader__.load({
3481
3761
  const seed = seedInfo.get(row.id.trim());
3482
3762
  const seedBadge = seedBadgeLabel(seed, t);
3483
3763
  const personaBlank = row.persona.trim() === "";
3484
- const roleSummary = row.selectors.map(selectorRowToRaw).find((entry) => entry !== "") ?? row.fallback;
3764
+ const roleChainSummary = chainSummary(row.selectors, allowedModels);
3765
+ const roleSummary = roleChainSummary ?? row.fallback;
3485
3766
  const roleExpanded = !row.collapsed || !writable;
3486
3767
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3487
3768
  className: FallbacksCard_module_css_default.editorCard,
@@ -3509,6 +3790,7 @@ window.__ModuleLoader__.load({
3509
3790
  }),
3510
3791
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3511
3792
  className: FallbacksCard_module_css_default.collapseMeta,
3793
+ title: roleChainSummary,
3512
3794
  children: roleSummary
3513
3795
  })
3514
3796
  ]
@@ -3590,34 +3872,37 @@ window.__ModuleLoader__.load({
3590
3872
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3591
3873
  className: FallbacksCard_module_css_default.chainBlock,
3592
3874
  "data-chain-block": "",
3593
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3594
- className: FallbacksCard_module_css_default.chainSelectors,
3595
- children: [row.selectors.map((selector, selectorIndex) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChainSelectorEditor, {
3596
- index: selectorIndex + 1,
3597
- selector,
3875
+ children: [
3876
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChainSelectorList, {
3877
+ selectors: row.selectors,
3598
3878
  catalog: catalogOf(state),
3599
3879
  configuredProviders: state.configuredProviders,
3600
3880
  disabled: !writable,
3601
3881
  t,
3602
- onChange: (patch) => {
3882
+ onChange: (selectorIndex, patch) => {
3603
3883
  updateRoleSelector(index, selectorIndex, patch);
3604
3884
  },
3605
- onRemove: () => {
3885
+ onRemove: (selectorIndex) => {
3606
3886
  removeRoleSelector(index, selectorIndex);
3887
+ },
3888
+ onMove: (from, to) => {
3889
+ reorderRoleSelector(index, from, to);
3607
3890
  }
3608
- }, selectorIndex)), row.selectors.every((selector) => selectorRowToRaw(selector) === "") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3891
+ }),
3892
+ row.selectors.every((selector) => selectorRowToRaw(selector) === "") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3609
3893
  className: FallbacksCard_module_css_default.hint,
3610
3894
  children: seed !== void 0 ? t("roles.seedChainOptional", { id: row.id }) : t("validation.roleChainRequired", { id: row.id })
3611
- })]
3612
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3613
- type: "button",
3614
- className: `${FallbacksCard_module_css_default.outlineButton} ${FallbacksCard_module_css_default.addButton}`,
3615
- disabled: !writable,
3616
- onClick: () => {
3617
- addRoleSelector(index);
3618
- },
3619
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(PlusIcon, {}), t("roles.selector.add")]
3620
- })]
3895
+ }),
3896
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3897
+ type: "button",
3898
+ className: `${FallbacksCard_module_css_default.outlineButton} ${FallbacksCard_module_css_default.addButton}`,
3899
+ disabled: !writable,
3900
+ onClick: () => {
3901
+ addRoleSelector(index);
3902
+ },
3903
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(PlusIcon, {}), t("roles.selector.add")]
3904
+ })
3905
+ ]
3621
3906
  }),
3622
3907
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3623
3908
  className: FallbacksCard_module_css_default.ruleGrid,
@@ -4432,7 +4717,7 @@ window.__ModuleLoader__.load({
4432
4717
  }
4433
4718
  //#endregion
4434
4719
  //#region \0dsh-css:/home/runner/work/dsh-llm-fallbacks/dsh-llm-fallbacks/src/client/SubagentRoleBadge.module.css.mjs
4435
- const css = "\n\n._35648278_badge {\n display: inline-block;\n max-width: 120px;\n padding: 0 8px;\n border-radius: 999px;\n font-size: 12px;\n line-height: 20px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n vertical-align: middle;\n background: var(--dsw-alias-bg-module-platform);\n color: var(--dsw-alias-label-primary);\n}\n";
4720
+ const css = "\n\n._35648278_badge {\n display: inline-block;\n max-width: 240px;\n padding: 0 8px;\n border-radius: 999px;\n font-size: 12px;\n line-height: 20px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n vertical-align: middle;\n background: var(--dsw-alias-bg-module-platform);\n color: var(--dsw-alias-label-primary);\n}\n\n\n._321abf1e_route {\n color: var(--dsw-alias-label-tertiary);\n}\n";
4436
4721
  const tagId = "dsh-llm-fallbacks/SubagentRoleBadge.module.css";
4437
4722
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
4438
4723
  const tag = document.createElement("style");
@@ -4441,51 +4726,84 @@ window.__ModuleLoader__.load({
4441
4726
  tag.textContent = css;
4442
4727
  document.head.appendChild(tag);
4443
4728
  }
4444
- var SubagentRoleBadge_module_css_default = { "badge": "_35648278_badge" };
4729
+ var SubagentRoleBadge_module_css_default = {
4730
+ "badge": "_35648278_badge",
4731
+ "route": "_321abf1e_route"
4732
+ };
4445
4733
  //#endregion
4446
4734
  //#region src/client/SubagentRoleBadge.tsx
4447
4735
  /**
4448
4736
  * The host's own durable model-selection projection key (`dsh-api-session-
4449
4737
  * controller`, merged into `SessionProjectionMap`): its client value carries
4450
- * `lastUsed` — the route the LATEST recorded request ran on. Read through the
4451
- * same structural seat as the role key.
4738
+ * `next` (the route the next request will use) and `lastUsed` (the route the
4739
+ * latest recorded request ran on). Read through the same structural seat as the
4740
+ * role key.
4452
4741
  */
4453
4742
  const MODEL_SELECTION_PROJECTION_KEY = "modelSelection";
4454
4743
  /**
4455
- * `provider/model` of the model-selection projection's `lastUsed`, or
4456
- * `undefined` when the value/key/fields are absent or foreign — the badge then
4457
- * hovers the role alone instead of rendering a wrong route.
4744
+ * `provider/model` of one model selection, or `undefined` when the value's
4745
+ * fields are absent or foreign.
4746
+ */
4747
+ function selectionRoute(value) {
4748
+ if (typeof value !== "object" || value === null) return void 0;
4749
+ const { provider, model } = value;
4750
+ if (typeof provider !== "string" || typeof model !== "string") return void 0;
4751
+ const trimmedProvider = provider.trim();
4752
+ const trimmedModel = model.trim();
4753
+ if (trimmedProvider === "" || trimmedModel === "") return void 0;
4754
+ return `${trimmedProvider}/${trimmedModel}`;
4755
+ }
4756
+ /** Display form of a route in the pill body; the title keeps the full route. */
4757
+ function displayRoute(route) {
4758
+ return route.length > 120 ? `${route.slice(0, 119)}…` : route;
4759
+ }
4760
+ /**
4761
+ * `provider/model` the child's NEXT request will use, falling back to the route
4762
+ * its latest recorded request ran on (`modelSelection.next` → `lastUsed`), or
4763
+ * `undefined` when neither carries a route — the badge then renders the role
4764
+ * alone instead of a wrong route.
4458
4765
  */
4459
- function latestRequestRoute(value) {
4766
+ function modelSelectionRoute(value) {
4460
4767
  if (typeof value !== "object" || value === null) return void 0;
4461
- const { lastUsed } = value;
4462
- if (typeof lastUsed !== "object" || lastUsed === null) return void 0;
4463
- const { provider, model } = lastUsed;
4464
- if (typeof provider !== "string" || provider === "" || typeof model !== "string" || model === "") return void 0;
4465
- return `${provider}/${model}`;
4768
+ const { next, lastUsed } = value;
4769
+ return selectionRoute(next) ?? selectionRoute(lastUsed);
4466
4770
  }
4467
4771
  /**
4468
- * Render the viewed session's dispatch-resolved role badge.
4469
- * @param props - composed slot props (the session-kit `useProjection` seat is
4470
- * read structurally — see the module docblock).
4772
+ * Render the viewed SUBAGENT session's role and model.
4773
+ * @param props - composed slot props (the session-kit `useProjection` /
4774
+ * `useSessions` seats are read structurally — see the module docblock).
4471
4775
  * @returns the badge element, or `null` when there is nothing to show.
4472
4776
  */
4473
4777
  function SubagentRoleBadge(props) {
4474
4778
  const { t } = props;
4475
4779
  const readProjection = props.useProjection;
4476
- if (typeof readProjection !== "function") return null;
4780
+ const useSessions = props.useSessions;
4781
+ const sessionId = props.sessionId;
4782
+ if (typeof readProjection !== "function" || typeof useSessions !== "function") return null;
4783
+ if (typeof sessionId !== "string") return null;
4784
+ const isChild = useSessions((state) => state.byId?.[sessionId]?.origin === "subagent");
4477
4785
  const rawRole = readProjection(ROLE_PROJECTION_KEY);
4478
4786
  const rawModelSelection = readProjection(MODEL_SELECTION_PROJECTION_KEY);
4479
- const role = readRoleProjectionValue(rawRole);
4480
- if (role === void 0 || role === "inherit") return null;
4481
- const model = latestRequestRoute(rawModelSelection);
4482
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4787
+ if (!isChild) return null;
4788
+ const declared = readRoleProjectionValue(rawRole);
4789
+ const role = declared === "inherit" ? void 0 : declared;
4790
+ const model = modelSelectionRoute(rawModelSelection);
4791
+ if (role === void 0 && model === void 0) return null;
4792
+ const title = role !== void 0 && model !== void 0 ? t("subagentRole.hover", {
4793
+ role,
4794
+ model
4795
+ }) : role ?? model;
4796
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4483
4797
  className: SubagentRoleBadge_module_css_default.badge,
4484
- title: model === void 0 ? role : t("subagentRole.hover", {
4798
+ title,
4799
+ children: [
4485
4800
  role,
4486
- model
4487
- }),
4488
- children: role
4801
+ role !== void 0 && model !== void 0 && " · ",
4802
+ model !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4803
+ className: SubagentRoleBadge_module_css_default.route,
4804
+ children: displayRoute(model)
4805
+ })
4806
+ ]
4489
4807
  });
4490
4808
  }
4491
4809
  //#endregion
@@ -37,6 +37,7 @@ export declare const zh: {
37
37
  'chains.selector.remove': string;
38
38
  'chains.selector.providerAria': string;
39
39
  'chains.selector.modelAria': string;
40
+ 'chains.selector.reorder': string;
40
41
  'chains.selector.providerPlaceholder': string;
41
42
  'chains.selector.modelPlaceholder': string;
42
43
  'chains.selector.wildcardLegacy': string;
@@ -50,6 +51,12 @@ export declare const zh: {
50
51
  'subagents.policy.source.injected': string;
51
52
  'subagents.policy.blocked': string;
52
53
  'subagents.policy.unprovable': string;
54
+ 'subagents.policy.offAllowlist': string;
55
+ 'subagents.policy.recommend': string;
56
+ 'subagents.policy.sync': string;
57
+ 'subagents.policy.sync.done': string;
58
+ 'subagents.policy.sync.failed': string;
59
+ 'subagents.policy.sync.dirty': string;
53
60
  'timeSlots.label': string;
54
61
  'timeSlots.hint': string;
55
62
  'timeSlots.tz.label': string;
@@ -228,6 +235,7 @@ export declare const en: {
228
235
  'chains.selector.remove': string;
229
236
  'chains.selector.providerAria': string;
230
237
  'chains.selector.modelAria': string;
238
+ 'chains.selector.reorder': string;
231
239
  'chains.selector.providerPlaceholder': string;
232
240
  'chains.selector.modelPlaceholder': string;
233
241
  'chains.selector.wildcardLegacy': string;
@@ -241,6 +249,12 @@ export declare const en: {
241
249
  'subagents.policy.source.injected': string;
242
250
  'subagents.policy.blocked': string;
243
251
  'subagents.policy.unprovable': string;
252
+ 'subagents.policy.offAllowlist': string;
253
+ 'subagents.policy.recommend': string;
254
+ 'subagents.policy.sync': string;
255
+ 'subagents.policy.sync.done': string;
256
+ 'subagents.policy.sync.failed': string;
257
+ 'subagents.policy.sync.dirty': string;
244
258
  'timeSlots.label': string;
245
259
  'timeSlots.hint': string;
246
260
  'timeSlots.tz.label': string;
package/dist/gateway.d.ts CHANGED
@@ -275,6 +275,22 @@ export declare class FallbacksConfigGateway extends TypertRemoteService {
275
275
  * write channel is unavailable (KD-G5).
276
276
  */
277
277
  revertSeed(id: string): Promise<FallbacksRevertResult>;
278
+ /**
279
+ * Merge the CURRENT host allowlist with the exact routes configured in this
280
+ * plugin's chains (every `roles.list[].chain` entry plus `rootChain`), plus
281
+ * the virtual `FallbacksChain/Auto` route. De-duplicated by exact
282
+ * provider+model with first-seen order kept (the current allowlist first).
283
+ * Wildcard (`provider/*`) and unparseable entries are skipped — never
284
+ * expanded into routes. This is the only allowlist write face; it runs
285
+ * exclusively from the explicit card button (never silently) and writes
286
+ * ONLY `allowedModels` — never `enabled`.
287
+ * @returns the post-write read result (the fresh `subagentPolicy` snapshot
288
+ * reflects the new allowlist).
289
+ * @throws when no live policy snapshot is wired, the policy is not proven
290
+ * enabled (`disabled` / `unprovable`), or no settings service is composed
291
+ * (KD-G5: no allowlist write without a proven-enabled policy).
292
+ */
293
+ syncAllowlist(): Promise<FallbacksReadResult>;
278
294
  /**
279
295
  * Read the live composed config and normalize it to the typertGateway JSON
280
296
  * wire boundary. Containment (guide §10): a malformed stored patch section
package/dist/index.js CHANGED
@@ -1572,6 +1572,11 @@ function overrideConfigWithRouteRule(seed, to, explicitEffort) {
1572
1572
  //#endregion
1573
1573
  //#region .build/host/gateway.js
1574
1574
  const FALLBACKS_PROFILE_ENTRY = "llm-fallbacks";
1575
+ const SUBAGENT_POLICY_SETTINGS_NS = "subagent-model-selection-settings";
1576
+ const VIRTUAL_ALL_ROUTE = {
1577
+ provider: "FallbacksChain",
1578
+ model: "Auto"
1579
+ };
1575
1580
  const CONFIG_KEYS = {
1576
1581
  triggerCodes: true,
1577
1582
  rootChain: true,
@@ -1645,6 +1650,21 @@ var FallbacksConfigGateway = class extends TypertRemoteService {
1645
1650
  outcome
1646
1651
  };
1647
1652
  }
1653
+ async syncAllowlist() {
1654
+ const snapshot = this.subagentPolicy;
1655
+ if (snapshot === void 0) throw new Error("fallbacks: subagent policy is unavailable — allowlist cannot be synced");
1656
+ const { policy } = snapshot();
1657
+ if (policy.state !== "enabled") throw new Error(`fallbacks: subagent policy is ${policy.state} — allowlist cannot be synced`);
1658
+ const settings = this.settings;
1659
+ if (settings === void 0) throw new Error("fallbacks: settings service is unavailable — configuration cannot be written");
1660
+ const merged = mergedAllowlist(policy.allowedModels, this.bridge.source());
1661
+ await settings.mutate(SUBAGENT_POLICY_SETTINGS_NS, [{
1662
+ op: "set",
1663
+ path: ["allowedModels"],
1664
+ value: merged
1665
+ }]);
1666
+ return this.readResult();
1667
+ }
1648
1668
  readConfig(source = this.bridge.source()) {
1649
1669
  const wire = {};
1650
1670
  for (const key of Object.keys(CONFIG_KEYS)) {
@@ -1676,6 +1696,36 @@ var FallbacksConfigGateway = class extends TypertRemoteService {
1676
1696
  };
1677
1697
  }
1678
1698
  };
1699
+ function mergedAllowlist(allowedModels, source) {
1700
+ const merged = [];
1701
+ const seen = new Set();
1702
+ const push = (route) => {
1703
+ const key = `${route.provider}\0${route.model}`;
1704
+ if (seen.has(key)) return;
1705
+ seen.add(key);
1706
+ merged.push({
1707
+ provider: route.provider,
1708
+ model: route.model
1709
+ });
1710
+ };
1711
+ for (const route of allowedModels) push(route);
1712
+ const entries = [...source.roles.list.flatMap((role) => role.chain ?? []), ...source.rootChain];
1713
+ for (const entry of entries) {
1714
+ let parsed;
1715
+ try {
1716
+ parsed = parseSelector(entry);
1717
+ } catch {
1718
+ continue;
1719
+ }
1720
+ if (parsed.model === void 0) continue;
1721
+ push({
1722
+ provider: parsed.provider,
1723
+ model: parsed.model
1724
+ });
1725
+ }
1726
+ push(VIRTUAL_ALL_ROUTE);
1727
+ return merged;
1728
+ }
1679
1729
  function projectSubagentPolicy(snapshot) {
1680
1730
  if (snapshot === void 0) return void 0;
1681
1731
  let captured;
@@ -1825,6 +1875,16 @@ function fallbacksTypertContribution() {
1825
1875
  codec: { mode: "src-json" }
1826
1876
  }],
1827
1877
  result: { mode: "src-json" }
1878
+ },
1879
+ {
1880
+ id: "dsh-llm-fallbacks#fallbacks/sync-allowlist",
1881
+ service: "fallbacks",
1882
+ namespace: "fallbacks",
1883
+ method: "sync-allowlist",
1884
+ implementation: "syncAllowlist",
1885
+ invocation: { kind: "direct" },
1886
+ parameters: [],
1887
+ result: { mode: "src-json" }
1828
1888
  }
1829
1889
  ]
1830
1890
  };
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "dsh",
6
6
  "dsh-plugin"
7
7
  ],
8
- "version": "0.6.8",
8
+ "version": "0.6.10",
9
9
  "type": "module",
10
10
  "main": "./dist/index.js",
11
11
  "types": "./dist/index.d.ts",
@@ -104,7 +104,7 @@
104
104
  "clsx": "^2.1.1",
105
105
  "immer": "^10.2.0",
106
106
  "jsdom": "29.1.1",
107
- "katex": "^0.16.47",
107
+ "katex": "^0.18.2",
108
108
  "mdast-util-from-markdown": "^2.0.3",
109
109
  "mdast-util-gfm": "^3.1.0",
110
110
  "mdast-util-math": "^3.0.0",
@@ -123,7 +123,7 @@
123
123
  "tsdown": "^0.22.2",
124
124
  "tsx": "^4.22.4",
125
125
  "typescript": "^5.9.3",
126
- "vitest": "^4.1.8",
126
+ "vitest": "^4.1.12",
127
127
  "zustand": "~4.4.7"
128
128
  },
129
129
  "repository": {