@timurproko/a1 0.2.7-dev.820 → 0.2.7-dev.828

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 (157) hide show
  1. package/README.md +7 -1
  2. package/bin/automatic-update.js +7 -0
  3. package/bin/tab-launch-context.d.ts +1 -0
  4. package/bin/tab-launch-context.js +3 -3
  5. package/bin/ui.js +96 -66
  6. package/dist/app/session-shell/session-shell-root.d.ts +13 -6
  7. package/dist/app/session-shell/session-shell-root.js +9 -5
  8. package/dist/app/session-shell/session-shell.js +68 -18
  9. package/dist/app/session-shell/shell-host.js +6 -3
  10. package/dist/app/session-shell/transcript-document.js +1 -1
  11. package/dist/cli/tabs.d.ts +1 -1
  12. package/dist/cli/tabs.js +10 -14
  13. package/dist/composition/owned-settings.d.ts +12 -0
  14. package/dist/composition/owned-settings.js +36 -0
  15. package/dist/composition/owned-ui.d.ts +5 -14
  16. package/dist/composition/owned-ui.js +93 -63
  17. package/dist/composition/resident-tab-bridge.d.ts +10 -1
  18. package/dist/composition/resident-tab-bridge.js +21 -0
  19. package/dist/composition/settings-route-host.d.ts +12 -2
  20. package/dist/composition/settings-route-host.js +27 -9
  21. package/dist/composition/settings-search-input.d.ts +9 -0
  22. package/dist/composition/settings-search-input.js +58 -0
  23. package/dist/composition/utility-tab.d.ts +28 -0
  24. package/dist/composition/utility-tab.js +242 -0
  25. package/dist/contracts/owned-ui/session-backend.d.ts +2 -0
  26. package/dist/contracts/owned-ui/session-workflows.d.ts +7 -3
  27. package/dist/contracts/presentation/index.d.ts +11 -0
  28. package/dist/contracts/presentation/index.js +12 -0
  29. package/dist/features/launch/development-launch.js +6 -0
  30. package/dist/features/launch/profile-paths.js +4 -0
  31. package/dist/features/owned-ui/plus-menu-dialog.d.ts +22 -0
  32. package/dist/features/owned-ui/plus-menu-dialog.js +76 -0
  33. package/dist/features/owned-ui/project-trust-dialog.d.ts +1 -1
  34. package/dist/features/owned-ui/project-trust-dialog.js +6 -8
  35. package/dist/features/owned-ui/project-trust-prompt.d.ts +1 -0
  36. package/dist/features/owned-ui/project-trust-prompt.js +7 -3
  37. package/dist/features/owned-ui/reference-screen-app.d.ts +2 -0
  38. package/dist/features/owned-ui/reference-screen-app.js +12 -2
  39. package/dist/features/owned-ui/settings-app.d.ts +10 -3
  40. package/dist/features/owned-ui/settings-app.js +98 -45
  41. package/dist/features/owned-ui/settings-search-input.d.ts +29 -0
  42. package/dist/features/owned-ui/settings-search-input.js +1 -0
  43. package/dist/features/prompt-history/index.d.ts +1 -1
  44. package/dist/features/prompt-history/paths.d.ts +2 -1
  45. package/dist/features/prompt-history/paths.js +10 -2
  46. package/dist/features/prompt-history/service.d.ts +5 -0
  47. package/dist/features/prompt-history/service.js +1 -1
  48. package/dist/features/resident-tab/shortcut-declarations.d.ts +1 -1
  49. package/dist/features/resident-tab/shortcut-declarations.js +2 -0
  50. package/dist/foundation/launch-context/index.d.ts +2 -0
  51. package/dist/foundation/launch-context/index.js +1 -0
  52. package/dist/foundation/lifecycle/session-repository-context.js +16 -2
  53. package/dist/foundation/release/automatic-update.d.ts +37 -0
  54. package/dist/foundation/release/automatic-update.js +265 -0
  55. package/dist/foundation/release/bootstrap.d.ts +1 -9
  56. package/dist/foundation/release/bootstrap.js +7 -3
  57. package/dist/foundation/release/early-accent.d.ts +12 -0
  58. package/dist/foundation/release/early-accent.js +59 -0
  59. package/dist/foundation/release/index.d.ts +1 -1
  60. package/dist/foundation/release/index.js +1 -1
  61. package/dist/foundation/release/latest-release.d.ts +3 -2
  62. package/dist/foundation/release/latest-release.js +1 -1
  63. package/dist/foundation/release/progress-palette.generated.d.ts +137 -3
  64. package/dist/foundation/release/progress-palette.generated.js +137 -3
  65. package/dist/foundation/release/release-gc.js +19 -4
  66. package/dist/foundation/release/update-execution-lease.d.ts +36 -0
  67. package/dist/foundation/release/update-execution-lease.js +145 -0
  68. package/dist/foundation/release/update.d.ts +9 -7
  69. package/dist/foundation/release/update.js +244 -175
  70. package/dist/foundation/resident-tabs/bridge-client.d.ts +9 -2
  71. package/dist/foundation/resident-tabs/bridge-client.js +19 -7
  72. package/dist/foundation/resident-tabs/maintenance.d.ts +2 -6
  73. package/dist/foundation/resident-tabs/maintenance.js +5 -23
  74. package/dist/foundation/resident-tabs/progress-observation-schema.d.ts +34 -0
  75. package/dist/foundation/resident-tabs/progress-observation-schema.js +1 -0
  76. package/dist/foundation/resident-tabs/progress-observation.d.ts +3 -6
  77. package/dist/foundation/resident-tabs/progress-observation.js +43 -12
  78. package/dist/foundation/resident-tabs/release-holds.js +3 -14
  79. package/dist/foundation/resident-tabs/routes.d.ts +5 -0
  80. package/dist/foundation/resident-tabs/routes.js +5 -0
  81. package/dist/integrations/pi/components/editor-interaction.d.ts +2 -0
  82. package/dist/integrations/pi/components/modal-frame.d.ts +17 -1
  83. package/dist/integrations/pi/components/modal-frame.js +48 -9
  84. package/dist/integrations/pi/components/models-dialog.js +4 -52
  85. package/dist/integrations/pi/components/owned-editor-ux.d.ts +3 -0
  86. package/dist/integrations/pi/components/owned-editor-ux.js +75 -4
  87. package/dist/integrations/pi/components/shell-editor-autocomplete.js +57 -37
  88. package/dist/integrations/pi/components/shell-footer-status.js +19 -3
  89. package/dist/integrations/pi/components/shell-presenters-transcript.js +4 -1
  90. package/dist/integrations/pi/components/shell-selectors-dialogs.js +4 -1
  91. package/dist/integrations/pi/components/shell-shared-facade.d.ts +9 -2
  92. package/dist/integrations/pi/components/skills-dialog.js +2 -2
  93. package/dist/integrations/pi/components/upstream/components/owned-editor.d.ts +7 -5
  94. package/dist/integrations/pi/components/upstream/components/owned-editor.js +15 -8
  95. package/dist/integrations/pi/components/upstream/components/scoped-models-selector.d.ts +3 -3
  96. package/dist/integrations/pi/components/upstream/components/scoped-models-selector.js +2 -2
  97. package/dist/integrations/pi/components/upstream/components/session-footer.js +26 -23
  98. package/dist/integrations/pi/components/upstream/components/session-selector.js +7 -6
  99. package/dist/integrations/pi/components/upstream/components/thinking-selector.d.ts +7 -6
  100. package/dist/integrations/pi/components/upstream/components/thinking-selector.js +9 -8
  101. package/dist/integrations/pi/components/upstream/components/tool-execution.js +6 -4
  102. package/dist/integrations/pi/components/upstream/components/tree-selector.d.ts +8 -8
  103. package/dist/integrations/pi/components/upstream/components/tree-selector.js +10 -10
  104. package/dist/integrations/pi/components/upstream/history/editor-core.js +2 -0
  105. package/dist/integrations/pi/components/upstream/theme/theme.d.ts +2 -0
  106. package/dist/integrations/pi/components/upstream/theme/theme.js +5 -1
  107. package/dist/integrations/pi/engine/adapter.js +7 -2
  108. package/dist/integrations/pi/engine/offline-settings-port.d.ts +23 -0
  109. package/dist/integrations/pi/engine/offline-settings-port.js +53 -0
  110. package/dist/integrations/pi/engine/ports.d.ts +1 -1
  111. package/dist/integrations/pi/engine/ports.js +4 -0
  112. package/dist/integrations/pi/engine/settings-bridge.d.ts +5 -0
  113. package/dist/integrations/pi/engine/settings-bridge.js +13 -0
  114. package/dist/integrations/pi/engine/settings-port.js +1 -14
  115. package/dist/integrations/pi/engine/workflow-runner.js +9 -2
  116. package/dist/native/darwin-arm64/manifest.json +1 -1
  117. package/dist/native/darwin-arm64/terminal-host.manifest.json +3 -3
  118. package/dist/native/darwin-arm64/terminal-host.node +0 -0
  119. package/dist/native/darwin-x64/manifest.json +1 -1
  120. package/dist/native/darwin-x64/terminal-host.manifest.json +3 -3
  121. package/dist/native/darwin-x64/terminal-host.node +0 -0
  122. package/dist/native/linux-x64/manifest.json +1 -1
  123. package/dist/native/linux-x64/terminal-host.manifest.json +3 -3
  124. package/dist/native/linux-x64/terminal-host.node +0 -0
  125. package/dist/native/win32-x64/manifest.json +2 -2
  126. package/dist/native/win32-x64/process-guardian.node +0 -0
  127. package/dist/native/win32-x64/terminal-host.manifest.json +3 -3
  128. package/dist/native/win32-x64/terminal-host.node +0 -0
  129. package/dist/product-identity.d.ts +1 -1
  130. package/dist/product-identity.js +2 -2
  131. package/dist/product-identity.json +2 -0
  132. package/dist/ui/apps/host.d.ts +2 -0
  133. package/dist/ui/apps/host.js +7 -1
  134. package/dist/ui/components/dialog-panel.js +35 -30
  135. package/dist/ui/components/line-input.d.ts +3 -0
  136. package/dist/ui/components/line-input.js +3 -1
  137. package/dist/ui/components/prompt-input.d.ts +3 -2
  138. package/dist/ui/components/prompt-input.js +6 -4
  139. package/dist/ui/components/spans.js +2 -2
  140. package/dist/ui/components/surface.js +4 -2
  141. package/dist/ui/components/text.d.ts +3 -6
  142. package/dist/ui/components/text.js +6 -8
  143. package/dist/ui/settings/declarations.d.ts +56 -10
  144. package/dist/ui/settings/declarations.js +38 -9
  145. package/dist/ui/settings/early-accent.d.ts +81 -0
  146. package/dist/ui/settings/early-accent.js +50 -0
  147. package/dist/ui/settings/manager.js +7 -3
  148. package/dist/ui/settings/migrations.js +15 -0
  149. package/dist/ui/settings/progress-palette.generated.d.ts +139 -0
  150. package/dist/ui/settings/progress-palette.generated.js +139 -0
  151. package/dist/ui/settings/sections.d.ts +4 -0
  152. package/dist/ui/settings/sections.js +9 -3
  153. package/docs/architecture/resident-tabs.md +48 -17
  154. package/docs/architecture/tool-image-presentation.md +2 -1
  155. package/docs/features/prompt-history.md +41 -20
  156. package/docs/validation.md +25 -5
  157. package/package.json +1 -1
