@timurproko/a1 0.2.7-dev.754 → 0.2.7-dev.772
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -0
- package/bin/cli.js +4 -0
- package/bin/guardian.js +5 -0
- package/bin/tab-launch-context.d.ts +26 -0
- package/bin/tab-launch-context.js +94 -0
- package/bin/ui.js +24 -6
- package/dist/app/session-shell/helper-pool.d.ts +3 -5
- package/dist/app/session-shell/helper-pool.js +3 -5
- package/dist/app/session-shell/session-shell-root.d.ts +28 -1
- package/dist/app/session-shell/session-shell-root.js +4 -3
- package/dist/app/session-shell/session-shell.js +149 -31
- package/dist/app/session-shell/session-viewport-controller.d.ts +1 -1
- package/dist/app/session-shell/session-viewport-controller.js +3 -3
- package/dist/app/session-shell/shell-host.d.ts +2 -0
- package/dist/app/session-shell/shell-host.js +20 -1
- package/dist/cli/dispatch.d.ts +11 -1
- package/dist/cli/dispatch.js +79 -5
- package/dist/cli/tabs-request.d.ts +7 -0
- package/dist/cli/tabs-request.js +1 -0
- package/dist/cli/tabs.d.ts +14 -0
- package/dist/cli/tabs.js +140 -0
- package/dist/composition/owned-ui.d.ts +26 -0
- package/dist/composition/owned-ui.js +176 -6
- package/dist/contracts/owned-ui/session-backend.d.ts +1 -0
- package/dist/features/launch/index.d.ts +1 -1
- package/dist/features/launch/intent.d.ts +3 -1
- package/dist/features/launch/intent.js +7 -4
- package/dist/features/owned-ui/settings-app.js +8 -35
- package/dist/features/resident-tab/auto-name.d.ts +11 -0
- package/dist/features/resident-tab/auto-name.js +42 -0
- package/dist/features/resident-tab/index.d.ts +3 -0
- package/dist/features/resident-tab/index.js +3 -0
- package/dist/features/resident-tab/shortcuts.d.ts +10 -0
- package/dist/features/resident-tab/shortcuts.js +35 -0
- package/dist/features/resident-tab/status-deriver.d.ts +9 -0
- package/dist/features/resident-tab/status-deriver.js +15 -0
- package/dist/foundation/launch-context/index.d.ts +10 -0
- package/dist/foundation/launch-context/index.js +25 -0
- package/dist/foundation/launch-guardian/main.d.ts +5 -0
- package/dist/foundation/launch-guardian/main.js +2 -1
- package/dist/foundation/release/bootstrap.d.ts +6 -0
- package/dist/foundation/release/bootstrap.js +51 -10
- package/dist/foundation/release/cohort-state.d.ts +1 -1
- package/dist/foundation/release/cohort-state.js +1 -1
- package/dist/foundation/release/dependency-certification.js +54 -19
- package/dist/foundation/release/progress-palette.generated.d.ts +2 -2
- package/dist/foundation/release/progress-palette.generated.js +2 -2
- package/dist/foundation/release/release-gc.d.ts +3 -0
- package/dist/foundation/release/release-gc.js +44 -8
- package/dist/foundation/resident-tabs/bounded-host-command.d.ts +16 -0
- package/dist/foundation/resident-tabs/bounded-host-command.js +35 -0
- package/dist/foundation/resident-tabs/bridge-client.d.ts +56 -0
- package/dist/foundation/resident-tabs/bridge-client.js +279 -0
- package/dist/foundation/resident-tabs/doctor.d.ts +29 -0
- package/dist/foundation/resident-tabs/doctor.js +166 -0
- package/dist/foundation/resident-tabs/draft-checkpoint.d.ts +27 -0
- package/dist/foundation/resident-tabs/draft-checkpoint.js +145 -0
- package/dist/foundation/resident-tabs/ensure-terminal-host.d.ts +31 -0
- package/dist/foundation/resident-tabs/ensure-terminal-host.js +147 -0
- package/dist/foundation/resident-tabs/fault-injection.d.ts +8 -0
- package/dist/foundation/resident-tabs/fault-injection.js +26 -0
- package/dist/foundation/resident-tabs/index.d.ts +12 -0
- package/dist/foundation/resident-tabs/index.js +6 -0
- package/dist/foundation/resident-tabs/maintenance.d.ts +42 -0
- package/dist/foundation/resident-tabs/maintenance.js +78 -0
- package/dist/foundation/resident-tabs/prompt-journal.d.ts +65 -0
- package/dist/foundation/resident-tabs/prompt-journal.js +214 -0
- package/dist/foundation/resident-tabs/protocol.d.ts +12 -0
- package/dist/foundation/resident-tabs/protocol.js +195 -0
- package/dist/foundation/resident-tabs/recovery.d.ts +26 -0
- package/dist/foundation/resident-tabs/recovery.js +205 -0
- package/dist/foundation/resident-tabs/release-holds.d.ts +27 -0
- package/dist/foundation/resident-tabs/release-holds.js +77 -0
- package/dist/foundation/resident-tabs/routing.d.ts +11 -0
- package/dist/foundation/resident-tabs/routing.js +10 -0
- package/dist/foundation/resident-tabs/session-sync.d.ts +14 -0
- package/dist/foundation/resident-tabs/session-sync.js +37 -0
- package/dist/foundation/resident-tabs/settings.d.ts +14 -0
- package/dist/foundation/resident-tabs/settings.js +66 -0
- package/dist/foundation/resident-tabs/terminal-host-artifact.d.ts +18 -0
- package/dist/foundation/resident-tabs/terminal-host-artifact.js +53 -0
- package/dist/foundation/resident-tabs/zip-archive.d.ts +9 -0
- package/dist/foundation/resident-tabs/zip-archive.js +56 -0
- package/dist/foundation/session-writer-lock/index.d.ts +32 -0
- package/dist/foundation/session-writer-lock/index.js +201 -0
- package/dist/foundation/startup/startup-descriptor.js +2 -2
- package/dist/foundation/terminal-cleanup/index.d.ts +1 -1
- package/dist/foundation/terminal-cleanup/index.js +1 -1
- package/dist/foundation/terminal-cleanup/terminal-reset.d.ts +1 -0
- package/dist/foundation/terminal-cleanup/terminal-reset.js +1 -0
- package/dist/integrations/pi/components/owned-editor-ux.d.ts +1 -1
- package/dist/integrations/pi/components/owned-editor-ux.js +2 -2
- package/dist/integrations/pi/components/resources/builtin-themes.d.ts +4 -4
- package/dist/integrations/pi/components/resources/builtin-themes.js +4 -4
- package/dist/integrations/pi/components/shell-hotkey-sections.js +1 -1
- package/dist/integrations/pi/components/shell-presenters-info.d.ts +4 -2
- package/dist/integrations/pi/components/shell-presenters-info.js +38 -4
- package/dist/integrations/pi/components/shell-shared-facade.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.js +17 -4
- package/dist/integrations/pi/components/upstream/assets/earendil-image.json +2 -2
- package/dist/integrations/pi/components/upstream/components/countdown-timer.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/earendil-announcement.js +1 -1
- package/dist/integrations/pi/components/upstream/components/extension-editor.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/extension-editor.js +1 -1
- package/dist/integrations/pi/components/upstream/components/extension-input.js +1 -1
- package/dist/integrations/pi/components/upstream/components/extension-selector.js +1 -1
- package/dist/integrations/pi/components/upstream/components/mermaid.js +1 -1
- package/dist/integrations/pi/components/upstream/components/owned-editor.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/owned-editor.js +1 -1
- package/dist/integrations/pi/components/upstream/components/pi-logo.js +1 -1
- package/dist/integrations/pi/components/upstream/components/scoped-models-selector.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/session-footer.js +1 -1
- package/dist/integrations/pi/components/upstream/components/session-selector-search.js +1 -1
- package/dist/integrations/pi/components/upstream/components/session-selector.js +1 -1
- package/dist/integrations/pi/components/upstream/components/skill-invocation-message.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/skill-invocation-message.js +1 -1
- package/dist/integrations/pi/components/upstream/components/status-indicator.d.ts +2 -2
- package/dist/integrations/pi/components/upstream/components/status-indicator.js +3 -3
- package/dist/integrations/pi/components/upstream/components/themed-text.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/themed-text.js +1 -1
- package/dist/integrations/pi/components/upstream/components/thinking-selector.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/thinking-selector.js +1 -1
- package/dist/integrations/pi/components/upstream/components/tool-execution.d.ts +0 -2
- package/dist/integrations/pi/components/upstream/components/tool-execution.js +41 -20
- package/dist/integrations/pi/components/upstream/components/tree-selector.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/tree-selector.js +1 -1
- package/dist/integrations/pi/components/upstream/components/trust-selector.js +1 -1
- package/dist/integrations/pi/components/upstream/external-editor.js +1 -1
- package/dist/integrations/pi/components/upstream/history/editor-core.d.ts +2 -2
- package/dist/integrations/pi/components/upstream/history/editor-core.js +5 -5
- package/dist/integrations/pi/components/upstream/history/kill-ring.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/history/kill-ring.js +1 -1
- package/dist/integrations/pi/components/upstream/history/printable-key.js +1 -1
- package/dist/integrations/pi/components/upstream/history/text-helpers.js +1 -1
- package/dist/integrations/pi/components/upstream/history/undo-stack.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/history/undo-stack.js +1 -1
- package/dist/integrations/pi/components/upstream/history/word-navigation.js +1 -1
- package/dist/integrations/pi/components/upstream/model-search.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/program-status-reporter.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/theme/theme-controller.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/theme/theme.d.ts +3 -0
- package/dist/integrations/pi/components/upstream/theme/theme.js +24 -1
- package/dist/integrations/pi/engine/adapter.d.ts +1 -0
- package/dist/integrations/pi/engine/adapter.js +4 -0
- package/dist/integrations/pi/engine/pi-settings-metadata.json +2 -2
- package/dist/integrations/pi/engine/prompt-suggestions.d.ts +1 -0
- package/dist/integrations/pi/engine/prompt-suggestions.js +40 -0
- package/dist/integrations/pi/engine/resources/changelog.json +4 -4
- package/dist/integrations/pi/engine/runtime-integration.js +102 -29
- package/dist/integrations/pi/engine/session-runtime.d.ts +0 -6
- package/dist/integrations/pi/engine/session-runtime.js +8 -3
- package/dist/integrations/pi/engine/session-selection.d.ts +3 -0
- package/dist/integrations/pi/engine/session-selection.js +45 -9
- package/dist/integrations/pi/engine/workflow-contexts.js +10 -1
- package/dist/integrations/pi/startup-public.js +152 -222
- package/dist/integrations/pi/startup-public.manifest.json +266 -177
- package/dist/integrations/pi/tui-runtime/damage-aware-terminal.d.ts +1 -1
- package/dist/integrations/pi/tui-runtime/damage-aware-terminal.js +1 -1
- package/dist/native/darwin-arm64/a1_session_lock.node +0 -0
- package/dist/native/darwin-arm64/manifest.json +1 -1
- package/dist/native/darwin-arm64/terminal-host +0 -0
- package/dist/native/darwin-arm64/terminal-host-THIRD-PARTY-NOTICES.txt +10500 -0
- package/dist/native/darwin-arm64/terminal-host.manifest.json +44 -0
- package/dist/native/darwin-x64/a1_session_lock.node +0 -0
- package/dist/native/darwin-x64/manifest.json +20 -0
- package/dist/native/darwin-x64/process-guardian +0 -0
- package/dist/native/darwin-x64/terminal-host +0 -0
- package/dist/native/darwin-x64/terminal-host-THIRD-PARTY-NOTICES.txt +10737 -0
- package/dist/native/darwin-x64/terminal-host.manifest.json +44 -0
- package/dist/native/linux-x64/a1_session_lock.node +0 -0
- package/dist/native/linux-x64/manifest.json +1 -1
- package/dist/native/linux-x64/terminal-host +0 -0
- package/dist/native/linux-x64/terminal-host-THIRD-PARTY-NOTICES.txt +11226 -0
- package/dist/native/linux-x64/terminal-host.manifest.json +44 -0
- package/dist/native/win32-x64/a1_session_lock.node +0 -0
- package/dist/native/win32-x64/manifest.json +2 -2
- package/dist/native/win32-x64/process-guardian.exe +0 -0
- package/dist/native/win32-x64/terminal-host-THIRD-PARTY-NOTICES.txt +10757 -0
- package/dist/native/win32-x64/terminal-host.exe +0 -0
- package/dist/native/win32-x64/terminal-host.manifest.json +44 -0
- package/dist/product-identity.d.ts +1 -1
- package/dist/product-identity.js +1 -1
- package/dist/product-identity.json +5 -0
- package/dist/runtime-payload-inventory.json +5349 -6035
- package/dist/ui/components/line-input.d.ts +1 -1
- package/dist/ui/components/prompt-input.d.ts +7 -5
- package/dist/ui/components/prompt-input.js +7 -7
- package/dist/ui/components/transcript-viewport.d.ts +6 -3
- package/dist/ui/components/transcript-viewport.js +13 -15
- package/dist/ui/settings/declarations.d.ts +25 -1
- package/dist/ui/settings/declarations.js +21 -1
- package/dist/ui/settings/index.d.ts +4 -2
- package/dist/ui/settings/index.js +2 -1
- package/dist/ui/settings/manager.d.ts +10 -3
- package/dist/ui/settings/manager.js +90 -30
- package/dist/ui/settings/migrations.d.ts +6 -0
- package/dist/ui/settings/migrations.js +44 -0
- package/dist/ui/settings/profile-document.d.ts +26 -0
- package/dist/ui/settings/profile-document.js +164 -0
- package/docs/architecture/evidence/resident-tabs/linux-x64.md +37 -0
- package/docs/architecture/evidence/resident-tabs/macos-arm64.md +36 -0
- package/docs/architecture/evidence/resident-tabs/macos-x64.md +35 -0
- package/docs/architecture/evidence/resident-tabs/windows-x64.md +36 -0
- package/docs/architecture/internal-naming.md +3 -1
- package/docs/architecture/process-guardian-provenance.md +2 -0
- package/docs/architecture/resident-tabs-requirements.md +18 -0
- package/docs/architecture/resident-tabs.md +594 -0
- package/docs/architecture/terminal-host-proof-gate.md +2 -0
- package/docs/architecture/terminal-host-provenance.md +13 -3
- package/docs/architecture/toolchain.md +1 -1
- package/docs/ci-release-runbook.md +2 -2
- package/docs/features/launch-profiles.md +11 -8
- package/docs/manual-resident-tabs-acceptance.md +51 -0
- package/docs/openspec-archive-automation.md +17 -16
- package/docs/validation.md +9 -1
- package/package.json +10 -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
|
-
/**
|
|
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
|
-
|
|
5
|
-
export declare function
|
|
6
|
-
export declare function
|
|
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:
|
|
23
|
+
readonly theme: RuleTheme;
|
|
23
24
|
readonly metrics: PromptInputMetrics;
|
|
24
|
-
constructor(theme?:
|
|
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
|
|
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
|
-
|
|
195
|
-
|
|
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 =
|
|
1
|
+
export declare const OWNED_UI_SETTINGS_VERSION = 16;
|
|
2
2
|
export type OwnedUiSettingValue = string | number | boolean;
|
|
3
3
|
export type OwnedUiSettingApplication = "live" | "restart";
|
|
4
4
|
export interface OwnedUiSettingDeclaration {
|
|
@@ -164,6 +164,30 @@ 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 tabsMax: Readonly<{
|
|
168
|
+
id: "tabsMax";
|
|
169
|
+
label: "Maximum tabs";
|
|
170
|
+
section: Readonly<{
|
|
171
|
+
id: "tabs";
|
|
172
|
+
title: "Tabs";
|
|
173
|
+
}>;
|
|
174
|
+
description: "Maximum resident A1 tabs kept in the shared strip.";
|
|
175
|
+
application: "live";
|
|
176
|
+
defaultValue: 10;
|
|
177
|
+
allowedValues: readonly number[];
|
|
178
|
+
}>;
|
|
179
|
+
readonly tabsAutoName: Readonly<{
|
|
180
|
+
id: "tabsAutoName";
|
|
181
|
+
label: "Auto name";
|
|
182
|
+
section: Readonly<{
|
|
183
|
+
id: "tabs";
|
|
184
|
+
title: "Tabs";
|
|
185
|
+
}>;
|
|
186
|
+
description: "Automatically name a new tab from its first completed turn with the tab's current model.";
|
|
187
|
+
application: "live";
|
|
188
|
+
defaultValue: true;
|
|
189
|
+
allowedValues: readonly [true, false];
|
|
190
|
+
}>;
|
|
167
191
|
}>;
|
|
168
192
|
export type OwnedSettingId = keyof typeof OWNED_SETTING_DECLARATIONS;
|
|
169
193
|
/** 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 =
|
|
1
|
+
export const OWNED_UI_SETTINGS_VERSION = 16;
|
|
2
2
|
const UI_ACCENT_COLOR_CHOICES = Object.freeze([
|
|
3
3
|
"purple", "blue", "cyan", "green", "orange", "pink",
|
|
4
4
|
]);
|
|
@@ -9,6 +9,8 @@ const GENERIC_SECTION = Object.freeze({ id: "generic", title: "Generic" });
|
|
|
9
9
|
const APPEARANCE_SECTION = Object.freeze({ id: "appearance", title: "Appearance" });
|
|
10
10
|
const SCROLL_SECTION = Object.freeze({ id: "scroll", title: "Scroll" });
|
|
11
11
|
const AGENT_SECTION = Object.freeze({ id: "agent", title: "Agent" });
|
|
12
|
+
const TABS_SECTION = Object.freeze({ id: "tabs", title: "Tabs" });
|
|
13
|
+
const TABS_MAX_CHOICES = Object.freeze(Array.from({ length: 50 }, (_, index) => index + 1));
|
|
12
14
|
/**
|
|
13
15
|
* Every A1 setting, keyed by its id. The table is the one declaration: ids, defaults, and allowed
|
|
14
16
|
* values are read from it, and `OwnedSettingValueOf` derives each getter's type from it.
|
|
@@ -124,6 +126,24 @@ export const OWNED_SETTING_DECLARATIONS = Object.freeze({
|
|
|
124
126
|
defaultValue: 8,
|
|
125
127
|
allowedValues: Object.freeze([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]),
|
|
126
128
|
}),
|
|
129
|
+
tabsMax: Object.freeze({
|
|
130
|
+
id: "tabsMax",
|
|
131
|
+
label: "Maximum tabs",
|
|
132
|
+
section: TABS_SECTION,
|
|
133
|
+
description: "Maximum resident A1 tabs kept in the shared strip.",
|
|
134
|
+
application: "live",
|
|
135
|
+
defaultValue: 10,
|
|
136
|
+
allowedValues: TABS_MAX_CHOICES,
|
|
137
|
+
}),
|
|
138
|
+
tabsAutoName: Object.freeze({
|
|
139
|
+
id: "tabsAutoName",
|
|
140
|
+
label: "Auto name",
|
|
141
|
+
section: TABS_SECTION,
|
|
142
|
+
description: "Automatically name a new tab from its first completed turn with the tab's current model.",
|
|
143
|
+
application: "live",
|
|
144
|
+
defaultValue: true,
|
|
145
|
+
allowedValues: Object.freeze([true, false]),
|
|
146
|
+
}),
|
|
127
147
|
});
|
|
128
148
|
/** The declarations in declaration order, for resolution, persistence, and the settings screen. */
|
|
129
149
|
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";
|
|
3
|
+
export { OWNED_UI_SETTINGS_MIGRATIONS, assertOwnedUiSettingsMigrations, migrationsFrom, normalizeOwnedUiSettingsValues, } from "./migrations.js";
|
|
4
|
+
export type { OwnedUiSettingsCompatibilityResult, OwnedUiSettingsMigration } from "./migrations.js";
|
|
5
5
|
export { documentFrom, parseOwnedUiSettingsDocument, resolveOwnedUiSettings, 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";
|
|
2
|
+
export { OWNED_UI_SETTINGS_MIGRATIONS, assertOwnedUiSettingsMigrations, migrationsFrom, normalizeOwnedUiSettingsValues, } from "./migrations.js";
|
|
3
3
|
export { documentFrom, parseOwnedUiSettingsDocument, resolveOwnedUiSettings, 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,13 +4,20 @@ 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
|
-
/**
|
|
8
|
-
readonly
|
|
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;
|
|
13
19
|
readonly agentProvider?: () => AgentSettingsPort | null;
|
|
20
|
+
readonly hiddenOwnedSettingIds?: readonly string[];
|
|
14
21
|
readonly hiddenAgentSettingIds?: readonly string[];
|
|
15
22
|
readonly agentSettingLabelOverrides?: Readonly<Record<string, string>>;
|
|
16
23
|
}
|
|
@@ -37,7 +44,7 @@ export declare class OwnedSettingsManager {
|
|
|
37
44
|
constructor(options: OwnedSettingsManagerOptions);
|
|
38
45
|
get file(): string;
|
|
39
46
|
get resolution(): OwnedUiSettingsResolution;
|
|
40
|
-
/** Reads and resolves the
|
|
47
|
+
/** Reads and resolves the product namespace. Never throws: every failure becomes a notice. */
|
|
41
48
|
read(): OwnedUiSettingsResolution;
|
|
42
49
|
load(): Promise<void>;
|
|
43
50
|
sections(): readonly OwnedUiSettingsSection[];
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import {
|
|
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 {
|
|
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,11 +16,14 @@ 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;
|
|
21
24
|
#agentProvider;
|
|
22
25
|
#listeners = new Set();
|
|
26
|
+
#hiddenOwnedSettingIds;
|
|
23
27
|
#hiddenAgentSettingIds;
|
|
24
28
|
#agentSettingLabelOverrides;
|
|
25
29
|
#resolution;
|
|
@@ -30,11 +34,19 @@ export class OwnedSettingsManager {
|
|
|
30
34
|
if (!PROFILE_ID_PATTERN.test(options.profileId)) {
|
|
31
35
|
throw new Error(`owned UI settings profile id is not a bounded slug: ${options.profileId}`);
|
|
32
36
|
}
|
|
33
|
-
|
|
37
|
+
const profileDir = options.profileDir ?? options.configDir;
|
|
38
|
+
if (profileDir === undefined)
|
|
39
|
+
throw new Error("owned UI settings require a profile directory");
|
|
40
|
+
this.#file = path.join(path.resolve(profileDir), "settings.json");
|
|
41
|
+
this.#legacyFile = options.legacyConfigDir === undefined
|
|
42
|
+
? null
|
|
43
|
+
: path.join(path.resolve(options.legacyConfigDir), "settings", `${options.profileId}.json`);
|
|
44
|
+
this.#namespace = options.namespace ?? options.profileId;
|
|
34
45
|
this.#declarations = options.declarations ?? OWNED_UI_SETTING_DECLARATIONS;
|
|
35
46
|
this.#migrations = options.migrations ?? OWNED_UI_SETTINGS_MIGRATIONS;
|
|
36
47
|
this.#agent = options.agent ?? null;
|
|
37
48
|
this.#agentProvider = options.agentProvider ?? null;
|
|
49
|
+
this.#hiddenOwnedSettingIds = new Set(options.hiddenOwnedSettingIds ?? []);
|
|
38
50
|
this.#hiddenAgentSettingIds = new Set(options.hiddenAgentSettingIds ?? []);
|
|
39
51
|
this.#agentSettingLabelOverrides = { ...(options.agentSettingLabelOverrides ?? {}) };
|
|
40
52
|
this.#resolution = this.read();
|
|
@@ -49,24 +61,59 @@ export class OwnedSettingsManager {
|
|
|
49
61
|
get resolution() {
|
|
50
62
|
return this.#resolution;
|
|
51
63
|
}
|
|
52
|
-
/** Reads and resolves the
|
|
64
|
+
/** Reads and resolves the product namespace. Never throws: every failure becomes a notice. */
|
|
53
65
|
read() {
|
|
66
|
+
try {
|
|
67
|
+
repairProfileSettingsDocument(this.#file);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// Compatibility: recovery is opportunistic; unreadable-document handling stays authoritative.
|
|
71
|
+
}
|
|
72
|
+
const shared = readProfileSettingsNamespace(this.#file, this.#namespace);
|
|
73
|
+
if (shared.status === "available") {
|
|
74
|
+
if (Buffer.byteLength(JSON.stringify(shared.value), "utf8") > MAX_DOCUMENT_BYTES) {
|
|
75
|
+
return this.#resolveNamespace(null, `${this.#file} contains a ${this.#namespace} namespace exceeding the ${MAX_DOCUMENT_BYTES}-byte settings limit`);
|
|
76
|
+
}
|
|
77
|
+
return this.#resolveCompatibleNamespace(shared.value);
|
|
78
|
+
}
|
|
79
|
+
if (shared.status === "invalid")
|
|
80
|
+
return this.#resolveNamespace(null, shared.detail);
|
|
81
|
+
return this.#readLegacy();
|
|
82
|
+
}
|
|
83
|
+
#resolveCompatibleNamespace(values) {
|
|
84
|
+
const compatibility = normalizeOwnedUiSettingsValues(values);
|
|
85
|
+
return compatibility.values === null
|
|
86
|
+
? this.#resolveNamespace(null, compatibility.failure ?? "product settings compatibility failed")
|
|
87
|
+
: this.#resolveNamespace(compatibility.values);
|
|
88
|
+
}
|
|
89
|
+
#resolveNamespace(values, unreadableDetail) {
|
|
90
|
+
return resolveOwnedUiSettings({
|
|
91
|
+
declarations: this.#declarations,
|
|
92
|
+
migrations: [],
|
|
93
|
+
document: values === null ? null : { version: OWNED_UI_SETTINGS_VERSION, values },
|
|
94
|
+
...(unreadableDetail === undefined ? {} : { unreadableDetail }),
|
|
95
|
+
currentVersion: OWNED_UI_SETTINGS_VERSION,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
#readLegacy() {
|
|
99
|
+
if (this.#legacyFile === null)
|
|
100
|
+
return this.#resolveNamespace(null);
|
|
54
101
|
let raw = null;
|
|
55
102
|
let unreadable = null;
|
|
56
103
|
try {
|
|
57
|
-
raw = readFileSync(this.#
|
|
104
|
+
raw = readFileSync(this.#legacyFile, "utf8");
|
|
58
105
|
}
|
|
59
106
|
catch (error) {
|
|
60
107
|
if (!isMissing(error))
|
|
61
|
-
unreadable = `${this.#
|
|
108
|
+
unreadable = `${this.#legacyFile} could not be read: ${describe(error)}`;
|
|
62
109
|
}
|
|
63
110
|
if (raw !== null && Buffer.byteLength(raw, "utf8") > MAX_DOCUMENT_BYTES) {
|
|
64
|
-
unreadable = `${this.#
|
|
111
|
+
unreadable = `${this.#legacyFile} exceeds the ${MAX_DOCUMENT_BYTES}-byte settings limit and was ignored`;
|
|
65
112
|
raw = null;
|
|
66
113
|
}
|
|
67
114
|
const document = raw === null ? null : parseOwnedUiSettingsDocument(raw);
|
|
68
115
|
if (raw !== null && document === null && unreadable === null) {
|
|
69
|
-
unreadable = `${this.#
|
|
116
|
+
unreadable = `${this.#legacyFile} is not a valid settings document and was ignored`;
|
|
70
117
|
}
|
|
71
118
|
return resolveOwnedUiSettings({
|
|
72
119
|
declarations: this.#declarations,
|
|
@@ -83,7 +130,13 @@ export class OwnedSettingsManager {
|
|
|
83
130
|
this.#notify();
|
|
84
131
|
}
|
|
85
132
|
sections() {
|
|
86
|
-
|
|
133
|
+
const resolution = this.#hiddenOwnedSettingIds.size === 0
|
|
134
|
+
? this.#resolution
|
|
135
|
+
: {
|
|
136
|
+
...this.#resolution,
|
|
137
|
+
settings: this.#resolution.settings.filter(setting => !this.#hiddenOwnedSettingIds.has(setting.declaration.id)),
|
|
138
|
+
};
|
|
139
|
+
return buildOwnedUiSettingsSections({ resolution, agent: this.#agentSnapshot }).map(section => ({
|
|
87
140
|
...section,
|
|
88
141
|
entries: section.entries.map(entry => {
|
|
89
142
|
if (entry.backend === "a1" && this.#restartEffective.has(entry.id)) {
|
|
@@ -126,7 +179,7 @@ export class OwnedSettingsManager {
|
|
|
126
179
|
return backend === "a1" ? this.#changeOwned(id, value) : await this.#changeAgentValue(id, value);
|
|
127
180
|
}
|
|
128
181
|
#changeOwned(id, value) {
|
|
129
|
-
const outcome = this.#write(
|
|
182
|
+
const outcome = this.#write(id, value);
|
|
130
183
|
if (!outcome.stored)
|
|
131
184
|
return failed(outcome.failure ?? `${id} could not be stored`);
|
|
132
185
|
const previous = this.#resolution;
|
|
@@ -141,35 +194,34 @@ export class OwnedSettingsManager {
|
|
|
141
194
|
this.#notify();
|
|
142
195
|
return changed("applied", "live", value, value);
|
|
143
196
|
}
|
|
144
|
-
// Invariant:
|
|
145
|
-
// Concurrency:
|
|
146
|
-
|
|
147
|
-
#write(resolution, id, value) {
|
|
197
|
+
// Invariant: only non-default declared values and unknown product properties are stored.
|
|
198
|
+
// Concurrency: the namespace updater rereads current JSON while holding Pi's compatible lock.
|
|
199
|
+
#write(id, value) {
|
|
148
200
|
const declaration = this.#declarations.find(candidate => candidate.id === id);
|
|
149
201
|
if (!declaration)
|
|
150
202
|
return { stored: false, failure: `unknown owned UI setting: ${id}` };
|
|
151
203
|
if (!declaration.allowedValues.includes(value)) {
|
|
152
204
|
return { stored: false, failure: `value ${JSON.stringify(value)} is not allowed for ${id}` };
|
|
153
205
|
}
|
|
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
206
|
try {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
207
|
+
updateProfileSettingsNamespace(this.#file, this.#namespace, current => {
|
|
208
|
+
let latest = this.#readLegacy();
|
|
209
|
+
if (current !== null) {
|
|
210
|
+
const compatibility = normalizeOwnedUiSettingsValues(current);
|
|
211
|
+
if (compatibility.values === null)
|
|
212
|
+
throw new Error(compatibility.failure ?? "product settings compatibility failed");
|
|
213
|
+
latest = this.#resolveNamespace(compatibility.values);
|
|
214
|
+
}
|
|
215
|
+
const next = compactValues(latest);
|
|
216
|
+
if (value === declaration.defaultValue)
|
|
217
|
+
delete next[id];
|
|
218
|
+
else
|
|
219
|
+
next[id] = value;
|
|
220
|
+
return Object.keys(next).length === 0 ? null : next;
|
|
221
|
+
});
|
|
164
222
|
return { stored: true, failure: null };
|
|
165
223
|
}
|
|
166
224
|
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
225
|
return { stored: false, failure: `${this.#file} could not be written: ${describe(error)}` };
|
|
174
226
|
}
|
|
175
227
|
}
|
|
@@ -254,6 +306,14 @@ function fromAgentOutcome(result) {
|
|
|
254
306
|
failure: result.failure,
|
|
255
307
|
};
|
|
256
308
|
}
|
|
309
|
+
function compactValues(resolution) {
|
|
310
|
+
const values = { ...resolution.preserved };
|
|
311
|
+
for (const setting of resolution.settings) {
|
|
312
|
+
if (setting.value !== setting.declaration.defaultValue)
|
|
313
|
+
values[setting.declaration.id] = setting.value;
|
|
314
|
+
}
|
|
315
|
+
return values;
|
|
316
|
+
}
|
|
257
317
|
function isMissing(error) {
|
|
258
318
|
return typeof error === "object" && error !== null && error.code === "ENOENT";
|
|
259
319
|
}
|
|
@@ -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,51 @@ 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
|
+
}),
|
|
121
|
+
Object.freeze({
|
|
122
|
+
to: 16,
|
|
123
|
+
description: "Make bare A1 tabs unconditional and keep only tab UX controls.",
|
|
124
|
+
migrate(values) {
|
|
125
|
+
const migrated = { ...values };
|
|
126
|
+
delete migrated.residentTabs;
|
|
127
|
+
delete migrated.tabsMaxConcurrentStarts;
|
|
128
|
+
delete migrated.tabsUnresponsiveRestartSeconds;
|
|
129
|
+
delete migrated.tabsStallNoticeSeconds;
|
|
130
|
+
return migrated;
|
|
131
|
+
},
|
|
132
|
+
}),
|
|
107
133
|
]);
|
|
134
|
+
/** Shape-selected compatibility for the metadata-free product object. */
|
|
135
|
+
export function normalizeOwnedUiSettingsValues(source) {
|
|
136
|
+
const values = { ...source };
|
|
137
|
+
// Compatibility: "high" is valid again, so the old speed meaning is ambiguous without legacy metadata.
|
|
138
|
+
if (values.scrollbarAppearance === "hover")
|
|
139
|
+
values.scrollbarAppearance = "auto";
|
|
140
|
+
if (values.accentColor === "default")
|
|
141
|
+
values.accentColor = "purple";
|
|
142
|
+
if (values.quitEffect === "off") {
|
|
143
|
+
if (Object.hasOwn(values, "quitAnimation") && values.quitAnimation !== false) {
|
|
144
|
+
return { values: null, failure: "legacy quitEffect conflicts with quitAnimation" };
|
|
145
|
+
}
|
|
146
|
+
values.quitAnimation = false;
|
|
147
|
+
}
|
|
148
|
+
delete values.quitEffect;
|
|
149
|
+
delete values.quitEffectDurationMs;
|
|
150
|
+
return { values, failure: null };
|
|
151
|
+
}
|
|
108
152
|
export function assertOwnedUiSettingsMigrations(migrations, currentVersion = OWNED_UI_SETTINGS_VERSION) {
|
|
109
153
|
const firstProduced = currentVersion - migrations.length + 1;
|
|
110
154
|
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;
|