pi-fabric 0.67.0 → 0.68.0

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
@@ -118,7 +118,7 @@ Fabric includes a live activity surface in Pi:
118
118
 
119
119
  - A compact widget above the chat (like `pi-supervisor`) whose header follows the current phase while its rows show active/completed agents, active actors, and their recent nested tool or code-change activity.
120
120
  - `/fabric` (or `/fabric dashboard`): opens the **Activity** and **Topology** views. The user-facing Pi session appears as **Main**. You can queue or steer participants and inspect the project topology.
121
- - `/fabric settings`: mirrors Pi's `/settings` and writes changes to `fabric.json`.
121
+ - `/fabric settings`: mirrors Pi's `/settings` and writes changes to `fabric.json`. TUI hosts get the searchable settings component; RPC hosts get the same nested sections, value/input/model pickers, list editors, and project/global save scopes through native dialog primitives.
122
122
  - `Tool display` (`compact` by default, or `full`) is configured under `/fabric settings` → **UI**; compact elevates the declared display intent, hides the outer TypeScript, and applies to the current transcript immediately. Pi's tool-expand keybinding (`ctrl+o` by default) expands a compact card to the full transcript.
123
123
 
124
124
  See the [interface & commands reference](docs/interface.md) for every view, keybinding, and slash command.
@@ -91,6 +91,20 @@ var FabricModelSelector = class extends Container {
91
91
  this.filterModels(this.searchInput.getValue());
92
92
  }
93
93
  }
94
+ rpcChoices() {
95
+ return this.allEntries.map((entry) => ({
96
+ value: entry.value,
97
+ label: entry.id,
98
+ description: entry.isModel ? `${entry.provider} \xB7 ${entry.name}` : entry.name,
99
+ current: entry.value === this.currentValue
100
+ }));
101
+ }
102
+ selectRpc(value) {
103
+ const entry = this.allEntries.find((candidate) => candidate.value === value);
104
+ if (!entry) return false;
105
+ this.handleSelect(entry);
106
+ return true;
107
+ }
94
108
  handleSelect(entry) {
95
109
  this.onSelectCallback(entry.value);
96
110
  }
