@timurproko/a1 0.2.7-dev.1236 → 0.2.7-dev.1238

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/dist/contracts/owned-ui/session-workflows.d.ts +1 -1
  2. package/dist/features/owned-ui/settings-app.js +81 -11
  3. package/dist/integrations/pi/engine/default-model-setting.d.ts +12 -0
  4. package/dist/integrations/pi/engine/default-model-setting.js +70 -0
  5. package/dist/integrations/pi/engine/index.d.ts +1 -1
  6. package/dist/integrations/pi/engine/index.js +1 -1
  7. package/dist/integrations/pi/engine/offline-settings-port.js +1 -0
  8. package/dist/integrations/pi/engine/runtime-integration.d.ts +1 -1
  9. package/dist/integrations/pi/engine/runtime-integration.js +12 -14
  10. package/dist/integrations/pi/engine/settings-bridge.d.ts +1 -2
  11. package/dist/integrations/pi/engine/settings-bridge.js +23 -29
  12. package/dist/integrations/pi/engine/settings-port.js +2 -1
  13. package/dist/integrations/pi/engine/workflow-runner.js +2 -2
  14. package/dist/native/darwin-arm64/manifest.json +1 -1
  15. package/dist/native/darwin-arm64/terminal-host.manifest.json +3 -3
  16. package/dist/native/darwin-arm64/terminal-host.node +0 -0
  17. package/dist/native/darwin-x64/manifest.json +1 -1
  18. package/dist/native/darwin-x64/terminal-host.manifest.json +3 -3
  19. package/dist/native/darwin-x64/terminal-host.node +0 -0
  20. package/dist/native/linux-x64/manifest.json +1 -1
  21. package/dist/native/linux-x64/terminal-host.manifest.json +3 -3
  22. package/dist/native/linux-x64/terminal-host.node +0 -0
  23. package/dist/native/win32-x64/manifest.json +2 -2
  24. package/dist/native/win32-x64/process-guardian.node +0 -0
  25. package/dist/native/win32-x64/terminal-host.manifest.json +3 -3
  26. package/dist/native/win32-x64/terminal-host.node +0 -0
  27. package/dist/ui/components/dialog-panel.d.ts +6 -3
  28. package/dist/ui/components/dialog-panel.js +10 -7
  29. package/dist/ui/components/theme.d.ts +1 -1
  30. package/docs/architecture/ui-reference-provenance.md +3 -3
  31. package/package.json +3 -3
@@ -292,7 +292,7 @@ export type OwnedUiJsonValue = null | boolean | number | string | readonly Owned
292
292
  /** The layer that installs a setting's live effect. */
293
293
  export type OwnedUiSettingOwner = "agent" | "shell" | "terminal" | "startup" | "shutdown" | "installation";
294
294
  /** Engine settings whose live effect an owner may bind. */