@@ -1,8 +1,18 @@
1
+ import { dialogFrameGeometry, insetDialogContent } from "../../contracts/presentation/index.js";
1
2
  import { LineInput, renderInputRow } from "./line-input.js";
2
- import { renderShortcutHints, renderShortcutHintsWithClose, shortcutHintsText } from "./shortcut-hints.js";
3
+ import { renderShortcutHintsWithClose } from "./shortcut-hints.js";
3
4
  import { displayWidth, truncateToWidth } from "./text.js";
4
5
  const LABEL_COLUMN_CAP = 30;
5
6
  const STEPPED_LABEL_COLUMN_CAP = 46;
7
+ /** One frame path for every semantic row, regardless of which dialog supplies the content. */
8
+ function dialogPanelLayout(width, theme) {
9
+ const geometry = dialogFrameGeometry(width);
10
+ return {
11
+ rule: theme.fg("border", "─".repeat(geometry.frameWidth)),
12
+ contentWidth: geometry.contentWidth,
13
+ contentRow: (painted, raw = painted) => insetDialogContent(pad(truncateToWidth(painted, geometry.contentWidth), geometry.contentWidth, raw), geometry),
14
+ };
15
+ }
6
16
  /** The column values start at: past the widest label, capped so one long name cannot push them off. */