@@ -178,4 +192,4 @@ var FabricModelSelector = class extends Container {
178
192
  export {
179
193
  FabricModelSelector
180
194
  };
181
- //# sourceMappingURL=chunk-NFW7H77W.js.map
195
+ //# sourceMappingURL=chunk-VRYEGVHR.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/ui/fabric-model-selector.ts"],
4
+ "sourcesContent": ["import type { Theme } from \"@earendil-works/pi-coding-agent\";\nimport {\n Container,\n type Focusable,\n fuzzyFilter,\n getKeybindings,\n Input,\n Spacer,\n Text,\n} from \"@earendil-works/pi-tui\";\nimport { INHERIT_VALUE, modelKey, sortByLastUsed, type ModelLike, type ModelSource } from \"./model-picker.js\";\n\n/** A single pickable row in the Fabric model selector. */\ninterface ModelEntry {\n /** Value persisted to fabric.json: \"Inherit\" or `provider/id`. */\n value: string;\n /** Main text shown for the row (the model id, or \"Inherit\"). */\n id: string;\n /** Provider badge shown after the id; empty for the Inherit row. */\n provider: string;\n /** Human-readable name shown in the footer line. */\n name: string;\n /** Whether this is a real model (vs. the Inherit sentinel). */\n isModel: boolean;\n}\n\nexport interface FabricModelSelectorChoice {\n value: string;\n label: string;\n description: string;\n current: boolean;\n}\n\nexport interface FabricModelSelectorOptions {\n theme: Theme;\n source: ModelSource;\n /** The currently configured canonical model key or \"Inherit\". */\n currentValue: string;\n onSelect: (value: string) => void;\n onCancel: () => void;\n /** Header line above the search input. Defaults to the global-default wording. */\n headerText?: string;\n /** Label shown for the unset/default row. Defaults to Inherit. */\n inheritLabel?: string;\n /** Description shown for the unset/default row's footer name. */\n inheritName?: string;\n}\n\n/**\n * A /model-style searchable model picker adapted for Fabric: same look\n * (search input, list with `[provider]` badges and a \u2713 on\n * the current row, scroll indicator, and a \"Model Name:\" footer) but it writes\n * the Fabric default-model setting instead of the host's default model, and\n * pins an unset/default row on top. Order respects pi-model-sort (most recently\n * used first); search filters by fuzzy match and re-sorts by recency, matching\n * pi-model-sort's patched /model behavior.\n */\nexport class FabricModelSelector extends Container implements Focusable {\n private readonly theme: Theme;\n private readonly allEntries: ModelEntry[];\n private filteredEntries: ModelEntry[];\n private readonly lastUsed: Record<string, number>;\n private readonly currentKey: string | null;\n private readonly currentValue: string;\n private selectedIndex = 0;\n private readonly searchInput: Input;\n private readonly listContainer = new Container();\n private readonly onSelectCallback: (value: string) => void;\n private readonly onCancelCallback: () => void;\n private readonly headerText: string;\n private readonly inheritLabel: string;\n private readonly inheritName: string;\n private _focused = false;\n\n constructor(options: FabricModelSelectorOptions) {\n super();\n this.theme = options.theme;\n this.lastUsed = options.source.lastUsed;\n this.currentValue = options.currentValue;\n this.currentKey = options.currentValue === INHERIT_VALUE ? null : options.currentValue;\n this.onSelectCallback = options.onSelect;\n this.onCancelCallback = options.onCancel;\n this.headerText =\n options.headerText ??\n \"Default model for Fabric agents and actors. Pick Inherit to use the host session's model.\";\n this.inheritLabel = options.inheritLabel ?? INHERIT_VALUE;\n this.inheritName = options.inheritName ?? \"Use the host session's default model\";\n\n this.allEntries = this.buildEntries(options.source.models);\n this.filteredEntries = this.allEntries;\n const current = this.allEntries.findIndex((entry) => entry.value === this.currentValue);\n this.selectedIndex = current >= 0 ? current : 0;\n\n this.addChild(\n new Text(\n this.theme.fg(\"muted\", this.headerText),\n 0,\n 0,\n ),\n );\n this.addChild(new Spacer(1));\n this.searchInput = new Input();\n this.searchInput.focused = true;\n this.searchInput.onSubmit = () => {\n const entry = this.filteredEntries[this.selectedIndex];\n if (entry) this.handleSelect(entry);\n };\n this.addChild(this.searchInput);\n this.addChild(new Spacer(1));\n this.addChild(this.listContainer);\n this.addChild(new Spacer(1));\n this.updateList();\n }\n\n get focused(): boolean {\n return this._focused;\n }\n\n set focused(value: boolean) {\n this._focused = value;\n this.searchInput.focused = value;\n }\n\n handleInput(keyData: string): void {\n const kb = getKeybindings();\n if (kb.matches(keyData, \"tui.select.up\")) {\n if (this.filteredEntries.length === 0) return;\n this.selectedIndex =\n this.selectedIndex === 0 ? this.filteredEntries.length - 1 : this.selectedIndex - 1;\n this.updateList();\n } else if (kb.matches(keyData, \"tui.select.down\")) {\n if (this.filteredEntries.length === 0) return;\n this.selectedIndex =\n this.selectedIndex === this.filteredEntries.length - 1 ? 0 : this.selectedIndex + 1;\n this.updateList();\n } else if (kb.matches(keyData, \"tui.select.confirm\")) {\n const entry = this.filteredEntries[this.selectedIndex];\n if (entry) this.handleSelect(entry);\n } else if (kb.matches(keyData, \"tui.select.cancel\")) {\n this.onCancelCallback();\n } else {\n this.searchInput.handleInput(keyData);\n this.filterModels(this.searchInput.getValue());\n }\n }\n\n rpcChoices(): FabricModelSelectorChoice[] {\n return this.allEntries.map((entry) => ({\n value: entry.value,\n label: entry.id,\n description: entry.isModel ? `${entry.provider} \u00B7 ${entry.name}` : entry.name,\n current: entry.value === this.currentValue,\n }));\n }\n\n selectRpc(value: string): boolean {\n const entry = this.allEntries.find((candidate) => candidate.value === value);\n if (!entry) return false;\n this.handleSelect(entry);\n return true;\n }\n\n private handleSelect(entry: ModelEntry): void {\n this.onSelectCallback(entry.value);\n }\n\n private buildEntries(models: ModelLike[]): ModelEntry[] {\n const sorted = sortByLastUsed(models, this.lastUsed, this.currentKey);\n const inherit: ModelEntry = {\n value: INHERIT_VALUE,\n id: this.inheritLabel,\n provider: \"\",\n name: this.inheritName,\n isModel: false,\n };\n const modelEntries: ModelEntry[] = sorted.map((model) => ({\n value: modelKey(model.provider, model.id),\n id: model.id,\n provider: model.provider,\n name: model.name ?? model.id,\n isModel: true,\n }));\n return [inherit, ...modelEntries];\n }\n\n /** Filter by fuzzy match, then re-sort by recency (mirrors pi-model-sort). */\n private sortEntries(entries: ModelEntry[]): ModelEntry[] {\n const inherit = entries.find((entry) => !entry.isModel);\n const models = entries.filter((entry) => entry.isModel);\n const sorted = sortByLastUsed(\n models.map((entry) => ({ provider: entry.provider, id: entry.id, entry })),\n this.lastUsed,\n this.currentKey,\n ).map((item) => item.entry);\n return inherit ? [inherit, ...sorted] : sorted;\n }\n\n private filterModels(query: string): void {\n const matches = query.trim()\n ? fuzzyFilter(this.allEntries, query, (entry) => this.searchText(entry))\n : this.allEntries;\n this.filteredEntries = this.sortEntries(matches);\n const current = this.filteredEntries.findIndex((entry) => entry.value === this.currentValue);\n this.selectedIndex =\n current >= 0\n ? current\n : Math.min(this.selectedIndex, Math.max(0, this.filteredEntries.length - 1));\n this.updateList();\n }\n\n /** Mirrors getModelSelectorSearchText from pi's /model selector. */\n private searchText(entry: ModelEntry): string {\n if (!entry.isModel) return `${entry.id} ${entry.name}`;\n return `${entry.provider} ${entry.provider}/${entry.id} ${entry.provider} ${entry.id} ${entry.name}`;\n }\n\n private updateList(): void {\n this.listContainer.clear();\n const maxVisible = 10;\n const total = this.filteredEntries.length;\n const startIndex = Math.max(\n 0,\n Math.min(this.selectedIndex - Math.floor(maxVisible / 2), total - maxVisible),\n );\n const endIndex = Math.min(startIndex + maxVisible, total);\n for (let i = startIndex; i < endIndex; i++) {\n const entry = this.filteredEntries[i];\n if (!entry) continue;\n const isSelected = i === this.selectedIndex;\n const isCurrent = entry.value === this.currentValue;\n const badge = entry.provider ? ` ${this.theme.fg(\"muted\", `[${entry.provider}]`)}` : \"\";\n const check = isCurrent ? this.theme.fg(\"success\", \" \u2713\") : \"\";\n const line = isSelected\n ? `${this.theme.fg(\"accent\", \"\\u2192 \")}${this.theme.fg(\"accent\", entry.id)}${badge}${check}`\n : ` ${entry.id}${badge}${check}`;\n this.listContainer.addChild(new Text(line, 0, 0));\n }\n if (startIndex > 0 || endIndex < total) {\n this.listContainer.addChild(\n new Text(this.theme.fg(\"muted\", ` (${this.selectedIndex + 1}/${total})`), 0, 0),\n );\n }\n if (total === 0) {\n this.listContainer.addChild(new Text(this.theme.fg(\"muted\", \" No matching models\"), 0, 0));\n } else {\n const selected = this.filteredEntries[this.selectedIndex];\n this.listContainer.addChild(new Spacer(1));\n this.listContainer.addChild(\n new Text(\n this.theme.fg(\"muted\", ` Model Name: ${selected ? selected.name : \"\"}`),\n 0,\n 0,\n ),\n );\n }\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  FabricModelSelector
3
- } from "./chunk-NFW7H77W.js";
3
+ } from "./chunk-VRYEGVHR.js";
4
4
  import {
5
5
  INHERIT_VALUE
6
6
  } from "./chunk-CQFP2RCP.js";