295
- export type OwnedUiSessionSettingKey = "autoCompact" | "showImages" | "imageWidthCells" | "autoResizeImages" | "blockImages" | "enableSkillCommands" | "steeringMode" | "followUpMode" | "transport" | "httpIdleTimeoutMs" | "cacheWarmingMode" | "modelThinkingLevels" | "theme" | "hideThinkingBlock" | "mermaidRenderingMode" | "showCacheMissNotices" | "collapseChangelog" | "enableInstallTelemetry" | "quietStartup" | "defaultProjectTrust" | "sessionShareDestination" | "doubleEscapeAction" | "treeFilterMode" | "showHardwareCursor" | "editorPaddingX" | "outputPad" | "autocompleteMaxVisible" | "clearOnShrink" | "showTerminalProgress" | "tuiMode" | "fullscreenExitOutput" | "fullscreenScrollbar" | "fullscreenCopyOnSelect" | "fullscreenWheelScrollLines" | "warnings";
295
+ export type OwnedUiSessionSettingKey = "autoCompact" | "showImages" | "imageWidthCells" | "autoResizeImages" | "blockImages" | "enableSkillCommands" | "steeringMode" | "followUpMode" | "transport" | "httpIdleTimeoutMs" | "cacheWarmingMode" | "defaultModel" | "modelThinkingLevels" | "theme" | "hideThinkingBlock" | "mermaidRenderingMode" | "showCacheMissNotices" | "collapseChangelog" | "enableInstallTelemetry" | "quietStartup" | "defaultProjectTrust" | "sessionShareDestination" | "doubleEscapeAction" | "treeFilterMode" | "showHardwareCursor" | "editorPaddingX" | "outputPad" | "autocompleteMaxVisible" | "clearOnShrink" | "showTerminalProgress" | "tuiMode" | "fullscreenExitOutput" | "fullscreenScrollbar" | "fullscreenCopyOnSelect" | "fullscreenWheelScrollLines" | "warnings";
296
296
  export interface OwnedUiSettingEffectHandler {
297
297
  /** Install one value in the active owner. Handlers must be idempotent and reversible. */
298
298
  apply(value: OwnedUiJsonValue): void | Promise<void>;
@@ -281,7 +281,7 @@ export class SettingsApp {
281
281
  return { consumed: false };
282
282
  case "activate": {
283
283
  const row = rows[selected];
284
- if (row?.kind === "element" && row.value.structured)
284
+ if (row?.kind === "element" && (row.value.structured || isDefaultModelEntry(row.value)))
285
285
  this.#openStructured(row.value);
286
286
  else if (row?.kind === "element" && typeof this.#shownValue(row.value) === "number")
287
287
  this.#cycle(rows, selected, 1);
@@ -469,7 +469,7 @@ export class SettingsApp {
469
469
  if (row === undefined || row.kind !== "element")
470
470
  return;
471
471
  const entry = row.value;
472
- if (entry.structured) {
472
+ if (entry.structured || isDefaultModelEntry(entry)) {
473
473
  this.#openStructured(entry);
474
474
  return;
475
475
  }
@@ -508,9 +508,13 @@ export class SettingsApp {
508
508
  }
509
509
  // Rationale: a structured setting opens as its own flag list rather than a value menu.
510
510
  #openStructured(entry) {
511
+ const modelDialog = entry.backend === "agent" && entry.id === "modelThinkingLevels"
512
+ ? "thinking"
513
+ : isDefaultModelEntry(entry) ? "default" : null;
511
514
  // Invariant: the flags come from the declaration, not from the stored value: an unset
512
- // flag still has a row, showing the default the source would apply.
513
- if (entry.flags.length === 0)
515
+ // flag still has a row, showing the default the source would apply. The default-model
516
+ // selector may open empty so it can truthfully report that no authenticated models exist.
517
+ if (entry.flags.length === 0 && modelDialog !== "default")
514
518
  return;
515
519
  // Rationale: beside a tab bar, its own Plus Menu dialog configures `+`, so both ways look the same.
516
520
  if (entry.backend === "a1" && entry.id === PLUS_MENU_SETTING_ID && this.#options.configurePlusMenu?.() === true)
@@ -530,17 +534,18 @@ export class SettingsApp {
530
534
  this.#hoverKey = null;
531
535
  this.#hoverRegion = "label";
532
536
  this.#rails.clear();
533
- const modelDialog = entry.backend === "agent" && entry.id === "modelThinkingLevels";
537
+ const currentDefault = typeof entry.rawValue === "string" ? entry.rawValue : null;
534
538
  this.#structured = {
535
539
  entry,
536
540
  flags: entry.flags.map(flag => flag.key),
537
541
  modelDialog,
538
542
  plusMenu: entry.backend === "a1" && entry.id === PLUS_MENU_SETTING_ID ? new PlusMenuDialog(entry.flags) : null,
539
- filter: modelDialog ? new LineInput("") : null,
543
+ filter: modelDialog === null ? null : new LineInput(""),
540
544
  step: "model",
541
545
  modelKey: null,
542
546
  modelIndex: 0,
543
- index: 0,
547
+ index: modelDialog === "default" ? Math.max(0, entry.flags.findIndex(flag => flag.key === currentDefault)) : 0,
548
+ error: null,
544
549
  record,
545
550
  };
546
551
  }
@@ -548,7 +553,7 @@ export class SettingsApp {
548
553
  const open = this.#structured;
549
554
  if (open === null)
550
555
  return { consumed: false };
551
- if (open.modelDialog)
556
+ if (open.modelDialog !== null)
552
557
  return this.#modelDialogKey(open, data);
553
558
  if (open.plusMenu !== null) {
554
559
  const outcome = open.plusMenu.handle(KEYS[data] ?? data, data, key => this.#toggleFlag(open.flags.indexOf(key)));
@@ -578,6 +583,38 @@ export class SettingsApp {
578
583
  }
579
584
  #modelDialogKey(open, data) {
580
585
  const key = KEYS[data] ?? data;
586
+ if (open.modelDialog === "default") {
587
+ const flags = this.#filteredModelFlags(open);
588
+ const action = MODEL_SHORTCUTS.resolve(key, MODEL_SCOPE) ?? "edit-filter";
589
+ if (action === "close") {
590
+ this.#structured = null;
591
+ return { consumed: true };
592
+ }
593
+ if (action === "previous" || action === "next") {
594
+ open.index = Math.min(Math.max(0, flags.length - 1), Math.max(0, open.index + (action === "next" ? 1 : -1)));
595
+ return { consumed: true };
596
+ }
597
+ if (action === "select") {
598
+ const selected = flags[open.index];
599
+ if (selected === undefined)
600
+ return { consumed: true, render: false };
601
+ open.error = null;
602
+ this.#structured = null;
603
+ void this.#session.change(open.entry.backend, open.entry.id, selected.key).then(outcome => {
604
+ const failure = changeFailure(outcome);
605
+ if (failure !== null) {
606
+ open.error = `Could not save: ${failure}`;
607
+ this.#structured = open;
608
+ }
609
+ });
610
+ return { consumed: true };
611
+ }
612
+ if (open.filter === null)
613
+ return { consumed: true, render: false };
614
+ handleLineInputKey(open.filter, data);
615
+ open.index = 0;
616
+ return { consumed: true };
617
+ }
581
618
  if (open.step === "model") {
582
619
  const flags = this.#filteredModelFlags(open);
583
620
  const action = MODEL_SHORTCUTS.resolve(key, MODEL_SCOPE) ?? "edit-filter";
@@ -640,6 +677,7 @@ export class SettingsApp {
640
677
  const needle = open.filter?.value.trim().toLowerCase() ?? "";
641
678
  return open.entry.flags.filter(flag => needle.length === 0
642
679
  || (flag.label ?? humanizeLabel(flag.key)).toLowerCase().includes(needle)
680
+ || flag.description.toLowerCase().includes(needle)
643
681
  || flag.key.toLowerCase().includes(needle));
644
682
  }
645
683
  #modelLevelOptions(open, selected) {
@@ -897,7 +935,8 @@ export class SettingsApp {
897
935
  // Rationale: a section named by the search is what the reader asked for, so it arrives
898
936
  // whole rather than narrowed to the entries that happen to repeat its name.
899
937
  const named = needle.length > 0 && section.title.toLowerCase().includes(needle);
900
- const entries = named ? section.entries : section.entries.filter(matches);
938
+ const matching = named ? section.entries : section.entries.filter(matches);
939
+ const entries = orderDefaultModelBeforeThinking(matching, section.id);
901
940
  if (needle.length > 0 && entries.length === 0)
902
941
  continue;
903
942
  if (rows.length > 0)
@@ -947,7 +986,7 @@ export class SettingsApp {
947
986
  #viewRow(entry) {
948
987
  const key = `${entry.backend}:${entry.id}`;
949
988
  const shown = this.#shownValue(entry);
950
- const value = entry.structured
989
+ const value = entry.structured || isDefaultModelEntry(entry)
951
990
  ? CONFIGURE
952
991
  : shown === null
953
992
  ? describeRaw(entry.rawValue)
@@ -988,7 +1027,7 @@ export class SettingsApp {
988
1027
  return renderValueMenu(lines, state, frame, theme);
989
1028
  }
990
1029
  #dialogLines(open, width, height, theme) {
991
- if (open.modelDialog)
1030
+ if (open.modelDialog !== null)
992
1031
  return this.#modelDialogLines(open, width, height, theme);
993
1032
  if (open.plusMenu !== null)
994
1033
  return open.plusMenu.lines(open.record, width, theme);
@@ -1005,6 +1044,23 @@ export class SettingsApp {
1005
1044
  return renderDialogPanel({ title: labelOf(open.entry), rows: visible.rows, index: visible.index, hint: SETTINGS_SHORTCUTS.hintEntries(DIALOG_SCOPE) }, width, theme);
1006
1045
  }
1007
1046
  #modelDialogLines(open, width, height, theme) {
1047
+ if (open.modelDialog === "default") {
1048
+ const flags = this.#filteredModelFlags(open);
1049
+ const current = typeof open.entry.rawValue === "string" ? open.entry.rawValue : null;
1050
+ const rows = flags.length === 0
1051
+ ? [{ label: "No models found", value: "" }]
1052
+ : flags.map(flag => ({
1053
+ ...modelLabelParts(flag.label ?? humanizeLabel(flag.key)),
1054
+ ...(flag.key === current ? { successSuffix: "✓" } : {}),
1055
+ value: "",
1056
+ }));
1057
+ const description = open.error ?? "Select the model used for new sessions";
1058
+ const visible = dialogWindow(rows, open.index, configureDialogChoiceCapacity(height, true, configureDialogTextRows(description, width)));
1059
+ return renderSteppedDialogPanel({
1060
+ title: "Default Model", description, input: open.filter ?? new LineInput(""),
1061
+ rows: visible.rows, index: visible.index, hint: MODEL_SHORTCUTS.hintEntries(MODEL_SCOPE),
1062
+ }, width, theme);
1063
+ }
1008
1064
  if (open.step === "model") {
1009
1065
  const flags = this.#filteredModelFlags(open);
1010
1066
  const stored = structuredValue(open.entry, open.record);
@@ -1086,6 +1142,17 @@ function handleSettingsSearchPointer(state, event, panelTop) {
1086
1142
  const handled = input.handlePointer({ kind: event.kind, button: event.button, column: event.column, row });
1087
1143
  return { consumed: handled || overInput, render: handled };
1088
1144
  }
1145
+ function orderDefaultModelBeforeThinking(entries, sectionId) {
1146
+ if (sectionId !== "agent")
1147
+ return entries;
1148
+ const defaultModel = entries.find(isDefaultModelEntry);
1149
+ if (defaultModel === undefined)
1150
+ return entries;
1151
+ const ordered = entries.filter(entry => entry !== defaultModel);
1152
+ const thinking = ordered.findIndex(entry => entry.backend === "agent" && entry.id === "modelThinkingLevels");
1153
+ ordered.splice(thinking < 0 ? ordered.length : thinking, 0, defaultModel);
1154
+ return ordered;
1155
+ }
1089
1156
  function modelLabelParts(label) {
1090
1157
  const match = /^(.*?)(\s+\[[^\]]+\])$/u.exec(label);
1091
1158
  return match === null ? { label } : { label: match[1] ?? label, labelSuffix: match[2] ?? "" };
@@ -1139,6 +1206,9 @@ function changeFailure(outcome) {
1139
1206
  return outcome.limitationReason ?? "the setting is unavailable";
1140
1207
  return outcome.limitationReason;
1141
1208
  }
1209
+ function isDefaultModelEntry(entry) {
1210
+ return entry.backend === "agent" && entry.id === "defaultModel";
1211
+ }
1142
1212
  /** The source's own wording when it has one, otherwise the id made readable. */
1143
1213
  function labelOf(entry) {
1144
1214
  return entry.label ?? humanizeLabel(entry.id);
@@ -0,0 +1,12 @@
1
+ import type { SettingsManager } from "../startup-public.js";
2
+ import type { AgentSettingChangeOutcome, AgentSettingDescriptor } from "../../../contracts/agent-engine/index.js";
3
+ interface DefaultModelChoice {
4
+ readonly key: string;
5
+ readonly label: string;
6
+ readonly name: string;
7
+ }
8
+ export declare const DEFAULT_MODEL_SETTING_KEY = "defaultModel";
9
+ export declare const SUPPLEMENTAL_SETTING_KEYS: readonly ["defaultModel"];
10
+ export declare function defaultModelDescriptor(settings: SettingsManager, models: readonly DefaultModelChoice[]): AgentSettingDescriptor;
11
+ export declare function writeDefaultModel(settings: SettingsManager, models: readonly DefaultModelChoice[], value: unknown): Promise<AgentSettingChangeOutcome>;
12
+ export {};
@@ -0,0 +1,70 @@
1
+ export const DEFAULT_MODEL_SETTING_KEY = "defaultModel";
2
+ export const SUPPLEMENTAL_SETTING_KEYS = [DEFAULT_MODEL_SETTING_KEY];
3
+ const UNSET = "not set";
4
+ function read(settings) {
5
+ const provider = settings.getDefaultProvider();
6
+ const model = settings.getDefaultModel();
7
+ return provider && model ? `${provider}/${model}` : UNSET;
8
+ }
9
+ export function defaultModelDescriptor(settings, models) {
10
+ const value = read(settings);
11
+ return {
12
+ key: DEFAULT_MODEL_SETTING_KEY,
13
+ valueType: "enum",
14
+ writable: true,
15
+ application: "next-session",
16
+ owner: "startup",
17
+ available: true,
18
+ limitationReason: null,
19
+ storedValue: value,
20
+ effectiveValue: value,
21
+ choices: models.map(model => model.key),
22
+ flags: models.map(model => ({
23
+ key: model.key, label: model.label, description: model.name, fallback: false,
24
+ })),
25
+ label: "Default model",
26
+ description: "Model used when a new session starts",
27
+ resolvedWhenRead: true,
28
+ };
29
+ }
30
+ export async function writeDefaultModel(settings, models, value) {
31
+ if (typeof value !== "string" || !models.some(model => model.key === value)) {
32
+ throw new TypeError("setting value is invalid: defaultModel");
33
+ }
34
+ const previous = read(settings);
35
+ const at = value.indexOf("/");
36
+ settings.setDefaultModelAndProvider(value.slice(0, at), value.slice(at + 1));
37
+ try {
38
+ await settings.flush();
39
+ const errors = settings.drainErrors();
40
+ if (errors.length > 0)
41
+ throw new Error(errors.map(item => item.error.message).join("; "));
42
+ return {
43
+ status: "deferred", application: "next-session", storedValue: value, effectiveValue: previous,
44
+ failure: null, limitationReason: null,
45
+ };
46
+ }
47
+ catch (error) {
48
+ let failure = error instanceof Error ? error.message : String(error);
49
+ try {
50
+ if (previous === UNSET)
51
+ await settings.reload();
52
+ else {
53
+ const split = previous.indexOf("/");
54
+ settings.setDefaultModelAndProvider(previous.slice(0, split), previous.slice(split + 1));
55
+ await settings.flush();
56
+ const errors = settings.drainErrors();
57
+ if (errors.length > 0)
58
+ throw new Error(errors.map(item => item.error.message).join("; "));
59
+ }
60
+ }
61
+ catch (rollbackError) {
62
+ failure += `; persistence rollback failed: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`;
63
+ }
64
+ const restored = read(settings);
65
+ return {
66
+ status: "failed", application: "next-session", storedValue: restored, effectiveValue: previous,
67
+ failure, limitationReason: null,
68
+ };
69
+ }
70
+ }
@@ -10,7 +10,7 @@ export { PiSessionSelectionError, openSelectedPiSession, resolveSessionArgumentP
10
10
  export type { PiSessionForkPrompt, PiSessionSelection } from "./session-selection.js";
11
11
  export { PiSessionCommandIntegration } from "./session-integration.js";
12
12
  export type { PiDocumentedSessionCommands, PiSessionCommand, PiSessionCommandResult } from "./session-integration.js";
13
- export { AUTOMATIC_THEME, EXPOSED_SETTING_KEYS, PI_SETTING_EFFECTS, PiSettingsBridge, PiSettingsCoordinator, parseAutomaticTheme, settingsEffectInventoryDrift, settingsInventoryDrift, settingsVisualInventoryViolations, } from "./settings-bridge.js";
13
+ export { AUTOMATIC_THEME, EXPOSED_SETTING_KEYS, PI_SETTING_EFFECTS, PiSettingsBridge, PiSettingsCoordinator, parseAutomaticTheme, settingsEffectInventoryDrift, settingsInventoryDrift, } from "./settings-bridge.js";
14
14
  export { assertPiSettingsMetadata, loadPiSettingsMetadata, PI_SETTINGS_METADATA_FILE } from "./settings-metadata.js";
15
15
  export type { PiSettingBounds, PiSettingPresentation, PiSettingsMetadata } from "./settings-metadata.js";
16
16
  export type { PiSettingEffectDefinition, PiSettingEffectHandler, PiSettingKey, PiSettingOwnerHandlers, PiSettingStorageOperation, PiSettingVisualClass, PiSettingVisualEvidence, PiSettingsCoordinatorOptions, PiSettingsModelChoice, PiSettingsProviders, } from "./settings-bridge.js";
@@ -4,7 +4,7 @@ export { detachedEngineHostPorts } from "./host-ports.js";
4
4
  export { bindPiRuntimeSession, createPiRuntimeIntegration, createPiRuntimeServicesAfterTrust, disposePiRuntimeIntegration, replacePiRuntimeSession, resolveConfiguredModelScope, } from "./runtime-integration.js";
5
5
  export { PiSessionSelectionError, openSelectedPiSession, resolveSessionArgumentPath } from "./session-selection.js";
6
6
  export { PiSessionCommandIntegration } from "./session-integration.js";
7
- export { AUTOMATIC_THEME, EXPOSED_SETTING_KEYS, PI_SETTING_EFFECTS, PiSettingsBridge, PiSettingsCoordinator, parseAutomaticTheme, settingsEffectInventoryDrift, settingsInventoryDrift, settingsVisualInventoryViolations, } from "./settings-bridge.js";
7
+ export { AUTOMATIC_THEME, EXPOSED_SETTING_KEYS, PI_SETTING_EFFECTS, PiSettingsBridge, PiSettingsCoordinator, parseAutomaticTheme, settingsEffectInventoryDrift, settingsInventoryDrift, } from "./settings-bridge.js";
8
8
  export { assertPiSettingsMetadata, loadPiSettingsMetadata, PI_SETTINGS_METADATA_FILE } from "./settings-metadata.js";
9
9
  export { resolvePiProjectTrustPreflight } from "./project-trust-preflight.js";
10
10
  export { configureOwnedHttpDispatcher, configuredOwnedHttpIdleTimeoutMs } from "./http-dispatcher.js";
@@ -40,6 +40,7 @@ function modelChoices(models, overrides) {
40
40
  return {
41
41
  key,
42
42
  label: `${model.id} [${model.provider}]`,
43
+ name: model.name ?? model.id,
43
44
  description: overrides[key] === undefined ? "global default" : `override: ${overrides[key]}`,
44
45
  levels: supportedThinkingLevels(model),
45
46
  };
@@ -41,7 +41,7 @@ interface ConfiguredModelScope {
41
41
  * Mirrors pinned Pi's CLI startup: resolve the `models` patterns from settings
42
42
  * into a scoped model list, keep the resolver's warnings (e.g. "No models match
43
43
  * pattern ..."), and pick the same initial model pinned Pi would pick — the
44
- * saved default when it is in scope, otherwise the first scoped model.
44
+ * saved authenticated default, otherwise the first scoped model.
45
45
  */
46
46
  export declare function resolveConfiguredModelScope(services: Pick<AgentSessionServices, "settingsManager" | "modelRuntime">, signal?: AbortSignal): Promise<ConfiguredModelScope>;
47
47
  export declare function createPiRuntimeServicesAfterTrust(options: Pick<PiRuntimeIntegrationOptions, "agentDir" | "projectTrustPrompt" | "preflightDependencies" | "markStartupPhase"> & {
@@ -62,7 +62,7 @@ const runtimeSessionWriters = new WeakMap();
62
62
  * Mirrors pinned Pi's CLI startup: resolve the `models` patterns from settings
63
63
  * into a scoped model list, keep the resolver's warnings (e.g. "No models match
64
64
  * pattern ..."), and pick the same initial model pinned Pi would pick — the
65
- * saved default when it is in scope, otherwise the first scoped model.
65
+ * saved authenticated default, otherwise the first scoped model.
66
66
  */
67
67
  export async function resolveConfiguredModelScope(services, signal) {
68
68
  const patterns = services.settingsManager.getEnabledModels();
@@ -70,21 +70,19 @@ export async function resolveConfiguredModelScope(services, signal) {
70
70
  return { scopedModels: [], model: undefined, thinkingLevel: undefined, diagnostics: [] };
71
71
  }
72
72
  const { scopedModels, diagnostics } = await resolveModelScopeWithDiagnostics([...patterns], services.modelRuntime, { signal: signal === undefined ? AbortSignal.timeout(15_000) : AbortSignal.any([signal, AbortSignal.timeout(15_000)]) });
73
- let selected;
74
- if (scopedModels.length > 0) {
75
- const savedProvider = services.settingsManager.getDefaultProvider();
76
- const savedModelId = services.settingsManager.getDefaultModel();
77
- const savedModel = savedProvider && savedModelId
78
- ? services.modelRuntime.getModel(savedProvider, savedModelId)
79
- : undefined;
80
- selected = (savedModel
81
- ? scopedModels.find(scoped => scoped.model.provider === savedModel.provider && scoped.model.id === savedModel.id)
82
- : undefined) ?? scopedModels[0];
83
- }
73
+ const savedProvider = services.settingsManager.getDefaultProvider();
74
+ const savedModelId = services.settingsManager.getDefaultModel();
75
+ const savedModel = savedProvider && savedModelId
76
+ ? services.modelRuntime.getAvailableSnapshot().find(model => model.provider === savedProvider && model.id === savedModelId)
77
+ : undefined;
78
+ const fallback = scopedModels[0];
84
79
  return {
85
80
  scopedModels,
86
- model: selected?.model,
87
- thinkingLevel: selected?.thinkingLevel,
81
+ model: savedModel ?? fallback?.model,
82
+ thinkingLevel: savedModel
83
+ ? services.settingsManager.getModelThinkingLevel(savedModel.provider, savedModel.id)
84
+ ?? services.settingsManager.getDefaultThinkingLevel()
85
+ : fallback?.thinkingLevel,
88
86
  diagnostics: diagnostics.map(diagnostic => ({ type: diagnostic.type, message: diagnostic.message })),
89
87
  };
90
88
  }
@@ -65,11 +65,9 @@ export declare function settingsEffectInventoryDrift(presented: readonly string[
65
65
  readonly stale: readonly string[];
66
66
  readonly duplicated: readonly string[];
67
67
  };
68
- export declare function settingsVisualInventoryViolations(presented: readonly string[], reviewed?: Readonly<Partial<Record<PiSettingKey, PiSettingEffectDefinition>>>): readonly string[];
69
68
  /** Values only the running engine knows. */
70
69
  export interface PiSettingsProviders {
71
70
  readonly themes?: () => readonly string[];
72
- /** Models a per-model thinking override may name, with the levels each one supports. */
73
71
  readonly models?: () => readonly PiSettingsModelChoice[];
74
72
  readonly productMode?: "bare" | "comparison";
75
73
  }
@@ -77,6 +75,7 @@ export interface PiSettingsModelChoice {
77
75
  /** `provider/modelId`, the key the engine stores the override under. */
78
76
  readonly key: string;
79
77
  readonly label: string;
78
+ readonly name: string;
80
79
  readonly description: string;
81
80
  readonly levels: readonly string[];
82
81
  }
@@ -15,6 +15,7 @@ export const PI_SETTING_EFFECTS = Object.freeze({
15
15
  transport: effect("live", "agent", "status-error", "pinned provider request status and failure rows", "settings-effects-provider-parity"),
16
16
  httpIdleTimeoutMs: effect("live", "agent", "retry-error", "pinned timeout, retry, and terminal failure rows", "pinned-transcript-lifecycle-parity"),
17
17
  cacheWarmingMode: effect("live", "agent", "footer-transcript", "pinned cache warming notices and session usage rows", "pinned-transcript-lifecycle-parity"),
18
+ defaultModel: effect("next-session", "startup", "selector", "pinned default-model selector", "owned-default-model-setting"),
18
19
  modelThinkingLevels: effect("live", "agent", "footer-transcript", "pinned per-model thinking override, footer indicator, and thinking rows", "pinned-status-indicator-parity"),
19
20
  theme: hiddenEffect("live", "shell", "pinned theme selector and complete themed shell", "pinned-theme-parity"),
20
21
  hideThinkingBlock: effect("live", "shell", "transcript", "pinned thinking block presence and spacing", "pinned-transcript-lifecycle-parity"),
@@ -225,27 +226,6 @@ export function settingsEffectInventoryDrift(presented, reviewed = Object.keys(P
225
226
  duplicated: [...counts].filter(([, count]) => count > 1).map(([key]) => key),
226
227
  };
227
228
  }
228
- export function settingsVisualInventoryViolations(presented, reviewed = PI_SETTING_EFFECTS) {
229
- const violations = [];
230
- for (const candidate of presented) {
231
- const key = candidate;
232
- const definition = reviewed[key];
233
- if (definition === undefined) {
234
- violations.push(`${candidate}: missing visual classification`);
235
- continue;
236
- }
237
- const visual = definition.visual;
238
- if (!visual || visual.pinnedSurface.trim().length === 0)
239
- violations.push(`${candidate}: missing pinned visual source`);
240
- if (!visual || visual.evidence.trim().length === 0)
241
- violations.push(`${candidate}: missing independent visual evidence`);
242
- if (definition.hiddenInBare === true && visual?.class !== "hidden")
243
- violations.push(`${candidate}: hidden setting declares a visible frame`);
244
- if (definition.hiddenInBare !== true && visual?.class === "hidden")
245
- violations.push(`${candidate}: visible setting declares hidden evidence`);
246
- }
247
- return violations;
248
- }
249
229
  function outcome(status, application, storedValue, effectiveValue, failure, limitationReason) {
250
230
  return { status, application, storedValue, effectiveValue, failure, limitationReason };
251
231
  }
@@ -271,6 +251,7 @@ export const AUTOMATIC_THEME = "automatic";
271
251
  const LIGHT_APPEARANCE_THEME = "light";
272
252
  const DARK_APPEARANCE_THEME = "dark";
273
253
  const THEME_KEY = "theme";
254
+ const DEFAULT_MODEL_KEY = "defaultModel";
274
255
  const MODEL_THINKING_KEY = "modelThinkingLevels";
275
256
  export function parseAutomaticTheme(stored) {
276
257
  const at = stored.indexOf("/");
@@ -307,13 +288,16 @@ export class PiSettingsBridge {
307
288
  capabilities = { write: true, flush: true };
308
289
  #operations;
309
290
  #providers;
291
+ #settings;
310
292
  #coordinator;
311
293
  constructor(settings, providers = {}) {
312
294
  this.#providers = providers;
295
+ this.#settings = settings;
313
296
  const mapped = operations(settings, providers);
314
297
  this.#operations = new Map(mapped.map(operation => [operation.key, operation]));
315
298
  const presentationDrift = settingsInventoryDrift(EXPOSED_SETTING_KEYS, mapped.map(operation => operation.key));
316
- const effectDrift = settingsEffectInventoryDrift(EXPOSED_SETTING_KEYS);
299
+ const generatedEffects = Object.keys(PI_SETTING_EFFECTS).filter(key => key !== DEFAULT_MODEL_KEY);
300
+ const effectDrift = settingsEffectInventoryDrift(EXPOSED_SETTING_KEYS, generatedEffects);
317
301
  if (presentationDrift.unmapped.length > 0 || presentationDrift.stale.length > 0
318
302
  || effectDrift.unmapped.length > 0 || effectDrift.stale.length > 0 || effectDrift.duplicated.length > 0) {
319
303
  throw new Error([
@@ -331,7 +315,8 @@ export class PiSettingsBridge {
331
315
  });
332
316
  }
333
317
  bindOwner(owner, handlers) {
334
- return this.#coordinator.bindOwner(owner, handlers);
318
+ const { defaultModel: _defaultModel, ...generated } = handlers;
319
+ return this.#coordinator.bindOwner(owner, generated);
335
320
  }
336
321
  unbindOwner(owner) {
337
322
  this.#coordinator.unbindOwner(owner);
@@ -341,14 +326,14 @@ export class PiSettingsBridge {
341
326
  const at = PRESENTATION.order.indexOf(key);
342
327
  return at < 0 ? PRESENTATION.order.length : at;
343
328
  };
344
- return [...this.#operations.values()]
329
+ const descriptors = [...this.#operations.values()]
345
330
  .filter(operation => this.#coordinator.available(operation.key))
346
331
  .map(operation => {
347
332
  const key = operation.key;
348
333
  const wording = PRESENTATION.settings[key];
349
334
  const flags = PRESENTATION.dialogs[key];
350
335
  const bounds = PRESENTATION.bounds[key];
351
- const descriptor = {
336
+ return this.#resolved({
352
337
  ...operation.descriptor,
353
338
  application: PI_SETTING_EFFECTS[key].application,
354
339
  owner: PI_SETTING_EFFECTS[key].owner,
@@ -360,10 +345,11 @@ export class PiSettingsBridge {
360
345
  ...(bounds === undefined ? {} : bounds),
361
346
  ...(wording === undefined ? {} : { label: wording.label, description: wording.description }),
362
347
  ...(flags === undefined ? {} : { flags }),
363
- };
364
- return this.#resolved(descriptor);
365
- })
366
- .sort((left, right) => rank(left.key) - rank(right.key));
348
+ });
349
+ });
350
+ const { defaultModelDescriptor } = await import("./default-model-setting.js");
351
+ descriptors.push(defaultModelDescriptor(this.#settings, this.#providers.models?.() ?? []));
352
+ return descriptors.sort((left, right) => rank(left.key) - rank(right.key));
367
353
  }
368
354
  #resolved(descriptor) {
369
355
  if (descriptor.key === THEME_KEY) {
@@ -387,9 +373,17 @@ export class PiSettingsBridge {
387
373
  return this.#providers.themes?.() ?? [];
388
374
  }
389
375
  async readSetting(key) {
376
+ if (key === DEFAULT_MODEL_KEY) {
377
+ const { defaultModelDescriptor } = await import("./default-model-setting.js");
378
+ return defaultModelDescriptor(this.#settings, this.#providers.models?.() ?? []).storedValue;
379
+ }
390
380
  return isPiSettingKey(key) && this.#operations.has(key) ? this.#coordinator.storedValue(key) : undefined;
391
381
  }
392
382
  async writeSetting(key, value) {
383
+ if (key === DEFAULT_MODEL_KEY) {
384
+ const { writeDefaultModel } = await import("./default-model-setting.js");
385
+ return await writeDefaultModel(this.#settings, this.#providers.models?.() ?? [], value);
386
+ }
393
387
  if (!isPiSettingKey(key) || !this.#operations.has(key))
394
388
  throw new Error(`setting is unavailable: ${key}`);
395
389
  return await this.#coordinator.apply(key, value);
@@ -77,7 +77,7 @@ export class PiEngineSettings {
77
77
  });
78
78
  this.#settingsIntegration.bindOwner("startup", {
79
79
  // Invariant: deferred application is the owner operation: the next preflight reads
80
- // the persisted default before constructing project-backed services.
80
+ // the persisted defaults before constructing project-backed services.
81
81
  defaultProjectTrust: { apply() { } },
82
82
  collapseChangelog: { apply() { } },
83
83
  });
@@ -167,6 +167,7 @@ export class PiEngineSettings {
167
167
  return {
168
168
  key,
169
169
  label: `${model.id} [${model.provider}]`,
170
+ name: model.name,
170
171
  description: overrides[key] === undefined ? "global default" : `override: ${overrides[key]}`,
171
172
  levels: supportedThinkingLevels(model),
172
173
  };
@@ -333,13 +333,13 @@ export class PiWorkflowRunner {
333
333
  });
334
334
  }
335
335
  case "models": {
336
- // Invariant: the bare dialog seeds its query from the argument; only an explicit selection switches models.
336
+ // Invariant: the bare dialog seeds its query from the argument; only an explicit selection switches the current session.
337
337
  if (!selection)
338
338
  return workflowResult(request.command, "requires-selection", "Select a model", argument || undefined, "silent");
339
339
  return this.#selectModel(request, selection, {
340
340
  pool: [...(runtime.services.modelRuntime.getAvailableSnapshot?.() ?? [])],
341
341
  refreshOnMiss: true,
342
- persist: true,
342
+ persist: false,
343
343
  onMiss: "failed",
344
344
  });
345
345
  }
@@ -7,7 +7,7 @@
7
7
  "platform": "darwin",
8
8
  "architecture": "arm64",
9
9
  "capability": "supported",
10
- "builtAt": "2026-11-03T12:34:41.201Z",
10
+ "builtAt": "2026-11-03T17:03:50.345Z",
11
11
  "artifact": {
12
12
  "filename": "process-guardian.node",
13
13
  "sha256": "a87eaaffcd986b624c8b044b756446cc1bdaf4334a1775b97820542fe34f9fb7",
@@ -9,11 +9,11 @@
9
9
  "architecture": "arm64",
10
10
  "target": "aarch64-apple-darwin",
11
11
  "capability": "supported",
12
- "builtAt": "2026-11-03T12:38:29.199Z",
12
+ "builtAt": "2026-11-03T17:07:06.927Z",
13
13
  "artifact": {
14
14
  "filename": "terminal-host.node",
15
- "sha256": "f340571d5eef45a551e7c53f8c31d91dd75c948fe11c97bda0c23a2d9bbfa2a4",
16
- "size": 6585248
15
+ "sha256": "1a0b3f286201df724ebed4c0596f0fe7d8ab5d27dea36c588dc4f4a60049995f",
16
+ "size": 6585632
17
17
  },
18
18
  "entry": {
19
19
  "filename": "dist/foundation/resident-tabs/host-entry.js",
@@ -7,7 +7,7 @@
7
7
  "platform": "darwin",
8
8
  "architecture": "x64",
9
9
  "capability": "supported",
10
- "builtAt": "2026-11-03T12:34:49.251Z",
10
+ "builtAt": "2026-11-03T17:03:58.940Z",
11
11
  "artifact": {
12
12
  "filename": "process-guardian.node",
13
13
  "sha256": "783a65f0de287a1c02cd5429dbe97bc8633f4cc8a7b92e3e961180b9bdcff814",
@@ -9,11 +9,11 @@
9
9
  "architecture": "x64",
10
10
  "target": "x86_64-apple-darwin",
11
11
  "capability": "supported",
12
- "builtAt": "2026-11-03T12:41:50.549Z",
12
+ "builtAt": "2026-11-03T17:09:41.020Z",
13
13
  "artifact": {
14
14
  "filename": "terminal-host.node",
15
- "sha256": "92cceb0cf8a8b955b86762fc677396586f0e32569923827a96281fc5a29a3add",
16
- "size": 7173680
15
+ "sha256": "1d003bf193eb4c54439e711427faef94af2c395dd2a23cfb1497bb2c3ab4ad67",
16
+ "size": 7178048
17
17
  },
18
18
  "entry": {
19
19
  "filename": "dist/foundation/resident-tabs/host-entry.js",
@@ -7,7 +7,7 @@
7
7
  "platform": "linux",
8
8
  "architecture": "x64",
9
9
  "capability": "supported",
10
- "builtAt": "2026-11-03T12:33:48.123Z",
10
+ "builtAt": "2026-11-03T17:04:12.745Z",
11
11
  "artifact": {
12
12
  "filename": "process-guardian.node",
13
13
  "sha256": "118e5954e3864672bd8482ab7edf419cb27dcdf2533720316979b7f5cc5a53a5",
@@ -9,11 +9,11 @@
9
9
  "architecture": "x64",
10
10
  "target": "x86_64-unknown-linux-gnu",
11
11
  "capability": "supported",
12
- "builtAt": "2026-11-03T12:37:02.302Z",
12
+ "builtAt": "2026-11-03T17:06:54.055Z",
13
13
  "artifact": {
14
14
  "filename": "terminal-host.node",
15
- "sha256": "e5d97e8f613e7ca960f541861f9ae61435fb5e1cff1a5e80e34000665e5afecf",
16
- "size": 7954680
15
+ "sha256": "fb9f0498986293e8dbed8a2b043313bf78a07777f6a1eb0a4b62e82365a71110",
16
+ "size": 7959248
17
17
  },
18
18
  "entry": {
19
19
  "filename": "dist/foundation/resident-tabs/host-entry.js",
@@ -7,10 +7,10 @@
7
7
  "platform": "win32",
8
8
  "architecture": "x64",
9
9
  "capability": "supported",
10
- "builtAt": "2026-11-03T12:34:57.755Z",
10
+ "builtAt": "2026-11-03T17:04:32.725Z",
11
11
  "artifact": {
12
12
  "filename": "process-guardian.node",
13
- "sha256": "037cc7426ed420a010993dcd7fda7001707fc87d0c6104f93c01e32c789bd748",
13
+ "sha256": "3fb5f440a2febbb9c84e1d7761a785f0d275db0d689cecf175c6c3de47f6b50d",
14
14
  "size": 402432
15
15
  },
16
16
  "provenance": {
@@ -9,11 +9,11 @@
9
9
  "architecture": "x64",
10
10
  "target": "x86_64-pc-windows-msvc",
11
11
  "capability": "supported",
12
- "builtAt": "2026-11-03T12:36:35.360Z",
12
+ "builtAt": "2026-11-03T17:06:43.680Z",
13
13
  "artifact": {
14
14
  "filename": "terminal-host.node",
15
- "sha256": "2dd46801648d082efbf5c6122ed76a059a6fee052ca0364dea517ddb3f1f6ba9",
16
- "size": 7071232
15
+ "sha256": "5592caa99ee4c96544abaceeb025f833c1d8566529e3a7a8ecad51f8aa13e752",
16
+ "size": 7077888
17
17
  },
18
18
  "entry": {
19
19
  "filename": "dist/foundation/resident-tabs/host-entry.js",
@@ -9,6 +9,8 @@ import type { UiTheme } from "./theme.js";
9
9
  export interface DialogRow {
10
10
  readonly label: string;
11
11
  readonly labelSuffix?: string;
12
+ /** Success-colored marker rendered after the complete label. */
13
+ readonly successSuffix?: string;
12
14
  readonly value: string;
13
15
  /** What this part does, shown while it is the one in hand. */
14
16
  readonly description?: string;
@@ -31,8 +33,9 @@ export interface DialogPanelFrame {
31
33
  }
32
34
  export interface SteppedDialogPanelState {
33
35
  readonly title: string;
34
- readonly step: number;
35
- readonly steps: number;
36
+ /** Omitted for a one-step searchable selector whose title needs no step marker. */
37
+ readonly step?: number;
38
+ readonly steps?: number;
36
39
  readonly description: string;
37
40
  readonly input?: LineInput;
38
41
  readonly rows: readonly DialogRow[];
@@ -55,7 +58,7 @@ export declare function renderCenteredDialogOverlay(frame: readonly string[], di
55
58
  export declare function dialogValueColumn(rows: readonly DialogRow[]): number;
56
59
  /** The semantic rows for one generic configure modal. */
57
60
  export declare function renderDialogPanel(state: DialogPanelState, width: number, theme: UiTheme): readonly string[];
58
- /** Owned stepped-selector content for structured settings with nested choices. */
61
+ /** Owned searchable selector content, with an optional step marker for nested choices. */
59
62
  export declare function renderSteppedDialogPanel(state: SteppedDialogPanelState, width: number, theme: UiTheme): readonly string[];
60
63
  /** The row a pointer report lands on, or null when it is not on one. */
61
64
  export declare function dialogRowAt(frame: DialogPanelFrame, row: number, column: number, valueWidth: number): number | null;
@@ -99,18 +99,19 @@ export function renderDialogPanel(state, width, theme) {
99
99
  layout.blank,
100
100
  ];
101
101
  }
102
- /** Owned stepped-selector content for structured settings with nested choices. */
102
+ /** Owned searchable selector content, with an optional step marker for nested choices. */
103
103
  export function renderSteppedDialogPanel(state, width, theme) {
104
104
  const layout = dialogPanelLayout(width, theme);
105
- const labelColumn = Math.min(STEPPED_LABEL_COLUMN_CAP, Math.max(0, ...state.rows.map(row => displayWidth(`${row.label}${row.labelSuffix ?? ""}`))));
105
+ const labelColumn = Math.min(STEPPED_LABEL_COLUMN_CAP, Math.max(0, ...state.rows.map(row => displayWidth(`${row.label}${row.labelSuffix ?? ""}${row.successSuffix === undefined ? "" : ` ${row.successSuffix}`}`))));
106
106
  const rows = state.rows.map((row, index) => {
107
107
  const selected = index === state.index;
108
- const completeLabel = `${row.label}${row.labelSuffix ?? ""}`;
108
+ const success = row.successSuffix === undefined ? "" : ` ${row.successSuffix}`;
109
+ const completeLabel = `${row.label}${row.labelSuffix ?? ""}${success}`;
109
110
  const padding = " ".repeat(Math.max(0, labelColumn - displayWidth(completeLabel)));
110
111
  const suffix = row.value.length === 0 ? "" : ` ${row.value}`;
111
112
  const cursor = selected ? "→ " : " ";
112
113
  const raw = `${cursor}${completeLabel}${padding}${suffix}`;
113
- const label = `${theme.fg("text", row.label)}${theme.fg("muted", row.labelSuffix ?? "")}${padding}`;
114
+ const label = `${theme.fg("text", row.label)}${theme.fg("muted", row.labelSuffix ?? "")}${theme.fg("success", success)}${padding}`;
114
115
  const content = selected
115
116
  ? `${theme.fg("accent", cursor)}${label}${theme.fg("muted", suffix)}`
116
117
  : `${cursor}${label}${theme.fg("muted", suffix)}`;
@@ -128,9 +129,11 @@ export function renderSteppedDialogPanel(state, width, theme) {
128
129
  layout.blank,
129
130
  ];
130
131
  const descriptionRows = wrapText(state.description, layout.contentWidth);
131
- const step = `(step ${state.step}/${state.steps})`;
132
- const title = `${state.title} ${step}`;
133
- const paintedTitle = `${theme.bold(theme.fg("accent", state.title))} ${theme.fg("dim", step)}`;
132
+ const step = state.step === undefined || state.steps === undefined ? "" : `(step ${state.step}/${state.steps})`;
133
+ const title = step.length === 0 ? state.title : `${state.title} ${step}`;
134
+ const paintedTitle = step.length === 0
135
+ ? theme.bold(theme.fg("accent", state.title))
136
+ : `${theme.bold(theme.fg("accent", state.title))} ${theme.fg("dim", step)}`;
134
137
  return [
135
138
  layout.blank,
136
139
  layout.contentRow(paintedTitle, title),
@@ -1,4 +1,4 @@
1
- export type UiThemeToken = "accent" | "mdHeading" | "text" | "muted" | "dim" | "border" | "error";
1
+ export type UiThemeToken = "accent" | "mdHeading" | "text" | "muted" | "dim" | "border" | "error" | "success";
2
2
  export interface UiTheme {
3
3
  fg(token: UiThemeToken, text: string): string;
4
4
  bold(text: string): string;
@@ -29,8 +29,8 @@ its own `core` facade layer; A1 is a product, so the port adapts imports and kee
29
29
  | `ui-components/list-block.ts` — sticky scroll | `settings/impl.ts` — `stickyHeaderGroup`, `topPaddingRows`, `visibleRowCountAt`, `clampScrollForView` | Same reservation arithmetic and two-pass reveal. |
30
30
  | `ui-components/mouse.ts` | `core/panes/sgr-mouse.ts` | Same SGR decoding and per-call regex reset; A1 emits its own event shape. |
31
31
  | `ui-components/mouse.ts` — tracking sequences | `core/host/pi/providers/host-bridge-surface.ts` | Mouse modes only. A1 does not take the alternate screen, because the Pi TUI owns the screen A1 renders through. |
32
- | `features/owned-ui/settings-app.ts` — section layout, pointer controls, and scrolling | `settings/impl.ts` — `settingsValueColumn`, block navigation, sticky sections, and pointer hit regions | Setting discovery is A1's own A1/Agent section model. Section navigation and pointer-only numeric controls follow the A1 reference; explicit `/` search, ruled shared input, shortcut-derived hints, hidden description rows, configured wheel cadence, a rail presented through the shared scrollbar settings, `Ctrl+Home`/`Ctrl+End` boundary jumps, and distinct floating scalar menus are reviewed owned interactions. |
33
- | `pi-components/models-dialog.ts` — the bare-A1 `/models` dialog | `models/index.ts` and `core/editor/components.ts` — `EditorModal` | Keeps the `all`/`scoped` filters, substring search over id/name/provider, provider-then-id ordering, Enter to switch, Space to scope, Ctrl+S to save, the `(unsaved)` marker, and the `●`/`○` scope markers. A1 renders the marker before the id and the active `✓` after `[provider]`, lists the scoped filter in cycling order (saved-but-removed members last), keeps the pinned bulk/provider/reorder bindings, and hands every switch, scope write, save, and catalog refresh to the engine adapter instead of reading settings files itself. |
32
+ | `features/owned-ui/settings-app.ts` — section layout, pointer controls, scrolling, and Default Model configuration | `settings/impl.ts` — `settingsValueColumn`, block navigation, sticky sections, and pointer hit regions; pinned model selector default state | Setting discovery is A1's own A1/Agent section model. Section navigation and pointer-only numeric controls follow the A1 reference; explicit `/` search, ruled shared input, shortcut-derived hints, hidden description rows, configured wheel cadence, a rail presented through the shared scrollbar settings, `Ctrl+Home`/`Ctrl+End` boundary jumps, distinct floating scalar menus, and a searchable persisted-default model configurator are reviewed owned interactions. |
33
+ | `pi-components/models-dialog.ts` — the bare-A1 `/models` dialog | `models/index.ts` and `core/editor/components.ts` — `EditorModal` | Keeps the `all`/`scoped` filters, substring search over id/name/provider, provider-then-id ordering, Enter to switch the current session, Space to scope, Ctrl+S to save scope, the `(unsaved)` marker, and the `●`/`○` scope markers. A1 renders the marker before the id and the active `✓` after `[provider]`, lists the scoped filter in cycling order (saved-but-removed members last), keeps the pinned bulk/provider/reorder bindings, and hands every switch, scope write, save, and catalog refresh to the engine adapter. Persisted default-model changes are deliberately separate in owned Settings. |
34
34
 
35
35
  ## Ported from the pinned engine
36
36
 
@@ -38,7 +38,7 @@ its own `core` facade layer; A1 is a product, so the port adapts imports and kee
38
38
  | --- | --- | --- |
39
39
  | `src/cli/package-messages.ts`, `src/cli/packages.ts`, and `src/integrations/pi/engine/package-integration.ts` — package/model CLI messages | Pi 0.84.2 `packages/coding-agent/src/package-manager-cli.ts` and public `DefaultPackageManager` at commit `914cf1472e715297caa30db4b9535d534a9eb718` | Preserves terminal-aware Chalk success/error/progress styling, literal model-refresh messages, operational details, and user-settings warnings. Focused syntax/help substitutes the `a1 pi` namespace and supported grammar; A1 syntax exit status remains two. Profile isolation and independent-Pi-update rejection remain intentional. Independent CLI evidence: `test/cli/package-message-parity.test.ts` runs the untouched pinned CLI presenter in an isolated process with model-runtime test substitution, not A1-generated expected strings. This does not certify the still-pending interactive message outcomes. |
40
40
  | `pi-engine-adapter/adapter.ts`, `engine/workflows.ts`, and `session-shell/session-shell.ts` — interactive command outcomes | Pi 0.84.2 `modes/interactive/interactive-mode.ts`, `core/model-resolver.ts`, and `config.ts` at commit `914cf1472e715297caa30db4b9535d534a9eb718` | Preserves route-specific status/warning/error semantics, authentication partial-success sequences, stored-credential logout wording, fork/clone empty states, import context/recovery, share URL and failure/cancellation behavior, and post-login warning lifetime. A1 deliberately returns recoverable failed workflow results instead of adopting Pi's process-owning shutdown/exit for fatal `/new`, `/resume`, and `/import` outcomes. Focused outcome evidence is in `test/integrations/pi/engine/workflows.test.ts` and `test/app/session-shell/session-shell.test.ts`; terminal-cell geometry remains a separate milestone. |
41
- | `pi-engine-adapter/settings-bridge.ts` — presentation metadata | pinned Pi settings selector | Labels, descriptions, order, dialog parts, and bounds are extracted from the selector source at build time by `scripts/pi/build-pi-settings-metadata.mjs` into `dist/`, so an owned screen reads as the vanilla route words it. Ids are mapped from the selector kebab-case to the exposed camelCase keys. |
41
+ | `pi-engine-adapter/settings-bridge.ts` — presentation metadata | pinned Pi settings selector and model selector | Labels, descriptions, order, dialog parts, and bounds for generated settings are extracted from the settings selector source at build time by `scripts/pi/build-pi-settings-metadata.mjs` into `dist/`. The reviewed supplemental `defaultModel` setting comes from the pinned model selector and Pi's existing default-provider/model storage APIs; its separate inventory keeps generated metadata drift checks exact. |
42
42
  | `pi-engine/session-integration.ts` and `pi-components/shell-footer-status.ts` — steering queue | pinned Pi interactive mode `onSubmit` and `updatePendingMessagesDisplay` | Steering/follow-up uses `prompt(..., { streamingBehavior })`, allowing Pi to emit the accepted user row, while remaining steering rows preserve Pi's opening spacer, dim `Steering:` labels, dequeue hint, and order before `Working`. |
43
43
  | `ui-components/list-view.ts`, `dialog-panel.ts`, `value-menu.ts`, and `features/owned-ui/settings-app.ts` — setting presentation | pinned Pi `SettingsSelectorComponent`, Pi TUI `SettingsList`, `SelectList`, and `Input` at `0.84.2` | Cursor and selected label accents, unselected muted values, the 30-column label cap, dialog styling, notices, and narrow-width geometry retain pinned semantics. Scalar-menu placement and input remain shared, while the muted, hover-brightened selected value, A1/Agent grouping, pointer steppers, explicit `/` search, ruled shared input, shortcut-derived status hints, suppressed selected descriptions, `scrollbarSpeed`-driven wheel movement, a list rail that follows `scrollbarAppearance` and `scrollbarStyle` with transcript-style hover and drag, `Ctrl+Home`/`Ctrl+End` boundary jumps, and the dark floating menu with lighter active row and effective-value check mark are declared product-owned differences. Independent row evidence: `test/features/owned-ui/pinned-settings-presentation-parity.test.ts`; owned interaction evidence: `test/features/owned-ui/settings-app.test.ts`, `test/ui/components/value-menu.test.ts`, and `test/composition/settings-route-host.test.ts`. |
44
44
  | `features/owned-ui/project-trust-prompt.ts` — pre-resource selector | pinned Pi `cli/startup-ui.ts`, `cli/project-trust.ts`, and `core/project-trust.ts` at commit `914cf1472e715297caa30db4b9535d534a9eb718` | Uses a fixed, dependency-bounded A1 startup selector rather than importing private CLI modules. It preserves selected-option accent, navigation/accept/reject/cancel semantics, fail-closed behavior, raw-mode restoration, clearing, cursor restoration, and parent-screen restoration before diagnostics. |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timurproko/a1",
3
- "version": "0.2.7-dev.1236",
3
+ "version": "0.2.7-dev.1238",
4
4
  "description": "Standalone terminal workspace for supervised native and managed agents",
5
5
  "type": "module",
6
6
  "privateLaunchContract": "neutral-launch-v1",
@@ -123,7 +123,7 @@
123
123
  "@mariozechner/clipboard": "^0.3.9"
124
124
  },
125
125
  "publication": {
126
- "source": "ebe579aafe67e84a7d77b1769d8e8a972d5a30dc",
127
- "pullRequest": 861
126
+ "source": "32afd4b3acaa0de3fddb429058084448ef5874fb",
127
+ "pullRequest": 863
128
128
  }
129
129
  }