@timurproko/a1 0.1.8-dev.137 → 0.1.8-dev.151
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 +2 -4
- package/dist/cli/capabilities.d.ts +2 -11
- package/dist/cli/capabilities.js +1 -1
- package/dist/cli/dispatch.js +3 -6
- package/dist/composition/owned-ui.js +26 -5
- package/dist/composition/settings-route-host.d.ts +1 -1
- package/dist/composition/settings-route-host.js +1 -1
- package/dist/contracts/owned-ui/model.d.ts +28 -0
- package/dist/contracts/owned-ui/validation.js +14 -0
- package/dist/features/launch/development-launch.js +11 -3
- package/dist/features/launch/index.d.ts +0 -1
- package/dist/features/launch/index.js +0 -1
- package/dist/features/launch/intent.d.ts +3 -2
- package/dist/features/launch/intent.js +1 -2
- package/dist/features/launch/prepare-launch.d.ts +0 -2
- package/dist/features/launch/prepare-launch.js +2 -9
- package/dist/features/launch/profile-paths.d.ts +1 -2
- package/dist/features/launch/profile-paths.js +2 -8
- package/dist/features/launch/runtime-selection.d.ts +2 -5
- package/dist/features/owned-ui/settings-app.js +38 -14
- package/dist/foundation/lifecycle/model.d.ts +1 -1
- package/dist/foundation/lifecycle/model.js +1 -1
- package/dist/foundation/release/bootstrap.d.ts +1 -3
- package/dist/foundation/release/bootstrap.js +1 -1
- package/dist/foundation/storage/control-store.js +37 -3
- package/dist/integrations/pi/components/owned-editor-ux.d.ts +55 -0
- package/dist/integrations/pi/components/owned-editor-ux.js +778 -0
- package/dist/integrations/pi/components/shell-editor-autocomplete.js +44 -6
- package/dist/integrations/pi/components/shell-footer-status.d.ts +1 -1
- package/dist/integrations/pi/components/shell-footer-status.js +14 -5
- package/dist/integrations/pi/components/shell-presenters-transcript.d.ts +3 -2
- package/dist/integrations/pi/components/shell-presenters-transcript.js +9 -8
- package/dist/integrations/pi/components/shell-shared-facade.d.ts +41 -7
- package/dist/integrations/pi/components/shell-shared-facade.js +3 -1
- package/dist/integrations/pi/components/submitted-prompt-adapter.d.ts +13 -0
- package/dist/integrations/pi/components/submitted-prompt-adapter.js +27 -0
- package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.d.ts +10 -2
- package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.js +36 -3
- package/dist/integrations/pi/engine/adapter.js +21 -3
- package/dist/integrations/pi/engine/session-integration.d.ts +2 -1
- package/dist/integrations/pi/engine/session-integration.js +17 -3
- package/dist/integrations/pi/session-ui/prompt-chips.d.ts +23 -0
- package/dist/integrations/pi/session-ui/prompt-chips.js +242 -0
- package/dist/integrations/pi/session-ui/route-host.d.ts +5 -0
- package/dist/integrations/pi/{owned-ui → session-ui}/session-shell-root.d.ts +34 -5
- package/dist/integrations/pi/{owned-ui → session-ui}/session-shell-root.js +294 -45
- package/dist/integrations/pi/{owned-ui → session-ui}/session-shell.d.ts +1 -1
- package/dist/integrations/pi/{owned-ui → session-ui}/session-shell.js +167 -24
- package/dist/integrations/pi/session-ui/session-viewport-controller.d.ts +39 -0
- package/dist/integrations/pi/session-ui/session-viewport-controller.js +369 -0
- package/dist/integrations/pi/session-ui/system-clipboard.d.ts +11 -0
- package/dist/integrations/pi/session-ui/system-clipboard.js +101 -0
- package/dist/integrations/pi/tui-runtime/adapter.d.ts +2 -1
- package/dist/integrations/pi/tui-runtime/adapter.js +51 -2
- package/dist/integrations/pi/tui-runtime/contracts.d.ts +4 -6
- 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 +3 -3
- package/dist/native/win32-x64/process-guardian.exe +0 -0
- package/dist/product-identity.d.ts +2 -2
- package/dist/product-identity.js +3 -4
- package/dist/product-identity.json +3 -5
- package/dist/ui/apps/contracts.d.ts +23 -1
- package/dist/ui/apps/host.js +9 -1
- package/dist/ui/components/index.d.ts +3 -0
- package/dist/ui/components/index.js +3 -0
- package/dist/ui/components/mouse.d.ts +10 -0
- package/dist/ui/components/mouse.js +23 -0
- package/dist/ui/components/prompt-row.d.ts +14 -0
- package/dist/ui/components/prompt-row.js +20 -0
- package/dist/ui/components/scrollbar.d.ts +26 -0
- package/dist/ui/components/scrollbar.js +35 -0
- package/dist/ui/components/spans.d.ts +21 -0
- package/dist/ui/components/spans.js +178 -0
- package/dist/ui/components/submitted-prompt.d.ts +23 -0
- package/dist/ui/components/submitted-prompt.js +50 -0
- package/dist/ui/components/text-selection.d.ts +53 -0
- package/dist/ui/components/text-selection.js +168 -0
- package/dist/ui/components/text.d.ts +14 -0
- package/dist/ui/components/text.js +40 -0
- package/dist/ui/components/transcript-viewport.d.ts +104 -0
- package/dist/ui/components/transcript-viewport.js +381 -0
- package/dist/ui/settings/declarations.d.ts +8 -1
- package/dist/ui/settings/declarations.js +33 -5
- package/dist/ui/settings/migrations.d.ts +1 -1
- package/dist/ui/settings/migrations.js +21 -2
- package/dist/ui/settings/sections.js +15 -8
- package/dist/ui/settings/session.d.ts +2 -0
- package/dist/ui/settings/session.js +7 -5
- package/docs/architecture/boundaries.md +3 -3
- package/docs/architecture/project-structure.md +2 -2
- package/docs/architecture/resource-and-data-policy.md +1 -1
- package/docs/architecture/toolchain.md +5 -5
- package/docs/architecture/ui-reference-provenance.md +11 -2
- package/docs/features/launch-profiles.md +7 -14
- package/docs/manual-launch-instance-acceptance.md +12 -35
- package/docs/manual-owned-ui-checkpoint.md +2 -3
- package/docs/manual-pi-boundary-candidate.md +4 -5
- package/package.json +4 -2
- package/dist/features/launch/profiles.d.ts +0 -12
- package/dist/features/launch/profiles.js +0 -26
- package/dist/integrations/pi/owned-ui/route-host.d.ts +0 -29
- /package/dist/integrations/pi/{owned-ui → session-ui}/index.d.ts +0 -0
- /package/dist/integrations/pi/{owned-ui → session-ui}/index.js +0 -0
- /package/dist/integrations/pi/{owned-ui → session-ui}/route-host.js +0 -0
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
import { isThumbRow, scrollbarGeometry, scrollbarPresentation, } from "./scrollbar.js";
|
|
2
|
+
import { backgroundSgrSpan, overlaySpan } from "./spans.js";
|
|
3
|
+
import { faint, truncateToWidth, displayWidth, stripAnsi } from "./text.js";
|
|
4
|
+
import { extendTextSelection, orderedTextSelection, pressTextSelection, releaseTextSelection, textSelectionLineExtendColumn, textSelectionText, usefulTextLineContent, } from "./text-selection.js";
|
|
5
|
+
const CONTROL_STYLE_RESET = "\u001b]8;;\u001b\\\u001b[0m";
|
|
6
|
+
const DEFAULT_CONFIG = {
|
|
7
|
+
scrollbarAppearance: "auto",
|
|
8
|
+
scrollbarStyle: "thin",
|
|
9
|
+
};
|
|
10
|
+
const PLAIN_THEME = {
|
|
11
|
+
track: text => text,
|
|
12
|
+
thumb: text => text,
|
|
13
|
+
sticky: (text, hovered) => hovered ? `\u001b[7m${text}\u001b[27m` : text,
|
|
14
|
+
quietSticky: faint,
|
|
15
|
+
bottomControl: (text, hovered) => hovered ? `\u001b[7m${text}\u001b[27m` : `\u001b[7m${text}\u001b[27m`,
|
|
16
|
+
selection: (line, from, to) => backgroundSgrSpan(line, from, to),
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* A1-owned viewport state. It knows only rows and semantic prompt anchors; Pi
|
|
20
|
+
* components and transcript payloads remain outside this neutral boundary.
|
|
21
|
+
*/
|
|
22
|
+
export class TranscriptViewport {
|
|
23
|
+
#config = DEFAULT_CONFIG;
|
|
24
|
+
#scrollTop = 0;
|
|
25
|
+
#maxScroll = 0;
|
|
26
|
+
#followingEnd = true;
|
|
27
|
+
#newMessages = 0;
|
|
28
|
+
#activeUntil = 0;
|
|
29
|
+
#railHovered = false;
|
|
30
|
+
#railDragging = false;
|
|
31
|
+
#stickyHovered = false;
|
|
32
|
+
#bottomHovered = false;
|
|
33
|
+
#selection;
|
|
34
|
+
#selectionClick;
|
|
35
|
+
#documentRows = [];
|
|
36
|
+
#selectableDocumentRowCount = 0;
|
|
37
|
+
#promptAnchors = [];
|
|
38
|
+
#contentWidth = 0;
|
|
39
|
+
#frame = null;
|
|
40
|
+
get scrollTop() { return this.#scrollTop; }
|
|
41
|
+
get maxScroll() { return this.#maxScroll; }
|
|
42
|
+
get followingEnd() { return this.#followingEnd; }
|
|
43
|
+
get newMessages() { return this.#newMessages; }
|
|
44
|
+
get frame() { return this.#frame; }
|
|
45
|
+
/** Counts one completed assistant reply while detached; tool rows never call this. */
|
|
46
|
+
noteNewMessage() {
|
|
47
|
+
if (this.#followingEnd)
|
|
48
|
+
return false;
|
|
49
|
+
this.#newMessages += 1;
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
setConfig(config) {
|
|
53
|
+
this.#config = config;
|
|
54
|
+
if (config.scrollbarAppearance === "hidden") {
|
|
55
|
+
this.#railHovered = false;
|
|
56
|
+
this.#railDragging = false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
noteScrollActivity(now = Date.now(), lingerMs = 900) {
|
|
60
|
+
this.#activeUntil = Math.max(this.#activeUntil, now + lingerMs);
|
|
61
|
+
}
|
|
62
|
+
setRailHovered(hovered) { this.#railHovered = hovered; }
|
|
63
|
+
setRailDragging(dragging) { this.#railDragging = dragging; }
|
|
64
|
+
setStickyHovered(hovered) { this.#stickyHovered = hovered; }
|
|
65
|
+
setBottomHovered(hovered) { this.#bottomHovered = hovered; }
|
|
66
|
+
get selectionActive() { return this.#selection?.selecting === true; }
|
|
67
|
+
get hasSelection() { return orderedTextSelection(this.#selection) !== undefined; }
|
|
68
|
+
pressSelection(column, viewportRow, now = Date.now()) {
|
|
69
|
+
const viewportHeight = this.#frame?.hits.viewportHeight ?? 0;
|
|
70
|
+
if (viewportRow < 1 || viewportRow > viewportHeight || column < 1 || column > this.#contentWidth)
|
|
71
|
+
return false;
|
|
72
|
+
const line = clamp(this.#scrollTop + viewportRow - 1, 0, Math.max(0, this.#documentRows.length - 1));
|
|
73
|
+
if (line >= this.#selectableDocumentRowCount)
|
|
74
|
+
return false;
|
|
75
|
+
const pressed = pressTextSelection({
|
|
76
|
+
line,
|
|
77
|
+
column,
|
|
78
|
+
contentWidth: this.#contentWidth,
|
|
79
|
+
lineText: this.#documentRows[line] ?? "",
|
|
80
|
+
lineContent: this.#lineContentAt(line),
|
|
81
|
+
...(this.#selectionClick === undefined ? {} : { previousClick: this.#selectionClick }),
|
|
82
|
+
now,
|
|
83
|
+
});
|
|
84
|
+
this.#selection = pressed.selection;
|
|
85
|
+
this.#selectionClick = pressed.click;
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
extendSelection(column, viewportRow, now = Date.now(), autoScroll = true) {
|
|
89
|
+
const selection = this.#selection;
|
|
90
|
+
const viewportHeight = this.#frame?.hits.viewportHeight ?? 0;
|
|
91
|
+
if (selection?.selecting !== true || viewportHeight <= 0 || this.#documentRows.length === 0)
|
|
92
|
+
return false;
|
|
93
|
+
// Pointer motion updates only the endpoint; the shell's fixed-cadence timer
|
|
94
|
+
// owns edge scrolling. This option prevents high-rate motion reports from
|
|
95
|
+
// adding irregular extra rows between timer ticks.
|
|
96
|
+
if (autoScroll && viewportRow > viewportHeight)
|
|
97
|
+
this.scrollBy(1, now);
|
|
98
|
+
else if (autoScroll && viewportRow <= 1 && this.#scrollTop > 0)
|
|
99
|
+
this.scrollBy(-1, now);
|
|
100
|
+
const lastSelectableLine = Math.min(this.#documentRows.length, this.#selectableDocumentRowCount) - 1;
|
|
101
|
+
if (lastSelectableLine < 0)
|
|
102
|
+
return false;
|
|
103
|
+
const visibleRow = clamp(viewportRow, 1, viewportHeight);
|
|
104
|
+
const line = clamp(this.#scrollTop + visibleRow - 1, 0, lastSelectableLine);
|
|
105
|
+
const targetColumn = selection.fullRow
|
|
106
|
+
? textSelectionLineExtendColumn(selection, line, this.#contentWidth)
|
|
107
|
+
: clamp(column, 1, this.#contentWidth);
|
|
108
|
+
this.#selection = extendTextSelection(selection, { line, column: targetColumn });
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
releaseSelection() {
|
|
112
|
+
if (this.#selection?.selecting !== true)
|
|
113
|
+
return false;
|
|
114
|
+
this.#selection = releaseTextSelection(this.#selection);
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
clearSelection() {
|
|
118
|
+
if (this.#selection === undefined)
|
|
119
|
+
return false;
|
|
120
|
+
this.#selection = undefined;
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
selectedText() {
|
|
124
|
+
const selection = orderedTextSelection(this.#selection);
|
|
125
|
+
if (selection === undefined)
|
|
126
|
+
return null;
|
|
127
|
+
return textSelectionText(selection, this.#documentRows.slice(0, this.#selectableDocumentRowCount), line => this.#lineContentAt(line));
|
|
128
|
+
}
|
|
129
|
+
scrollBy(lines, now = Date.now()) {
|
|
130
|
+
if (this.#maxScroll <= 0 || lines === 0)
|
|
131
|
+
return false;
|
|
132
|
+
const base = this.#followingEnd ? this.#maxScroll : this.#scrollTop;
|
|
133
|
+
const next = clamp(base + lines, 0, this.#maxScroll);
|
|
134
|
+
this.#scrollTop = next;
|
|
135
|
+
this.#followingEnd = next >= this.#maxScroll;
|
|
136
|
+
if (this.#followingEnd)
|
|
137
|
+
this.#newMessages = 0;
|
|
138
|
+
this.noteScrollActivity(now);
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
scrollTo(position, now = Date.now()) {
|
|
142
|
+
const next = clamp(position, 0, this.#maxScroll);
|
|
143
|
+
const changed = next !== this.#scrollTop || this.#followingEnd;
|
|
144
|
+
this.#scrollTop = next;
|
|
145
|
+
// Every scrolling path that reaches the final legal top row resumes follow.
|
|
146
|
+
this.#followingEnd = next >= this.#maxScroll;
|
|
147
|
+
if (this.#followingEnd)
|
|
148
|
+
this.#newMessages = 0;
|
|
149
|
+
this.noteScrollActivity(now);
|
|
150
|
+
return changed;
|
|
151
|
+
}
|
|
152
|
+
scrollToEnd(now = Date.now()) {
|
|
153
|
+
const changed = !this.#followingEnd || this.#scrollTop !== this.#maxScroll;
|
|
154
|
+
this.#followingEnd = true;
|
|
155
|
+
this.#scrollTop = this.#maxScroll;
|
|
156
|
+
this.#newMessages = 0;
|
|
157
|
+
this.noteScrollActivity(now);
|
|
158
|
+
return changed;
|
|
159
|
+
}
|
|
160
|
+
/** Jumps to the pinned prompt governing this position, then earlier prompts. */
|
|
161
|
+
scrollToPreviousPrompt(now = Date.now()) {
|
|
162
|
+
let target = -1;
|
|
163
|
+
let earliest = Number.POSITIVE_INFINITY;
|
|
164
|
+
for (const anchor of this.#promptAnchors) {
|
|
165
|
+
earliest = Math.min(earliest, anchor.firstRow);
|
|
166
|
+
if (anchor.firstRow < this.#scrollTop && anchor.firstRow > target)
|
|
167
|
+
target = anchor.firstRow;
|
|
168
|
+
}
|
|
169
|
+
// The first prompt owns the document's opening breathing row. At that final
|
|
170
|
+
// navigation stop, reveal the spacer too rather than pinning the prompt to
|
|
171
|
+
// terminal row one as later prompt jumps do.
|
|
172
|
+
const destination = target === earliest ? 0 : target;
|
|
173
|
+
return target >= 0 && this.scrollTo(destination, now);
|
|
174
|
+
}
|
|
175
|
+
/** Jumps to the next submitted prompt after the prompt at the current stop. */
|
|
176
|
+
scrollToNextPrompt(now = Date.now()) {
|
|
177
|
+
if (this.#promptAnchors.length === 0)
|
|
178
|
+
return false;
|
|
179
|
+
const earliest = Math.min(...this.#promptAnchors.map(anchor => anchor.firstRow));
|
|
180
|
+
// Scroll position zero is the first-prompt stop because it includes that
|
|
181
|
+
// prompt's opening spacer. Do not spend an extra keypress moving one row.
|
|
182
|
+
const after = this.#scrollTop === 0 ? earliest : this.#scrollTop;
|
|
183
|
+
let target = Number.POSITIVE_INFINITY;
|
|
184
|
+
for (const anchor of this.#promptAnchors) {
|
|
185
|
+
if (anchor.firstRow > after && anchor.firstRow < target)
|
|
186
|
+
target = anchor.firstRow;
|
|
187
|
+
}
|
|
188
|
+
return Number.isFinite(target) && this.scrollTo(target, now);
|
|
189
|
+
}
|
|
190
|
+
reset() {
|
|
191
|
+
this.#scrollTop = 0;
|
|
192
|
+
this.#maxScroll = 0;
|
|
193
|
+
this.#followingEnd = true;
|
|
194
|
+
this.#newMessages = 0;
|
|
195
|
+
this.#selection = undefined;
|
|
196
|
+
this.#selectionClick = undefined;
|
|
197
|
+
this.#documentRows = [];
|
|
198
|
+
this.#selectableDocumentRowCount = 0;
|
|
199
|
+
this.#promptAnchors = [];
|
|
200
|
+
this.#contentWidth = 0;
|
|
201
|
+
this.clearTransient();
|
|
202
|
+
this.#frame = null;
|
|
203
|
+
}
|
|
204
|
+
clearTransient() {
|
|
205
|
+
this.#activeUntil = 0;
|
|
206
|
+
this.#railHovered = false;
|
|
207
|
+
this.#railDragging = false;
|
|
208
|
+
this.#stickyHovered = false;
|
|
209
|
+
this.#bottomHovered = false;
|
|
210
|
+
}
|
|
211
|
+
compose(input) {
|
|
212
|
+
const width = Math.max(1, input.width);
|
|
213
|
+
const height = Math.max(0, input.height);
|
|
214
|
+
const theme = input.theme ?? PLAIN_THEME;
|
|
215
|
+
const dock = input.dockRows.length > height ? input.dockRows.slice(-height) : [...input.dockRows];
|
|
216
|
+
const viewportHeight = Math.max(0, height - dock.length);
|
|
217
|
+
this.#maxScroll = Math.max(0, input.documentRows.length - viewportHeight);
|
|
218
|
+
if (this.#followingEnd)
|
|
219
|
+
this.#scrollTop = this.#maxScroll;
|
|
220
|
+
else
|
|
221
|
+
this.#scrollTop = clamp(this.#scrollTop, 0, this.#maxScroll);
|
|
222
|
+
if (this.#scrollTop >= this.#maxScroll)
|
|
223
|
+
this.#followingEnd = true;
|
|
224
|
+
if (this.#followingEnd)
|
|
225
|
+
this.#newMessages = 0;
|
|
226
|
+
const geometry = scrollbarGeometry({
|
|
227
|
+
contentLength: input.documentRows.length,
|
|
228
|
+
viewportHeight,
|
|
229
|
+
scroll: this.#scrollTop,
|
|
230
|
+
// The session rail deliberately starts one line below the viewport top.
|
|
231
|
+
trackHeight: Math.max(0, viewportHeight - 1),
|
|
232
|
+
});
|
|
233
|
+
const presentation = scrollbarPresentation({
|
|
234
|
+
geometry,
|
|
235
|
+
appearance: this.#config.scrollbarAppearance,
|
|
236
|
+
style: this.#config.scrollbarStyle,
|
|
237
|
+
hovered: this.#railHovered,
|
|
238
|
+
dragging: this.#railDragging,
|
|
239
|
+
activeUntil: this.#activeUntil,
|
|
240
|
+
now: input.now ?? Date.now(),
|
|
241
|
+
});
|
|
242
|
+
const contentWidth = presentation.reservesSpace ? Math.max(1, width - 1) : width;
|
|
243
|
+
this.#documentRows = input.documentRows;
|
|
244
|
+
this.#selectableDocumentRowCount = clamp(input.selectableDocumentRowCount ?? input.documentRows.length, 0, input.documentRows.length);
|
|
245
|
+
this.#promptAnchors = input.promptAnchors;
|
|
246
|
+
this.#contentWidth = contentWidth;
|
|
247
|
+
const paintDocumentRow = input.paintDocumentRow ?? (row => row);
|
|
248
|
+
const visible = input.documentRows
|
|
249
|
+
.slice(this.#scrollTop, this.#scrollTop + viewportHeight)
|
|
250
|
+
.map(paintDocumentRow);
|
|
251
|
+
while (visible.length < viewportHeight)
|
|
252
|
+
visible.push("");
|
|
253
|
+
const governing = governingPrompt(input.promptAnchors, this.#scrollTop);
|
|
254
|
+
const stickyActive = governing !== null && governing.firstRow < this.#scrollTop;
|
|
255
|
+
if (stickyActive && visible.length > 0) {
|
|
256
|
+
const quiet = this.#scrollTop > governing.lastRow;
|
|
257
|
+
// Sticky prompts use the same normal/hover surface roles as the bottom
|
|
258
|
+
// control. Hover always starts from the full source row, so a quiet prompt
|
|
259
|
+
// becomes prominent again with its timestamp intact.
|
|
260
|
+
const sticky = theme.sticky(paintDocumentRow(governing.sourceRow), this.#stickyHovered);
|
|
261
|
+
visible[0] = quiet && !this.#stickyHovered ? theme.quietSticky(sticky) : sticky;
|
|
262
|
+
}
|
|
263
|
+
const orderedSelection = orderedTextSelection(this.#selection);
|
|
264
|
+
for (let row = 0; row < visible.length; row += 1) {
|
|
265
|
+
let line = padRowPreservingBackground(visible[row] ?? "", width);
|
|
266
|
+
if (orderedSelection !== undefined && !(stickyActive && row === 0)) {
|
|
267
|
+
const documentLine = this.#scrollTop + row;
|
|
268
|
+
if (documentLine < this.#selectableDocumentRowCount
|
|
269
|
+
&& documentLine >= orderedSelection.start.line
|
|
270
|
+
&& documentLine <= orderedSelection.end.line) {
|
|
271
|
+
const fullVisualRow = orderedSelection.fullRow === true
|
|
272
|
+
|| (orderedSelection.start.line !== orderedSelection.end.line
|
|
273
|
+
&& documentLine !== orderedSelection.start.line
|
|
274
|
+
&& documentLine !== orderedSelection.end.line);
|
|
275
|
+
const from = fullVisualRow || documentLine !== orderedSelection.start.line
|
|
276
|
+
? 0
|
|
277
|
+
: orderedSelection.start.column - 1;
|
|
278
|
+
const throughFinalColumn = documentLine === orderedSelection.end.line
|
|
279
|
+
&& orderedSelection.throughFinalColumn === true;
|
|
280
|
+
const to = fullVisualRow || documentLine !== orderedSelection.end.line || throughFinalColumn
|
|
281
|
+
? width
|
|
282
|
+
: orderedSelection.end.column;
|
|
283
|
+
if (to > from)
|
|
284
|
+
line = theme.selection(line, from, Math.min(width, to));
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
// Paint the rail after selection. Full-row selection reaches the terminal
|
|
288
|
+
// edge, while the foreground thumb/track remains visible above that
|
|
289
|
+
// background instead of disappearing into it.
|
|
290
|
+
// Row zero is the intentional one-line breathing room above the rail.
|
|
291
|
+
if (presentation.visible && geometry !== null && row > 0) {
|
|
292
|
+
const trackRow = row - 1;
|
|
293
|
+
const thumb = isThumbRow(geometry, trackRow);
|
|
294
|
+
const glyph = thumb ? presentation.thumbGlyph : presentation.trackGlyph;
|
|
295
|
+
const cell = thumb ? theme.thumb(glyph, this.#railHovered || this.#railDragging) : theme.track(glyph);
|
|
296
|
+
line = overlaySpan(line, width - 1, width, cell);
|
|
297
|
+
}
|
|
298
|
+
visible[row] = line;
|
|
299
|
+
}
|
|
300
|
+
const frameRows = [...visible, ...dock].slice(0, height);
|
|
301
|
+
let bottomHit = null;
|
|
302
|
+
if (geometry !== null && !this.#followingEnd && frameRows.length > 0) {
|
|
303
|
+
const genericLabel = " Jump to bottom (End) ";
|
|
304
|
+
const countedLabel = this.#newMessages > 0
|
|
305
|
+
? ` ${this.#newMessages} new message${this.#newMessages === 1 ? "" : "s"} (End) `
|
|
306
|
+
: genericLabel;
|
|
307
|
+
const label = displayWidth(countedLabel) <= contentWidth ? countedLabel : genericLabel;
|
|
308
|
+
const labelWidth = displayWidth(label);
|
|
309
|
+
if (labelWidth <= contentWidth) {
|
|
310
|
+
const fallbackRow = Math.max(0, viewportHeight - 1);
|
|
311
|
+
const row = clamp(input.bottomControlRow ?? fallbackRow, 0, frameRows.length - 1);
|
|
312
|
+
const left = Math.floor((contentWidth - labelWidth) / 2);
|
|
313
|
+
frameRows[row] = overlaySpan(padRowPreservingBackground(frameRows[row] ?? "", width), left, left + labelWidth, `${CONTROL_STYLE_RESET}${theme.bottomControl(label, this.#bottomHovered)}`);
|
|
314
|
+
bottomHit = { row: row + 1, columnStart: left + 1, columnEnd: left + labelWidth };
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
const hits = {
|
|
318
|
+
viewportHeight,
|
|
319
|
+
rail: geometry === null || this.#config.scrollbarAppearance === "hidden" ? null : {
|
|
320
|
+
column: width,
|
|
321
|
+
rowStart: 2,
|
|
322
|
+
trackHeight: Math.max(0, viewportHeight - 1),
|
|
323
|
+
geometry,
|
|
324
|
+
},
|
|
325
|
+
sticky: stickyActive ? { row: 1, target: governing.firstRow, width: contentWidth } : null,
|
|
326
|
+
bottom: bottomHit,
|
|
327
|
+
};
|
|
328
|
+
const frame = {
|
|
329
|
+
rows: frameRows,
|
|
330
|
+
contentWidth,
|
|
331
|
+
scrollTop: this.#scrollTop,
|
|
332
|
+
maxScroll: this.#maxScroll,
|
|
333
|
+
followingEnd: this.#followingEnd,
|
|
334
|
+
hits,
|
|
335
|
+
};
|
|
336
|
+
this.#frame = frame;
|
|
337
|
+
return frame;
|
|
338
|
+
}
|
|
339
|
+
#lineContentAt(line) {
|
|
340
|
+
const plain = stripAnsi(this.#documentRows[line] ?? "");
|
|
341
|
+
const prompt = this.#promptAnchors.find(anchor => line >= anchor.firstRow && line <= anchor.lastRow);
|
|
342
|
+
if (prompt !== undefined) {
|
|
343
|
+
if (line === prompt.firstRow) {
|
|
344
|
+
const timestamp = /\s+\d{2}:\d{2}\s*$/.exec(plain);
|
|
345
|
+
if (timestamp !== null)
|
|
346
|
+
return usefulTextLineContent(plain.slice(0, timestamp.index), 3);
|
|
347
|
+
}
|
|
348
|
+
return usefulTextLineContent(plain, 3);
|
|
349
|
+
}
|
|
350
|
+
const from = plain.trim().length === 0 || (plain.startsWith(" ") && !plain.startsWith(" ")) ? 2 : 1;
|
|
351
|
+
const content = usefulTextLineContent(plain, from);
|
|
352
|
+
if (plain.trim().length === 0)
|
|
353
|
+
return { from, to: from + 1 };
|
|
354
|
+
return content;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
function governingPrompt(anchors, scrollTop) {
|
|
358
|
+
let result = null;
|
|
359
|
+
for (const anchor of anchors) {
|
|
360
|
+
if (anchor.firstRow > scrollTop)
|
|
361
|
+
break;
|
|
362
|
+
result = anchor;
|
|
363
|
+
}
|
|
364
|
+
return result;
|
|
365
|
+
}
|
|
366
|
+
function padRowPreservingBackground(line, width) {
|
|
367
|
+
const shown = displayWidth(line) > width ? truncateToWidth(line, width) : line;
|
|
368
|
+
const padding = Math.max(0, width - displayWidth(shown));
|
|
369
|
+
if (padding === 0)
|
|
370
|
+
return shown;
|
|
371
|
+
const background = /\u001b\[(?:4[0-8]|10[0-7]|48;(?:2;\d+;\d+;\d+|5;\d+))m/.exec(line)?.[0];
|
|
372
|
+
const reverse = /\u001b\[(?:7(?:;[0-9]+)*|[0-9;]*;7(?:;[0-9]+)*)m/.exec(line)?.[0];
|
|
373
|
+
if (background)
|
|
374
|
+
return `${shown}${background}${" ".repeat(padding)}\u001b[49m`;
|
|
375
|
+
if (reverse)
|
|
376
|
+
return `${shown}${reverse}${" ".repeat(padding)}\u001b[27m`;
|
|
377
|
+
return shown + " ".repeat(padding);
|
|
378
|
+
}
|
|
379
|
+
function clamp(value, minimum, maximum) {
|
|
380
|
+
return Math.min(Math.max(value, minimum), maximum);
|
|
381
|
+
}
|
|
@@ -1,8 +1,15 @@
|
|
|
1
|
-
export declare const OWNED_UI_SETTINGS_VERSION =
|
|
1
|
+
export declare const OWNED_UI_SETTINGS_VERSION = 3;
|
|
2
2
|
export type OwnedUiSettingValue = string | number | boolean;
|
|
3
3
|
export type OwnedUiSettingApplication = "live" | "restart";
|
|
4
4
|
export interface OwnedUiSettingDeclaration {
|
|
5
5
|
readonly id: string;
|
|
6
|
+
/** Optional presentation label; the id remains the persistence key. */
|
|
7
|
+
readonly label?: string;
|
|
8
|
+
/** Optional settings-screen group for related owned controls. */
|
|
9
|
+
readonly section?: {
|
|
10
|
+
readonly id: string;
|
|
11
|
+
readonly title: string;
|
|
12
|
+
};
|
|
6
13
|
readonly description: string;
|
|
7
14
|
readonly application: OwnedUiSettingApplication;
|
|
8
15
|
readonly defaultValue: OwnedUiSettingValue;
|
|
@@ -1,13 +1,34 @@
|
|
|
1
|
-
export const OWNED_UI_SETTINGS_VERSION =
|
|
1
|
+
export const OWNED_UI_SETTINGS_VERSION = 3;
|
|
2
2
|
const MAX_ID_LENGTH = 64;
|
|
3
3
|
const ID_PATTERN = /^[a-z][a-z0-9]*(?:[A-Z][a-z0-9]*)*$/;
|
|
4
|
+
const SCROLL_SECTION = Object.freeze({ id: "scroll", title: "Scroll" });
|
|
4
5
|
export const OWNED_UI_SETTING_DECLARATIONS = Object.freeze([
|
|
5
6
|
Object.freeze({
|
|
6
|
-
id: "
|
|
7
|
-
|
|
7
|
+
id: "scrollbarAppearance",
|
|
8
|
+
label: "Scrollbar mode",
|
|
9
|
+
section: SCROLL_SECTION,
|
|
10
|
+
description: "When the session transcript scrollbar is visible.",
|
|
8
11
|
application: "live",
|
|
9
|
-
defaultValue: "
|
|
10
|
-
allowedValues: Object.freeze(["
|
|
12
|
+
defaultValue: "auto",
|
|
13
|
+
allowedValues: Object.freeze(["auto", "always", "hidden"]),
|
|
14
|
+
}),
|
|
15
|
+
Object.freeze({
|
|
16
|
+
id: "scrollbarStyle",
|
|
17
|
+
label: "Scrollbar style",
|
|
18
|
+
section: SCROLL_SECTION,
|
|
19
|
+
description: "Visual weight of the session transcript scrollbar.",
|
|
20
|
+
application: "live",
|
|
21
|
+
defaultValue: "thin",
|
|
22
|
+
allowedValues: Object.freeze(["thin", "thick"]),
|
|
23
|
+
}),
|
|
24
|
+
Object.freeze({
|
|
25
|
+
id: "scrollbarSpeed",
|
|
26
|
+
label: "Speed",
|
|
27
|
+
section: SCROLL_SECTION,
|
|
28
|
+
description: "Distance moved by each session transcript wheel event.",
|
|
29
|
+
application: "live",
|
|
30
|
+
defaultValue: "normal",
|
|
31
|
+
allowedValues: Object.freeze(["normal", "fast", "high"]),
|
|
11
32
|
}),
|
|
12
33
|
]);
|
|
13
34
|
export function assertOwnedUiSettingDeclarations(declarations) {
|
|
@@ -19,6 +40,13 @@ export function assertOwnedUiSettingDeclarations(declarations) {
|
|
|
19
40
|
if (seen.has(declaration.id))
|
|
20
41
|
throw new Error(`duplicate owned UI setting id: ${declaration.id}`);
|
|
21
42
|
seen.add(declaration.id);
|
|
43
|
+
if (declaration.label !== undefined && declaration.label.trim().length === 0) {
|
|
44
|
+
throw new Error(`owned UI setting has an empty label: ${declaration.id}`);
|
|
45
|
+
}
|
|
46
|
+
if (declaration.section !== undefined
|
|
47
|
+
&& (declaration.section.id.trim().length === 0 || declaration.section.title.trim().length === 0)) {
|
|
48
|
+
throw new Error(`owned UI setting has an invalid section: ${declaration.id}`);
|
|
49
|
+
}
|
|
22
50
|
if (declaration.description.trim().length === 0) {
|
|
23
51
|
throw new Error(`owned UI setting has no description: ${declaration.id}`);
|
|
24
52
|
}
|
|
@@ -6,7 +6,7 @@ export interface OwnedUiSettingsMigration {
|
|
|
6
6
|
}
|
|
7
7
|
/**
|
|
8
8
|
* Ordered, contiguous migrations ending at OWNED_UI_SETTINGS_VERSION. Version 1
|
|
9
|
-
* is the first stored shape
|
|
9
|
+
* is the first stored shape.
|
|
10
10
|
*/
|
|
11
11
|
export declare const OWNED_UI_SETTINGS_MIGRATIONS: readonly OwnedUiSettingsMigration[];
|
|
12
12
|
export declare function assertOwnedUiSettingsMigrations(migrations: readonly OwnedUiSettingsMigration[], currentVersion?: number): void;
|
|
@@ -1,9 +1,28 @@
|
|
|
1
1
|
import { OWNED_UI_SETTINGS_VERSION } from "./declarations.js";
|
|
2
2
|
/**
|
|
3
3
|
* Ordered, contiguous migrations ending at OWNED_UI_SETTINGS_VERSION. Version 1
|
|
4
|
-
* is the first stored shape
|
|
4
|
+
* is the first stored shape.
|
|
5
5
|
*/
|
|
6
|
-
export const OWNED_UI_SETTINGS_MIGRATIONS = Object.freeze([
|
|
6
|
+
export const OWNED_UI_SETTINGS_MIGRATIONS = Object.freeze([
|
|
7
|
+
Object.freeze({
|
|
8
|
+
to: 2,
|
|
9
|
+
description: "Rename scrollbar speed high to fast.",
|
|
10
|
+
migrate(values) {
|
|
11
|
+
return values.scrollbarSpeed === "high"
|
|
12
|
+
? { ...values, scrollbarSpeed: "fast" }
|
|
13
|
+
: { ...values };
|
|
14
|
+
},
|
|
15
|
+
}),
|
|
16
|
+
Object.freeze({
|
|
17
|
+
to: 3,
|
|
18
|
+
description: "Match Pi's auto scrollbar appearance name and ordering.",
|
|
19
|
+
migrate(values) {
|
|
20
|
+
return values.scrollbarAppearance === "hover"
|
|
21
|
+
? { ...values, scrollbarAppearance: "auto" }
|
|
22
|
+
: { ...values };
|
|
23
|
+
},
|
|
24
|
+
}),
|
|
25
|
+
]);
|
|
7
26
|
export function assertOwnedUiSettingsMigrations(migrations, currentVersion = OWNED_UI_SETTINGS_VERSION) {
|
|
8
27
|
const firstProduced = currentVersion - migrations.length + 1;
|
|
9
28
|
if (migrations.length > 0 && firstProduced < 2) {
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
export const AGENT_SECTION_ID = "agent";
|
|
2
2
|
const BOOLEAN_CHOICES = Object.freeze([true, false]);
|
|
3
3
|
export function buildOwnedUiSettingsSections(input) {
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
title: "A1"
|
|
7
|
-
|
|
4
|
+
const grouped = new Map();
|
|
5
|
+
for (const setting of input.resolution.settings) {
|
|
6
|
+
const section = setting.declaration.section ?? { id: "a1", title: "A1" };
|
|
7
|
+
const group = grouped.get(section.id) ?? { title: section.title, entries: [] };
|
|
8
|
+
group.entries.push({
|
|
8
9
|
id: setting.declaration.id,
|
|
9
|
-
label: null,
|
|
10
|
+
label: setting.declaration.label ?? null,
|
|
10
11
|
backend: "a1",
|
|
11
12
|
description: setting.declaration.description,
|
|
12
13
|
value: setting.value,
|
|
@@ -19,11 +20,17 @@ export function buildOwnedUiSettingsSections(input) {
|
|
|
19
20
|
choices: setting.declaration.allowedValues,
|
|
20
21
|
origin: setting.source,
|
|
21
22
|
application: setting.declaration.application,
|
|
22
|
-
})
|
|
23
|
+
});
|
|
24
|
+
grouped.set(section.id, group);
|
|
25
|
+
}
|
|
26
|
+
const ownedSections = [...grouped].map(([id, group]) => ({
|
|
27
|
+
id,
|
|
28
|
+
title: group.title,
|
|
29
|
+
entries: Object.freeze(group.entries),
|
|
23
30
|
unavailableReason: null,
|
|
24
31
|
readOnlyReason: null,
|
|
25
|
-
};
|
|
26
|
-
return Object.freeze([
|
|
32
|
+
}));
|
|
33
|
+
return Object.freeze([...ownedSections, agentSection(input.agent)]);
|
|
27
34
|
}
|
|
28
35
|
export function findOwnedUiSettingsEntry(sections, id, backend) {
|
|
29
36
|
for (const section of sections) {
|
|
@@ -19,6 +19,8 @@ export interface OwnedUiSettingsSessionOptions {
|
|
|
19
19
|
* whose runtime is not up yet. Takes precedence over `agent`.
|
|
20
20
|
*/
|
|
21
21
|
readonly agentProvider?: () => AgentSettingsPort | null;
|
|
22
|
+
/** Agent-owned controls fixed by this product surface and therefore omitted. */
|
|
23
|
+
readonly hiddenAgentSettingIds?: readonly string[];
|
|
22
24
|
}
|
|
23
25
|
export type OwnedUiSettingsListener = (session: OwnedUiSettingsSession) => void;
|
|
24
26
|
/**
|
|
@@ -9,6 +9,7 @@ export class OwnedUiSettingsSession {
|
|
|
9
9
|
#agent;
|
|
10
10
|
#agentProvider;
|
|
11
11
|
#listeners = new Set();
|
|
12
|
+
#hiddenAgentSettingIds;
|
|
12
13
|
#resolution;
|
|
13
14
|
#agentSnapshot = null;
|
|
14
15
|
#pending = new Map();
|
|
@@ -16,6 +17,7 @@ export class OwnedUiSettingsSession {
|
|
|
16
17
|
this.#store = options.store;
|
|
17
18
|
this.#agent = options.agent ?? null;
|
|
18
19
|
this.#agentProvider = options.agentProvider ?? null;
|
|
20
|
+
this.#hiddenAgentSettingIds = new Set(options.hiddenAgentSettingIds ?? []);
|
|
19
21
|
this.#resolution = options.store.read();
|
|
20
22
|
}
|
|
21
23
|
get resolution() {
|
|
@@ -25,7 +27,7 @@ export class OwnedUiSettingsSession {
|
|
|
25
27
|
async load() {
|
|
26
28
|
this.#resolution = this.#store.read();
|
|
27
29
|
const agent = this.#agentProvider === null ? this.#agent : this.#agentProvider();
|
|
28
|
-
this.#agentSnapshot = agent === null ? null : await snapshotOf(agent);
|
|
30
|
+
this.#agentSnapshot = agent === null ? null : await snapshotOf(agent, this.#hiddenAgentSettingIds);
|
|
29
31
|
this.#notify();
|
|
30
32
|
}
|
|
31
33
|
sections() {
|
|
@@ -61,7 +63,7 @@ export class OwnedUiSettingsSession {
|
|
|
61
63
|
catch (error) {
|
|
62
64
|
return failed(`${id} could not be written to the agent engine: ${describe(error)}`);
|
|
63
65
|
}
|
|
64
|
-
this.#agentSnapshot = await snapshotOf(agent);
|
|
66
|
+
this.#agentSnapshot = await snapshotOf(agent, this.#hiddenAgentSettingIds);
|
|
65
67
|
this.#notify();
|
|
66
68
|
return { applied: true, pendingRestart: false, failure: null };
|
|
67
69
|
}
|
|
@@ -105,7 +107,7 @@ export class OwnedUiSettingsSession {
|
|
|
105
107
|
catch (error) {
|
|
106
108
|
return failed(`${id} could not be written to the agent engine: ${describe(error)}`);
|
|
107
109
|
}
|
|
108
|
-
this.#agentSnapshot = await snapshotOf(agent);
|
|
110
|
+
this.#agentSnapshot = await snapshotOf(agent, this.#hiddenAgentSettingIds);
|
|
109
111
|
this.#notify();
|
|
110
112
|
return { applied: true, pendingRestart: false, failure: null };
|
|
111
113
|
}
|
|
@@ -114,9 +116,9 @@ export class OwnedUiSettingsSession {
|
|
|
114
116
|
listener(this);
|
|
115
117
|
}
|
|
116
118
|
}
|
|
117
|
-
async function snapshotOf(agent) {
|
|
119
|
+
async function snapshotOf(agent, hidden) {
|
|
118
120
|
try {
|
|
119
|
-
const descriptors = await agent.listSettings();
|
|
121
|
+
const descriptors = (await agent.listSettings()).filter(descriptor => !hidden.has(descriptor.key));
|
|
120
122
|
const values = {};
|
|
121
123
|
for (const descriptor of descriptors) {
|
|
122
124
|
try {
|
|
@@ -13,9 +13,9 @@ mutable a1 entry
|
|
|
13
13
|
-> owned Pi-backed composition, with A1 surfaces enabled only for bare a1
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
Launch-instance ownership is plural. The supervisor tracks any number of independently authenticated `a1
|
|
16
|
+
Launch-instance ownership is plural. The supervisor tracks any number of independently authenticated `a1` and `a1 pi` instances and never uses one product-wide foreground mutex. Closing one instance closes only its verified process tree. Persisted rows alone do not prove liveness, and uncertainty never authorizes terminating an unrelated process.
|
|
17
17
|
|
|
18
|
-
Neither guardian reads ordinary terminal input, parses or relays runtime output, renders cells, infers frames, synthesizes terminal responses, or emits display control.
|
|
18
|
+
Neither guardian reads ordinary terminal input, parses or relays runtime output, renders cells, infers frames, synthesizes terminal responses, or emits display control. Both profiles use the same owned rendering and input pipeline. Bare `a1` exposes A1-owned surfaces; `a1 pi` withholds those surfaces and uses Pi's ordinary profile root. The private guardian boundary carries only bounded identity, readiness, stop, and outcome data.
|
|
19
19
|
|
|
20
20
|
The owned Pi-backed surface is not an arbitrary-CLI terminal multiplexer. A feature that requires resident arbitrary CLI sessions, internal tabs, terminal-state reconnection, or A1-authoritative scrollback must introduce and certify the separate composed-terminal authority rather than reactivating the retired transparent attachment path.
|
|
21
21
|
|
|
@@ -30,7 +30,7 @@ The owned Pi-backed surface is not an arbitrary-CLI terminal multiplexer. A feat
|
|
|
30
30
|
- `protocol`: additive control handshake, bounded line framing, authenticated launch-instance commands, typed stop intent, snapshots, and command results.
|
|
31
31
|
- `storage`: SQLite migrations, prior-boot reconciliation, and plural launch-instance persistence. Legacy foreground rows are historical migration input and never authorize current ownership.
|
|
32
32
|
- `supervisor`: endpoint identity, plural cohort ownership, per-instance reconciliation, and aggregate release shutdown coordination. It owns no terminal surface.
|
|
33
|
-
- `pi-engine-adapter`, `pi-component-adapter`, `pi-tui-runtime-adapter`, and `pi-
|
|
33
|
+
- `pi-engine-adapter`, `pi-component-adapter`, `pi-tui-runtime-adapter`, and `pi-session-ui-integration`: isolate pinned Pi engine and presentation knowledge behind neutral contracts; product features do not import them directly. The session UI render root assembles semantic document and dock rows, while its focused viewport controller owns follow state, pointer routing, selection, and interaction timers. Owned-app route lifecycle belongs to the neutral `ui-apps` owner and is only hosted by the Pi session UI.
|
|
34
34
|
- release/update/bootstrap: package-derived immutable release identity, process-guardian integrity, cohort selection, durable update transactions, rollback, and dependency-light command entry.
|
|
35
35
|
|
|
36
36
|
## Dependency direction
|
|
@@ -23,7 +23,7 @@ src/
|
|
|
23
23
|
pi/
|
|
24
24
|
components/ pinned Pi component and theme adaptation
|
|
25
25
|
engine/ pinned Pi engine, settings, resource, package, and workflow integration
|
|
26
|
-
|
|
26
|
+
session-ui/ Pi-backed session shell and A1 viewport integration
|
|
27
27
|
tui-runtime/ neutral presentation runtime over pinned Pi TUI
|
|
28
28
|
foundation/
|
|
29
29
|
launch-guardian/ authenticated launch-instance coordination
|
|
@@ -97,7 +97,7 @@ Build output mirrors the production namespaces directly under ignored `dist/`, w
|
|
|
97
97
|
|
|
98
98
|
## Terminal capability boundary
|
|
99
99
|
|
|
100
|
-
|
|
100
|
+
Both interactive profiles use the owned rendering pipeline. Bare `a1` exposes A1-owned surfaces; `a1 pi` withholds those surfaces while using Pi's ordinary profile root. The retired transparent attachment owner is not part of the production tree or dependency map.
|
|
101
101
|
|
|
102
102
|
The current JavaScript control plane does not provide arbitrary-CLI tabs. Such tabs require the separately bounded console terminal host to own PTYs, authoritative terminal state, rendering, input routing, inactive-surface lifecycle, and reconnection. JavaScript may exchange typed identity, topology revision, capability, lifecycle, status, and recovery messages only; it must not relay terminal output, per-event child input, or rendered cells.
|
|
103
103
|
|
|
@@ -46,7 +46,7 @@ Sanitization must classify before logging or storing. Unknown or untyped values
|
|
|
46
46
|
|
|
47
47
|
- Structured/RPC semantics must not be inferred from ANSI text, terminal timing, or visual content.
|
|
48
48
|
- Native-host protocol code must not carry PTY bytes, per-event child input, rendered cells, cell grids, framebuffers, or screen buffers.
|
|
49
|
-
- `a1 pi`
|
|
49
|
+
- `a1 pi` must not import, initialize, launch, or connect to composed terminal-host infrastructure.
|
|
50
50
|
- Transparent direct attachment must remain independent of composed infrastructure and must not relay terminal data.
|
|
51
51
|
- Recovery records store identity and bounded references, not terminal streams, authentication material, or arbitrary environment values.
|
|
52
52
|
- A failed in-terminal 2×2 proof cannot be waived to merge composed infrastructure.
|