@timurproko/a1 0.1.8-dev.151 → 0.1.8-dev.182
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 +53 -9
- package/bin/cli.js +1 -0
- package/bin/ui.js +17 -2
- package/dist/cli/dispatch.d.ts +6 -0
- package/dist/cli/dispatch.js +107 -81
- package/dist/cli/version-stats.js +7 -1
- package/dist/composition/owned-ui.d.ts +4 -1
- package/dist/composition/owned-ui.js +8 -5
- package/dist/contracts/agent-engine/capability-ports.d.ts +2 -2
- package/dist/contracts/agent-engine/domain-validation.js +22 -4
- package/dist/contracts/agent-engine/domain.d.ts +17 -0
- package/dist/contracts/owned-ui/model.d.ts +8 -0
- package/dist/contracts/owned-ui/validation.js +11 -0
- package/dist/features/owned-ui/index.d.ts +1 -0
- package/dist/features/owned-ui/index.js +1 -0
- package/dist/features/owned-ui/project-trust-prompt.d.ts +20 -0
- package/dist/features/owned-ui/project-trust-prompt.js +32 -0
- package/dist/features/owned-ui/settings-app.js +37 -9
- package/dist/integrations/pi/components/shell-components.d.ts +1 -0
- package/dist/integrations/pi/components/shell-components.js +1 -0
- package/dist/integrations/pi/components/shell-editor-autocomplete.js +10 -0
- package/dist/integrations/pi/components/shell-footer-status.js +14 -9
- package/dist/integrations/pi/components/shell-presenters-info.d.ts +36 -0
- package/dist/integrations/pi/components/shell-presenters-info.js +78 -0
- package/dist/integrations/pi/components/shell-presenters-transcript.d.ts +3 -36
- package/dist/integrations/pi/components/shell-presenters-transcript.js +88 -128
- package/dist/integrations/pi/components/shell-shared-facade.d.ts +11 -1
- package/dist/integrations/pi/engine/adapter.d.ts +15 -4
- package/dist/integrations/pi/engine/adapter.js +185 -37
- package/dist/integrations/pi/engine/http-dispatcher.d.ts +4 -0
- package/dist/integrations/pi/engine/http-dispatcher.js +25 -0
- package/dist/integrations/pi/engine/index.d.ts +3 -0
- package/dist/integrations/pi/engine/index.js +3 -0
- package/dist/integrations/pi/engine/project-trust-preflight.d.ts +22 -0
- package/dist/integrations/pi/engine/project-trust-preflight.js +50 -0
- package/dist/integrations/pi/engine/runtime-integration.d.ts +16 -1
- package/dist/integrations/pi/engine/runtime-integration.js +34 -4
- package/dist/integrations/pi/engine/settings-effects.d.ts +55 -0
- package/dist/integrations/pi/engine/settings-effects.js +229 -0
- package/dist/integrations/pi/engine/settings-integration.d.ts +14 -26
- package/dist/integrations/pi/engine/settings-integration.js +102 -108
- package/dist/integrations/pi/engine/workflow-controllers.d.ts +1 -1
- package/dist/integrations/pi/session-ui/clipboard-image.d.ts +11 -0
- package/dist/integrations/pi/session-ui/clipboard-image.js +29 -0
- package/dist/integrations/pi/session-ui/prompt-chips.js +5 -1
- package/dist/integrations/pi/session-ui/session-shell-root.d.ts +9 -2
- package/dist/integrations/pi/session-ui/session-shell-root.js +83 -9
- package/dist/integrations/pi/session-ui/session-shell.d.ts +2 -0
- package/dist/integrations/pi/session-ui/session-shell.js +151 -15
- package/dist/integrations/pi/session-ui/session-viewport-controller.js +34 -2
- package/dist/integrations/pi/session-ui/system-clipboard.d.ts +9 -0
- package/dist/integrations/pi/session-ui/system-clipboard.js +34 -5
- package/dist/integrations/pi/tui-runtime/adapter.d.ts +4 -0
- package/dist/integrations/pi/tui-runtime/adapter.js +28 -0
- package/dist/native/darwin-arm64/manifest.json +1 -1
- package/dist/native/linux-x64/manifest.json +1 -1
- package/dist/native/win32-x64/manifest.json +2 -2
- package/dist/native/win32-x64/process-guardian.exe +0 -0
- package/dist/ui/components/spans.d.ts +2 -0
- package/dist/ui/components/spans.js +25 -0
- package/dist/ui/components/text.js +24 -2
- package/dist/ui/settings/sections.d.ts +8 -19
- package/dist/ui/settings/sections.js +12 -4
- package/dist/ui/settings/session.d.ts +8 -18
- package/dist/ui/settings/session.js +59 -52
- package/docs/architecture/project-structure.md +14 -0
- package/docs/ci-release-runbook.md +75 -9
- package/docs/features/launch-profiles.md +3 -3
- package/docs/repository-governance-live-acceptance.md +77 -0
- package/package.json +5 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TranscriptViewport, routeMouseInput, scrollForTrackPage, scrollForThumbRow, scrollbarSelectionRows, scrollbarWheelRows, } from "../../../ui/components/index.js";
|
|
1
|
+
import { TranscriptViewport, hyperlinkTargetAtColumn, routeMouseInput, scrollForTrackPage, scrollForThumbRow, scrollbarSelectionRows, scrollbarWheelRows, } from "../../../ui/components/index.js";
|
|
2
2
|
/**
|
|
3
3
|
* Owns the stateful interaction policy of the bare-A1 session viewport. The
|
|
4
4
|
* shell root supplies semantic rows and dock geometry; this controller owns
|
|
@@ -24,6 +24,8 @@ export class SessionViewportController {
|
|
|
24
24
|
#editorPointerFrame;
|
|
25
25
|
#selectionAutoScrollTimer;
|
|
26
26
|
#selectionAutoScrollPointer;
|
|
27
|
+
#pointerPosition;
|
|
28
|
+
#hoveredHyperlinkKey;
|
|
27
29
|
#activityTimer;
|
|
28
30
|
constructor(options) {
|
|
29
31
|
this.#enabled = options.enabled;
|
|
@@ -45,7 +47,17 @@ export class SessionViewportController {
|
|
|
45
47
|
}
|
|
46
48
|
compose(input) {
|
|
47
49
|
this.#viewport.setConfig(this.#config);
|
|
48
|
-
|
|
50
|
+
const frame = this.#viewport.compose(input);
|
|
51
|
+
if (this.#pointerPosition !== undefined && !this.#viewport.selectionActive && !this.#editorPointerSelecting) {
|
|
52
|
+
const next = this.#hyperlinkKeyAt(frame, this.#pointerPosition.column, this.#pointerPosition.row);
|
|
53
|
+
if (this.#hoveredHyperlinkKey !== undefined && next !== this.#hoveredHyperlinkKey) {
|
|
54
|
+
// Windows Terminal can leave its solid native-hover underline behind
|
|
55
|
+
// when a following viewport moves the link away from a stationary pointer.
|
|
56
|
+
this.#requestRender(true);
|
|
57
|
+
}
|
|
58
|
+
this.#hoveredHyperlinkKey = next;
|
|
59
|
+
}
|
|
60
|
+
return frame;
|
|
49
61
|
}
|
|
50
62
|
/** Returns true when document wrapping may have changed. */
|
|
51
63
|
setConfig(config) {
|
|
@@ -76,6 +88,8 @@ export class SessionViewportController {
|
|
|
76
88
|
this.#stopSelectionAutoScroll();
|
|
77
89
|
this.#editorPointerSelecting = false;
|
|
78
90
|
this.#editorPointerFrame = undefined;
|
|
91
|
+
this.#pointerPosition = undefined;
|
|
92
|
+
this.#hoveredHyperlinkKey = undefined;
|
|
79
93
|
this.#viewport.reset();
|
|
80
94
|
}
|
|
81
95
|
clearPointerState() {
|
|
@@ -84,6 +98,8 @@ export class SessionViewportController {
|
|
|
84
98
|
this.#dockPointerSuppressed = false;
|
|
85
99
|
this.#editorPointerSelecting = false;
|
|
86
100
|
this.#editorPointerFrame = undefined;
|
|
101
|
+
this.#pointerPosition = undefined;
|
|
102
|
+
this.#hoveredHyperlinkKey = undefined;
|
|
87
103
|
this.#stopSelectionAutoScroll();
|
|
88
104
|
this.#viewport.clearSelection();
|
|
89
105
|
this.#viewport.clearTransient();
|
|
@@ -160,6 +176,16 @@ export class SessionViewportController {
|
|
|
160
176
|
const overBottom = hits.bottom !== null && event.row === hits.bottom.row
|
|
161
177
|
&& event.column >= hits.bottom.columnStart && event.column <= hits.bottom.columnEnd;
|
|
162
178
|
if (event.kind === "motion") {
|
|
179
|
+
this.#pointerPosition = { column: event.column, row: event.row };
|
|
180
|
+
if (!this.#viewport.selectionActive && !this.#editorPointerSelecting) {
|
|
181
|
+
const nextHyperlink = this.#hyperlinkKeyAt(frame, event.column, event.row);
|
|
182
|
+
if (this.#hoveredHyperlinkKey !== undefined && nextHyperlink !== this.#hoveredHyperlinkKey) {
|
|
183
|
+
// A forced redraw overwrites Windows Terminal's cached solid hover
|
|
184
|
+
// underline exactly once when the pointer leaves or changes links.
|
|
185
|
+
forceRepaint = true;
|
|
186
|
+
}
|
|
187
|
+
this.#hoveredHyperlinkKey = nextHyperlink;
|
|
188
|
+
}
|
|
163
189
|
this.#viewport.setRailHovered(overRail);
|
|
164
190
|
this.#viewport.setStickyHovered(overSticky);
|
|
165
191
|
this.#viewport.setBottomHovered(overBottom);
|
|
@@ -307,6 +333,12 @@ export class SessionViewportController {
|
|
|
307
333
|
this.#requestRender(forceRepaint);
|
|
308
334
|
return routed;
|
|
309
335
|
}
|
|
336
|
+
#hyperlinkKeyAt(frame, column, row) {
|
|
337
|
+
if (row < 1 || row > frame.rows.length || column < 1)
|
|
338
|
+
return undefined;
|
|
339
|
+
const target = hyperlinkTargetAtColumn(frame.rows[row - 1] ?? "", column - 1);
|
|
340
|
+
return target === undefined ? undefined : `${row}\u0000${target}`;
|
|
341
|
+
}
|
|
310
342
|
#updateSelectionAutoScroll(column, row, viewportHeight) {
|
|
311
343
|
const beyondEdge = row <= 1 || row > viewportHeight;
|
|
312
344
|
if (!beyondEdge) {
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import type { PiShellClipboardContent } from "../components/index.js";
|
|
2
|
+
export interface SystemClipboardImageReader {
|
|
3
|
+
hasImage(): boolean;
|
|
4
|
+
getImageBinary?(): Promise<readonly number[]>;
|
|
5
|
+
getImageBase64?(): Promise<string>;
|
|
6
|
+
}
|
|
2
7
|
/** Load the native adapter during shell startup so the first paste is warm. */
|
|
3
8
|
export declare function preloadSystemClipboard(): void;
|
|
4
9
|
/**
|
|
@@ -7,5 +12,9 @@ export declare function preloadSystemClipboard(): void;
|
|
|
7
12
|
* unavailable clipboard simply makes the paste action a no-op.
|
|
8
13
|
*/
|
|
9
14
|
export declare function readSystemClipboardContent(): Promise<PiShellClipboardContent | null>;
|
|
15
|
+
export declare function readSystemClipboardImage(reader: SystemClipboardImageReader): Promise<{
|
|
16
|
+
readonly data: string;
|
|
17
|
+
readonly mimeType: string;
|
|
18
|
+
} | null>;
|
|
10
19
|
export declare function readSystemClipboardText(): Promise<string | null>;
|
|
11
20
|
export declare function writeSystemClipboardText(text: string): Promise<void>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
|
+
import { canonicalizeClipboardImage } from "./clipboard-image.js";
|
|
2
3
|
const MAX_CLIPBOARD_BYTES = 16 * 1024 * 1024;
|
|
3
4
|
let nativeClipboardPromise;
|
|
4
5
|
let pendingClipboardWrite = Promise.resolve();
|
|
@@ -22,11 +23,9 @@ export async function readSystemClipboardContent() {
|
|
|
22
23
|
if (files !== null)
|
|
23
24
|
return { kind: "text", text: files };
|
|
24
25
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
return { kind: "image", data, mimeType: "image/png" };
|
|
29
|
-
}
|
|
26
|
+
const image = await readSystemClipboardImage(native);
|
|
27
|
+
if (image !== null)
|
|
28
|
+
return { kind: "image", ...image };
|
|
30
29
|
}
|
|
31
30
|
catch {
|
|
32
31
|
// Fall through to text and platform readers.
|
|
@@ -35,6 +34,36 @@ export async function readSystemClipboardContent() {
|
|
|
35
34
|
const text = await readSystemClipboardText();
|
|
36
35
|
return text === null ? null : { kind: "text", text };
|
|
37
36
|
}
|
|
37
|
+
export async function readSystemClipboardImage(reader) {
|
|
38
|
+
if (!reader.hasImage())
|
|
39
|
+
return null;
|
|
40
|
+
if (reader.getImageBinary !== undefined) {
|
|
41
|
+
try {
|
|
42
|
+
const bytes = await reader.getImageBinary();
|
|
43
|
+
if (bytes.length > 0 && bytes.every(byte => Number.isInteger(byte) && byte >= 0 && byte <= 255)) {
|
|
44
|
+
return canonicalizeClipboardImage({
|
|
45
|
+
data: Buffer.from(bytes).toString("base64"),
|
|
46
|
+
mimeType: "image/png",
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// Older or partially available native bindings may still expose base64.
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (reader.getImageBase64 !== undefined) {
|
|
55
|
+
try {
|
|
56
|
+
return canonicalizeClipboardImage({
|
|
57
|
+
data: await reader.getImageBase64(),
|
|
58
|
+
mimeType: "image/png",
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// Fall through to the caller's text path.
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
38
67
|
export async function readSystemClipboardText() {
|
|
39
68
|
// Ctrl+C/Ctrl+X and Ctrl+V can arrive in adjacent input turns. Serialize the
|
|
40
69
|
// read behind our native write so paste never observes the previous value.
|
|
@@ -31,7 +31,11 @@ export declare class PiTuiRuntimeAdapter {
|
|
|
31
31
|
switchMode(mode: "regular" | "fullscreen"): boolean;
|
|
32
32
|
setHardwareCursor(enabled: boolean): void;
|
|
33
33
|
getHardwareCursor(): boolean;
|
|
34
|
+
setClearOnShrink(enabled: boolean): void;
|
|
35
|
+
getClearOnShrink(): boolean;
|
|
36
|
+
setTerminalProgress(active: boolean): void;
|
|
34
37
|
setTitle(title: string): void;
|
|
38
|
+
writeAfterStop(text: string): void;
|
|
35
39
|
invalidate(): void;
|
|
36
40
|
requestRender(force?: boolean): void;
|
|
37
41
|
renderNow(force?: boolean): void;
|
|
@@ -97,6 +97,7 @@ export class PiTuiRuntimeAdapter {
|
|
|
97
97
|
#state = "idle";
|
|
98
98
|
#stopPromise;
|
|
99
99
|
#rootDisposed = false;
|
|
100
|
+
#terminalProgress = false;
|
|
100
101
|
constructor(options) {
|
|
101
102
|
this.#root = options.root;
|
|
102
103
|
this.#terminal = options.terminal ?? new ProcessTerminal();
|
|
@@ -301,9 +302,26 @@ export class PiTuiRuntimeAdapter {
|
|
|
301
302
|
getHardwareCursor() {
|
|
302
303
|
return this.#tui.getShowHardwareCursor();
|
|
303
304
|
}
|
|
305
|
+
setClearOnShrink(enabled) {
|
|
306
|
+
this.#tui.setClearOnShrink(enabled);
|
|
307
|
+
}
|
|
308
|
+
getClearOnShrink() {
|
|
309
|
+
return this.#tui.getClearOnShrink();
|
|
310
|
+
}
|
|
311
|
+
setTerminalProgress(active) {
|
|
312
|
+
if (this.#terminalProgress === active)
|
|
313
|
+
return;
|
|
314
|
+
this.#terminal.setProgress(active);
|
|
315
|
+
this.#terminalProgress = active;
|
|
316
|
+
}
|
|
304
317
|
setTitle(title) {
|
|
305
318
|
this.#terminal.setTitle(title);
|
|
306
319
|
}
|
|
320
|
+
writeAfterStop(text) {
|
|
321
|
+
if (this.#state !== "stopped")
|
|
322
|
+
throw new Error("terminal output requires a stopped Pi TUI runtime");
|
|
323
|
+
this.#terminal.write(text);
|
|
324
|
+
}
|
|
307
325
|
invalidate() {
|
|
308
326
|
this.#tui.invalidate();
|
|
309
327
|
if (this.active)
|
|
@@ -322,6 +340,7 @@ export class PiTuiRuntimeAdapter {
|
|
|
322
340
|
return this.#stopPromise;
|
|
323
341
|
if (this.#state === "idle") {
|
|
324
342
|
this.#state = "stopped";
|
|
343
|
+
this.#clearTerminalProgress();
|
|
325
344
|
this.#preInputListeners.clear();
|
|
326
345
|
this.#disposeRoot();
|
|
327
346
|
return Promise.resolve();
|
|
@@ -354,6 +373,7 @@ export class PiTuiRuntimeAdapter {
|
|
|
354
373
|
this.#bestEffortTerminalRestore();
|
|
355
374
|
}
|
|
356
375
|
finally {
|
|
376
|
+
this.#clearTerminalProgress();
|
|
357
377
|
this.#disposeComponents();
|
|
358
378
|
}
|
|
359
379
|
this.#state = failure === undefined ? "stopped" : "failed";
|
|
@@ -459,7 +479,15 @@ export class PiTuiRuntimeAdapter {
|
|
|
459
479
|
this.#bestEffortTerminalRestore();
|
|
460
480
|
}
|
|
461
481
|
}
|
|
482
|
+
#clearTerminalProgress() {
|
|
483
|
+
try {
|
|
484
|
+
this.#terminal.setProgress(false);
|
|
485
|
+
}
|
|
486
|
+
catch { }
|
|
487
|
+
this.#terminalProgress = false;
|
|
488
|
+
}
|
|
462
489
|
#bestEffortTerminalRestore() {
|
|
490
|
+
this.#clearTerminalProgress();
|
|
463
491
|
try {
|
|
464
492
|
this.#terminal.showCursor();
|
|
465
493
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"platform": "darwin",
|
|
6
6
|
"architecture": "arm64",
|
|
7
7
|
"capability": "unsupported",
|
|
8
|
-
"builtAt": "2026-08-
|
|
8
|
+
"builtAt": "2026-08-28T18:22:44.284Z",
|
|
9
9
|
"artifact": {
|
|
10
10
|
"filename": "process-guardian",
|
|
11
11
|
"sha256": "7524bf568992517f50ed53196c861c5edeba0d7275633bb4735ab45bd5963a28",
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"platform": "linux",
|
|
6
6
|
"architecture": "x64",
|
|
7
7
|
"capability": "supported",
|
|
8
|
-
"builtAt": "2026-08-
|
|
8
|
+
"builtAt": "2026-08-28T18:22:55.678Z",
|
|
9
9
|
"artifact": {
|
|
10
10
|
"filename": "process-guardian",
|
|
11
11
|
"sha256": "ee8a00eaaf79c707459bbbfb52518e9739314967049fbe5fa625f36ce33db9ee",
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
"platform": "win32",
|
|
6
6
|
"architecture": "x64",
|
|
7
7
|
"capability": "supported",
|
|
8
|
-
"builtAt": "2026-08-
|
|
8
|
+
"builtAt": "2026-08-28T18:23:19.899Z",
|
|
9
9
|
"artifact": {
|
|
10
10
|
"filename": "process-guardian.exe",
|
|
11
|
-
"sha256": "
|
|
11
|
+
"sha256": "20d36267095f117000abb65e7e2edf9c2a6bd300ee2bd6368b544d823017e5c9",
|
|
12
12
|
"size": 177664
|
|
13
13
|
},
|
|
14
14
|
"provenance": {
|
|
Binary file
|
|
@@ -12,6 +12,8 @@ export declare function overlaySpan(line: string, from: number, to: number, span
|
|
|
12
12
|
*/
|
|
13
13
|
/** Wraps visible columns `[from,to)` in an explicit OSC 8 hyperlink. */
|
|
14
14
|
export declare function hyperlinkSgrSpan(line: string, from: number, to: number, target: string, widthOf?: (text: string) => number): string;
|
|
15
|
+
/** Returns the OSC 8 target painted at a zero-based visible terminal column. */
|
|
16
|
+
export declare function hyperlinkTargetAtColumn(line: string, targetColumn: number, widthOf?: (text: string) => number): string | undefined;
|
|
15
17
|
/**
|
|
16
18
|
* Gives existing and bare web links explicit, tightly bounded OSC 8 regions so
|
|
17
19
|
* the terminal owns dotted-idle/solid-hover decoration without leaking hover
|
|
@@ -93,6 +93,31 @@ export function hyperlinkSgrSpan(line, from, to, target, widthOf = displayWidth)
|
|
|
93
93
|
output += off;
|
|
94
94
|
return output;
|
|
95
95
|
}
|
|
96
|
+
/** Returns the OSC 8 target painted at a zero-based visible terminal column. */
|
|
97
|
+
export function hyperlinkTargetAtColumn(line, targetColumn, widthOf = displayWidth) {
|
|
98
|
+
if (targetColumn < 0)
|
|
99
|
+
return undefined;
|
|
100
|
+
let hyperlinkTarget;
|
|
101
|
+
let column = 0;
|
|
102
|
+
for (const token of line.split(ANSI_SPLIT)) {
|
|
103
|
+
if (!token)
|
|
104
|
+
continue;
|
|
105
|
+
const link = HYPERLINK.exec(token);
|
|
106
|
+
if (link) {
|
|
107
|
+
hyperlinkTarget = (link[1] ?? "") || undefined;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (token.startsWith("\u001b"))
|
|
111
|
+
continue;
|
|
112
|
+
for (const { segment } of GRAPHEMES.segment(token)) {
|
|
113
|
+
const width = widthOf(segment);
|
|
114
|
+
if (targetColumn >= column && targetColumn < column + width)
|
|
115
|
+
return hyperlinkTarget;
|
|
116
|
+
column += width;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
96
121
|
/**
|
|
97
122
|
* Gives existing and bare web links explicit, tightly bounded OSC 8 regions so
|
|
98
123
|
* the terminal owns dotted-idle/solid-hover decoration without leaking hover
|
|
@@ -7,6 +7,15 @@
|
|
|
7
7
|
const ANSI_PATTERN = /\[[0-9;:?]*[ -/]*[@-~]|\][^]*(?:|\\)|[@-Z\\-_]/g;
|
|
8
8
|
const SEGMENTER = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
9
9
|
const WORD_SEGMENTER = new Intl.Segmenter(undefined, { granularity: "word" });
|
|
10
|
+
/**
|
|
11
|
+
* Recommended-for-general-interchange emoji. Terminals give every one of these
|
|
12
|
+
* two columns wherever its code points sit, so the wide blocks in
|
|
13
|
+
* `codePointWidth` cannot carry the measurement alone: a lone U+26A1 or any
|
|
14
|
+
* U+FE0F presentation sequence escapes them and would measure one column short.
|
|
15
|
+
*/
|
|
16
|
+
const RGI_EMOJI_PATTERN = /^\p{RGI_Emoji}$/v;
|
|
17
|
+
/** Columns a terminal advances for a tab, matching the host runtime. */
|
|
18
|
+
const TAB_WIDTH = 3;
|
|
10
19
|
/** Removes styling sequences so only visible characters remain. */
|
|
11
20
|
export function stripAnsi(text) {
|
|
12
21
|
return text.replace(ANSI_PATTERN, "");
|
|
@@ -50,13 +59,26 @@ function codePointWidth(codePoint) {
|
|
|
50
59
|
return 2;
|
|
51
60
|
if (codePoint >= 0x20000 && codePoint <= 0x3fffd)
|
|
52
61
|
return 2;
|
|
53
|
-
//
|
|
62
|
+
// A tab advances to the host runtime's stop; other control characters occupy nothing.
|
|
63
|
+
if (codePoint === 0x09)
|
|
64
|
+
return TAB_WIDTH;
|
|
54
65
|
if (codePoint < 0x20 || (codePoint >= 0x7f && codePoint < 0xa0))
|
|
55
66
|
return 0;
|
|
56
67
|
return 1;
|
|
57
68
|
}
|
|
58
|
-
/**
|
|
69
|
+
/** Cheap pre-filter: only clusters that could be emoji pay for the RGI test. */
|
|
70
|
+
function couldBeEmoji(grapheme) {
|
|
71
|
+
const codePoint = grapheme.codePointAt(0) ?? 0;
|
|
72
|
+
return ((codePoint >= 0x1f000 && codePoint <= 0x1fbff) ||
|
|
73
|
+
(codePoint >= 0x2300 && codePoint <= 0x23ff) ||
|
|
74
|
+
(codePoint >= 0x2600 && codePoint <= 0x27bf) ||
|
|
75
|
+
(codePoint >= 0x2b00 && codePoint <= 0x2bff) ||
|
|
76
|
+
grapheme.length > 1);
|
|
77
|
+
}
|
|
78
|
+
/** Width of one grapheme cluster: two for emoji, else the widest code point it contains. */
|
|
59
79
|
function graphemeWidth(grapheme) {
|
|
80
|
+
if (couldBeEmoji(grapheme) && RGI_EMOJI_PATTERN.test(grapheme))
|
|
81
|
+
return 2;
|
|
60
82
|
let width = 0;
|
|
61
83
|
for (const character of grapheme) {
|
|
62
84
|
width = Math.max(width, codePointWidth(character.codePointAt(0) ?? 0));
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AgentSettingApplicationBoundary, AgentSettingDescriptor } from "../../contracts/agent-engine/index.js";
|
|
1
2
|
import type { OwnedUiSettingValue } from "./declarations.js";
|
|
2
3
|
import type { OwnedUiSettingsResolution } from "./resolution.js";
|
|
3
4
|
export type OwnedUiSettingsBackend = "a1" | "agent";
|
|
@@ -9,9 +10,13 @@ export interface OwnedUiSettingsEntry {
|
|
|
9
10
|
readonly description: string | null;
|
|
10
11
|
/** Present when the value is a scalar the surface can offer as a choice. */
|
|
11
12
|
readonly value: OwnedUiSettingValue | null;
|
|
12
|
-
/** Raw value as reported by its backend, for entries the surface can only display. */
|
|
13
|
+
/** Raw stored value as reported by its backend, for entries the surface can only display. */
|
|
13
14
|
readonly rawValue: unknown;
|
|
15
|
+
readonly storedValue: unknown;
|
|
16
|
+
readonly effectiveValue: unknown;
|
|
14
17
|
readonly editable: boolean;
|
|
18
|
+
readonly available: boolean;
|
|
19
|
+
readonly limitationReason: string | null;
|
|
15
20
|
readonly choices: readonly OwnedUiSettingValue[] | null;
|
|
16
21
|
/** True when the value is a structured object edited through its own surface. */
|
|
17
22
|
readonly structured: boolean;
|
|
@@ -26,7 +31,7 @@ export interface OwnedUiSettingsEntry {
|
|
|
26
31
|
readonly fallback: boolean;
|
|
27
32
|
}[];
|
|
28
33
|
readonly origin: "default" | "stored" | "engine";
|
|
29
|
-
readonly application:
|
|
34
|
+
readonly application: AgentSettingApplicationBoundary;
|
|
30
35
|
}
|
|
31
36
|
export interface OwnedUiSettingsSection {
|
|
32
37
|
readonly id: string;
|
|
@@ -38,23 +43,7 @@ export interface OwnedUiSettingsSection {
|
|
|
38
43
|
readonly readOnlyReason: string | null;
|
|
39
44
|
}
|
|
40
45
|
export interface AgentSettingsSnapshot {
|
|
41
|
-
readonly descriptors: readonly
|
|
42
|
-
readonly key: string;
|
|
43
|
-
readonly valueType: "boolean" | "number" | "string" | "enum" | "json";
|
|
44
|
-
readonly writable: boolean;
|
|
45
|
-
readonly choices?: readonly unknown[];
|
|
46
|
-
readonly label?: string;
|
|
47
|
-
readonly description?: string;
|
|
48
|
-
readonly minimum?: number;
|
|
49
|
-
readonly maximum?: number;
|
|
50
|
-
readonly flags?: readonly {
|
|
51
|
-
readonly key: string;
|
|
52
|
-
readonly label: string;
|
|
53
|
-
readonly description: string;
|
|
54
|
-
readonly fallback: boolean;
|
|
55
|
-
}[];
|
|
56
|
-
}[];
|
|
57
|
-
readonly values: Readonly<Record<string, unknown>>;
|
|
46
|
+
readonly descriptors: readonly AgentSettingDescriptor[];
|
|
58
47
|
/** Whether the engine advertises settings write capability at all. */
|
|
59
48
|
readonly writeAdvertised: boolean;
|
|
60
49
|
/** Set when the engine could not report its settings. */
|
|
@@ -12,14 +12,18 @@ export function buildOwnedUiSettingsSections(input) {
|
|
|
12
12
|
description: setting.declaration.description,
|
|
13
13
|
value: setting.value,
|
|
14
14
|
rawValue: setting.value,
|
|
15
|
+
storedValue: setting.value,
|
|
16
|
+
effectiveValue: setting.value,
|
|
15
17
|
editable: true,
|
|
18
|
+
available: true,
|
|
19
|
+
limitationReason: null,
|
|
16
20
|
structured: false,
|
|
17
21
|
minimum: null,
|
|
18
22
|
maximum: null,
|
|
19
23
|
flags: [],
|
|
20
24
|
choices: setting.declaration.allowedValues,
|
|
21
25
|
origin: setting.source,
|
|
22
|
-
application: setting.declaration.application,
|
|
26
|
+
application: setting.declaration.application === "restart" ? "next-start" : "live",
|
|
23
27
|
});
|
|
24
28
|
grouped.set(section.id, group);
|
|
25
29
|
}
|
|
@@ -51,7 +55,7 @@ function agentSection(snapshot) {
|
|
|
51
55
|
return frozenSection("The agent engine reported no settings", null, []);
|
|
52
56
|
}
|
|
53
57
|
const entries = snapshot.descriptors.map(descriptor => {
|
|
54
|
-
const raw =
|
|
58
|
+
const raw = descriptor.storedValue;
|
|
55
59
|
return {
|
|
56
60
|
id: descriptor.key,
|
|
57
61
|
label: descriptor.label ?? null,
|
|
@@ -59,9 +63,13 @@ function agentSection(snapshot) {
|
|
|
59
63
|
description: descriptor.description ?? null,
|
|
60
64
|
value: scalar(raw),
|
|
61
65
|
rawValue: raw,
|
|
66
|
+
storedValue: descriptor.storedValue,
|
|
67
|
+
effectiveValue: descriptor.effectiveValue,
|
|
68
|
+
available: descriptor.available,
|
|
69
|
+
limitationReason: descriptor.limitationReason,
|
|
62
70
|
// A structured value is editable through its own surface rather than a
|
|
63
71
|
// value menu, so it stays reachable instead of being reported as fixed.
|
|
64
|
-
editable: snapshot.writeAdvertised && descriptor.writable,
|
|
72
|
+
editable: snapshot.writeAdvertised && descriptor.writable && descriptor.available,
|
|
65
73
|
structured: descriptor.valueType === "json",
|
|
66
74
|
minimum: descriptor.minimum ?? null,
|
|
67
75
|
maximum: descriptor.maximum ?? null,
|
|
@@ -70,7 +78,7 @@ function agentSection(snapshot) {
|
|
|
70
78
|
// so it opens the same menu as any other enumerated setting.
|
|
71
79
|
choices: descriptor.valueType === "boolean" ? BOOLEAN_CHOICES : choicesOf(descriptor.choices),
|
|
72
80
|
origin: "engine",
|
|
73
|
-
application:
|
|
81
|
+
application: descriptor.application,
|
|
74
82
|
};
|
|
75
83
|
});
|
|
76
84
|
const readOnlyReason = snapshot.writeAdvertised
|
|
@@ -1,46 +1,36 @@
|
|
|
1
|
-
import type { AgentSettingsPort } from "../../contracts/agent-engine/index.js";
|
|
1
|
+
import type { AgentJsonValue, AgentSettingApplicationBoundary, AgentSettingsPort } from "../../contracts/agent-engine/index.js";
|
|
2
2
|
import type { OwnedUiSettingValue } from "./declarations.js";
|
|
3
3
|
import type { OwnedUiSettingsResolution } from "./resolution.js";
|
|
4
4
|
import { type OwnedUiSettingsBackend, type OwnedUiSettingsSection } from "./sections.js";
|
|
5
5
|
import type { OwnedUiSettingsStore } from "./store.js";
|
|
6
6
|
export interface OwnedUiSettingsChangeOutcome {
|
|
7
|
+
readonly status: "applied" | "deferred" | "unavailable" | "failed";
|
|
7
8
|
readonly applied: boolean;
|
|
8
|
-
/**
|
|
9
|
+
/** Compatibility projection for existing A1 restart-bound declarations. */
|
|
9
10
|
readonly pendingRestart: boolean;
|
|
10
|
-
|
|
11
|
+
readonly application: AgentSettingApplicationBoundary | null;
|
|
12
|
+
readonly storedValue: AgentJsonValue | null;
|
|
13
|
+
readonly effectiveValue: AgentJsonValue | null;
|
|
14
|
+
readonly limitationReason: string | null;
|
|
11
15
|
readonly failure: string | null;
|
|
12
16
|
}
|
|
13
17
|
export interface OwnedUiSettingsSessionOptions {
|
|
14
18
|
readonly store: OwnedUiSettingsStore;
|
|
15
|
-
/** Null when no engine is attached; the Agent section then reports itself unavailable. */
|
|
16
19
|
readonly agent?: AgentSettingsPort | null;
|
|
17
|
-
/**
|
|
18
|
-
* Resolves the port at load time instead of construction time, for an engine
|
|
19
|
-
* whose runtime is not up yet. Takes precedence over `agent`.
|
|
20
|
-
*/
|
|
21
20
|
readonly agentProvider?: () => AgentSettingsPort | null;
|
|
22
|
-
/** Agent-owned controls fixed by this product surface and therefore omitted. */
|
|
23
21
|
readonly hiddenAgentSettingIds?: readonly string[];
|
|
24
22
|
}
|
|
25
23
|
export type OwnedUiSettingsListener = (session: OwnedUiSettingsSession) => void;
|
|
26
|
-
/**
|
|
27
|
-
* Holds the resolved settings for one owned UI session and routes every accepted
|
|
28
|
-
* change to the backend that owns it: A1 settings to the A1 document, agent
|
|
29
|
-
* settings to the engine settings port. Neither backend ever sees the other's value.
|
|
30
|
-
*/
|
|
24
|
+
/** Routes every accepted setting change only to its owning backend. */
|
|
31
25
|
export declare class OwnedUiSettingsSession {
|
|
32
26
|
#private;
|
|
33
27
|
constructor(options: OwnedUiSettingsSessionOptions);
|
|
34
28
|
get resolution(): OwnedUiSettingsResolution;
|
|
35
|
-
/** Reads the engine's settings so the Agent section can be built. Never throws. */
|
|
36
29
|
load(): Promise<void>;
|
|
37
30
|
sections(): readonly OwnedUiSettingsSection[];
|
|
38
|
-
/** Resolved value of an A1 setting, or null when it is not declared. */
|
|
39
31
|
value(id: string): OwnedUiSettingValue | null;
|
|
40
|
-
/** Value stored this session but not yet in effect, for a restart-required setting. */
|
|
41
32
|
pendingValue(id: string): OwnedUiSettingValue | null;
|
|
42
33
|
onChange(listener: OwnedUiSettingsListener): () => void;
|
|
43
|
-
/** Writes a structured value, which only an engine-backed setting can hold. */
|
|
44
34
|
changeStructured(backend: OwnedUiSettingsBackend, id: string, value: Readonly<Record<string, unknown>>): Promise<OwnedUiSettingsChangeOutcome>;
|
|
45
35
|
change(backend: OwnedUiSettingsBackend, id: string, value: OwnedUiSettingValue): Promise<OwnedUiSettingsChangeOutcome>;
|
|
46
36
|
}
|