@@ -4567,4 +4567,4 @@ var FabricDashboard = class {
4567
4567
  export {
4568
4568
  FabricDashboard
4569
4569
  };
4570
- //# sourceMappingURL=dashboard-S3TINWGZ.js.map
4570
+ //# sourceMappingURL=dashboard-6HFBSZQ4.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  FabricModelSelector
3
- } from "./chunk-NFW7H77W.js";
3
+ } from "./chunk-VRYEGVHR.js";
4
4
  import {
5
5
  INHERIT_VALUE,
6
6
  buildClaudeModelSource,
@@ -378,6 +378,10 @@ var IntegerInputSubmenu = class extends Container {
378
378
  this.input.focused = true;
379
379
  return super.render(width);
380
380
  }
381
+ submitRpc(value) {
382
+ this.input.setValue(value);
383
+ this.input.handleInput("\r");
384
+ }
381
385
  };
382
386
  var StringInputSubmenu = class extends Container {
383
387
  input;
@@ -403,11 +407,17 @@ var StringInputSubmenu = class extends Container {
403
407
  this.input.focused = true;
404
408
  return super.render(width);
405
409
  }
410
+ submitRpc(value) {
411
+ this.input.setValue(value);
412
+ this.input.handleInput("\r");
413
+ }
406
414
  };