7
17
  export function dialogValueColumn(rows) {
8
18
  const widest = Math.min(LABEL_COLUMN_CAP, Math.max(0, ...rows.map(row => displayWidth(row.label))));
@@ -14,11 +24,7 @@ export function dialogValueColumn(rows) {
14
24
  */
15
25
  export function renderDialogPanel(state, width, theme) {
16
26
  const labelColumn = Math.min(LABEL_COLUMN_CAP, Math.max(0, ...state.rows.map(row => displayWidth(row.label))));
17
- const rule = theme.fg("border", "─".repeat(Math.max(0, width)));
18
- const contentPadding = Math.min(1, Math.max(0, width));
19
- const contentWidth = Math.max(0, width - contentPadding);
20
- const inset = " ".repeat(contentPadding);
21
- const contentRow = (painted, raw) => `${inset}${pad(truncateToWidth(painted, contentWidth), contentWidth, raw)}`;
27
+ const layout = dialogPanelLayout(width, theme);
22
28
  const rows = state.rows.map((row, index) => {
23
29
  const selected = index === state.index;
24
30
  const padded = `${row.label}${" ".repeat(Math.max(0, labelColumn - displayWidth(row.label)))}`;
@@ -28,31 +34,27 @@ export function renderDialogPanel(state, width, theme) {
28
34
  ? `${theme.fg("accent", cursor)}${theme.fg("text", padded)} ${theme.fg("muted", row.value)}`
29
35
  : `${cursor}${padded} ${theme.fg("muted", row.value)}`;
30
36
  const painted = selected ? theme.highlight(content) : content;
31
- return contentRow(painted, raw);
37
+ return layout.contentRow(painted, raw);
32
38
  });
33
39
  const description = state.rows[state.index]?.description ?? "";
34
40
  const hint = typeof state.hint === "string"
35
41
  ? theme.fg("dim", state.hint)
36
- : renderShortcutHintsWithClose(state.hint, theme, contentWidth, 0);
42
+ : renderShortcutHintsWithClose(state.hint, theme, layout.contentWidth, 0);
37
43
  const plainHint = typeof state.hint === "string" ? state.hint : " ".repeat(displayWidth(hint));
38
44
  return [
39
- rule,
40
- contentRow(theme.bold(theme.fg("accent", state.title)), state.title),
41
- contentRow(theme.fg("dim", description), description),
45
+ layout.rule,
46
+ layout.contentRow(theme.bold(theme.fg("accent", state.title)), state.title),
47
+ layout.contentRow(theme.fg("dim", description), description),
42
48
  "",
43
49
  ...rows,
44
50
  "",
45
- contentRow(hint, plainHint),
46
- rule,
51
+ layout.contentRow(hint, plainHint),
52
+ layout.rule,
47
53
  ];
48
54
  }
49
55
  /** Owned stepped-selector composition for structured settings with nested choices. */
50
56
  export function renderSteppedDialogPanel(state, width, theme) {
51
- const rule = theme.fg("border", "─".repeat(Math.max(0, width)));
52
- const contentPadding = Math.min(1, Math.max(0, width));
53
- const contentWidth = Math.max(0, width - contentPadding);
54
- const inset = " ".repeat(contentPadding);
55
- const contentRow = (painted, raw) => `${inset}${pad(truncateToWidth(painted, contentWidth), contentWidth, raw)}`;
57
+ const layout = dialogPanelLayout(width, theme);
56
58
  const labelColumn = Math.min(STEPPED_LABEL_COLUMN_CAP, Math.max(0, ...state.rows.map(row => displayWidth(`${row.label}${row.labelSuffix ?? ""}`))));
57
59
  const rows = state.rows.map((row, index) => {
58
60
  const selected = index === state.index;
@@ -65,31 +67,34 @@ export function renderSteppedDialogPanel(state, width, theme) {
65
67
  const content = selected
66
68
  ? `${theme.fg("accent", cursor)}${label}${theme.fg("muted", suffix)}`
67
69
  : `${cursor}${label}${theme.fg("muted", suffix)}`;
68
- return contentRow(selected ? theme.highlight(content) : content, raw);
70
+ return layout.contentRow(selected ? theme.highlight(content) : content, raw);
69
71
  });
70
72
  const input = state.input === undefined
71
73
  ? []
72
- : [...renderInputRow(state.input, width, { ruled: false, promptGlyph: "", theme }).lines, ""];
74
+ : [
75
+ ...renderInputRow(state.input, layout.contentWidth, {
76
+ ruled: false,
77
+ promptGlyph: "> ",
78
+ promptRole: "accent",
79
+ theme,
80
+ }).lines.map(row => layout.contentRow(row)),
81
+ "",
82
+ ];
73
83
  const step = `(step ${state.step}/${state.steps})`;
74
84
  const title = `${state.title} ${step}`;
75
85
  const paintedTitle = `${theme.bold(theme.fg("accent", state.title))} ${theme.fg("dim", step)}`;
76
86
  return [
77
- rule,
78
- contentRow(paintedTitle, title),
79
- contentRow(theme.fg("dim", state.description), state.description),
87
+ layout.rule,
88
+ layout.contentRow(paintedTitle, title),
89
+ layout.contentRow(theme.fg("dim", state.description), state.description),
80
90
  "",
81
91
  ...input,
82
92
  ...rows,
83
93
  "",
84
- renderSteppedHint(state.hint, width, theme),
85
- rule,
94
+ layout.contentRow(renderShortcutHintsWithClose(state.hint, theme, layout.contentWidth)),
95
+ layout.rule,
86
96
  ];
87
97
  }
88
- function renderSteppedHint(entries, width, theme) {
89
- const raw = shortcutHintsText(entries, 1, " · ");
90
- const painted = renderShortcutHints(entries, theme, 1, theme.fg("dim", " · "));
91
- return `${truncateToWidth(painted, width)}${" ".repeat(Math.max(0, width - displayWidth(raw)))}`;
92
- }
93
98
  /** The row a pointer report lands on, or null when it is not on one. */
94
99
  export function dialogRowAt(frame, row, column, valueWidth) {
95
100
  const at = row - frame.firstRow;
@@ -1,3 +1,4 @@
1
+ import { type PromptMarkerRole } from "./prompt-input.js";
1
2
  import type { UiTheme } from "./theme.js";
2
3
  export type LineInputOutcome = {
3
4
  readonly kind: "editing";
@@ -60,6 +61,8 @@ export interface InputRowOptions {
60
61
  readonly ruled?: boolean;
61
62
  /** Prompt marker including any following spacing. Defaults to the shared prompt glyph. */
62
63
  readonly promptGlyph?: string;
64
+ /** Semantic foreground for a configured prompt marker. */
65
+ readonly promptRole?: PromptMarkerRole;
63
66
  readonly theme?: Pick<UiTheme, "fg">;
64
67
  }
65
68
  export interface InputRow {
@@ -220,5 +220,7 @@ export function renderInputRow(input, width, options = {}) {
220
220
  const body = empty
221
221
  ? `${caretCell(placeholder.slice(0, 1))}${faint(placeholder.slice(1))}`
222
222
  : `${before}${caretCell(under)}${after}`;
223
- return { lines: presentation.render(width, () => ({ rows: [body] }), options.ruled, 0, options.promptGlyph) };
223
+ return {
224
+ lines: presentation.render(width, () => ({ rows: [body] }), options.ruled, 0, options.promptGlyph, options.promptRole),
225
+ };
224
226
  }
@@ -4,7 +4,8 @@ export declare const PROMPT_PREFIX_WIDTH = 0;
4
4
  type RuleTheme = Pick<UiTheme, "fg">;
5
5
  export declare function promptRuleText(text: string, theme: RuleTheme): string;
6
6
  export declare function promptRule(width: number, theme: RuleTheme): string;
7
- export declare function promptArrow(text: string, theme: RuleTheme): string;
7
+ export type PromptMarkerRole = "muted" | "accent";
8
+ export declare function promptArrow(text: string, theme: RuleTheme, role?: PromptMarkerRole): string;
8
9
  export interface PromptInputBody {
9
10
  readonly rows: readonly string[];
10
11
  /** Semantic border fragments retain editor history and scroll annotations. */
@@ -31,6 +32,6 @@ export declare class PromptInput {
31
32
  contentWidth: number;
32
33
  layoutWidth: number;
33
34
  };
34
- render(width: number, renderBody: (innerWidth: number) => PromptInputBody, ruled?: boolean, padding?: number, promptGlyph?: string): string[];
35
+ render(width: number, renderBody: (innerWidth: number) => PromptInputBody, ruled?: boolean, padding?: number, promptGlyph?: string, promptRole?: PromptMarkerRole): string[];
35
36
  }
36
37
  export {};
@@ -8,8 +8,10 @@ export function promptRuleText(text, theme) {
8
8
  export function promptRule(width, theme) {
9
9
  return promptRuleText("─".repeat(Math.max(0, width)), theme);
10
10
  }
11
- export function promptArrow(text, theme) {
12
- return theme.fg("muted", text);
11
+ export function promptArrow(text, theme, role = "muted") {
12
+ if (role === "accent" && text.startsWith(">"))
13
+ return `${theme.fg(role, ">")}${text.slice(1)}`;
14
+ return theme.fg(role, text);
13
15
  }
14
16
  /** Composes shared input chrome around controller-owned body rows without owning editing state. */
15
17
  export class PromptInput {
@@ -26,7 +28,7 @@ export class PromptInput {
26
28
  const contentWidth = Math.max(1, innerWidth - paddingX * 2);
27
29
  return { prefixWidth, innerWidth, paddingX, contentWidth, layoutWidth: Math.max(1, contentWidth - (paddingX ? 0 : 1)) };
28
30
  }
29
- render(width, renderBody, ruled = true, padding = 0, promptGlyph = PROMPT_GLYPH) {
31
+ render(width, renderBody, ruled = true, padding = 0, promptGlyph = PROMPT_GLYPH, promptRole = "muted") {
30
32
  if (width <= 0)
31
33
  return [];
32
34
  const { prefixWidth, innerWidth } = this.geometry(width, padding, promptGlyph);
@@ -40,7 +42,7 @@ export class PromptInput {
40
42
  return clipped + " ".repeat(Math.max(0, width - this.metrics.measure(clipped)));
41
43
  };
42
44
  const rows = body.rows.map((row, index) => fit(`${index === 0
43
- ? promptArrow(promptGlyph, this.theme)
45
+ ? promptArrow(promptGlyph, this.theme, promptRole)
44
46
  : " ".repeat(prefixWidth)}${row}`));
45
47
  const rule = (fragment) => fragment === undefined
46
48
  ? promptRule(width, this.theme)
@@ -1,4 +1,4 @@
1
- import { displayWidth } from "./text.js";
1
+ import { displayWidth, graphemeWidth } from "./text.js";
2
2
  /**
3
3
  * Ported from the A1 UI reference implementation (`core/presentation/spans.ts`).
4
4
  * Writing over part of a rendered row is not string slicing: the row carries
@@ -53,7 +53,7 @@ export function overlaySpan(line, from, to, span, options = {}) {
53
53
  continue;
54
54
  }
55
55
  for (const { segment } of GRAPHEMES.segment(token)) {
56
- const width = displayWidth(segment);
56
+ const width = graphemeWidth(segment);
57
57
  if (column + width <= from)
58
58
  head += segment;
59
59
  else if (column >= to)
@@ -29,10 +29,12 @@ export function withScrollbarRail(lines, geometry, contentWidth, theme, options
29
29
  */
30
30
  export function renderEmptyState(message, mark, height, width, theme) {
31
31
  const middle = Math.floor(height / 2);
32
+ const messageRow = Math.min(Math.max(0, height - 1), middle + 1);
33
+ const markRow = messageRow - 1;
32
34
  return Array.from({ length: Math.max(0, height) }, (_line, index) => {
33
- if (index === middle - 1)
35
+ if (index === markRow)
34
36
  return centre(theme.fg("muted", mark), mark, width);
35
- return index === middle ? centre(theme.fg("muted", message), message, width) : "";
37
+ return index === messageRow ? centre(theme.fg("muted", message), message, width) : "";
36
38
  });
37
39
  }
38
40
  function centre(painted, raw, width) {
@@ -1,11 +1,8 @@
1
- /**
2
- * Display-width text handling. Terminals lay out by column, not by code unit,
3
- * so measuring with `String.length` mis-sizes CJK, emoji, and combining marks,
4
- * and counting styling escapes as visible width breaks every layout that uses
5
- * colour.
6
- */
1
+ /** Terminal-column geometry: preserve graphemes, Unicode cell widths and ANSI styling. */
7
2
  /** Removes styling sequences so only visible characters remain. */
8
3
  export declare function stripAnsi(text: string): string;
4
+ /** Width of one grapheme cluster: two for emoji, else the widest code point it contains. */
5
+ export declare function graphemeWidth(grapheme: string): number;
9
6
  export interface DisplayColumnRange {
10
7
  readonly from: number;
11
8
  readonly to: number;
@@ -1,9 +1,4 @@
1
- /**
2
- * Display-width text handling. Terminals lay out by column, not by code unit,
3
- * so measuring with `String.length` mis-sizes CJK, emoji, and combining marks,
4
- * and counting styling escapes as visible width breaks every layout that uses
5
- * colour.
6
- */
1
+ /** Terminal-column geometry: preserve graphemes, Unicode cell widths and ANSI styling. */
7
2
  const ANSI_PATTERN = /\[[0-9;:?]*[ -/]*[@-~]|[\]_][^]*(?:|\\)|[@-Z\\-_]/g;
8
3
  const SEGMENTER = new Intl.Segmenter(undefined, { granularity: "grapheme" });
9
4
  const WORD_SEGMENTER = new Intl.Segmenter(undefined, { granularity: "word" });
@@ -76,7 +71,7 @@ function couldBeEmoji(grapheme) {
76
71
  grapheme.length > 1);
77
72
  }
78
73
  /** Width of one grapheme cluster: two for emoji, else the widest code point it contains. */
79
- function graphemeWidth(grapheme) {
74
+ export function graphemeWidth(grapheme) {
80
75
  if (couldBeEmoji(grapheme) && RGI_EMOJI_PATTERN.test(grapheme))
81
76
  return 2;
82
77
  let width = 0;
@@ -126,8 +121,11 @@ export function displayColumnSlice(text, requestedFrom, requestedTo) {
126
121
  }
127
122
  /** Columns this text occupies once styling is removed. */
128
123
  export function displayWidth(text) {
124
+ const plain = stripAnsi(text);
125
+ if (!/[^\x20-\x7e]/u.test(plain))
126
+ return plain.length;
129
127
  let width = 0;
130
- for (const { segment } of SEGMENTER.segment(stripAnsi(text)))
128
+ for (const { segment } of SEGMENTER.segment(plain))
131
129
  width += graphemeWidth(segment);
132
130
  return width;
133
131
  }
@@ -1,4 +1,4 @@
1
- export declare const OWNED_UI_SETTINGS_VERSION = 18;
1
+ export declare const OWNED_UI_SETTINGS_VERSION = 19;
2
2
  export type OwnedUiSettingValue = string | number | boolean;
3
3
  export type OwnedUiSettingApplication = "live" | "restart";
4
4
  export interface OwnedUiSettingDeclaration {
@@ -12,19 +12,28 @@ export interface OwnedUiSettingDeclaration {
12
12
  readonly application: OwnedUiSettingApplication;
13
13
  readonly defaultValue: OwnedUiSettingValue;
14
14
  readonly allowedValues: readonly OwnedUiSettingValue[];
15
+ /**
16
+ * Parts of a structured setting, edited through their own dialog: the value is the comma-separated
17
+ * keys of the enabled parts in this order.
18
+ */
19
+ readonly flags?: readonly {
20
+ readonly key: string;
21
+ readonly label: string;
22
+ readonly description: string;
23
+ }[];
15
24
  }
16
25
  export declare const OWNED_SETTING_DECLARATIONS: Readonly<{
17
26
  readonly updateCheck: Readonly<{
18
27
  id: "updateCheck";
19
- label: "Update check";
28
+ label: "Update";
20
29
  section: Readonly<{
21
30
  id: "generic";
22
31
  title: "Generic";
23
32
  }>;
24
- description: "Check once a day at startup whether a newer A1 release exists on this channel and show a notice. Uses the public npm registry; applies on next start.";
33
+ description: "Auto installs verified channel updates silently, Notify shows the current update notice, and No leaves updates manual. Checks use the public npm registry; applies on next start.";
25
34
  application: "restart";
26
- defaultValue: true;
27
- allowedValues: readonly [true, false];
35
+ defaultValue: "auto";
36
+ allowedValues: readonly ["auto", "notify", "no"];
28
37
  }>;
29
38
  readonly timeFormat: Readonly<{
30
39
  id: "timeFormat";
@@ -47,8 +56,8 @@ export declare const OWNED_SETTING_DECLARATIONS: Readonly<{
47
56
  }>;
48
57
  description: "Color used by highlighted interface controls, list markers, and tonal interface surfaces.";
49
58
  application: "live";
50
- defaultValue: "purple";
51
- allowedValues: readonly ["purple", "blue", "cyan", "green", "orange", "pink"];
59
+ defaultValue: "blue";
60
+ allowedValues: readonly ["blue", "purple", "cyan", "green", "orange", "pink"];
52
61
  }>;
53
62
  readonly backgroundStyle: Readonly<{
54
63
  id: "backgroundStyle";
@@ -117,11 +126,23 @@ export declare const OWNED_SETTING_DECLARATIONS: Readonly<{
117
126
  id: "history";
118
127
  title: "History";
119
128
  }>;
120
- description: "Retain reusable prompts across sessions. Applies on next start; disabling does not erase saved history or stop existing instances.";
129
+ description: "Retain reusable prompts after restart. Applies on next start; disabling does not erase saved history or stop existing instances.";
121
130
  application: "restart";
122
131
  defaultValue: true;
123
132
  allowedValues: readonly [true, false];
124
133
  }>;
134
+ readonly promptHistoryScope: Readonly<{
135
+ id: "promptHistoryScope";
136
+ label: "History scope";
137
+ section: Readonly<{
138
+ id: "history";
139
+ title: "History";
140
+ }>;
141
+ description: "Unique shows each agent session's history; Shared shows the profile-wide history. Switches running agents without moving or erasing saved prompts.";
142
+ application: "live";
143
+ defaultValue: "unique";
144
+ allowedValues: readonly ["unique", "shared"];
145
+ }>;
125
146
  readonly promptHistoryMaxItems: Readonly<{
126
147
  id: "promptHistoryMaxItems";
127
148
  label: "History limit";
@@ -175,19 +196,44 @@ export declare const OWNED_SETTING_DECLARATIONS: Readonly<{
175
196
  label: "Position";
176
197
  section: Readonly<{
177
198
  id: "tabs";
178
- title: "Tabs";
199
+ title: "Tab Bar";
179
200
  }>;
180
201
  description: "Place the resident tab bar at the top or bottom of the terminal.";
181
202
  application: "live";
182
203
  defaultValue: "top";
183
204
  allowedValues: readonly ["top", "bottom"];
184
205
  }>;
206
+ readonly tabBarPlusMenu: Readonly<{
207
+ id: "tabBarPlusMenu";
208
+ label: "Plus menu";
209
+ section: Readonly<{
210
+ id: "tabs";
211
+ title: "Tab Bar";
212
+ }>;
213
+ description: "The tabs + offers. With only Agent, + adds an Agent at once; with more, it opens a menu; with none, + is hidden.";
214
+ application: "live";
215
+ defaultValue: "agent";
216
+ allowedValues: readonly ["", "agent", "settings", "changelog", "agent,settings", "agent,changelog", "settings,changelog", "agent,settings,changelog"];
217
+ flags: readonly (Readonly<{
218
+ key: "agent";
219
+ label: "Agent";
220
+ description: "Add a new Agent tab every time.";
221
+ }> | Readonly<{
222
+ key: "settings";
223
+ label: "Settings";
224
+ description: "Show the one Settings tab.";
225
+ }> | Readonly<{
226
+ key: "changelog";
227
+ label: "Changelog";
228
+ description: "Show the one Changelog tab.";
229
+ }>)[];
230
+ }>;
185
231
  readonly tabsAutoName: Readonly<{
186
232
  id: "tabsAutoName";
187
233
  label: "Auto name";
188
234
  section: Readonly<{
189
235
  id: "tabs";
190
- title: "Tabs";
236
+ title: "Tab Bar";
191
237
  }>;
192
238
  description: "Automatically name a new tab from its first completed turn with the tab's current model.";
193
239
  application: "live";
@@ -1,6 +1,6 @@
1
- export const OWNED_UI_SETTINGS_VERSION = 18;
1
+ export const OWNED_UI_SETTINGS_VERSION = 19;
2
2
  const UI_ACCENT_COLOR_CHOICES = Object.freeze([
3
- "purple", "blue", "cyan", "green", "orange", "pink",
3
+ "blue", "purple", "cyan", "green", "orange", "pink",
4
4
  ]);
5
5
  const MAX_ID_LENGTH = 64;
6
6
  const ID_PATTERN = /^[a-z][a-z0-9]*(?:[A-Z][a-z0-9]*)*$/;
@@ -8,16 +8,26 @@ const GENERIC_SECTION = Object.freeze({ id: "generic", title: "Generic" });
8
8
  const APPEARANCE_SECTION = Object.freeze({ id: "appearance", title: "Appearance" });
9
9
  const SCROLL_SECTION = Object.freeze({ id: "scroll", title: "Scroll" });
10
10
  const AGENT_SECTION = Object.freeze({ id: "agent", title: "Agent" });
11
- const TABS_SECTION = Object.freeze({ id: "tabs", title: "Tabs" });
11
+ const TABS_SECTION = Object.freeze({ id: "tabs", title: "Tab Bar" });
12
+ /** The tab kinds the strip's `+` can offer, in the order it shows them. */
13
+ const PLUS_MENU_KINDS = Object.freeze([
14
+ Object.freeze({ key: "agent", label: "Agent", description: "Add a new Agent tab every time." }),
15
+ Object.freeze({ key: "settings", label: "Settings", description: "Show the one Settings tab." }),
16
+ Object.freeze({ key: "changelog", label: "Changelog", description: "Show the one Changelog tab." }),
17
+ ]);
18
+ /** Every enabled subset of the plus-menu kinds, each in kind order. */
19
+ const PLUS_MENU_VALUES = Object.freeze([
20
+ "", "agent", "settings", "changelog", "agent,settings", "agent,changelog", "settings,changelog", "agent,settings,changelog",
21
+ ]);
12
22
  export const OWNED_SETTING_DECLARATIONS = Object.freeze({
13
23
  updateCheck: Object.freeze({
14
24
  id: "updateCheck",
15
- label: "Update check",
25
+ label: "Update",
16
26
  section: GENERIC_SECTION,
17
- description: "Check once a day at startup whether a newer A1 release exists on this channel and show a notice. Uses the public npm registry; applies on next start.",
27
+ description: "Auto installs verified channel updates silently, Notify shows the current update notice, and No leaves updates manual. Checks use the public npm registry; applies on next start.",
18
28
  application: "restart",
19
- defaultValue: true,
20
- allowedValues: Object.freeze([true, false]),
29
+ defaultValue: "auto",
30
+ allowedValues: Object.freeze(["auto", "notify", "no"]),
21
31
  }),
22
32
  timeFormat: Object.freeze({
23
33
  id: "timeFormat",
@@ -36,7 +46,7 @@ export const OWNED_SETTING_DECLARATIONS = Object.freeze({
36
46
  section: APPEARANCE_SECTION,
37
47
  description: "Color used by highlighted interface controls, list markers, and tonal interface surfaces.",
38
48
  application: "live",
39
- defaultValue: "purple",
49
+ defaultValue: "blue",
40
50
  allowedValues: UI_ACCENT_COLOR_CHOICES,
41
51
  }),
42
52
  backgroundStyle: Object.freeze({
@@ -90,11 +100,20 @@ export const OWNED_SETTING_DECLARATIONS = Object.freeze({
90
100
  id: "promptHistoryEnabled",
91
101
  label: "Persistent history",
92
102
  section: Object.freeze({ id: "history", title: "History" }),
93
- description: "Retain reusable prompts across sessions. Applies on next start; disabling does not erase saved history or stop existing instances.",
103
+ description: "Retain reusable prompts after restart. Applies on next start; disabling does not erase saved history or stop existing instances.",
94
104
  application: "restart",
95
105
  defaultValue: true,
96
106
  allowedValues: Object.freeze([true, false]),
97
107
  }),
108
+ promptHistoryScope: Object.freeze({
109
+ id: "promptHistoryScope",
110
+ label: "History scope",
111
+ section: Object.freeze({ id: "history", title: "History" }),
112
+ description: "Unique shows each agent session's history; Shared shows the profile-wide history. Switches running agents without moving or erasing saved prompts.",
113
+ application: "live",
114
+ defaultValue: "unique",
115
+ allowedValues: Object.freeze(["unique", "shared"]),
116
+ }),
98
117
  promptHistoryMaxItems: Object.freeze({
99
118
  id: "promptHistoryMaxItems",
100
119
  label: "History limit",
@@ -140,6 +159,16 @@ export const OWNED_SETTING_DECLARATIONS = Object.freeze({
140
159
  defaultValue: "top",
141
160
  allowedValues: Object.freeze(["top", "bottom"]),
142
161
  }),
162
+ tabBarPlusMenu: Object.freeze({
163
+ id: "tabBarPlusMenu",
164
+ label: "Plus menu",
165
+ section: TABS_SECTION,
166
+ description: "The tabs + offers. With only Agent, + adds an Agent at once; with more, it opens a menu; with none, + is hidden.",
167
+ application: "live",
168
+ defaultValue: "agent",
169
+ allowedValues: PLUS_MENU_VALUES,
170
+ flags: PLUS_MENU_KINDS,
171
+ }),
143
172
  tabsAutoName: Object.freeze({
144
173
  id: "tabsAutoName",
145
174
  label: "Auto name",
@@ -0,0 +1,81 @@
1
+ import type { UiAccentColor } from "../../contracts/owned-ui/index.js";
2
+ type Appearance = "dark" | "light";
3
+ type ColorMode = "truecolor" | "256color";
4
+ /** Global settings only: project resources must remain unavailable until trust is resolved. */
5
+ export declare function readPreTrustAccent(environment?: NodeJS.ProcessEnv): UiAccentColor;
6
+ export declare function preTrustPalette(color: UiAccentColor, appearance: Appearance, mode: ColorMode): {
7
+ accent: string;
8
+ border: string;
9
+ } | {
10
+ accent: string;
11
+ border: string;
12
+ } | {
13
+ accent: string;
14
+ border: string;
15
+ } | {
16
+ accent: string;
17
+ border: string;
18
+ } | {
19
+ accent: string;
20
+ border: string;
21
+ } | {
22
+ accent: string;
23
+ border: string;
24
+ } | {
25
+ accent: string;
26
+ border: string;
27
+ } | {
28
+ accent: string;
29
+ border: string;
30
+ } | {
31
+ accent: string;
32
+ border: string;
33
+ } | {
34
+ accent: string;
35
+ border: string;
36
+ } | {
37
+ accent: string;
38
+ border: string;
39
+ } | {
40
+ accent: string;
41
+ border: string;
42
+ } | {
43
+ accent: string;
44
+ border: string;
45
+ } | {
46
+ accent: string;
47
+ border: string;
48
+ } | {
49
+ accent: string;
50
+ border: string;
51
+ } | {
52
+ accent: string;
53
+ border: string;
54
+ } | {
55
+ accent: string;
56
+ border: string;
57
+ } | {
58
+ accent: string;
59
+ border: string;
60
+ } | {
61
+ accent: string;
62
+ border: string;
63
+ } | {
64
+ accent: string;
65
+ border: string;
66
+ } | {
67
+ accent: string;
68
+ border: string;
69
+ } | {
70
+ accent: string;
71
+ border: string;
72
+ } | {
73
+ accent: string;
74
+ border: string;
75
+ } | {
76
+ accent: string;
77
+ border: string;
78
+ };
79
+ export declare function preTrustTerminalAppearance(environment?: NodeJS.ProcessEnv): Appearance;
80
+ export declare function preTrustTerminalColorMode(environment?: NodeJS.ProcessEnv): ColorMode;
81
+ export {};
@@ -0,0 +1,50 @@
1
+ import { readFileSync, statSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { isAbsolute, join } from "node:path";
4
+ import { PRODUCT_IDENTITY } from "../../product-identity.js";
5
+ import { SHARED_PROGRESS_PALETTE } from "./progress-palette.generated.js";
6
+ /** Global settings only: project resources must remain unavailable until trust is resolved. */
7
+ export function readPreTrustAccent(environment = process.env) {
8
+ try {
9
+ const home = environment[PRODUCT_IDENTITY.environment.profileHome] ?? homedir();
10
+ if (!isAbsolute(home) || home.includes("\0"))
11
+ return "blue";
12
+ const file = join(home, PRODUCT_IDENTITY.state.piAgentProfile, "settings.json");
13
+ if (statSync(file).size > 256 * 1024)
14
+ return "blue";
15
+ const raw = readFileSync(file, "utf8");
16
+ if (Buffer.byteLength(raw, "utf8") > 256 * 1024)
17
+ return "blue";
18
+ const root = JSON.parse(raw.replace(/^\uFEFF/u, ""));
19
+ if (root === null || typeof root !== "object" || Array.isArray(root))
20
+ return "blue";
21
+ const namespace = root[PRODUCT_IDENTITY.commandName];
22
+ if (namespace === null || typeof namespace !== "object" || Array.isArray(namespace))
23
+ return "blue";
24
+ const value = namespace.accentColor;
25
+ if (value === "default")
26
+ return "purple";
27
+ return typeof value === "string" && Object.hasOwn(SHARED_PROGRESS_PALETTE, value) ? value : "blue";
28
+ }
29
+ catch {
30
+ return "blue";
31
+ }
32
+ }
33
+ export function preTrustPalette(color, appearance, mode) {
34
+ return SHARED_PROGRESS_PALETTE[color][appearance][mode];
35
+ }
36
+ export function preTrustTerminalAppearance(environment = process.env) {
37
+ const index = Number.parseInt((environment.COLORFGBG ?? "").split(";").at(-1) ?? "", 10);
38
+ return Number.isInteger(index) && (index === 7 || index === 15 || index >= 231) ? "light" : "dark";
39
+ }
40
+ export function preTrustTerminalColorMode(environment = process.env) {
41
+ if (environment.PI_TRUE_COLOR === "1")
42
+ return "truecolor";
43
+ if (environment.PI_TRUE_COLOR === "0")
44
+ return "256color";
45
+ const program = (environment.TERM_PROGRAM ?? "").toLowerCase();
46
+ const known = ["kitty", "ghostty", "wezterm", "rex", "warpterminal", "iterm.app", "alacritty", "vscode", "zed"];
47
+ return /^(truecolor|24bit)$/iu.test(environment.COLORTERM ?? "") || (environment.TERM ?? "").endsWith("-direct")
48
+ || process.platform === "win32" || environment.WT_SESSION !== undefined || known.includes(program)
49
+ ? "truecolor" : "256color";
50
+ }
@@ -188,9 +188,13 @@ export class OwnedSettingsManager {
188
188
  return () => this.#listeners.delete(listener);
189
189
  }
190
190
  async changeStructured(backend, id, value) {
191
- if (backend !== "agent")
192
- return failed("only an agent setting holds a structured value");
193
- return await this.#changeAgentValue(id, value);
191
+ if (backend === "agent")
192
+ return await this.#changeAgentValue(id, value);
193
+ // Invariant: a flagged A1 setting stores its enabled parts as one value, in declaration order.
194
+ const flags = findOwnedUiSettingsEntry(this.sections(), id, "a1")?.flags ?? [];
195
+ if (flags.length === 0)
196
+ return failed(`${id} holds no structured value`);
197
+ return await this.change("a1", id, flags.filter(flag => value[flag.key] === true).map(flag => flag.key).join(","));
194
198
  }
195
199
  async change(backend, id, value) {
196
200
  const entry = findOwnedUiSettingsEntry(this.sections(), id, backend);