@timurproko/a1 0.2.7-dev.754 → 0.2.7-dev.767

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 (149) hide show
  1. package/bin/guardian.js +5 -0
  2. package/bin/tab-launch-context.d.ts +23 -0
  3. package/bin/tab-launch-context.js +45 -0
  4. package/bin/ui.js +20 -5
  5. package/dist/app/session-shell/helper-pool.d.ts +3 -5
  6. package/dist/app/session-shell/helper-pool.js +3 -5
  7. package/dist/app/session-shell/session-shell-root.d.ts +29 -1
  8. package/dist/app/session-shell/session-shell-root.js +3 -2
  9. package/dist/app/session-shell/session-shell.js +153 -30
  10. package/dist/app/session-shell/session-viewport-controller.d.ts +1 -1
  11. package/dist/app/session-shell/session-viewport-controller.js +3 -3
  12. package/dist/composition/owned-ui.d.ts +20 -0
  13. package/dist/composition/owned-ui.js +76 -3
  14. package/dist/contracts/owned-ui/session-backend.d.ts +1 -0
  15. package/dist/features/owned-ui/settings-app.js +8 -35
  16. package/dist/foundation/launch-context/index.d.ts +6 -0
  17. package/dist/foundation/launch-context/index.js +15 -0
  18. package/dist/foundation/launch-guardian/main.d.ts +5 -0
  19. package/dist/foundation/launch-guardian/main.js +2 -1
  20. package/dist/foundation/release/bootstrap.d.ts +4 -0
  21. package/dist/foundation/release/bootstrap.js +44 -6
  22. package/dist/foundation/release/dependency-certification.js +54 -19
  23. package/dist/foundation/release/progress-palette.generated.d.ts +2 -2
  24. package/dist/foundation/release/progress-palette.generated.js +2 -2
  25. package/dist/foundation/resident-tabs/bridge-client.d.ts +41 -0
  26. package/dist/foundation/resident-tabs/bridge-client.js +199 -0
  27. package/dist/foundation/resident-tabs/draft-checkpoint.d.ts +27 -0
  28. package/dist/foundation/resident-tabs/draft-checkpoint.js +145 -0
  29. package/dist/foundation/resident-tabs/ensure-terminal-host.d.ts +30 -0
  30. package/dist/foundation/resident-tabs/ensure-terminal-host.js +121 -0
  31. package/dist/foundation/resident-tabs/fault-injection.d.ts +8 -0
  32. package/dist/foundation/resident-tabs/fault-injection.js +26 -0
  33. package/dist/foundation/resident-tabs/index.d.ts +6 -0
  34. package/dist/foundation/resident-tabs/index.js +3 -0
  35. package/dist/foundation/resident-tabs/prompt-journal.d.ts +65 -0
  36. package/dist/foundation/resident-tabs/prompt-journal.js +214 -0
  37. package/dist/foundation/resident-tabs/protocol.d.ts +12 -0
  38. package/dist/foundation/resident-tabs/protocol.js +195 -0
  39. package/dist/foundation/resident-tabs/recovery.d.ts +26 -0
  40. package/dist/foundation/resident-tabs/recovery.js +205 -0
  41. package/dist/foundation/resident-tabs/routing.d.ts +10 -0
  42. package/dist/foundation/resident-tabs/routing.js +8 -0
  43. package/dist/foundation/resident-tabs/session-sync.d.ts +14 -0
  44. package/dist/foundation/resident-tabs/session-sync.js +37 -0
  45. package/dist/foundation/resident-tabs/settings.d.ts +9 -0
  46. package/dist/foundation/resident-tabs/settings.js +65 -0
  47. package/dist/foundation/session-writer-lock/index.d.ts +27 -0
  48. package/dist/foundation/session-writer-lock/index.js +197 -0
  49. package/dist/foundation/startup/startup-descriptor.js +2 -2
  50. package/dist/foundation/terminal-cleanup/index.d.ts +1 -1
  51. package/dist/foundation/terminal-cleanup/index.js +1 -1
  52. package/dist/foundation/terminal-cleanup/terminal-reset.d.ts +1 -0
  53. package/dist/foundation/terminal-cleanup/terminal-reset.js +1 -0
  54. package/dist/integrations/pi/components/owned-editor-ux.d.ts +1 -1
  55. package/dist/integrations/pi/components/owned-editor-ux.js +2 -2
  56. package/dist/integrations/pi/components/resources/builtin-themes.d.ts +4 -4
  57. package/dist/integrations/pi/components/resources/builtin-themes.js +4 -4
  58. package/dist/integrations/pi/components/shell-shared-facade.d.ts +1 -1
  59. package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.d.ts +1 -1
  60. package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.js +17 -4
  61. package/dist/integrations/pi/components/upstream/assets/earendil-image.json +2 -2
  62. package/dist/integrations/pi/components/upstream/components/countdown-timer.d.ts +1 -1
  63. package/dist/integrations/pi/components/upstream/components/earendil-announcement.js +1 -1
  64. package/dist/integrations/pi/components/upstream/components/extension-editor.d.ts +1 -1
  65. package/dist/integrations/pi/components/upstream/components/extension-editor.js +1 -1
  66. package/dist/integrations/pi/components/upstream/components/extension-input.js +1 -1
  67. package/dist/integrations/pi/components/upstream/components/extension-selector.js +1 -1
  68. package/dist/integrations/pi/components/upstream/components/mermaid.js +1 -1
  69. package/dist/integrations/pi/components/upstream/components/owned-editor.d.ts +1 -1
  70. package/dist/integrations/pi/components/upstream/components/owned-editor.js +1 -1
  71. package/dist/integrations/pi/components/upstream/components/pi-logo.js +1 -1
  72. package/dist/integrations/pi/components/upstream/components/scoped-models-selector.d.ts +1 -1
  73. package/dist/integrations/pi/components/upstream/components/session-footer.js +1 -1
  74. package/dist/integrations/pi/components/upstream/components/session-selector-search.js +1 -1
  75. package/dist/integrations/pi/components/upstream/components/session-selector.js +1 -1
  76. package/dist/integrations/pi/components/upstream/components/skill-invocation-message.d.ts +1 -1
  77. package/dist/integrations/pi/components/upstream/components/skill-invocation-message.js +1 -1
  78. package/dist/integrations/pi/components/upstream/components/status-indicator.d.ts +2 -2
  79. package/dist/integrations/pi/components/upstream/components/status-indicator.js +3 -3
  80. package/dist/integrations/pi/components/upstream/components/themed-text.d.ts +1 -1
  81. package/dist/integrations/pi/components/upstream/components/themed-text.js +1 -1
  82. package/dist/integrations/pi/components/upstream/components/thinking-selector.d.ts +1 -1
  83. package/dist/integrations/pi/components/upstream/components/thinking-selector.js +1 -1
  84. package/dist/integrations/pi/components/upstream/components/tool-execution.d.ts +0 -2
  85. package/dist/integrations/pi/components/upstream/components/tool-execution.js +41 -20
  86. package/dist/integrations/pi/components/upstream/components/tree-selector.d.ts +1 -1
  87. package/dist/integrations/pi/components/upstream/components/tree-selector.js +1 -1
  88. package/dist/integrations/pi/components/upstream/components/trust-selector.js +1 -1
  89. package/dist/integrations/pi/components/upstream/external-editor.js +1 -1
  90. package/dist/integrations/pi/components/upstream/history/editor-core.d.ts +2 -2
  91. package/dist/integrations/pi/components/upstream/history/editor-core.js +5 -5
  92. package/dist/integrations/pi/components/upstream/history/kill-ring.d.ts +1 -1
  93. package/dist/integrations/pi/components/upstream/history/kill-ring.js +1 -1
  94. package/dist/integrations/pi/components/upstream/history/printable-key.js +1 -1
  95. package/dist/integrations/pi/components/upstream/history/text-helpers.js +1 -1
  96. package/dist/integrations/pi/components/upstream/history/undo-stack.d.ts +1 -1
  97. package/dist/integrations/pi/components/upstream/history/undo-stack.js +1 -1
  98. package/dist/integrations/pi/components/upstream/history/word-navigation.js +1 -1
  99. package/dist/integrations/pi/components/upstream/model-search.d.ts +1 -1
  100. package/dist/integrations/pi/components/upstream/program-status-reporter.d.ts +1 -1
  101. package/dist/integrations/pi/components/upstream/theme/theme-controller.d.ts +1 -1
  102. package/dist/integrations/pi/components/upstream/theme/theme.d.ts +3 -0
  103. package/dist/integrations/pi/components/upstream/theme/theme.js +24 -1
  104. package/dist/integrations/pi/engine/adapter.js +1 -0
  105. package/dist/integrations/pi/engine/pi-settings-metadata.json +2 -2
  106. package/dist/integrations/pi/engine/resources/changelog.json +4 -4
  107. package/dist/integrations/pi/engine/runtime-integration.js +102 -29
  108. package/dist/integrations/pi/engine/session-runtime.d.ts +0 -6
  109. package/dist/integrations/pi/engine/session-runtime.js +8 -3
  110. package/dist/integrations/pi/engine/session-selection.d.ts +3 -0
  111. package/dist/integrations/pi/engine/session-selection.js +45 -9
  112. package/dist/integrations/pi/engine/workflow-contexts.js +10 -1
  113. package/dist/integrations/pi/startup-public.js +152 -222
  114. package/dist/integrations/pi/startup-public.manifest.json +266 -177
  115. package/dist/integrations/pi/tui-runtime/damage-aware-terminal.d.ts +1 -1
  116. package/dist/integrations/pi/tui-runtime/damage-aware-terminal.js +1 -1
  117. package/dist/native/darwin-arm64/manifest.json +1 -1
  118. package/dist/native/linux-x64/manifest.json +1 -1
  119. package/dist/native/win32-x64/manifest.json +2 -2
  120. package/dist/native/win32-x64/process-guardian.exe +0 -0
  121. package/dist/product-identity.d.ts +1 -1
  122. package/dist/product-identity.js +1 -1
  123. package/dist/product-identity.json +5 -0
  124. package/dist/runtime-payload-inventory.json +5349 -6035
  125. package/dist/ui/components/line-input.d.ts +1 -1
  126. package/dist/ui/components/prompt-input.d.ts +7 -5
  127. package/dist/ui/components/prompt-input.js +7 -7
  128. package/dist/ui/components/transcript-viewport.d.ts +6 -3
  129. package/dist/ui/components/transcript-viewport.js +13 -15
  130. package/dist/ui/settings/declarations.d.ts +49 -1
  131. package/dist/ui/settings/declarations.js +37 -1
  132. package/dist/ui/settings/index.d.ts +5 -3
  133. package/dist/ui/settings/index.js +3 -2
  134. package/dist/ui/settings/manager.d.ts +9 -3
  135. package/dist/ui/settings/manager.js +81 -29
  136. package/dist/ui/settings/migrations.d.ts +6 -0
  137. package/dist/ui/settings/migrations.js +32 -0
  138. package/dist/ui/settings/profile-document.d.ts +26 -0
  139. package/dist/ui/settings/profile-document.js +164 -0
  140. package/dist/ui/settings/resolution.d.ts +2 -0
  141. package/dist/ui/settings/resolution.js +13 -1
  142. package/dist/ui/settings/sections.js +4 -0
  143. package/docs/architecture/internal-naming.md +1 -1
  144. package/docs/architecture/resident-tabs-requirements.md +580 -0
  145. package/docs/architecture/resident-tabs.md +522 -0
  146. package/docs/architecture/terminal-host-proof-gate.md +2 -0
  147. package/docs/openspec-archive-automation.md +17 -16
  148. package/docs/validation.md +1 -1
  149. package/package.json +6 -4