407
415
  var SelectSubmenu = class extends Container {
408
416
  selectList;
417
+ options;
409
418
  constructor(theme, title, description, options, currentValue, onSelect, onCancel) {
410
419
  super();
420
+ this.options = options;
411
421
  this.addChild(new Text(theme.bold(theme.fg("accent", title)), 0, 0));
412
422
  if (description) {
413
423
  this.addChild(new Spacer(1));
@@ -431,6 +441,12 @@ var SelectSubmenu = class extends Container {
431
441
  handleInput(data) {
432
442
  this.selectList.handleInput(data);
433
443
  }
444
+ selectRpc(value) {
445
+ const option = this.options.find((candidate) => candidate.value === value);
446
+ if (!option) return false;
447
+ this.selectList.onSelect?.(option);
448
+ return true;
449
+ }
434
450
  };
435
451
  var CompactionThresholdSubmenu = class extends Container {
436
452
  constructor(theme, currentValue, done) {
@@ -503,6 +519,9 @@ var CompactionThresholdSubmenu = class extends Container {
503
519
  handleInput(data) {
504
520
  this.active.handleInput(data);
505
521
  }
522
+ completeRpc(selectedValue) {
523
+ this.done(selectedValue);
524
+ }
506
525
  };
507
526
  var thinkingSubmenu = (theme, overrides = {}) => (currentValue, done) => {
508
527
  const canonicalCurrent = THINKING_LEVELS.find((level) => thinkingLabel(level) === currentValue) ?? currentValue;
@@ -543,8 +562,12 @@ var modelPickerSubmenu = (theme, source, options = {}) => (currentValue, done) =
543
562
  };
544
563
  var SectionSubmenu = class extends Container {
545
564
  settingsList;
565
+ items;
566
+ applyChange;
546
567
  constructor(theme, title, description, items, onChange, onCancel, enableSearch = false) {
547
568
  super();
569
+ this.items = items;
570
+ this.applyChange = onChange;
548
571
  this.addChild(new Text(theme.bold(theme.fg("accent", title)), 0, 0));
549
572
  if (description) {
550
573
  this.addChild(new Spacer(1));
@@ -1490,11 +1513,176 @@ var buildFabricSettingsItems = (theme, config, apply, options) => {
1490
1513
  ];
1491
1514
  return markDrillIn(items);
1492
1515
  };
1493
- async function openFabricSettings(context, deps) {
1494
- if (context.mode !== "tui") {
1495
- context.ui.notify("Fabric settings are available in TUI mode", "warning");
1516
+ var RPC_BACK = "\u2190 Back";
1517
+ var RPC_DONE = "Done";
1518
+ var RPC_SWITCH_SCOPE = "Switch save scope";
1519
+ var rpcTitle = (path, description) => description ? `${path}
1520
+ ${description}` : path;
1521
+ var cleanSettingLabel = (label) => label.replace(/\s+›$/, "");
1522
+ var rpcSettingRow = (item) => {
1523
+ const label = cleanSettingLabel(item.label);
1524
+ const current = item.currentValue ? ` \xB7 ${item.currentValue}` : "";
1525
+ return item.description ? `${label}${current} \u2014 ${item.description}` : `${label}${current}`;
1526
+ };
1527
+ var rpcChoiceRow = (choice) => {
1528
+ const current = choice.current ? " \xB7 Current" : "";
1529
+ return choice.description ? `${choice.label}${current} \u2014 ${choice.description}` : `${choice.label}${current}`;
1530
+ };
1531
+ var selectRpcChoice = async (context, title, choices) => {
1532
+ const rows = choices.map(rpcChoiceRow);
1533
+ const selected = await context.ui.select(title, rows);
1534
+ if (selected === void 0) return void 0;
1535
+ const index = rows.indexOf(selected);
1536
+ return index < 0 ? void 0 : choices[index]?.value;
1537
+ };
1538
+ var browseRpcSettings = async (context, path, description, items, onChange) => {
1539
+ while (true) {
1540
+ const rows = items.map(rpcSettingRow);
1541
+ const selected = await context.ui.select(rpcTitle(path, description), [...rows, RPC_BACK]);
1542
+ if (selected === void 0 || selected === RPC_BACK) return;
1543
+ const index = rows.indexOf(selected);
1544
+ const item = index < 0 ? void 0 : items[index];
1545
+ if (!item) continue;
1546
+ await editRpcSetting(context, `${path} \u203A ${cleanSettingLabel(item.label)}`, item, onChange);
1547
+ }
1548
+ };
1549
+ var editRpcSetting = async (context, path, item, onChange) => {
1550
+ if (!item.submenu) {
1551
+ const values = item.values ?? [];
1552
+ if (values.length === 0) {
1553
+ context.ui.notify(`${cleanSettingLabel(item.label)} is read-only`, "info");
1554
+ return;
1555
+ }
1556
+ const selected = await selectRpcChoice(
1557
+ context,
1558
+ rpcTitle(path, item.description),
1559
+ unique([item.currentValue, ...values]).map((value) => ({
1560
+ value,
1561
+ label: value,
1562
+ current: value === item.currentValue
1563
+ }))
1564
+ );
1565
+ if (selected === void 0) return;
1566
+ item.currentValue = selected;
1567
+ onChange(item.id, selected);
1496
1568
  return;
1497
1569
  }
1570
+ let completed = false;
1571
+ let selectedValue;
1572
+ const component = item.submenu(item.currentValue, (value) => {
1573
+ completed = true;
1574
+ selectedValue = value;
1575
+ });
1576
+ if (component instanceof SectionSubmenu) {
1577
+ await browseRpcSettings(
1578
+ context,
1579
+ path,
1580
+ item.description,
1581
+ component.items,
1582
+ (id, value) => {
1583
+ const child = component.items.find((candidate) => candidate.id === id);
1584
+ if (child) child.currentValue = value;
1585
+ component.applyChange(id, value);
1586
+ }
1587
+ );
1588
+ return;
1589
+ }
1590
+ if (component instanceof SelectSubmenu) {
1591
+ const selected = await selectRpcChoice(
1592
+ context,
1593
+ rpcTitle(path, item.description),
1594
+ component.options.map((option) => ({
1595
+ value: option.value,
1596
+ label: option.label,
1597
+ ...option.description ? { description: option.description } : {},
1598
+ current: option.value === item.currentValue || option.label === item.currentValue
1599
+ }))
1600
+ );
1601
+ if (selected === void 0 || !component.selectRpc(selected)) return;
1602
+ } else if (component instanceof IntegerInputSubmenu) {
1603
+ while (!completed) {
1604
+ const value = await context.ui.input(rpcTitle(path, item.description), component.input.getValue());
1605
+ if (value === void 0) return;
1606
+ if (!/^\d+$/.test(value.trim()) || !Number.isSafeInteger(Number(value.trim()))) {
1607
+ context.ui.notify("Enter a non-negative safe integer.", "warning");
1608
+ continue;
1609
+ }
1610
+ component.submitRpc(value);
1611
+ }
1612
+ } else if (component instanceof StringInputSubmenu) {
1613
+ const value = await context.ui.input(rpcTitle(path, item.description), component.input.getValue());
1614
+ if (value === void 0) return;
1615
+ component.submitRpc(value);
1616
+ } else if (component instanceof CompactionThresholdSubmenu) {
1617
+ const selected = await selectRpcChoice(
1618
+ context,
1619
+ rpcTitle(path, item.description),
1620
+ [
1621
+ { value: COMPACTION_DEFAULT_THRESHOLD_LABEL, label: COMPACTION_DEFAULT_THRESHOLD_LABEL, current: item.currentValue === COMPACTION_DEFAULT_THRESHOLD_LABEL },
1622
+ { value: COMPACTION_PERCENT_OPTION_LABEL, label: COMPACTION_PERCENT_OPTION_LABEL, current: item.currentValue.endsWith("%") },
1623
+ { value: COMPACTION_TOKENS_OPTION_LABEL, label: COMPACTION_TOKENS_OPTION_LABEL, current: item.currentValue.endsWith(" tokens") }
1624
+ ]
1625
+ );
1626
+ if (selected === void 0) return;
1627
+ if (selected === COMPACTION_DEFAULT_THRESHOLD_LABEL) {
1628
+ component.completeRpc(selected);
1629
+ } else {
1630
+ const percent = /^(\d+)%$/.exec(item.currentValue)?.[1] ?? "";
1631
+ const tokenText = /^(.+?) tokens$/.exec(item.currentValue)?.[1];
1632
+ const placeholder = selected === COMPACTION_PERCENT_OPTION_LABEL ? percent : tokenText === void 0 ? "" : String(parseFormattedNumericValue(tokenText));
1633
+ const input = await context.ui.input(rpcTitle(path, item.description), placeholder);
1634
+ if (input === void 0 || !/^\d+$/.test(input.trim())) return;
1635
+ const numeric = Number(input.trim());
1636
+ component.completeRpc(
1637
+ selected === COMPACTION_PERCENT_OPTION_LABEL ? `${clampCompactionPercentThreshold(numeric)}%` : `${formatTokens(clampCompactionTokenThreshold(numeric))} tokens`
1638
+ );
1639
+ }
1640
+ } else if (component instanceof FabricModelSelector) {
1641
+ const selected = await selectRpcChoice(
1642
+ context,
1643
+ rpcTitle(path, item.description),
1644
+ component.rpcChoices().map((choice) => ({
1645
+ value: choice.value,
1646
+ label: choice.label,
1647
+ description: choice.description,
1648
+ current: choice.current
1649
+ }))
1650
+ );
1651
+ if (selected === void 0 || !component.selectRpc(selected)) return;
1652
+ } else {
1653
+ context.ui.notify(`${cleanSettingLabel(item.label)} requires terminal UI`, "warning");
1654
+ return;
1655
+ }
1656
+ if (!completed || selectedValue === void 0) return;
1657
+ item.currentValue = selectedValue;
1658
+ onChange(item.id, selectedValue);
1659
+ };
1660
+ var openRpcFabricSettings = async (context, options) => {
1661
+ while (true) {
1662
+ const scope = options.getScope();
1663
+ const items = options.itemsForScope(scope);
1664
+ const rows = items.map(rpcSettingRow);
1665
+ const scopeDestination = scope === "project" ? "Project overrides (.pi/fabric.json)" : "Global defaults (~/.pi/agent/fabric.json)";
1666
+ const controls = [
1667
+ ...options.projectScopeAvailable ? [`${RPC_SWITCH_SCOPE} \xB7 ${scope === "project" ? "Global defaults" : "Project overrides"}`] : [],
1668
+ RPC_DONE
1669
+ ];
1670
+ const selected = await context.ui.select(
1671
+ rpcTitle("Fabric settings", `Editing: ${scopeDestination}`),
1672
+ [...rows, ...controls]
1673
+ );
1674
+ if (selected === void 0 || selected === RPC_DONE) return;
1675
+ if (selected.startsWith(RPC_SWITCH_SCOPE)) {
1676
+ options.setScope(scope === "project" ? "global" : "project");
1677
+ continue;
1678
+ }
1679
+ const index = rows.indexOf(selected);
1680
+ const item = index < 0 ? void 0 : items[index];
1681
+ if (!item) continue;
1682
+ await editRpcSetting(context, `Fabric settings \u203A ${cleanSettingLabel(item.label)}`, item, options.persist);
1683
+ }
1684
+ };
1685
+ async function openFabricSettings(context, deps) {
1498
1686
  await deps.state.ensure(context);
1499
1687
  const agentDir = resolveAgentDir();
1500
1688
  const projectTrusted = context.isProjectTrusted();
@@ -1556,36 +1744,51 @@ async function openFabricSettings(context, deps) {
1556
1744
  );
1557
1745
  }
1558
1746
  });
1559
- await context.ui.custom(
1560
- (tui, theme, _keybindings, done) => {
1561
- const itemsForScope = (scope) => {
1562
- settingsConfig = loadFabricConfigForScope(configLocation, scope);
1563
- return buildFabricSettingsItems(theme, settingsConfig, apply, {
1564
- keepVisibleCandidates,
1565
- modelSource,
1566
- claudeModelSource,
1567
- ...activeModelKey ? { activeModelKey } : {}
1568
- });
1569
- };
1570
- const component = new FabricSettingsComponent(
1571
- theme,
1572
- itemsForScope(saveScope),
1573
- persist,
1574
- () => done(),
1575
- {
1576
- initialSaveScope: saveScope,
1577
- projectScopeAvailable: projectTrusted,
1578
- onSaveScopeChange: (scope) => {
1579
- saveScope = scope;
1580
- tui.requestRender();
1581
- },
1582
- itemsForSaveScope: itemsForScope
1583
- }
1584
- );
1585
- rootComponent = component;
1586
- return component;
1587
- }
1588
- );
1747
+ const itemsForScope = (scope, theme) => {
1748
+ settingsConfig = loadFabricConfigForScope(configLocation, scope);
1749
+ return buildFabricSettingsItems(theme, settingsConfig, apply, {
1750
+ keepVisibleCandidates,
1751
+ modelSource,
1752
+ claudeModelSource,
1753
+ ...activeModelKey ? { activeModelKey } : {}
1754
+ });
1755
+ };
1756
+ if (context.mode === "rpc") {
1757
+ await openRpcFabricSettings(context, {
1758
+ projectScopeAvailable: projectTrusted,
1759
+ getScope: () => saveScope,
1760
+ setScope: (scope) => {
1761
+ saveScope = scope;
1762
+ },
1763
+ itemsForScope: (scope) => itemsForScope(scope, context.ui.theme),
1764
+ persist
1765
+ });
1766
+ } else if (context.mode !== "tui") {
1767
+ context.ui.notify("Fabric settings require an interactive UI", "warning");
1768
+ return;
1769
+ } else {
1770
+ await context.ui.custom(
1771
+ (tui, theme, _keybindings, done) => {
1772
+ const component = new FabricSettingsComponent(
1773
+ theme,
1774
+ itemsForScope(saveScope, theme),
1775
+ persist,
1776
+ () => done(),
1777
+ {
1778
+ initialSaveScope: saveScope,
1779
+ projectScopeAvailable: projectTrusted,
1780
+ onSaveScopeChange: (scope) => {
1781
+ saveScope = scope;
1782
+ tui.requestRender();
1783
+ },
1784
+ itemsForSaveScope: (scope) => itemsForScope(scope, theme)
1785
+ }
1786
+ );
1787
+ rootComponent = component;
1788
+ return component;
1789
+ }
1790
+ );
1791
+ }
1589
1792
  if (dirty) {
1590
1793
  deps.applyFabricMode();
1591
1794
  const needsReload = [...changedSections].some((section) => RELOAD_SECTIONS.has(section));
@@ -1609,4 +1812,4 @@ export {
1609
1812
  parseFormattedNumericValue,
1610
1813
  populateClaudeModelSource
1611
1814
  };
1612
- //# sourceMappingURL=settings-E2RINXBO.js.map
1815
+ //# sourceMappingURL=settings-J26PVVBO.js.map