@timurproko/a1 0.2.7-dev.788 → 0.2.7-dev.792
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/dist/app/session-shell/session-shell-root.d.ts +0 -1
- package/dist/app/session-shell/session-shell-root.js +41 -15
- package/dist/app/session-shell/session-shell.js +5 -4
- package/dist/app/session-shell/session-viewport-controller.d.ts +2 -2
- package/dist/app/session-shell/session-viewport-controller.js +24 -12
- package/dist/composition/owned-ui.d.ts +1 -0
- package/dist/composition/owned-ui.js +27 -2
- package/dist/contracts/owned-ui/index.d.ts +1 -1
- package/dist/contracts/owned-ui/model.d.ts +3 -0
- package/dist/features/owned-ui/settings-app.js +9 -6
- package/dist/features/resident-tab/index.d.ts +3 -1
- package/dist/features/resident-tab/index.js +5 -1
- package/dist/features/resident-tab/shortcut-bindings-file.d.ts +8 -0
- package/dist/features/resident-tab/shortcut-bindings-file.js +40 -0
- package/dist/features/resident-tab/{shortcuts.d.ts → shortcut-declarations.d.ts} +5 -2
- package/dist/features/resident-tab/{shortcuts.js → shortcut-declarations.js} +5 -17
- package/dist/features/resident-tab/shortcut-projection.d.ts +29 -0
- package/dist/features/resident-tab/shortcut-projection.js +64 -0
- package/dist/foundation/release/bootstrap.js +1 -0
- package/dist/foundation/resident-tabs/bridge-client.d.ts +5 -0
- package/dist/foundation/resident-tabs/bridge-client.js +17 -1
- package/dist/foundation/resident-tabs/prompt-journal.d.ts +0 -2
- package/dist/foundation/resident-tabs/prompt-journal.js +0 -1
- package/dist/integrations/pi/components/owned-editor-ux.d.ts +2 -0
- package/dist/integrations/pi/components/owned-editor-ux.js +13 -0
- package/dist/integrations/pi/components/shell-editor-autocomplete.js +1 -0
- package/dist/integrations/pi/components/shell-presenters-info.d.ts +1 -0
- package/dist/integrations/pi/components/shell-presenters-info.js +10 -30
- package/dist/integrations/pi/components/shell-shared-facade.d.ts +2 -0
- package/dist/native/darwin-arm64/a1_session_lock.node +0 -0
- package/dist/native/darwin-arm64/manifest.json +3 -2
- package/dist/native/darwin-arm64/terminal-host +0 -0
- package/dist/native/darwin-arm64/terminal-host.manifest.json +7 -6
- package/dist/native/darwin-x64/a1_session_lock.node +0 -0
- package/dist/native/darwin-x64/manifest.json +5 -4
- 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.manifest.json +7 -6
- package/dist/native/linux-x64/a1_session_lock.node +0 -0
- package/dist/native/linux-x64/manifest.json +3 -2
- package/dist/native/linux-x64/terminal-host +0 -0
- package/dist/native/linux-x64/terminal-host.manifest.json +7 -6
- package/dist/native/win32-x64/a1_session_lock.node +0 -0
- package/dist/native/win32-x64/manifest.json +4 -3
- package/dist/native/win32-x64/process-guardian.exe +0 -0
- package/dist/native/win32-x64/terminal-host.exe +0 -0
- package/dist/native/win32-x64/terminal-host.manifest.json +6 -5
- package/dist/ui/components/index.d.ts +2 -2
- package/dist/ui/components/index.js +1 -1
- package/dist/ui/components/selection-copy.d.ts +2 -0
- package/dist/ui/components/selection-copy.js +13 -0
- package/dist/ui/components/submitted-prompt.d.ts +7 -4
- package/dist/ui/components/submitted-prompt.js +18 -10
- package/dist/ui/components/transcript-viewport.d.ts +10 -6
- package/dist/ui/components/transcript-viewport.js +28 -13
- package/dist/ui/settings/declarations.d.ts +13 -1
- package/dist/ui/settings/declarations.js +12 -1
- package/dist/ui/settings/migrations.js +7 -0
- package/docs/architecture/evidence/resident-tabs/macos-x64.md +1 -1
- package/docs/architecture/process-guardian-provenance.md +1 -1
- package/docs/architecture/project-structure.md +8 -0
- package/docs/architecture/resident-tabs.md +18 -2
- package/docs/architecture/terminal-host-provenance.md +1 -1
- package/docs/architecture/toolchain.md +1 -1
- package/docs/validation.md +3 -3
- package/package.json +1 -1
|
@@ -6,6 +6,8 @@ export interface TranscriptPromptAnchor {
|
|
|
6
6
|
readonly lastRow: number;
|
|
7
7
|
/** The source first row, including its submitted-prompt styling and timestamp. */
|
|
8
8
|
readonly sourceRow: string;
|
|
9
|
+
/** Right-edge metadata cells omitted from semantic prompt copies. */
|
|
10
|
+
readonly copySuffixWidth?: number;
|
|
9
11
|
/** Optional source-styled row used only while prominent and not hovered. */
|
|
10
12
|
readonly prominentSourceRow?: string;
|
|
11
13
|
}
|
|
@@ -36,7 +38,11 @@ export interface TranscriptViewportFrameInput {
|
|
|
36
38
|
/** Final transient rows to bottom-align with unused viewport space while content fits. */
|
|
37
39
|
readonly bottomAlignedTailRowCount?: number;
|
|
38
40
|
readonly dockRows: readonly string[];
|
|
39
|
-
|
|
41
|
+
/** Zero-based dock rows admitted to frame copying, classified before dock composition. */
|
|
42
|
+
readonly copyableDockRows?: readonly {
|
|
43
|
+
readonly row: number;
|
|
44
|
+
readonly prompt?: "first" | "continuation";
|
|
45
|
+
}[];
|
|
40
46
|
readonly promptAnchors: readonly TranscriptPromptAnchor[];
|
|
41
47
|
readonly width: number;
|
|
42
48
|
readonly height: number;
|
|
@@ -142,7 +148,6 @@ export interface TranscriptViewportFrame {
|
|
|
142
148
|
readonly cacheEntries: number;
|
|
143
149
|
};
|
|
144
150
|
}
|
|
145
|
-
type SelectionCopyIntent = "automatic" | "explicit";
|
|
146
151
|
/**
|
|
147
152
|
* A1-owned viewport state. It knows only rows and semantic prompt anchors; Pi
|
|
148
153
|
* components and transcript payloads remain outside this neutral boundary.
|
|
@@ -172,9 +177,9 @@ export declare class TranscriptViewport {
|
|
|
172
177
|
extendSelection(column: number, frameRow: number, now?: number, autoScroll?: boolean): boolean;
|
|
173
178
|
releaseSelection(): boolean;
|
|
174
179
|
clearSelection(): boolean;
|
|
175
|
-
selectedText(
|
|
180
|
+
selectedText(): string | null;
|
|
176
181
|
/** Captures visible selected text, not off-screen history. */
|
|
177
|
-
captureSelectedText(
|
|
182
|
+
captureSelectedText(): SelectionCopySnapshot | null;
|
|
178
183
|
scrollBy(lines: number, now?: number): boolean;
|
|
179
184
|
scrollTo(position: number, now?: number): boolean;
|
|
180
185
|
scrollToEnd(now?: number): boolean;
|
|
@@ -186,7 +191,6 @@ export declare class TranscriptViewport {
|
|
|
186
191
|
clearTransient(): void;
|
|
187
192
|
compose(input: TranscriptViewportFrameInput): TranscriptViewportFrame;
|
|
188
193
|
/** Reuses an established transcript frame when only same-height dock rows changed. */
|
|
189
|
-
composeDockOnly(dockRows: readonly string[], width: number, height: number,
|
|
194
|
+
composeDockOnly(dockRows: readonly string[], width: number, height: number, copyableDockRows?: TranscriptViewportFrameInput["copyableDockRows"]): TranscriptViewportFrame | null;
|
|
190
195
|
}
|
|
191
196
|
export declare function assertTranscriptViewportFrameDescriptor(descriptor: TranscriptViewportFrameDescriptor): void;
|
|
192
|
-
export {};
|
|
@@ -63,7 +63,7 @@ export class TranscriptViewport {
|
|
|
63
63
|
#contentWidth = 0;
|
|
64
64
|
#selectionWidth = 0;
|
|
65
65
|
#viewportHeight = 0;
|
|
66
|
-
#
|
|
66
|
+
#copyableDockRows = new Map();
|
|
67
67
|
#firstSelectableRow = 0;
|
|
68
68
|
#selectionRowAnchors = [];
|
|
69
69
|
#frameId = 0;
|
|
@@ -179,14 +179,14 @@ export class TranscriptViewport {
|
|
|
179
179
|
this.#selectionRevision += 1;
|
|
180
180
|
return true;
|
|
181
181
|
}
|
|
182
|
-
selectedText(
|
|
182
|
+
selectedText() {
|
|
183
183
|
const selection = this.#visibleSelection();
|
|
184
184
|
if (selection === undefined)
|
|
185
185
|
return null;
|
|
186
186
|
const copiedLines = Array.from({ length: Math.max(0, selection.end.line - selection.start.line + 1) }, (_value, index) => selection.start.line + index).filter(line => {
|
|
187
187
|
const anchor = this.#selectionRowAnchors[line];
|
|
188
188
|
return anchor?.kind === "document" && anchor.row < this.#selectableDocumentRowCount
|
|
189
|
-
||
|
|
189
|
+
|| anchor?.kind === "dock" && this.#copyableDockRows.has(anchor.fromBottom);
|
|
190
190
|
});
|
|
191
191
|
if (copiedLines.length === 0)
|
|
192
192
|
return null;
|
|
@@ -199,18 +199,24 @@ export class TranscriptViewport {
|
|
|
199
199
|
const text = textSelectionText(selection, this.#selectionRows, line => {
|
|
200
200
|
const row = this.#selectionRows[line] ?? "";
|
|
201
201
|
const anchor = this.#selectionRowAnchors[line];
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
202
|
+
const dock = anchor?.kind === "dock" ? this.#copyableDockRows.get(anchor.fromBottom) : undefined;
|
|
203
|
+
if (prompt !== undefined && anchor?.kind === "document") {
|
|
204
|
+
const first = anchor.row === prompt.firstRow;
|
|
205
|
+
const copyRow = first && (prompt.copySuffixWidth ?? 0) > 0
|
|
206
|
+
? truncateToWidth(row, Math.max(0, displayWidth(row) - (prompt.copySuffixWidth ?? 0)))
|
|
207
|
+
: row;
|
|
208
|
+
return selectionCopyLineContent(copyRow, first ? "first" : "continuation");
|
|
209
|
+
}
|
|
210
|
+
return dock?.prompt !== undefined ? selectionCopyLineContent(row, dock.prompt) : usefulTextLineContent(row);
|
|
205
211
|
}, line => included.has(line));
|
|
206
212
|
return text.length === 0 ? null : text;
|
|
207
213
|
}
|
|
208
214
|
/** Captures visible selected text, not off-screen history. */
|
|
209
|
-
captureSelectedText(
|
|
210
|
-
if (
|
|
215
|
+
captureSelectedText() {
|
|
216
|
+
if (!this.#copyableSelection)
|
|
211
217
|
return null;
|
|
212
218
|
const started = performance.now();
|
|
213
|
-
let text = this.selectedText(
|
|
219
|
+
let text = this.selectedText();
|
|
214
220
|
if (text === null)
|
|
215
221
|
return null;
|
|
216
222
|
text = text.trim();
|
|
@@ -313,7 +319,7 @@ export class TranscriptViewport {
|
|
|
313
319
|
this.#contentWidth = 0;
|
|
314
320
|
this.#selectionWidth = 0;
|
|
315
321
|
this.#viewportHeight = 0;
|
|
316
|
-
this.#
|
|
322
|
+
this.#copyableDockRows.clear();
|
|
317
323
|
this.clearTransient();
|
|
318
324
|
this.#frame = null;
|
|
319
325
|
}
|
|
@@ -329,7 +335,7 @@ export class TranscriptViewport {
|
|
|
329
335
|
const theme = input.theme ?? PLAIN_THEME;
|
|
330
336
|
const previousDescriptor = this.#frame?.descriptor ?? null;
|
|
331
337
|
const dock = input.dockRows.length > height ? input.dockRows.slice(-height) : [...input.dockRows];
|
|
332
|
-
this.#
|
|
338
|
+
this.#copyableDockRows = copyableDockRowsBySuffix(input.dockRows, input.copyableDockRows);
|
|
333
339
|
const viewportHeight = Math.max(0, height - dock.length);
|
|
334
340
|
const bottomAlignedTailRowCount = clamp(input.bottomAlignedTailRowCount ?? 0, 0, input.documentRows.length);
|
|
335
341
|
const transientAlignmentGapRows = bottomAlignedTailRowCount > 0
|
|
@@ -606,7 +612,7 @@ export class TranscriptViewport {
|
|
|
606
612
|
return frame;
|
|
607
613
|
}
|
|
608
614
|
/** Reuses an established transcript frame when only same-height dock rows changed. */
|
|
609
|
-
composeDockOnly(dockRows, width, height,
|
|
615
|
+
composeDockOnly(dockRows, width, height, copyableDockRows = []) {
|
|
610
616
|
const previous = this.#frame;
|
|
611
617
|
const boundedWidth = Math.max(1, width);
|
|
612
618
|
const boundedHeight = Math.max(0, height);
|
|
@@ -642,7 +648,7 @@ export class TranscriptViewport {
|
|
|
642
648
|
};
|
|
643
649
|
assertTranscriptViewportFrameDescriptor(descriptor);
|
|
644
650
|
this.#selectionRows = [...this.#selectionRows.slice(0, viewportHeight), ...dock].slice(0, boundedHeight);
|
|
645
|
-
this.#
|
|
651
|
+
this.#copyableDockRows = copyableDockRowsBySuffix(dockRows, copyableDockRows);
|
|
646
652
|
const frame = {
|
|
647
653
|
rows: [...previous.rows.slice(0, viewportHeight), ...dock].slice(0, boundedHeight),
|
|
648
654
|
contentWidth: previous.contentWidth,
|
|
@@ -961,6 +967,15 @@ function assertDocumentRange(range, label) {
|
|
|
961
967
|
function sameRectangle(left, right) {
|
|
962
968
|
return left === null ? right === null : right !== null && left.rowStart === right.rowStart && left.rowEnd === right.rowEnd;
|
|
963
969
|
}
|
|
970
|
+
function copyableDockRowsBySuffix(dockRows, declarations) {
|
|
971
|
+
const result = new Map();
|
|
972
|
+
for (const declaration of declarations ?? []) {
|
|
973
|
+
if (!Number.isSafeInteger(declaration.row) || declaration.row < 0 || declaration.row >= dockRows.length)
|
|
974
|
+
continue;
|
|
975
|
+
result.set(dockRows.length - declaration.row - 1, declaration.prompt === undefined ? {} : { prompt: declaration.prompt });
|
|
976
|
+
}
|
|
977
|
+
return result;
|
|
978
|
+
}
|
|
964
979
|
function clamp(value, minimum, maximum) {
|
|
965
980
|
return Math.min(Math.max(value, minimum), maximum);
|
|
966
981
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const OWNED_UI_SETTINGS_VERSION =
|
|
1
|
+
export declare const OWNED_UI_SETTINGS_VERSION = 17;
|
|
2
2
|
export type OwnedUiSettingValue = string | number | boolean;
|
|
3
3
|
export type OwnedUiSettingApplication = "live" | "restart";
|
|
4
4
|
export interface OwnedUiSettingDeclaration {
|
|
@@ -32,6 +32,18 @@ export declare const OWNED_SETTING_DECLARATIONS: Readonly<{
|
|
|
32
32
|
defaultValue: true;
|
|
33
33
|
allowedValues: readonly [true, false];
|
|
34
34
|
}>;
|
|
35
|
+
readonly timeFormat: Readonly<{
|
|
36
|
+
id: "timeFormat";
|
|
37
|
+
label: "Time format";
|
|
38
|
+
section: Readonly<{
|
|
39
|
+
id: "generic";
|
|
40
|
+
title: "Generic";
|
|
41
|
+
}>;
|
|
42
|
+
description: "Format used by submitted-prompt timestamps and the resident tab clock.";
|
|
43
|
+
application: "live";
|
|
44
|
+
defaultValue: "8:00 PM";
|
|
45
|
+
allowedValues: readonly ["20:00", "8:00 PM"];
|
|
46
|
+
}>;
|
|
35
47
|
readonly accentColor: Readonly<{
|
|
36
48
|
id: "accentColor";
|
|
37
49
|
label: "Accent color";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const OWNED_UI_SETTINGS_VERSION =
|
|
1
|
+
export const OWNED_UI_SETTINGS_VERSION = 17;
|
|
2
2
|
const UI_ACCENT_COLOR_CHOICES = Object.freeze([
|
|
3
3
|
"purple", "blue", "cyan", "green", "orange", "pink",
|
|
4
4
|
]);
|
|
@@ -25,6 +25,17 @@ export const OWNED_SETTING_DECLARATIONS = Object.freeze({
|
|
|
25
25
|
defaultValue: true,
|
|
26
26
|
allowedValues: Object.freeze([true, false]),
|
|
27
27
|
}),
|
|
28
|
+
timeFormat: Object.freeze({
|
|
29
|
+
id: "timeFormat",
|
|
30
|
+
label: "Time format",
|
|
31
|
+
section: GENERIC_SECTION,
|
|
32
|
+
description: "Format used by submitted-prompt timestamps and the resident tab clock.",
|
|
33
|
+
application: "live",
|
|
34
|
+
defaultValue: "8:00 PM",
|
|
35
|
+
allowedValues: Object.freeze([
|
|
36
|
+
"20:00", "8:00 PM",
|
|
37
|
+
]),
|
|
38
|
+
}),
|
|
28
39
|
accentColor: Object.freeze({
|
|
29
40
|
id: "accentColor",
|
|
30
41
|
label: "Accent color",
|
|
@@ -130,6 +130,13 @@ export const OWNED_UI_SETTINGS_MIGRATIONS = Object.freeze([
|
|
|
130
130
|
return migrated;
|
|
131
131
|
},
|
|
132
132
|
}),
|
|
133
|
+
Object.freeze({
|
|
134
|
+
to: 17,
|
|
135
|
+
description: "Introduce the shared twelve-hour time format default.",
|
|
136
|
+
migrate(values) {
|
|
137
|
+
return { ...values };
|
|
138
|
+
},
|
|
139
|
+
}),
|
|
133
140
|
]);
|
|
134
141
|
/** Shape-selected compatibility for the metadata-free product object. */
|
|
135
142
|
export function normalizeOwnedUiSettingsValues(source) {
|
|
@@ -15,7 +15,7 @@ Physical acceptance against a published candidate package is pending; the operat
|
|
|
15
15
|
|
|
16
16
|
## Automated evidence
|
|
17
17
|
|
|
18
|
-
- Publication `terminal_hosts` and `guardians` jobs on `macos-15
|
|
18
|
+
- Publication `terminal_hosts` and `guardians` jobs on `macos-15` (arm64): the release terminal host and process guardian are cross-compiled for Intel macOS with `--target x86_64-apple-darwin`, the host's `--version` identity is checked through Rosetta 2 when the runner can execute it (otherwise the manifest records a Mach-O architecture check), and packing refuses a missing or altered artifact. Before 2026-10-16 the same builds ran natively on `macos-15-intel`.
|
|
19
19
|
- Known gap: the pull-request `terminal-host` and `resident-chaos` suites run on macOS arm64 only, because modular validation outcomes are keyed by platform without architecture. Intel macOS has build and identity evidence but no automated behavior suite of its own.
|
|
20
20
|
|
|
21
21
|
## Physical verdicts
|
|
@@ -21,7 +21,7 @@ dist/native/darwin-x64/process-guardian
|
|
|
21
21
|
dist/native/darwin-arm64/process-guardian
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
The publication `guardians` job builds them on `windows-2025`, `ubuntu-24.04`, `macos-15` (arm64)
|
|
24
|
+
The publication `guardians` job builds them on `windows-2025`, `ubuntu-24.04`, and `macos-15` (arm64); the macOS arm64 job also cross-compiles the x64 guardian with `--target x86_64-apple-darwin`, and its manifest records whether the identity was observed by execution or only from the Mach-O header.
|
|
25
25
|
|
|
26
26
|
Each supported artifact must be named in the release integrity manifest with target triple, crate version, guardian protocol version, SHA-256, byte size, build provenance, and signature/attestation status. The build marks a platform supported only after its native containment integration runs on that platform. A missing, altered, incompatible, unsupported, or wrong-platform artifact is a concise containment launch error; A1 never silently falls back to PID-only cleanup.
|
|
27
27
|
|
|
@@ -103,6 +103,14 @@ a1 session link-worktree <absolute-worktree>
|
|
|
103
103
|
git -C .worktrees/<task-id> status
|
|
104
104
|
```
|
|
105
105
|
|
|
106
|
+
Claude Code sessions have no A1 shell-tool session, so they link with Claude Code's `EnterWorktree` tool by task name. The tracked `.claude/settings.json` wires a `WorktreeCreate` hook, `scripts/governance/claude-worktree-create.mjs`, that creates `.worktrees/<task-id>` detached at fresh `origin/develop`, or returns it when it is already registered, so Claude Code anchors the session there without an approval prompt. The agent then creates the task branch inside it:
|
|
107
|
+
|
|
108
|
+
```sh
|
|
109
|
+
git -C .worktrees/<task-id> switch -c <type>/<short-description>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Passing an absolute `path` to `EnterWorktree` is reserved for a worktree created by hand, from the primary checkout, before any shell command in that session; Claude Code otherwise keeps its shell tools anchored to the primary checkout and refuses their commands. The matching `WorktreeRemove` hook declines removal, so sessions leave with `ExitWorktree` `keep` and worktrees are removed only by the cleanup tool.
|
|
113
|
+
|
|
106
114
|
Inventory is read-only and advisory because another runtime may win a later race. A successful link response confirming the exact canonical worktree is required before planning, implementation, test, or delivery-documentation edits. If inventory or linking fails, reports a live/unverifiable owner, loses a race, or confirms another path, stop task edits and report the blocker; use a separate fresh worktree rather than overriding, unlinking, or recovering that owner. A session resuming an existing delivery or switching streams atomically claims and links the exact worktree before editing it.
|
|
107
115
|
|
|
108
116
|
The link acquires a cooperative live-session editing claim and switches bare A1's footer repository context and pull-request discovery. It does not change process or tool cwd, create a Git worktree lock, or register, release, or authorize cleanup. Clean unlink/disposal releases live editing authority; a crashed owner becomes reusable only after exact process death is verified. Repository commands therefore keep an explicit worktree path. The primary worktree stays on `develop` for integration and must not be used for task edits. Do not edit, adopt, move, or remove another session's worktree.
|
|
@@ -238,6 +238,18 @@ The channels carry bounded generation-1 frames: a u32 little-endian length follo
|
|
|
238
238
|
- Image protocols are not forwarded in the first version; the terminal identity the child sees disables them and existing text fallback applies. Extension parity is claimed only for the certified text-terminal contract.
|
|
239
239
|
- **Detach.** On detach the client restores the outer terminal's keyboard and mouse modes exactly. Enhanced keyboard sequences must not leak into the parent shell (herdr CHANGELOG lesson). Preserve A1's surviving-owner restoration for a killed attach process; an in-process panic/fatal hook cannot run after forced termination. The outer bootstrap/exit-notice owner performs bounded emergency restoration without giving either guardian terminal-byte relay authority.
|
|
240
240
|
|
|
241
|
+
### 6a. Solo interaction parity (2026-10-17)
|
|
242
|
+
|
|
243
|
+
Direct `a1 solo` is the behavioural oracle for every viewed resident child. Only the tab strip row and its tab actions, detach instead of exit, the hidden session name, process residency, and the text fallback for unsupported image protocols may differ.
|
|
244
|
+
|
|
245
|
+
- **Semantic input.** Attach resolves tab actions first, then names every other outer event as a bounded generation-1 `input.event`: key identity from the libghostty key table, `MOD_*` modifiers, produced text, repeat, focus, or a pointer gesture already translated to child rows. The server validates and fences it like byte input. The holder encodes it with libghostty against the child's live key, mouse, focus, and bracketed-paste modes, and falls back to the CSI u bytes direct terminals send for modified Enter, Tab, Backspace, and Escape. Double `Ctrl+C` is recognized from the key identity before any encoding; a paste or pointer activation completes another input sequence and resets the chord.
|
|
246
|
+
- **Compatibility.** `semantic-input` and `terminal-effects` are advertised features. A same-generation holder without semantic encoding answers `legacy-holder`; attach re-sends the refused event as bytes and keeps the byte path. That fallback is not certified against the parity contract.
|
|
247
|
+
- **Typed effects.** The holder registers libghostty's clipboard-write callback, which normalizes OSC 52 and OSC 1337. A write from the viewed tab crosses as one bounded `terminal.effect`; attach writes one OSC 52 for it. A hidden tab's write is dropped, never replayed into a later view. Payloads are never logged.
|
|
248
|
+
- **Modes and cursor.** Each surface carries the child's cursor cell and modes. Attach mirrors a cursor shape the child chose and resets it on detach or failure; a default block cursor is left to the outer terminal, as direct A1 leaves it.
|
|
249
|
+
- **Shortcut projection.** The owned child projects the effective A1 keybindings plus `keybindings.json` into one assignment per tab action: a key an A1 action holds never becomes a tab shortcut, and the earlier declaration keeps a duplicated key. It publishes the projection as `bridge.shortcuts`; the server relays it as `tabs.shortcuts`, and attach dispatches and labels exactly that projection. `/hotkeys` renders the same projection. A shared case file is replayed by both resolvers.
|
|
250
|
+
- **Committed presentation.** A requested view commits only with a topology that lists it and a complete target surface. Surfaces of the committed view apply in revision order, and revisions rise across holder incarnations. A retained re-offer is marked stale and parks for at most 120 ms while the fresh frame arrives. One compositor writes strip, body, overlay, and final cursor inside a single synchronized update, and refuses clear-only, blank, and cursor-less strip-only frames. Clock, hover, and title-animation damage repaints only the strip and restores the child's cursor.
|
|
251
|
+
- **Evidence.** One interaction matrix drives three layers: an owned-shell oracle that records content-free observations, role fixtures that drive real server, holder, and attach processes with a certified parity child, and an exact-artifact harness, `verify-interaction-parity.mjs`, that compares the bytes the same child observes directly and through the built host on each platform. Observations carry digests and lengths, never content. Each platform's verdict stands alone.
|
|
252
|
+
|
|
241
253
|
### 7. Status and attention come from the tab bridge
|
|
242
254
|
|
|
243
255
|
Tab status is an A1-owned state machine fed by `bridge.status`:
|
|
@@ -278,7 +290,7 @@ Tab status is an A1-owned state machine fed by `bridge.status`:
|
|
|
278
290
|
- `registryRevision`, `epoch`, and `bootId`
|
|
279
291
|
- **No per-tab credential is persisted in the registry.** The owner-only profile secret is stored separately and derives per-tab/per-incarnation credentials from recorded non-secret identities. The environment sent with `tab.create` is kept in holder memory only; a tab restarted after a crash reuses its holder's environment.
|
|
280
292
|
- **Session-writer lease.** All A1-owned session writers (resident, solo/fallback, and Pi-comparison) on the supported platforms use one profile-neutral native lock primitive before opening a session for writing, independent of launch mode. It does not start a host, holder, or bridge for direct modes. The primitive is an OS lock held by the writer process itself (`LockFileEx` on Windows, an open-file-description `fcntl` lock on Linux, `flock` on macOS), so the kernel releases it only when the writer is gone. Node writers take it in-process through a Node-API 9 artifact produced by the terminal-host Cargo package from the same lock implementation; a helper process cannot hold a lock on their behalf. Existing files are opened, identified by stable volume/device plus file identity, locked through that identity, and revalidated so case, junction/symlink, and hard-link aliases converge. A new file first holds a canonical-parent/name reservation, creates/opens without writing session content, acquires and revalidates file-identity authority while the reservation remains held, and only then admits writing; losing that race writes nothing. The writer routes include startup, `--session`, in-runtime switches, fork, import, new session, and the picker rename. The registry and holder coordinate admission, but a holder-only lock is insufficient: native lease custody must remain valid for the actual writer's lifetime, including any interval after holder death. Pin and prove the writer-bound lock/handle design on each platform in milestone 1; do not infer child death from pseudoterminal loss. Replacement requires lease acquisition and verified prior-writer/tree exit; uncertainty blocks it. Session switching acquires the target before relinquishing the old writer lease, and failed switches preserve the original session. Resident selection focuses an existing tab; direct-mode conflicts fail safely or offer an explicit fork, never silently attach or overwrite. Unmodified external Pi, older nonparticipating builds, and arbitrary file writers are outside this cooperative guarantee; document that limitation rather than claiming an OS sandbox.
|
|
281
|
-
- **Prompt journal.** `<dataDir>/tabs/<profile-token>/<tabId>/journal.jsonl` is an owner-only child-owned recovery journal, separate from asynchronous prompt history. Each submission has a stable ID and session/incarnation identity. Admission
|
|
293
|
+
- **Prompt journal.** `<dataDir>/tabs/<profile-token>/<tabId>/journal.jsonl` is an owner-only child-owned recovery journal, separate from asynchronous prompt history. Each submission has a stable ID and session/incarnation identity. Admission durably records bounded identity and recoverable editor text, never duplicate attachment payload bytes, before dispatch; timeout/failure leaves the prompt in the editor with a visible failure and dispatches nothing. The actual engine command retains every validated attachment. Correlate the record with the committed session entry, and retire it only after the corresponding session data is synchronized, including when command completion observes that the lifecycle already returned to ready; a settled turn's durable completion also records its submission ID so recovery never offers a completed prompt as unfinished. Recovery merges journal/session evidence by identity and never automatically resends, including when a crash occurs between dispatch, session append, sync, and journal retirement. It rehydrates image chips only from validated profile sidecars and removes unavailable tokens rather than presenting inert attachment labels. Preserve the first-turn journal and reserved session identity if Pi has not created its file yet; distinguish that from an unexpectedly missing existing transcript.
|
|
282
294
|
- **Draft checkpoint.** Use a periodic dirty checkpoint with a maximum one-second dirty interval while storage is healthy, including uninterrupted typing, rather than inactivity-only debounce. Expose failed/late checkpoints as degraded recovery; never continue claiming the one-second bound during a blocked disk or event-loop stall. Drafts and submitted prompts have different guarantees.
|
|
283
295
|
|
|
284
296
|
### 9. Failure and recovery
|
|
@@ -389,7 +401,7 @@ Every queue, buffer, file, and collection the resident roles keep has a declared
|
|
|
389
401
|
| Diagnostic logs | 5 MiB × 3 generations per role | `roles/diagnostics.rs` | `logs_rotate_at_their_cap_and_keep_a_bounded_number_of_generations` |
|
|
390
402
|
| Crash records | 64 KiB each, 50 records, 30 days | `roles/diagnostics.rs`, `shell/watchdog.rs` | `crash_records_are_distinct_bounded_and_allowlisted`, `crash_record_is_bounded_and_owner_only` |
|
|
391
403
|
| Last screen and private stderr sink | 2 MiB; 5 MiB × 3 generations, 7 days | `roles/recovery_artifacts.rs` | `recovery_files_are_plain_bounded_private_and_rotated`, `fixture_stderr_sink_caps_content` |
|
|
392
|
-
| Prompt journal | 8 MiB, 2 MiB per record | `src/foundation/resident-tabs/prompt-journal.ts` | `bounds append size` |
|
|
404
|
+
| Prompt journal | 8 MiB, 2 MiB per bounded metadata/text record; attachment payloads excluded | `src/foundation/resident-tabs/prompt-journal.ts` | `bounds append size`; `keeps a legal multi-image payload out of bounded journal admission and sends every image` |
|
|
393
405
|
| Session reconciliation read | 64 MiB | `src/foundation/resident-tabs/recovery.ts` | `refuses to scan a session past the 64 MiB reconciliation cap` |
|
|
394
406
|
| Release-hold report | 32 uncertainty reasons; 1 MiB read | `roles/release_holds.rs`, `release-holds.ts`, `bounded-host-command.ts` | `uncertainty_reasons_stay_within_their_cap`; `fails a release-hold report that pushes past its byte cap without returning a prefix` |
|
|
395
407
|
| Maintenance exchange | 5 s reads, 60 s stops, 4 MiB report | `roles/maintain.rs`, `maintenance.ts`, `bounded-host-command.ts` | `fails a maintenance report that pushes past its byte cap without returning a prefix`, `kills and rejects a command that outlives its deadline` |
|
|
@@ -570,6 +582,10 @@ The user made these decisions on 2026-09-24:
|
|
|
570
582
|
- `Ctrl+C` twice leaves `a1` from any tab while tabs keep running. There is no separate detach key.
|
|
571
583
|
- Reliability is a first-class, release-gating requirement (Decision 16).
|
|
572
584
|
|
|
585
|
+
On 2026-10-16 the `ci-critical-path` change replaced the Intel runner for the release builds:
|
|
586
|
+
|
|
587
|
+
- The macOS x64 process guardian and terminal host are cross-compiled on the `macos-15` arm64 runner (`npm run build:terminal-host -- --target x86_64-apple-darwin`, same for the guardian) and uploaded beside the arm64 artifacts; the Intel runner took 6 to 11 minutes for builds the arm64 runner finishes in about 4, and it gated packing in every publication. The x64 executable's `--version` identity is read by executing it through Rosetta 2 when the runner can; otherwise the manifest records a Mach-O architecture check as `provenance.identityCheck: "mach-o-header"` instead of a report nobody observed. Intel macOS stays certified separately and its physical acceptance record is unchanged; no publication lane ran the x64 bytes beyond that identity check before either, because the published-pair smoke matrix has no Intel lane.
|
|
588
|
+
|
|
573
589
|
On 2026-10-14 the user resolved the milestone-6 `darwin-x64` question:
|
|
574
590
|
|
|
575
591
|
- Add an Intel macOS runner (`macos-15-intel`) for both the process guardian and the terminal host, so every release carries `darwin-x64` binaries beside `darwin-arm64`. Intel macOS is certified separately from Apple silicon: it needs its own physical acceptance record, and the chaos group runs on macOS arm64 only.
|
|
@@ -53,7 +53,7 @@ The console proof requires:
|
|
|
53
53
|
- Rust/Cargo for the A1 terminal host;
|
|
54
54
|
- Zig `0.15.x` with patch `>= 0.15.2` to build `libghostty-vt`;
|
|
55
55
|
- the platform C/C++ build toolchain required by Rust and the PTY/input crates (Visual Studio 2022 Build Tools/MSVC on Windows, the runner image's Xcode Clang on macOS, GCC on Linux);
|
|
56
|
-
- release builds on `windows-2025`, `ubuntu-24.04`, `macos-15` (arm64
|
|
56
|
+
- release builds on `windows-2025`, `ubuntu-24.04`, and `macos-15` (arm64, which also cross-compiles the x64 artifact with `--target x86_64-apple-darwin`);
|
|
57
57
|
- an existing terminal emulator for manual validation;
|
|
58
58
|
- an isolated disposable worker for automated physical validation.
|
|
59
59
|
|
|
@@ -36,7 +36,7 @@ It contains no PTY, terminal emulator, browser/desktop GUI, custom renderer, inp
|
|
|
36
36
|
|
|
37
37
|
`npm run doctor` reports this contract against the current machine—Node range, npm major, git, the GitHub CLI, Rust/Cargo, and whether `node_modules` matches the lockfile—and names the install command for anything missing. `npm run build` runs the same check as its first step, so an unmet prerequisite fails by name rather than inside a module resolver or Cargo. Unmet tool prerequisites block; a Node or npm version outside the declared contract is reported as an advisory without failing the build.
|
|
38
38
|
|
|
39
|
-
Source builds require Rust/Cargo 1.85 or newer. `npm run build` compiles the standalone `native/process-guardian` crate for the host platform, places it under `dist/native/<platform>-<architecture>/`, and writes an integrity manifest. Preview and stable candidate workflows build platform artifacts on isolated Windows, Linux, macOS arm64,
|
|
39
|
+
Source builds require Rust/Cargo 1.85 or newer; `rust-toolchain.toml` pins the exact version CI and publication install through `scripts/development/prepare-ci-rust.sh`, and rustup selects that version for Cargo commands inside the repository, installing it on first use. The Zig cache (`mlugg/setup-zig`, one entry per workflow and platform per run, bounded to 512 MiB) stays enabled: measured on 2026-10-16, a cold Zig build of `libghostty-vt` added about 40 to 60 seconds to a terminal-host test build (Linux 2m21s cold against 1m20s warm, Windows 2m05s against 1m26s), which the cache saves on every warm run. `npm run build` compiles the standalone `native/process-guardian` crate for the host platform, places it under `dist/native/<platform>-<architecture>/`, and writes an integrity manifest. Preview and stable candidate workflows build platform artifacts on isolated Windows, Linux, and macOS arm64 runners, cross-compile the macOS x64 artifacts on the arm64 runner with `--target x86_64-apple-darwin`, and assemble them before packing. The same runners build the release terminal host and its session-writer lock with `npm run build:terminal-host`; the local `npm run build` does not, because the Zig fetch for `libghostty-vt` is quarantined by workstation antivirus on Windows (see [`terminal-host-provenance.md`](terminal-host-provenance.md)). macOS remains explicitly unsupported until its exact containment adapter is certified; the package must fail before runtime startup rather than use PID-only cleanup.
|
|
40
40
|
|
|
41
41
|
The process guardian inherits terminal handles but transports no terminal bytes. See [`process-guardian-provenance.md`](process-guardian-provenance.md).
|
|
42
42
|
|
package/docs/validation.md
CHANGED
|
@@ -48,14 +48,14 @@ Conservative Development retains every pull-request owner but does not schedule
|
|
|
48
48
|
node scripts/release/generate-validation-ownership-ledger.mjs --output .artifacts/validation/ownership-ledger.json
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
-
The `terminal-host` owner is CI-only. A change under `native/terminal-host/` or to its run scripts selects Windows x64, Linux x64,
|
|
51
|
+
The `terminal-host` owner is CI-only. A change under `native/terminal-host/` or to its run scripts selects, per platform (Windows x64, Linux x64, macOS arm64), one **Native terminal host** job and three **Native terminal host tests** partition jobs. The partition jobs each build the test profile and run one third of the serialized `role_integration` suite (`run-terminal-host-probe.mjs --partition <i>/3`, partitioned by sorted test name read from the suite's source); the build-and-probe job runs `npm run test:terminal-host` with the role suite skipped (`TERMINAL_HOST_PARTITION=none`), building the binary and Node-API lock artifact with Rust and Zig 0.15.2 for the lane's exact target and baseline CPU model and uploading the debug artifacts and frozen protocol manifest; Linux also runs bounded decoder fuzzing. The protected aggregate requires every partition of every selected platform, keyed by `partition` in the outcome authority and job envelope. Because the role suite no longer warms the resident launch paths in the build-and-probe job, its certification step runs one unmeasured `measure-resident-tabs.mjs` pass before the measured pass whose budgets are unchanged. Locally, `npm run test:terminal-host` without a partition is unchanged and runs everything in one process. Local Windows builds fail in `build.rs` unless `TERMINAL_HOST_LOCAL_BUILD=1` is set, because Zig's package fetch unpacks test data that workstation antivirus quarantines along with the installed process guardian. Native lanes also publish the terminal library they linked; a local build can link it instead of running Zig (see Local native iteration in [`architecture/resident-tabs.md`](architecture/resident-tabs.md)), which is development evidence only and never replaces selected CI. `TERMINAL_HOST_CONTRACTS_ONLY=1` is a library-only diagnostic and cannot substitute for selected CI. See [`native/terminal-host/README.md`](../native/terminal-host/README.md).
|
|
52
52
|
|
|
53
53
|
## Windows Full regression shards
|
|
54
54
|
|
|
55
|
-
Standalone and PR-attached Full regression run each Windows runtime (Node 22 and Node 24) as
|
|
55
|
+
Standalone and PR-attached Full regression run each Windows runtime (Node 22 and Node 24) as five hosted-runner shards derived from the one canonical `full-release` plan: `core` (command gates and the bounded-parallel ordinary remainder), `resource` (the serial resource-sensitive invocation with its unchanged explicit bound), `rendering` (the serial isolated rendering invocation), `package` (packing, its own exact-package installation, Defender-backed first-attempt startup before package contracts, package smoke, and update timing), and `predecessor` (packing, its own exact-package installation, and the exhaustive published-predecessor invocation, which took 11 to 12 minutes of the former package shard on Windows). Each shard checks out, installs, builds, and records its own build receipt; the package and predecessor shards each pack and install their own candidate, and no installed state crosses shards. Linux, macOS, local `npm run test:full`, and the sequential publication lanes keep one complete plan with one shared installation.
|
|
56
56
|
|
|
57
57
|
```
|
|
58
|
-
node scripts/release/run-validation-tier.mjs --full-shard <core|resource|rendering|package> --result <path>
|
|
58
|
+
node scripts/release/run-validation-tier.mjs --full-shard <core|resource|rendering|package|predecessor> --result <path>
|
|
59
59
|
```
|
|
60
60
|
|
|
61
61
|
`partitionFullRegressionPlan` assigns every canonical command and Vitest invocation to exactly one shard and rejects unknown or duplicate work; the build is the only per-shard prerequisite. Each shard result carries its shard identity, assigned work, and the canonical-plan digest. A per-runtime collector job reconstructs one ordinary `a1-validation-outcomes-v1` result only when all four current-run shards for the exact source, runtime, run, attempt, and plan passed and each reported exactly its assigned work; the reconstructed result then binds the unchanged `windows-2025-node22` or `windows-2025-node24` lane, and the required aggregate still demands exactly four lanes. The collector's summary reports each shard's time and the elapsed span across overlapping shards. Nightly triage maps a failed shard's owners to the canonical Windows lane and names the shard job, and it rejects more than one startup evidence file for a lane.
|
package/package.json
CHANGED