@@ -56,7 +56,7 @@ export declare function caretCell(text: string): string;
56
56
  export interface InputRowOptions {
57
57
  /** Shown quietly while nothing has been typed, with the caret on its first cell. */
58
58
  readonly placeholder?: string;
59
- /** Rules above and below, in the prompt's own grey. Default true. */
59
+ /** Theme-border rules. */
60
60
  readonly ruled?: boolean;
61
61
  /** Prompt marker including any following spacing. Defaults to the shared prompt glyph. */
62
62
  readonly promptGlyph?: string;
@@ -1,9 +1,10 @@
1
1
  import { type UiTheme } from "./theme.js";
2
2
  export declare const PROMPT_GLYPH = "\u276F ";
3
3
  export declare const PROMPT_PREFIX_WIDTH = 2;
4
- export declare function promptRuleText(text: string): string;
5
- export declare function promptRule(width: number): string;
6
- export declare function promptArrow(text: string, theme: Pick<UiTheme, "fg">): string;
4
+ type RuleTheme = Pick<UiTheme, "fg">;
5
+ export declare function promptRuleText(text: string, theme: RuleTheme): string;
6
+ export declare function promptRule(width: number, theme: RuleTheme): string;
7
+ export declare function promptArrow(text: string, theme: RuleTheme): string;
7
8
  export interface PromptInputBody {
8
9
  readonly rows: readonly string[];
9
10
  /** Semantic border fragments retain editor history and scroll annotations. */
@@ -19,9 +20,9 @@ export interface PromptInputMetrics {
19
20
  }
20
21
  /** Composes shared input chrome around controller-owned body rows without owning editing state. */
21
22
  export declare class PromptInput {
22
- readonly theme: Pick<UiTheme, "fg">;
23
+ readonly theme: RuleTheme;
23
24
  readonly metrics: PromptInputMetrics;
24
- constructor(theme?: Pick<UiTheme, "fg">, metrics?: PromptInputMetrics);
25
+ constructor(theme?: RuleTheme, metrics?: PromptInputMetrics);
25
26
  styleRule(text: string): string;
26
27
  geometry(width: number, padding?: number, promptGlyph?: string): {
27
28
  prefixWidth: number;
@@ -32,3 +33,4 @@ export declare class PromptInput {
32
33
  };
33
34
  render(width: number, renderBody: (innerWidth: number) => PromptInputBody, ruled?: boolean, padding?: number, promptGlyph?: string): string[];
34
35
  }
36
+ export {};
@@ -2,11 +2,11 @@ import { displayWidth, truncateToWidth } from "./text.js";
2
2
  import { PLAIN_THEME } from "./theme.js";
3
3
  export const PROMPT_GLYPH = "❯ ";
4
4
  export const PROMPT_PREFIX_WIDTH = 2;
5
- export function promptRuleText(text) {
6
- return `\u001b[38;2;154;160;166m${text}\u001b[39m`;
5
+ export function promptRuleText(text, theme) {
6
+ return theme.fg("border", text);
7
7
  }
8
- export function promptRule(width) {
9
- return promptRuleText("─".repeat(Math.max(0, width)));
8
+ export function promptRule(width, theme) {
9
+ return promptRuleText("─".repeat(Math.max(0, width)), theme);
10
10
  }
11
11
  export function promptArrow(text, theme) {
12
12
  return theme.fg("muted", text);
@@ -16,7 +16,7 @@ export class PromptInput {
16
16
  theme;
17
17
  metrics;
18
18
  constructor(theme = PLAIN_THEME, metrics = { measure: displayWidth, truncate: truncateToWidth }) { this.theme = theme; this.metrics = metrics; }
19
- styleRule(text) { return promptRuleText(text); }
19
+ styleRule(text) { return promptRuleText(text, this.theme); }
20
20
  geometry(width, padding = 0, promptGlyph = PROMPT_GLYPH) {
21
21
  const prefixWidth = Math.min(this.metrics.measure(promptGlyph), Math.max(0, width));
22
22
  // Compatibility: editor wrapping needs room for a two-cell grapheme plus its cursor cell.
@@ -43,8 +43,8 @@ export class PromptInput {
43
43
  ? promptArrow(promptGlyph, this.theme)
44
44
  : " ".repeat(prefixWidth)}${row}`));
45
45
  const rule = (fragment) => fragment === undefined
46
- ? promptRule(width)
47
- : fit(fragment + promptRule(Math.max(0, width - this.metrics.measure(fragment))));
46
+ ? promptRule(width, this.theme)
47
+ : fit(fragment + promptRule(Math.max(0, width - this.metrics.measure(fragment)), this.theme));
48
48
  return [
49
49
  ...(ruled ? [rule(body.topRule)] : []),
50
50
  ...rows,
@@ -36,6 +36,7 @@ export interface TranscriptViewportFrameInput {
36
36
  /** Final transient rows to bottom-align with unused viewport space while content fits. */
37
37
  readonly bottomAlignedTailRowCount?: number;
38
38
  readonly dockRows: readonly string[];
39
+ readonly explicitCopyDockRows?: number;
39
40
  readonly promptAnchors: readonly TranscriptPromptAnchor[];
40
41
  readonly width: number;
41
42
  readonly height: number;
@@ -141,6 +142,7 @@ export interface TranscriptViewportFrame {
141
142
  readonly cacheEntries: number;
142
143
  };
143
144
  }
145
+ type SelectionCopyIntent = "automatic" | "explicit";
144
146
  /**
145
147
  * A1-owned viewport state. It knows only rows and semantic prompt anchors; Pi
146
148
  * components and transcript payloads remain outside this neutral boundary.
@@ -170,9 +172,9 @@ export declare class TranscriptViewport {
170
172
  extendSelection(column: number, frameRow: number, now?: number, autoScroll?: boolean): boolean;
171
173
  releaseSelection(): boolean;
172
174
  clearSelection(): boolean;
173
- selectedText(): string | null;
175
+ selectedText(intent?: SelectionCopyIntent): string | null;
174
176
  /** Captures visible selected text, not off-screen history. */
175
- captureSelectedText(): SelectionCopySnapshot | null;
177
+ captureSelectedText(intent?: SelectionCopyIntent): SelectionCopySnapshot | null;
176
178
  scrollBy(lines: number, now?: number): boolean;
177
179
  scrollTo(position: number, now?: number): boolean;
178
180
  scrollToEnd(now?: number): boolean;
@@ -184,6 +186,7 @@ export declare class TranscriptViewport {
184
186
  clearTransient(): void;
185
187
  compose(input: TranscriptViewportFrameInput): TranscriptViewportFrame;
186
188
  /** Reuses an established transcript frame when only same-height dock rows changed. */
187
- composeDockOnly(dockRows: readonly string[], width: number, height: number): TranscriptViewportFrame | null;
189
+ composeDockOnly(dockRows: readonly string[], width: number, height: number, explicitCopyDockRows?: number): TranscriptViewportFrame | null;
188
190
  }
189
191
  export declare function assertTranscriptViewportFrameDescriptor(descriptor: TranscriptViewportFrameDescriptor): void;
192
+ export {};
@@ -63,6 +63,7 @@ export class TranscriptViewport {
63
63
  #contentWidth = 0;
64
64
  #selectionWidth = 0;
65
65
  #viewportHeight = 0;
66
+ #explicitCopyDockRows = 0;
66
67
  #firstSelectableRow = 0;
67
68
  #selectionRowAnchors = [];
68
69
  #frameId = 0;
@@ -178,22 +179,19 @@ export class TranscriptViewport {
178
179
  this.#selectionRevision += 1;
179
180
  return true;
180
181
  }
181
- selectedText() {
182
+ selectedText(intent = "automatic") {
182
183
  const selection = this.#visibleSelection();
183
184
  if (selection === undefined)
184
185
  return null;
185
- // Invariant: the blue range spans the complete frame, but automatic and Ctrl+C frame copy
186
- // admit only persistent transcript rows. Transient status, suggestions, input, and footer
187
- // chrome remain visually selectable without becoming clipboard payload.
188
186
  const copiedLines = Array.from({ length: Math.max(0, selection.end.line - selection.start.line + 1) }, (_value, index) => selection.start.line + index).filter(line => {
189
187
  const anchor = this.#selectionRowAnchors[line];
190
- return anchor?.kind === "document" && anchor.row < this.#selectableDocumentRowCount;
188
+ return anchor?.kind === "document" && anchor.row < this.#selectableDocumentRowCount
189
+ || intent === "explicit" && anchor?.kind === "dock" && anchor.fromBottom < this.#explicitCopyDockRows;
191
190
  });
192
191
  if (copiedLines.length === 0)
193
192
  return null;
194
- // Rationale: a prompt selected by itself uses semantic prompt text. Once surrounding transcript text
195
- // participates, preserve the prompt's visible prefix, alignment, and timestamp in the bulk range.
196
- const prompt = this.#promptAnchors.find(candidate => copiedLines.every(line => {
193
+ const transcriptLines = copiedLines.filter(line => this.#selectionRowAnchors[line]?.kind === "document");
194
+ const prompt = transcriptLines.length === 0 ? undefined : this.#promptAnchors.find(candidate => transcriptLines.every(line => {
197
195
  const anchor = this.#selectionRowAnchors[line];
198
196
  return anchor?.kind === "document" && anchor.row >= candidate.firstRow && anchor.row <= candidate.lastRow;
199
197
  }));
@@ -208,11 +206,11 @@ export class TranscriptViewport {
208
206
  return text.length === 0 ? null : text;
209
207
  }
210
208
  /** Captures visible selected text, not off-screen history. */
211
- captureSelectedText() {
212
- if (!this.#copyableSelection)
209
+ captureSelectedText(intent = "automatic") {
210
+ if (intent === "automatic" && !this.#copyableSelection)
213
211
  return null;
214
212
  const started = performance.now();
215
- let text = this.selectedText();
213
+ let text = this.selectedText(intent);
216
214
  if (text === null)
217
215
  return null;
218
216
  text = text.trim();
@@ -315,6 +313,7 @@ export class TranscriptViewport {
315
313
  this.#contentWidth = 0;
316
314
  this.#selectionWidth = 0;
317
315
  this.#viewportHeight = 0;
316
+ this.#explicitCopyDockRows = 0;
318
317
  this.clearTransient();
319
318
  this.#frame = null;
320
319
  }
@@ -330,6 +329,7 @@ export class TranscriptViewport {
330
329
  const theme = input.theme ?? PLAIN_THEME;
331
330
  const previousDescriptor = this.#frame?.descriptor ?? null;
332
331
  const dock = input.dockRows.length > height ? input.dockRows.slice(-height) : [...input.dockRows];
332
+ this.#explicitCopyDockRows = clamp(input.explicitCopyDockRows ?? 0, 0, dock.length);
333
333
  const viewportHeight = Math.max(0, height - dock.length);
334
334
  const bottomAlignedTailRowCount = clamp(input.bottomAlignedTailRowCount ?? 0, 0, input.documentRows.length);
335
335
  const transientAlignmentGapRows = bottomAlignedTailRowCount > 0
@@ -414,8 +414,6 @@ export class TranscriptViewport {
414
414
  visible[0] = quiet && !this.#stickyHovered ? theme.quietSticky(sticky) : sticky;
415
415
  visibleSource[0] = sourceRow;
416
416
  }
417
- // Invariant: selection uses exact visible source rows within the rendered
418
- // content boundary; paint-only guards and the rail gutter stay presentation-only.
419
417
  this.#selectionRows = [
420
418
  ...visibleSource.map(row => truncateToWidth(row, contentWidth)),
421
419
  ...dock,
@@ -426,7 +424,6 @@ export class TranscriptViewport {
426
424
  ? viewportSelectionAnchor(documentRow, documentRows[documentRow] ?? "", this.#selectableDocumentRowCount, documentRows.length)
427
425
  : { kind: "screen", row, source: this.#selectionRows[row] ?? "" };
428
426
  });
429
- // Invariant: a pinned prompt is chrome over its hidden source row, which clips like off-screen rows.
430
427
  this.#firstSelectableRow = stickyActive && viewportHeight > 0 ? 1 : 0;
431
428
  this.#selectionRowAnchors = [
432
429
  ...visibleAnchors,
@@ -609,7 +606,7 @@ export class TranscriptViewport {
609
606
  return frame;
610
607
  }
611
608
  /** Reuses an established transcript frame when only same-height dock rows changed. */
612
- composeDockOnly(dockRows, width, height) {
609
+ composeDockOnly(dockRows, width, height, explicitCopyDockRows = 0) {
613
610
  const previous = this.#frame;
614
611
  const boundedWidth = Math.max(1, width);
615
612
  const boundedHeight = Math.max(0, height);
@@ -645,6 +642,7 @@ export class TranscriptViewport {
645
642
  };
646
643
  assertTranscriptViewportFrameDescriptor(descriptor);
647
644
  this.#selectionRows = [...this.#selectionRows.slice(0, viewportHeight), ...dock].slice(0, boundedHeight);
645
+ this.#explicitCopyDockRows = clamp(explicitCopyDockRows, 0, dock.length);
648
646
  const frame = {
649
647
  rows: [...previous.rows.slice(0, viewportHeight), ...dock].slice(0, boundedHeight),
650
648
  contentWidth: previous.contentWidth,
@@ -1,4 +1,4 @@
1
- export declare const OWNED_UI_SETTINGS_VERSION = 13;
1
+ export declare const OWNED_UI_SETTINGS_VERSION = 15;
2
2
  export type OwnedUiSettingValue = string | number | boolean;
3
3
  export type OwnedUiSettingApplication = "live" | "restart";
4
4
  export interface OwnedUiSettingDeclaration {
@@ -164,6 +164,54 @@ export declare const OWNED_SETTING_DECLARATIONS: Readonly<{
164
164
  defaultValue: 8;
165
165
  allowedValues: readonly [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
166
166
  }>;
167
+ readonly residentTabs: Readonly<{
168
+ id: "residentTabs";
169
+ label: "Resident tabs";
170
+ section: Readonly<{
171
+ id: "generic";
172
+ title: "Generic";
173
+ }>;
174
+ description: "Keep bare A1 running when its terminal closes and reattach on the next launch. Turning this off prevents new resident launches but does not stop existing tabs.";
175
+ application: "restart";
176
+ defaultValue: false;
177
+ allowedValues: readonly [true, false];
178
+ }>;
179
+ readonly tabsMaxConcurrentStarts: Readonly<{
180
+ id: "tabsMaxConcurrentStarts";
181
+ label: "Concurrent tab starts";
182
+ section: Readonly<{
183
+ id: "generic";
184
+ title: "Generic";
185
+ }>;
186
+ description: "Maximum A1 tabs started together. Starts remain spaced to protect shared profile files.";
187
+ application: "restart";
188
+ defaultValue: 2;
189
+ allowedValues: readonly [1, 2, 3, 4, 5, 6, 7, 8];
190
+ }>;
191
+ readonly tabsUnresponsiveRestartSeconds: Readonly<{
192
+ id: "tabsUnresponsiveRestartSeconds";
193
+ label: "Unresponsive tab restart";
194
+ section: Readonly<{
195
+ id: "generic";
196
+ title: "Generic";
197
+ }>;
198
+ description: "Seconds of continuously healthy supervision without a tab heartbeat before A1 restarts that tab.";
199
+ application: "restart";
200
+ defaultValue: 120;
201
+ allowedValues: readonly [60, 120, 300, 600];
202
+ }>;
203
+ readonly tabsStallNoticeSeconds: Readonly<{
204
+ id: "tabsStallNoticeSeconds";
205
+ label: "Tab stall notice";
206
+ section: Readonly<{
207
+ id: "generic";
208
+ title: "Generic";
209
+ }>;
210
+ description: "Seconds without model or tool progress before a working tab shows a stall notice; A1 never kills a stalled tab automatically.";
211
+ application: "restart";
212
+ defaultValue: 300;
213
+ allowedValues: readonly [120, 300, 600, 1800];
214
+ }>;
167
215
  }>;
168
216
  export type OwnedSettingId = keyof typeof OWNED_SETTING_DECLARATIONS;
169
217
  /** The value type a setting's declaration allows: a literal union for a choice, `number` for a stepped range. */
@@ -1,4 +1,4 @@
1
- export const OWNED_UI_SETTINGS_VERSION = 13;
1
+ export const OWNED_UI_SETTINGS_VERSION = 15;
2
2
  const UI_ACCENT_COLOR_CHOICES = Object.freeze([
3
3
  "purple", "blue", "cyan", "green", "orange", "pink",
4
4
  ]);
@@ -124,6 +124,42 @@ export const OWNED_SETTING_DECLARATIONS = Object.freeze({
124
124
  defaultValue: 8,
125
125
  allowedValues: Object.freeze([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]),
126
126
  }),
127
+ residentTabs: Object.freeze({
128
+ id: "residentTabs",
129
+ label: "Resident tabs",
130
+ section: GENERIC_SECTION,
131
+ description: "Keep bare A1 running when its terminal closes and reattach on the next launch. Turning this off prevents new resident launches but does not stop existing tabs.",
132
+ application: "restart",
133
+ defaultValue: false,
134
+ allowedValues: Object.freeze([true, false]),
135
+ }),
136
+ tabsMaxConcurrentStarts: Object.freeze({
137
+ id: "tabsMaxConcurrentStarts",
138
+ label: "Concurrent tab starts",
139
+ section: GENERIC_SECTION,
140
+ description: "Maximum A1 tabs started together. Starts remain spaced to protect shared profile files.",
141
+ application: "restart",
142
+ defaultValue: 2,
143
+ allowedValues: Object.freeze([1, 2, 3, 4, 5, 6, 7, 8]),
144
+ }),
145
+ tabsUnresponsiveRestartSeconds: Object.freeze({
146
+ id: "tabsUnresponsiveRestartSeconds",
147
+ label: "Unresponsive tab restart",
148
+ section: GENERIC_SECTION,
149
+ description: "Seconds of continuously healthy supervision without a tab heartbeat before A1 restarts that tab.",
150
+ application: "restart",
151
+ defaultValue: 120,
152
+ allowedValues: Object.freeze([60, 120, 300, 600]),
153
+ }),
154
+ tabsStallNoticeSeconds: Object.freeze({
155
+ id: "tabsStallNoticeSeconds",
156
+ label: "Tab stall notice",
157
+ section: GENERIC_SECTION,
158
+ description: "Seconds without model or tool progress before a working tab shows a stall notice; A1 never kills a stalled tab automatically.",
159
+ application: "restart",
160
+ defaultValue: 300,
161
+ allowedValues: Object.freeze([120, 300, 600, 1800]),
162
+ }),
127
163
  });
128
164
  /** The declarations in declaration order, for resolution, persistence, and the settings screen. */
129
165
  export const OWNED_UI_SETTING_DECLARATIONS = Object.freeze(Object.values(OWNED_SETTING_DECLARATIONS));
@@ -1,10 +1,12 @@
1
1
  export { OWNED_SETTING_DECLARATIONS, OWNED_UI_SETTINGS_VERSION, OWNED_UI_SETTING_DECLARATIONS, assertOwnedUiSettingDeclarations, findOwnedUiSettingDeclaration, isOwnedSettingId, } from "./declarations.js";
2
2
  export type { OwnedSettingId, OwnedSettingValueOf, OwnedUiSettingApplication, OwnedUiSettingDeclaration, OwnedUiSettingValue } from "./declarations.js";
3
- export { OWNED_UI_SETTINGS_MIGRATIONS, assertOwnedUiSettingsMigrations, migrationsFrom } from "./migrations.js";
4
- export type { OwnedUiSettingsMigration } from "./migrations.js";
5
- export { documentFrom, parseOwnedUiSettingsDocument, resolveOwnedUiSettings, settingValue } from "./resolution.js";
3
+ export { OWNED_UI_SETTINGS_MIGRATIONS, assertOwnedUiSettingsMigrations, migrationsFrom, normalizeOwnedUiSettingsValues, } from "./migrations.js";
4
+ export type { OwnedUiSettingsCompatibilityResult, OwnedUiSettingsMigration } from "./migrations.js";
5
+ export { documentFrom, parseOwnedUiSettingsDocument, resolveOwnedUiSettings, resolveResidentTabsSetting, settingValue } from "./resolution.js";
6
6
  export type { OwnedUiResolvedSetting, OwnedUiSettingSource, OwnedUiSettingsDocument, OwnedUiSettingsNotice, OwnedUiSettingsNoticeCode, OwnedUiSettingsResolution, ResolveOwnedUiSettingsInput, } from "./resolution.js";
7
7
  export { AGENT_SECTION_ID, buildOwnedUiSettingsSections, findOwnedUiSettingsEntry } from "./sections.js";
8
8
  export type { AgentSettingsSnapshot, BuildOwnedUiSettingsSectionsInput, OwnedUiSettingsBackend, OwnedUiSettingsEntry, OwnedUiSettingsSection, } from "./sections.js";
9
9
  export { OwnedSettingsManager } from "./manager.js";
10
10
  export type { OwnedSettingsManagerOptions, OwnedUiSettingsChangeOutcome, OwnedUiSettingsListener } from "./manager.js";
11
+ export { assertProfileSettingsNamespaceAvailable, readProfileSettingsNamespace, repairProfileSettingsDocument, updateProfileSettingsNamespace, } from "./profile-document.js";
12
+ export type { ProfileSettingsNamespaceRead, ProfileSettingsNamespaceUpdate } from "./profile-document.js";
@@ -1,5 +1,6 @@
1
1
  export { OWNED_SETTING_DECLARATIONS, OWNED_UI_SETTINGS_VERSION, OWNED_UI_SETTING_DECLARATIONS, assertOwnedUiSettingDeclarations, findOwnedUiSettingDeclaration, isOwnedSettingId, } from "./declarations.js";
2
- export { OWNED_UI_SETTINGS_MIGRATIONS, assertOwnedUiSettingsMigrations, migrationsFrom } from "./migrations.js";
3
- export { documentFrom, parseOwnedUiSettingsDocument, resolveOwnedUiSettings, settingValue } from "./resolution.js";
2
+ export { OWNED_UI_SETTINGS_MIGRATIONS, assertOwnedUiSettingsMigrations, migrationsFrom, normalizeOwnedUiSettingsValues, } from "./migrations.js";
3
+ export { documentFrom, parseOwnedUiSettingsDocument, resolveOwnedUiSettings, resolveResidentTabsSetting, settingValue } from "./resolution.js";
4
4
  export { AGENT_SECTION_ID, buildOwnedUiSettingsSections, findOwnedUiSettingsEntry } from "./sections.js";
5
5
  export { OwnedSettingsManager } from "./manager.js";
6
+ export { assertProfileSettingsNamespaceAvailable, readProfileSettingsNamespace, repairProfileSettingsDocument, updateProfileSettingsNamespace, } from "./profile-document.js";
@@ -4,9 +4,15 @@ import { type OwnedUiSettingsMigration } from "./migrations.js";
4
4
  import { type OwnedUiSettingsResolution } from "./resolution.js";
5
5
  import { type OwnedUiSettingsBackend, type OwnedUiSettingsSection } from "./sections.js";
6
6
  export interface OwnedSettingsManagerOptions {
7
- /** A1 configuration root. Settings live under `<configDir>/settings/`. */
8
- readonly configDir: string;
7
+ /** Selected global agent profile containing the shared `settings.json`. */
8
+ readonly profileDir?: string;
9
+ /** Compatibility alias for SDK/tests that supplied the former root option. */
10
+ readonly configDir?: string;
9
11
  readonly profileId: string;
12
+ /** Product namespace; defaults to profileId for generic and hermetic consumers. */
13
+ readonly namespace?: string;
14
+ /** Former product configuration root, consulted only while the namespace is absent. */
15
+ readonly legacyConfigDir?: string;
10
16
  readonly declarations?: readonly OwnedUiSettingDeclaration[];
11
17
  readonly migrations?: readonly OwnedUiSettingsMigration[];
12
18
  readonly agent?: AgentSettingsPort | null;
@@ -37,7 +43,7 @@ export declare class OwnedSettingsManager {
37
43
  constructor(options: OwnedSettingsManagerOptions);
38
44
  get file(): string;
39
45
  get resolution(): OwnedUiSettingsResolution;
40
- /** Reads and resolves the stored document. Never throws: every failure becomes a notice. */
46
+ /** Reads and resolves the product namespace. Never throws: every failure becomes a notice. */
41
47
  read(): OwnedUiSettingsResolution;
42
48
  load(): Promise<void>;
43
49
  sections(): readonly OwnedUiSettingsSection[];
@@ -1,10 +1,11 @@
1
- import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
1
+ import { readFileSync } from "node:fs";
2
2
  import path from "node:path";
3
3
  import { assertAgentSettingDescriptor } from "../../contracts/agent-engine/index.js";
4
4
  import { OWNED_SETTING_DECLARATIONS, OWNED_UI_SETTINGS_VERSION, OWNED_UI_SETTING_DECLARATIONS, } from "./declarations.js";
5
- import { OWNED_UI_SETTINGS_MIGRATIONS } from "./migrations.js";
6
- import { documentFrom, parseOwnedUiSettingsDocument, resolveOwnedUiSettings, } from "./resolution.js";
5
+ import { OWNED_UI_SETTINGS_MIGRATIONS, normalizeOwnedUiSettingsValues, } from "./migrations.js";
6
+ import { parseOwnedUiSettingsDocument, resolveOwnedUiSettings, } from "./resolution.js";
7
7
  import { buildOwnedUiSettingsSections, findOwnedUiSettingsEntry, } from "./sections.js";
8
+ import { readProfileSettingsNamespace, repairProfileSettingsDocument, updateProfileSettingsNamespace, } from "./profile-document.js";
8
9
  const PROFILE_ID_PATTERN = /^[a-z][a-z0-9-]{0,63}$/;
9
10
  const MAX_DOCUMENT_BYTES = 256 * 1024;
10
11
  /**
@@ -15,6 +16,8 @@ const MAX_DOCUMENT_BYTES = 256 * 1024;
15
16
  */
16
17
  export class OwnedSettingsManager {
17
18
  #file;
19
+ #legacyFile;
20
+ #namespace;
18
21
  #declarations;
19
22
  #migrations;
20
23
  #agent;
@@ -30,7 +33,14 @@ export class OwnedSettingsManager {
30
33
  if (!PROFILE_ID_PATTERN.test(options.profileId)) {
31
34
  throw new Error(`owned UI settings profile id is not a bounded slug: ${options.profileId}`);
32
35
  }
33
- this.#file = path.join(path.resolve(options.configDir), "settings", `${options.profileId}.json`);
36
+ const profileDir = options.profileDir ?? options.configDir;
37
+ if (profileDir === undefined)
38
+ throw new Error("owned UI settings require a profile directory");
39
+ this.#file = path.join(path.resolve(profileDir), "settings.json");
40
+ this.#legacyFile = options.legacyConfigDir === undefined
41
+ ? null
42
+ : path.join(path.resolve(options.legacyConfigDir), "settings", `${options.profileId}.json`);
43
+ this.#namespace = options.namespace ?? options.profileId;
34
44
  this.#declarations = options.declarations ?? OWNED_UI_SETTING_DECLARATIONS;
35
45
  this.#migrations = options.migrations ?? OWNED_UI_SETTINGS_MIGRATIONS;
36
46
  this.#agent = options.agent ?? null;
@@ -49,24 +59,59 @@ export class OwnedSettingsManager {
49
59
  get resolution() {
50
60
  return this.#resolution;
51
61
  }
52
- /** Reads and resolves the stored document. Never throws: every failure becomes a notice. */
62
+ /** Reads and resolves the product namespace. Never throws: every failure becomes a notice. */
53
63
  read() {
64
+ try {
65
+ repairProfileSettingsDocument(this.#file);
66
+ }
67
+ catch {
68
+ // Compatibility: recovery is opportunistic; unreadable-document handling stays authoritative.
69
+ }
70
+ const shared = readProfileSettingsNamespace(this.#file, this.#namespace);
71
+ if (shared.status === "available") {
72
+ if (Buffer.byteLength(JSON.stringify(shared.value), "utf8") > MAX_DOCUMENT_BYTES) {
73
+ return this.#resolveNamespace(null, `${this.#file} contains a ${this.#namespace} namespace exceeding the ${MAX_DOCUMENT_BYTES}-byte settings limit`);
74
+ }
75
+ return this.#resolveCompatibleNamespace(shared.value);
76
+ }
77
+ if (shared.status === "invalid")
78
+ return this.#resolveNamespace(null, shared.detail);
79
+ return this.#readLegacy();
80
+ }
81
+ #resolveCompatibleNamespace(values) {
82
+ const compatibility = normalizeOwnedUiSettingsValues(values);
83
+ return compatibility.values === null
84
+ ? this.#resolveNamespace(null, compatibility.failure ?? "product settings compatibility failed")
85
+ : this.#resolveNamespace(compatibility.values);
86
+ }
87
+ #resolveNamespace(values, unreadableDetail) {
88
+ return resolveOwnedUiSettings({
89
+ declarations: this.#declarations,
90
+ migrations: [],
91
+ document: values === null ? null : { version: OWNED_UI_SETTINGS_VERSION, values },
92
+ ...(unreadableDetail === undefined ? {} : { unreadableDetail }),
93
+ currentVersion: OWNED_UI_SETTINGS_VERSION,
94
+ });
95
+ }
96
+ #readLegacy() {
97
+ if (this.#legacyFile === null)
98
+ return this.#resolveNamespace(null);
54
99
  let raw = null;
55
100
  let unreadable = null;
56
101
  try {
57
- raw = readFileSync(this.#file, "utf8");
102
+ raw = readFileSync(this.#legacyFile, "utf8");
58
103
  }
59
104
  catch (error) {
60
105
  if (!isMissing(error))
61
- unreadable = `${this.#file} could not be read: ${describe(error)}`;
106
+ unreadable = `${this.#legacyFile} could not be read: ${describe(error)}`;
62
107
  }
63
108
  if (raw !== null && Buffer.byteLength(raw, "utf8") > MAX_DOCUMENT_BYTES) {
64
- unreadable = `${this.#file} exceeds the ${MAX_DOCUMENT_BYTES}-byte settings limit and was ignored`;
109
+ unreadable = `${this.#legacyFile} exceeds the ${MAX_DOCUMENT_BYTES}-byte settings limit and was ignored`;
65
110
  raw = null;
66
111
  }
67
112
  const document = raw === null ? null : parseOwnedUiSettingsDocument(raw);
68
113
  if (raw !== null && document === null && unreadable === null) {
69
- unreadable = `${this.#file} is not a valid settings document and was ignored`;
114
+ unreadable = `${this.#legacyFile} is not a valid settings document and was ignored`;
70
115
  }
71
116
  return resolveOwnedUiSettings({
72
117
  declarations: this.#declarations,
@@ -126,7 +171,7 @@ export class OwnedSettingsManager {
126
171
  return backend === "a1" ? this.#changeOwned(id, value) : await this.#changeAgentValue(id, value);
127
172
  }
128
173
  #changeOwned(id, value) {
129
- const outcome = this.#write(this.#resolution, id, value);
174
+ const outcome = this.#write(id, value);
130
175
  if (!outcome.stored)
131
176
  return failed(outcome.failure ?? `${id} could not be stored`);
132
177
  const previous = this.#resolution;
@@ -141,35 +186,34 @@ export class OwnedSettingsManager {
141
186
  this.#notify();
142
187
  return changed("applied", "live", value, value);
143
188
  }
144
- // Invariant: the resolved form is written with `id` set to `value` and undeclared keys preserved.
145
- // Concurrency: a temporary sibling is renamed over the target, so an interrupted write leaves
146
- // either the complete previous document or the complete new one.
147
- #write(resolution, id, value) {
189
+ // Invariant: only non-default declared values and unknown product properties are stored.
190
+ // Concurrency: the namespace updater rereads current JSON while holding Pi's compatible lock.
191
+ #write(id, value) {
148
192
  const declaration = this.#declarations.find(candidate => candidate.id === id);
149
193
  if (!declaration)
150
194
  return { stored: false, failure: `unknown owned UI setting: ${id}` };
151
195
  if (!declaration.allowedValues.includes(value)) {
152
196
  return { stored: false, failure: `value ${JSON.stringify(value)} is not allowed for ${id}` };
153
197
  }
154
- const current = documentFrom(resolution);
155
- const next = {
156
- version: OWNED_UI_SETTINGS_VERSION,
157
- values: { ...current.values, [id]: value },
158
- };
159
- const temporary = `${this.#file}.${process.pid}.tmp`;
160
198
  try {
161
- mkdirSync(path.dirname(this.#file), { recursive: true, mode: 0o700 });
162
- writeFileSync(temporary, `${JSON.stringify(next, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
163
- renameSync(temporary, this.#file);
199
+ updateProfileSettingsNamespace(this.#file, this.#namespace, current => {
200
+ let latest = this.#readLegacy();
201
+ if (current !== null) {
202
+ const compatibility = normalizeOwnedUiSettingsValues(current);
203
+ if (compatibility.values === null)
204
+ throw new Error(compatibility.failure ?? "product settings compatibility failed");
205
+ latest = this.#resolveNamespace(compatibility.values);
206
+ }
207
+ const next = compactValues(latest);
208
+ if (value === declaration.defaultValue)
209
+ delete next[id];
210
+ else
211
+ next[id] = value;
212
+ return Object.keys(next).length === 0 ? null : next;
213
+ });
164
214
  return { stored: true, failure: null };
165
215
  }
166
216
  catch (error) {
167
- try {
168
- rmSync(temporary, { force: true });
169
- }
170
- catch {
171
- // Security: a leftover temporary file is inert: read() only ever opens the target path.
172
- }
173
217
  return { stored: false, failure: `${this.#file} could not be written: ${describe(error)}` };
174
218
  }
175
219
  }
@@ -254,6 +298,14 @@ function fromAgentOutcome(result) {
254
298
  failure: result.failure,
255
299
  };
256
300
  }
301
+ function compactValues(resolution) {
302
+ const values = { ...resolution.preserved };
303
+ for (const setting of resolution.settings) {
304
+ if (setting.value !== setting.declaration.defaultValue)
305
+ values[setting.declaration.id] = setting.value;
306
+ }
307
+ return values;
308
+ }
257
309
  function isMissing(error) {
258
310
  return typeof error === "object" && error !== null && error.code === "ENOENT";
259
311
  }
@@ -9,5 +9,11 @@ export interface OwnedUiSettingsMigration {
9
9
  * is the first stored shape.
10
10
  */
11
11
  export declare const OWNED_UI_SETTINGS_MIGRATIONS: readonly OwnedUiSettingsMigration[];
12
+ export interface OwnedUiSettingsCompatibilityResult {
13
+ readonly values: Readonly<Record<string, unknown>> | null;
14
+ readonly failure: string | null;
15
+ }
16
+ /** Shape-selected compatibility for the metadata-free product object. */
17
+ export declare function normalizeOwnedUiSettingsValues(source: Readonly<Record<string, unknown>>): OwnedUiSettingsCompatibilityResult;
12
18
  export declare function assertOwnedUiSettingsMigrations(migrations: readonly OwnedUiSettingsMigration[], currentVersion?: number): void;
13
19
  export declare function migrationsFrom(migrations: readonly OwnedUiSettingsMigration[], storedVersion: number): readonly OwnedUiSettingsMigration[];
@@ -104,7 +104,39 @@ export const OWNED_UI_SETTINGS_MIGRATIONS = Object.freeze([
104
104
  return { ...values };
105
105
  },
106
106
  }),
107
+ Object.freeze({
108
+ to: 14,
109
+ description: "Introduce the opt-in resident tabs preview with a disabled default.",
110
+ migrate(values) {
111
+ return { ...values };
112
+ },
113
+ }),
114
+ Object.freeze({
115
+ to: 15,
116
+ description: "Introduce the bounded concurrent resident-tab start limit.",
117
+ migrate(values) {
118
+ return { ...values };
119
+ },
120
+ }),
107
121
  ]);
122
+ /** Shape-selected compatibility for the metadata-free product object. */
123
+ export function normalizeOwnedUiSettingsValues(source) {
124
+ const values = { ...source };
125
+ // Compatibility: "high" is valid again, so the old speed meaning is ambiguous without legacy metadata.
126
+ if (values.scrollbarAppearance === "hover")
127
+ values.scrollbarAppearance = "auto";
128
+ if (values.accentColor === "default")
129
+ values.accentColor = "purple";
130
+ if (values.quitEffect === "off") {
131
+ if (Object.hasOwn(values, "quitAnimation") && values.quitAnimation !== false) {
132
+ return { values: null, failure: "legacy quitEffect conflicts with quitAnimation" };
133
+ }
134
+ values.quitAnimation = false;
135
+ }
136
+ delete values.quitEffect;
137
+ delete values.quitEffectDurationMs;
138
+ return { values, failure: null };
139
+ }
108
140
  export function assertOwnedUiSettingsMigrations(migrations, currentVersion = OWNED_UI_SETTINGS_VERSION) {
109
141
  const firstProduced = currentVersion - migrations.length + 1;
110
142
  if (migrations.length > 0 && firstProduced < 2) {
@@ -0,0 +1,26 @@
1
+ export type ProfileSettingsNamespaceRead = {
2
+ readonly status: "missing";
3
+ readonly value: null;
4
+ } | {
5
+ readonly status: "available";
6
+ readonly value: Readonly<Record<string, unknown>>;
7
+ } | {
8
+ readonly status: "invalid";
9
+ readonly value: null;
10
+ readonly detail: string;
11
+ };
12
+ export type ProfileSettingsNamespaceUpdate = (current: Readonly<Record<string, unknown>> | null) => Readonly<Record<string, unknown>> | null;
13
+ /** Refuse an injected namespace already claimed by the surrounding root schema. */
14
+ export declare function assertProfileSettingsNamespaceAvailable(namespace: string, rootSettingKeys: readonly string[]): void;
15
+ /** Read one product-owned root object without exposing unrelated profile settings. */
16
+ export declare function readProfileSettingsNamespace(file: string, namespace: string): ProfileSettingsNamespaceRead;
17
+ /**
18
+ * Remove bounded leading noise only when the remaining bytes are one complete root object.
19
+ * The repair is silent and uses the same lock and atomic replacement path as namespace writes.
20
+ */
21
+ export declare function repairProfileSettingsDocument(file: string): boolean;
22
+ /**
23
+ * Replace one root namespace through the lock protocol used by pinned Pi.
24
+ * The updater receives bytes reread while locked, so unrelated concurrent writes survive.
25
+ */
26
+ export declare function updateProfileSettingsNamespace(file: string, namespace: string, update: ProfileSettingsNamespaceUpdate): void;