@timurproko/a1 0.2.7-dev.820 → 0.2.7-dev.834
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 +7 -1
- package/bin/automatic-update.js +7 -0
- package/bin/ui.js +1 -0
- package/dist/app/session-shell/session-shell-root.d.ts +8 -1
- package/dist/app/session-shell/session-shell.js +68 -18
- package/dist/app/session-shell/shell-host.js +6 -3
- package/dist/app/session-shell/transcript-document.js +1 -1
- package/dist/composition/owned-ui.d.ts +3 -11
- package/dist/composition/owned-ui.js +93 -42
- package/dist/composition/resident-tab-bridge.d.ts +10 -1
- package/dist/composition/resident-tab-bridge.js +21 -0
- package/dist/composition/settings-route-host.d.ts +12 -2
- package/dist/composition/settings-route-host.js +27 -9
- package/dist/composition/settings-search-input.d.ts +9 -0
- package/dist/composition/settings-search-input.js +58 -0
- package/dist/contracts/owned-ui/session-backend.d.ts +2 -0
- package/dist/contracts/owned-ui/session-workflows.d.ts +7 -3
- package/dist/contracts/presentation/index.d.ts +11 -0
- package/dist/contracts/presentation/index.js +12 -0
- package/dist/features/launch/development-launch.js +6 -0
- package/dist/features/launch/profile-paths.js +4 -0
- package/dist/features/owned-ui/plus-menu-dialog.d.ts +22 -0
- package/dist/features/owned-ui/plus-menu-dialog.js +76 -0
- package/dist/features/owned-ui/reference-screen-app.d.ts +2 -0
- package/dist/features/owned-ui/reference-screen-app.js +12 -2
- package/dist/features/owned-ui/settings-app.d.ts +10 -3
- package/dist/features/owned-ui/settings-app.js +98 -45
- package/dist/features/owned-ui/settings-search-input.d.ts +29 -0
- package/dist/features/owned-ui/settings-search-input.js +1 -0
- package/dist/features/prompt-history/index.d.ts +1 -1
- package/dist/features/prompt-history/paths.d.ts +2 -1
- package/dist/features/prompt-history/paths.js +10 -2
- package/dist/features/prompt-history/service.d.ts +5 -0
- package/dist/features/prompt-history/service.js +1 -1
- package/dist/features/resident-tab/shortcut-declarations.d.ts +1 -1
- package/dist/features/resident-tab/shortcut-declarations.js +2 -0
- package/dist/foundation/launch-context/index.d.ts +2 -0
- package/dist/foundation/launch-context/index.js +1 -0
- package/dist/foundation/release/automatic-update.d.ts +37 -0
- package/dist/foundation/release/automatic-update.js +265 -0
- package/dist/foundation/release/bootstrap.d.ts +1 -9
- package/dist/foundation/release/bootstrap.js +7 -3
- package/dist/foundation/release/index.d.ts +1 -1
- package/dist/foundation/release/index.js +1 -1
- package/dist/foundation/release/latest-release.d.ts +3 -2
- package/dist/foundation/release/latest-release.js +1 -1
- package/dist/foundation/release/update-execution-lease.d.ts +36 -0
- package/dist/foundation/release/update-execution-lease.js +145 -0
- package/dist/foundation/release/update.d.ts +8 -6
- package/dist/foundation/release/update.js +232 -167
- package/dist/foundation/resident-tabs/bridge-client.d.ts +8 -2
- package/dist/foundation/resident-tabs/bridge-client.js +13 -6
- package/dist/foundation/resident-tabs/progress-observation-schema.d.ts +34 -0
- package/dist/foundation/resident-tabs/progress-observation-schema.js +1 -0
- package/dist/foundation/resident-tabs/progress-observation.d.ts +3 -6
- package/dist/foundation/resident-tabs/progress-observation.js +42 -12
- package/dist/foundation/resident-tabs/routes.d.ts +5 -0
- package/dist/foundation/resident-tabs/routes.js +5 -0
- package/dist/integrations/pi/components/editor-interaction.d.ts +2 -0
- package/dist/integrations/pi/components/modal-frame.d.ts +17 -1
- package/dist/integrations/pi/components/modal-frame.js +48 -9
- package/dist/integrations/pi/components/models-dialog.js +4 -52
- package/dist/integrations/pi/components/owned-editor-ux.d.ts +3 -0
- package/dist/integrations/pi/components/owned-editor-ux.js +75 -4
- package/dist/integrations/pi/components/shell-editor-autocomplete.js +57 -37
- package/dist/integrations/pi/components/shell-footer-status.js +19 -3
- package/dist/integrations/pi/components/shell-presenters-transcript.js +4 -1
- package/dist/integrations/pi/components/shell-selectors-dialogs.js +4 -1
- package/dist/integrations/pi/components/shell-shared-facade.d.ts +9 -2
- package/dist/integrations/pi/components/skills-dialog.js +2 -2
- package/dist/integrations/pi/components/upstream/components/owned-editor.d.ts +7 -5
- package/dist/integrations/pi/components/upstream/components/owned-editor.js +15 -8
- package/dist/integrations/pi/components/upstream/components/scoped-models-selector.d.ts +3 -3
- package/dist/integrations/pi/components/upstream/components/scoped-models-selector.js +2 -2
- package/dist/integrations/pi/components/upstream/components/session-footer.js +26 -23
- package/dist/integrations/pi/components/upstream/components/session-selector.js +7 -6
- package/dist/integrations/pi/components/upstream/components/thinking-selector.d.ts +7 -6
- package/dist/integrations/pi/components/upstream/components/thinking-selector.js +9 -8
- package/dist/integrations/pi/components/upstream/components/tool-execution.js +6 -4
- package/dist/integrations/pi/components/upstream/components/tree-selector.d.ts +8 -8
- package/dist/integrations/pi/components/upstream/components/tree-selector.js +10 -10
- package/dist/integrations/pi/components/upstream/history/editor-core.js +2 -0
- package/dist/integrations/pi/engine/adapter.js +7 -2
- package/dist/integrations/pi/engine/ports.d.ts +1 -1
- package/dist/integrations/pi/engine/ports.js +4 -0
- package/dist/integrations/pi/engine/workflow-runner.js +9 -2
- package/dist/native/darwin-arm64/manifest.json +1 -1
- package/dist/native/darwin-arm64/terminal-host.manifest.json +3 -3
- package/dist/native/darwin-arm64/terminal-host.node +0 -0
- package/dist/native/darwin-x64/manifest.json +1 -1
- package/dist/native/darwin-x64/terminal-host.manifest.json +3 -3
- package/dist/native/darwin-x64/terminal-host.node +0 -0
- package/dist/native/linux-x64/manifest.json +1 -1
- package/dist/native/linux-x64/terminal-host.manifest.json +3 -3
- package/dist/native/linux-x64/terminal-host.node +0 -0
- package/dist/native/win32-x64/manifest.json +2 -2
- package/dist/native/win32-x64/process-guardian.node +0 -0
- package/dist/native/win32-x64/terminal-host.manifest.json +3 -3
- package/dist/native/win32-x64/terminal-host.node +0 -0
- package/dist/product-identity.d.ts +1 -1
- package/dist/product-identity.js +1 -1
- package/dist/product-identity.json +1 -0
- package/dist/ui/apps/host.d.ts +2 -0
- package/dist/ui/apps/host.js +7 -1
- package/dist/ui/components/dialog-panel.js +35 -30
- package/dist/ui/components/line-input.d.ts +3 -0
- package/dist/ui/components/line-input.js +3 -1
- package/dist/ui/components/prompt-input.d.ts +3 -2
- package/dist/ui/components/prompt-input.js +6 -4
- package/dist/ui/components/spans.js +2 -2
- package/dist/ui/components/surface.js +4 -2
- package/dist/ui/components/text.d.ts +3 -6
- package/dist/ui/components/text.js +6 -8
- package/dist/ui/settings/declarations.d.ts +54 -8
- package/dist/ui/settings/declarations.js +36 -7
- package/dist/ui/settings/manager.js +7 -3
- package/dist/ui/settings/migrations.js +15 -0
- package/dist/ui/settings/sections.d.ts +4 -0
- package/dist/ui/settings/sections.js +9 -3
- package/docs/architecture/resident-tabs.md +36 -8
- package/docs/architecture/tool-image-presentation.md +2 -1
- package/docs/features/prompt-history.md +41 -20
- package/docs/validation.md +25 -5
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const OWNED_UI_SETTINGS_VERSION =
|
|
1
|
+
export const OWNED_UI_SETTINGS_VERSION = 19;
|
|
2
2
|
const UI_ACCENT_COLOR_CHOICES = Object.freeze([
|
|
3
3
|
"purple", "blue", "cyan", "green", "orange", "pink",
|
|
4
4
|
]);
|
|
@@ -8,16 +8,26 @@ const GENERIC_SECTION = Object.freeze({ id: "generic", title: "Generic" });
|
|
|
8
8
|
const APPEARANCE_SECTION = Object.freeze({ id: "appearance", title: "Appearance" });
|
|
9
9
|
const SCROLL_SECTION = Object.freeze({ id: "scroll", title: "Scroll" });
|
|
10
10
|
const AGENT_SECTION = Object.freeze({ id: "agent", title: "Agent" });
|
|
11
|
-
const TABS_SECTION = Object.freeze({ id: "tabs", title: "
|
|
11
|
+
const TABS_SECTION = Object.freeze({ id: "tabs", title: "Tab Bar" });
|
|
12
|
+
/** The tab kinds the strip's `+` can offer, in the order it shows them. */
|
|
13
|
+
const PLUS_MENU_KINDS = Object.freeze([
|
|
14
|
+
Object.freeze({ key: "agent", label: "Agent", description: "Add a new Agent tab every time." }),
|
|
15
|
+
Object.freeze({ key: "settings", label: "Settings", description: "Show the one Settings tab." }),
|
|
16
|
+
Object.freeze({ key: "changelog", label: "Changelog", description: "Show the one Changelog tab." }),
|
|
17
|
+
]);
|
|
18
|
+
/** Every enabled subset of the plus-menu kinds, each in kind order. */
|
|
19
|
+
const PLUS_MENU_VALUES = Object.freeze([
|
|
20
|
+
"", "agent", "settings", "changelog", "agent,settings", "agent,changelog", "settings,changelog", "agent,settings,changelog",
|
|
21
|
+
]);
|
|
12
22
|
export const OWNED_SETTING_DECLARATIONS = Object.freeze({
|
|
13
23
|
updateCheck: Object.freeze({
|
|
14
24
|
id: "updateCheck",
|
|
15
|
-
label: "Update
|
|
25
|
+
label: "Update",
|
|
16
26
|
section: GENERIC_SECTION,
|
|
17
|
-
description: "
|
|
27
|
+
description: "Auto installs verified channel updates silently, Notify shows the current update notice, and No leaves updates manual. Checks use the public npm registry; applies on next start.",
|
|
18
28
|
application: "restart",
|
|
19
|
-
defaultValue:
|
|
20
|
-
allowedValues: Object.freeze([
|
|
29
|
+
defaultValue: "auto",
|
|
30
|
+
allowedValues: Object.freeze(["auto", "notify", "no"]),
|
|
21
31
|
}),
|
|
22
32
|
timeFormat: Object.freeze({
|
|
23
33
|
id: "timeFormat",
|
|
@@ -90,11 +100,20 @@ export const OWNED_SETTING_DECLARATIONS = Object.freeze({
|
|
|
90
100
|
id: "promptHistoryEnabled",
|
|
91
101
|
label: "Persistent history",
|
|
92
102
|
section: Object.freeze({ id: "history", title: "History" }),
|
|
93
|
-
description: "Retain reusable prompts
|
|
103
|
+
description: "Retain reusable prompts after restart. Applies on next start; disabling does not erase saved history or stop existing instances.",
|
|
94
104
|
application: "restart",
|
|
95
105
|
defaultValue: true,
|
|
96
106
|
allowedValues: Object.freeze([true, false]),
|
|
97
107
|
}),
|
|
108
|
+
promptHistoryScope: Object.freeze({
|
|
109
|
+
id: "promptHistoryScope",
|
|
110
|
+
label: "History scope",
|
|
111
|
+
section: Object.freeze({ id: "history", title: "History" }),
|
|
112
|
+
description: "Unique shows each agent session's history; Shared shows the profile-wide history. Switches running agents without moving or erasing saved prompts.",
|
|
113
|
+
application: "live",
|
|
114
|
+
defaultValue: "unique",
|
|
115
|
+
allowedValues: Object.freeze(["unique", "shared"]),
|
|
116
|
+
}),
|
|
98
117
|
promptHistoryMaxItems: Object.freeze({
|
|
99
118
|
id: "promptHistoryMaxItems",
|
|
100
119
|
label: "History limit",
|
|
@@ -140,6 +159,16 @@ export const OWNED_SETTING_DECLARATIONS = Object.freeze({
|
|
|
140
159
|
defaultValue: "top",
|
|
141
160
|
allowedValues: Object.freeze(["top", "bottom"]),
|
|
142
161
|
}),
|
|
162
|
+
tabBarPlusMenu: Object.freeze({
|
|
163
|
+
id: "tabBarPlusMenu",
|
|
164
|
+
label: "Plus menu",
|
|
165
|
+
section: TABS_SECTION,
|
|
166
|
+
description: "The tabs + offers. With only Agent, + adds an Agent at once; with more, it opens a menu; with none, + is hidden.",
|
|
167
|
+
application: "live",
|
|
168
|
+
defaultValue: "agent",
|
|
169
|
+
allowedValues: PLUS_MENU_VALUES,
|
|
170
|
+
flags: PLUS_MENU_KINDS,
|
|
171
|
+
}),
|
|
143
172
|
tabsAutoName: Object.freeze({
|
|
144
173
|
id: "tabsAutoName",
|
|
145
174
|
label: "Auto name",
|
|
@@ -188,9 +188,13 @@ export class OwnedSettingsManager {
|
|
|
188
188
|
return () => this.#listeners.delete(listener);
|
|
189
189
|
}
|
|
190
190
|
async changeStructured(backend, id, value) {
|
|
191
|
-
if (backend
|
|
192
|
-
return
|
|
193
|
-
|
|
191
|
+
if (backend === "agent")
|
|
192
|
+
return await this.#changeAgentValue(id, value);
|
|
193
|
+
// Invariant: a flagged A1 setting stores its enabled parts as one value, in declaration order.
|
|
194
|
+
const flags = findOwnedUiSettingsEntry(this.sections(), id, "a1")?.flags ?? [];
|
|
195
|
+
if (flags.length === 0)
|
|
196
|
+
return failed(`${id} holds no structured value`);
|
|
197
|
+
return await this.change("a1", id, flags.filter(flag => value[flag.key] === true).map(flag => flag.key).join(","));
|
|
194
198
|
}
|
|
195
199
|
async change(backend, id, value) {
|
|
196
200
|
const entry = findOwnedUiSettingsEntry(this.sections(), id, backend);
|
|
@@ -144,6 +144,17 @@ export const OWNED_UI_SETTINGS_MIGRATIONS = Object.freeze([
|
|
|
144
144
|
return { ...values };
|
|
145
145
|
},
|
|
146
146
|
}),
|
|
147
|
+
Object.freeze({
|
|
148
|
+
to: 19,
|
|
149
|
+
description: "Expand the update check into auto, notify, and no modes.",
|
|
150
|
+
migrate(values) {
|
|
151
|
+
if (values.updateCheck === true)
|
|
152
|
+
return { ...values, updateCheck: "notify" };
|
|
153
|
+
if (values.updateCheck === false)
|
|
154
|
+
return { ...values, updateCheck: "no" };
|
|
155
|
+
return { ...values };
|
|
156
|
+
},
|
|
157
|
+
}),
|
|
147
158
|
]);
|
|
148
159
|
/** Shape-selected compatibility for the metadata-free product object. */
|
|
149
160
|
export function normalizeOwnedUiSettingsValues(source) {
|
|
@@ -153,6 +164,10 @@ export function normalizeOwnedUiSettingsValues(source) {
|
|
|
153
164
|
values.scrollbarAppearance = "auto";
|
|
154
165
|
if (values.accentColor === "default")
|
|
155
166
|
values.accentColor = "purple";
|
|
167
|
+
if (values.updateCheck === true)
|
|
168
|
+
values.updateCheck = "notify";
|
|
169
|
+
else if (values.updateCheck === false)
|
|
170
|
+
values.updateCheck = "no";
|
|
156
171
|
if (values.quitEffect === "off") {
|
|
157
172
|
if (Object.hasOwn(values, "quitAnimation") && values.quitAnimation !== false) {
|
|
158
173
|
return { values: null, failure: "legacy quitEffect conflicts with quitAnimation" };
|
|
@@ -49,4 +49,8 @@ export interface BuildOwnedUiSettingsSectionsInput {
|
|
|
49
49
|
}
|
|
50
50
|
export declare const AGENT_SECTION_ID = "agent";
|
|
51
51
|
export declare function buildOwnedUiSettingsSections(input: BuildOwnedUiSettingsSectionsInput): readonly OwnedUiSettingsSection[];
|
|
52
|
+
/** The enabled parts a flagged A1 setting's comma-separated value names, as the record its dialog edits. */
|
|
53
|
+
export declare function flagRecord(flags: readonly {
|
|
54
|
+
readonly key: string;
|
|
55
|
+
}[], value: unknown): Record<string, boolean>;
|
|
52
56
|
export declare function findOwnedUiSettingsEntry(sections: readonly OwnedUiSettingsSection[], id: string, backend: OwnedUiSettingsBackend): OwnedUiSettingsEntry | null;
|
|
@@ -5,20 +5,21 @@ export function buildOwnedUiSettingsSections(input) {
|
|
|
5
5
|
for (const setting of input.resolution.settings) {
|
|
6
6
|
const section = setting.declaration.section ?? { id: "a1", title: "A1" };
|
|
7
7
|
const group = grouped.get(section.id) ?? { title: section.title, entries: [] };
|
|
8
|
+
const flagged = setting.declaration.flags;
|
|
8
9
|
group.entries.push({
|
|
9
10
|
id: setting.declaration.id,
|
|
10
11
|
label: setting.declaration.label ?? null,
|
|
11
12
|
backend: "a1",
|
|
12
13
|
description: setting.declaration.description,
|
|
13
14
|
value: setting.value,
|
|
14
|
-
rawValue: setting.value,
|
|
15
|
+
rawValue: flagged === undefined ? setting.value : flagRecord(flagged, setting.value),
|
|
15
16
|
storedValue: setting.value,
|
|
16
17
|
effectiveValue: setting.value,
|
|
17
18
|
editable: true,
|
|
18
|
-
structured:
|
|
19
|
+
structured: flagged !== undefined,
|
|
19
20
|
minimum: null,
|
|
20
21
|
maximum: null,
|
|
21
|
-
flags: [],
|
|
22
|
+
flags: flagged?.map(flag => ({ ...flag, fallback: false })) ?? [],
|
|
22
23
|
choices: setting.declaration.allowedValues,
|
|
23
24
|
origin: setting.source,
|
|
24
25
|
application: setting.declaration.application === "restart" ? "next-start" : "live",
|
|
@@ -44,6 +45,11 @@ export function buildOwnedUiSettingsSections(input) {
|
|
|
44
45
|
};
|
|
45
46
|
return Object.freeze([...ownedSections, combinedAgent]);
|
|
46
47
|
}
|
|
48
|
+
/** The enabled parts a flagged A1 setting's comma-separated value names, as the record its dialog edits. */
|
|
49
|
+
export function flagRecord(flags, value) {
|
|
50
|
+
const enabled = new Set(typeof value === "string" ? value.split(",") : []);
|
|
51
|
+
return Object.fromEntries(flags.map(flag => [flag.key, enabled.has(flag.key)]));
|
|
52
|
+
}
|
|
47
53
|
export function findOwnedUiSettingsEntry(sections, id, backend) {
|
|
48
54
|
for (const section of sections) {
|
|
49
55
|
const entry = section.entries.find(candidate => candidate.id === id && candidate.backend === backend);
|
|
@@ -167,6 +167,16 @@ Every attach, server, and holder role is `process.execPath` running the release'
|
|
|
167
167
|
Because ConPTY belongs to its creating process, each tab has its own creator. A server crash therefore does not destroy a tab's ConPTY; holder failure affects only that tab. Actual child/tree exit and writer-lease release still require verification, not inference from the pseudoconsole closing. The 2026-10-17 Windows x64 migration measurement put an idle Node-hosted holder at approximately 30–40 MiB of resident memory, versus a few MiB for the former native-executable holder; allocator and shared-page accounting make this a range rather than a per-tab budget. The separate holder process is retained for certified crash isolation, and latency budgets were not raised to absorb Node startup.
|
|
168
168
|
- **Why the server relays surfaces instead of clients connecting to holders directly.** It gives one authenticated endpoint per client, one ordering domain for focus and topology, and simpler multi-client arbitration. Measure the extra local pipe hop in the native hot-path evidence rather than assuming its latency. The server forwards encoded holder surfaces and does not re-render them.
|
|
169
169
|
|
|
170
|
+
### Windows completion-driven transport and wake diagnosis
|
|
171
|
+
|
|
172
|
+
Windows server and client endpoints use overlapped named-pipe I/O. Each operation owns a stable `OVERLAPPED` and non-inherited completion event; read and write workers share handle custody but never serialize a pending read against a write. A worker waits for completion, connection shutdown, or an absolute admission deadline, not `PeekNamedPipe` plus a short sleep. The four-second first-envelope deadline covers fragmented headers and bodies. Cancellation targets one operation and drains its kernel completion before releasing its buffer/event; split-worker guards wake the other direction when the connection owner exits. Owner ACLs, first-instance protection, peer checks, frame limits, control ordering, and latest-surface bounds are unchanged. Unix socket behavior is unchanged.
|
|
173
|
+
|
|
174
|
+
Transport correctness does not rely on `timeBeginPeriod`. The existing per-role timer request remains for non-I/O timers, including ConPTY render-pass settling and animation/maintenance deadlines; removing it requires separate scheduling evidence. A detected attach gap still requests one complete cached repaint, never a process restart or prompt replay.
|
|
175
|
+
|
|
176
|
+
The existing opt-in progress route (`A1_RESIDENT_PROGRESS_OBSERVATION_DIR` plus `A1_RESIDENT_PROGRESS_RUN_ID`) adds `pipe-frame-read`, `pipe-write-complete`, `outer-write-complete`, `wake-gap`, and `wake-repaint-requested` stages. Connection-level records may omit tab identity. `durationUs` is local monotonic completion/assembly time; `idleWaitUs` separately describes waiting for the next frame and is not delivery latency. Native records include `monotonicUs`, `clockEpoch`, and optional `clockDiscontinuity`. Consumers exclude negative, epoch-crossing, and clock-discontinuity-crossing derived samples, while retaining valid local completion durations. The queue remains bounded and asynchronous, with loss accounting and no terminal content or credentials. Outer-write completion is not proof of physical pixels.
|
|
177
|
+
|
|
178
|
+
For recurring post-wake stalls, compare an existing resident client, a new client on the same resident host, and direct `a1 solo` in the same outer terminal. Use matched holder/attach revisions and local I/O durations to distinguish child progress, transport delay, and outer writes. If solo and other terminal applications also stall, do not assign the failure to resident pipes without further evidence. Simulated-gap fixtures do not certify physical sleep/wake recovery; do not automate workstation suspend. Updates retain old resident cohorts, so new endpoint code takes effect only as those roles reach their ordinary lifecycle or the maintainer explicitly chooses to stop them. Installing the update never recycles running tabs.
|
|
179
|
+
|
|
170
180
|
### 3. Host scope, endpoints, writer authority, and credentials
|
|
171
181
|
|
|
172
182
|
- There is **one server per OS user × canonical A1 profile root**, independent of the release cohort. Tabs must outlive release changes, and the supervisor is forbidden from retaining runtime processes.
|
|
@@ -229,7 +239,7 @@ The channels carry bounded generation-1 frames: a u32 little-endian length follo
|
|
|
229
239
|
|
|
230
240
|
### 6. Rendering, input, and outer-terminal fidelity
|
|
231
241
|
|
|
232
|
-
- **Composition.** The attach client composes row 0 (the strip) and rows `1..N` (the viewed tab's surface). The tab PTY is sized `cols × (rows − 1)`. Ghostty remains the sole child-terminal parser. Its stream parser reports synchronized-output begin/end/reset boundaries to the holder, which keeps mutable parser state separate from an immutable committed surface and never publishes an open transaction. A non-renewable 250 ms deadline publishes one typed recovery commit for a malformed transaction. On Windows, ConPTY re-renders the child's cells on its own schedule and relays the DEC 2026 markers when it parses them, so a render pass can land between a frame's markers and the rest of its cells can arrive after the end marker; the holder therefore treats a relayed end marker as "frame parsed", keeps the state private until the following render pass has arrived and settled for 2 ms, and
|
|
242
|
+
- **Composition.** The attach client composes row 0 (the strip) and rows `1..N` (the viewed tab's surface). The tab PTY is sized `cols × (rows − 1)`. Ghostty remains the sole child-terminal parser. Its stream parser reports synchronized-output begin/end/reset boundaries to the holder, which keeps mutable parser state separate from an immutable committed surface and never publishes an open transaction. A non-renewable 250 ms deadline publishes one typed recovery commit for a malformed transaction. On Windows, ConPTY re-renders the child's cells on its own schedule and relays the DEC 2026 markers when it parses them, so a render pass can land between a frame's markers and the rest of its cells can arrive after the end marker; the holder therefore treats a relayed end marker as "frame parsed", keeps the state private until the following render pass has arrived and settled for 2 ms, with a non-renewable 16 ms fallback for the oldest unpublished pass. New end markers and continuous settling fragments cannot postpone that bound; consuming a complete or recovery publication clears it for the next update. The fallback never authorizes ordinary publication inside an open transaction. A begin marker that arrives while that pass is still pending does not publish the torn state. Unix PTYs relay bytes verbatim, so their markers still delimit frames exactly. The outer composer owns the only synchronized envelope and falls back to a full repaint only when identity, geometry generation, dimensions, or the structured base changes. Decoded child grids move into shared immutable attach/outer-offer/completed-baseline custody, so chrome-only work neither deep-clones nor revalidates an unchanged body. Overlay restoration and new grids still take the damage/validation path, and resize fading uses copy-on-write. Each attach round admits one queued input before its bounded server-render round; neither input nor rendering can starve the other. Attach and holder wait on coalesced wake notifications only after a round receives no work, so an already-ready backlog never waits for another producer pulse; all per-lane round limits and quiescent idle waits remain intact.
|
|
233
243
|
- **Outer modes.** The client negotiates the outer terminal's modes: raw mode, alternate screen, bracketed paste, focus events, SGR mouse, kitty keyboard protocol where available, and synchronized output.
|
|
234
244
|
- Child-requested modes are tracked in the holder's model. Input is re-encoded per child mode by libghostty-vt, which the proof already does.
|
|
235
245
|
- Mouse rows are offset by the strip. Row 0 belongs to the client.
|
|
@@ -250,7 +260,7 @@ Direct `a1 solo` is the behavioural oracle for every viewed resident child. Only
|
|
|
250
260
|
- **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.
|
|
251
261
|
- **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.
|
|
252
262
|
- **Committed presentation.** A requested view commits only with a topology that lists it and a complete target surface. Retained rows, palette/default colors, modes, and cursor are captured atomically; equal captures do not invent a revision. Surfaces of the committed view apply in revision order, and revisions rise across holder incarnations. A retained re-offer is marked stale from the request start and parks for at most 120 ms while the fresh frame arrives. One compositor writes strip, body, overlay, modes, and the viewport-clipped final cursor inside a single synchronized update. It erases suffix cells when a complete replacement row becomes shorter and restores the newest underlying surface when an overlay leaves. Clock, hover, and title-animation damage repaints only the strip and restores the child's cursor.
|
|
253
|
-
- **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 exact-artifact harnesses. `verify-interaction-parity.mjs` compares the bytes the same child observes directly and through the built host. `verify-resident-parallel-progress.mjs` configures the real engine against a bounded loopback streaming provider and crosses all six first-response orders with all three initial views while every turn remains unfinished behind a completion barrier. At matching 100×29 child geometry, the same real-A1 prompt also runs through isolated direct-solo and resident profiles; the native Ghostty oracle compares a content-free digest of the intermediate fixture cells and their resolved styles together with normalized cursor and mode state. It advances only at parser-reported synchronized-output boundaries, rejects unbalanced transactions or disappearing intermediate cells, and excludes only the resident strip row rather than normalizing terminal semantics. Opt-in per-process JSONL observations identify engine delivery, completed child writes, PTY ingestion, distinct holder commits, attach receipt, and completed outer paint by run/tab/incarnation/process-instance/sequence without recording terminal or conversation content; queue loss, clock faults, duplicates, stale order, and missing stages fail the report. Named pressure variants keep all turns unfinished while exercising hidden progress, repeated switching, navigation input, rename overlays, tab-local resize, a bounded 35 ms attach-receipt delay, and a 900 ms paused outer sink; every tab must retain partial animation/progress before settlement and then present its final output. A detached/reconnect variant releases all three unfinished turns after terminating the authenticated attach and exact self-reported server identity; child writes, PTY ingestion, and holder commits must continue before a replacement server and attach present every retained final output. Separate isolated exact-artifact control markers stop one target tab at engine delivery, physical child rendering, holder commit, or outer paint; each control requires its upstream stages and the other tabs to advance, then removes the stop and requires final output from all three tabs. A fifth control fragments a real owned-shell synchronized write into at most 64 physical writes without changing its bytes, records the DEC private mode 2026 transaction classification, and requires ordinary complete holder commits without deadline recovery before final output. This characterizes the built PTY/ConPTY coherence path; a passing fragmented run does not by itself assign the historical stall to that path without a failing live reproduction. The marker path is absent in ordinary sessions and is scrubbed before extensions or tools inherit the child environment, while observation callbacks remain passive and cannot select a control. CI runs the vendored Ghostty VT tests and both exact-artifact harnesses on Windows x64, Linux x64, and macOS arm64. Each platform's verdict stands alone.
|
|
263
|
+
- **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 exact-artifact harnesses. `verify-interaction-parity.mjs` compares the bytes the same child observes directly and through the built host. `verify-resident-parallel-progress.mjs` configures the real engine against a bounded loopback streaming provider and crosses all six first-response orders with all three initial views while every turn remains unfinished behind a completion barrier. At matching 100×29 child geometry, the same real-A1 prompt also runs through isolated direct-solo and resident profiles; the native Ghostty oracle compares a content-free digest of the intermediate fixture cells and their resolved styles together with normalized cursor and mode state. It advances only at parser-reported synchronized-output boundaries, rejects unbalanced transactions or disappearing intermediate cells, and excludes only the resident strip row rather than normalizing terminal semantics. Opt-in per-process JSONL observations identify engine delivery, completed child writes, PTY ingestion, distinct holder commits, attach receipt, and completed outer paint by run/tab/incarnation/process-instance/sequence without recording terminal or conversation content; queue loss, clock faults, duplicates, stale order, and missing stages fail the report. The child observer batches check-phase appends with one directory initialization while retaining its pending-byte and loss limits. Auxiliary PTY-input completion and child input/semantic/composition/write phases narrow input-side delays without recording key or terminal content. Healthy viewed windows require at least forty matched live commit-to-first-paint samples per tab and enforce p95 at most 33 ms; activation/stale re-offers, later chrome paints of the same revision, and deliberately stopped or paused-sink windows cannot masquerade as live delivery samples. An additional real-A1 fixture compares forty synthetic typing, Models navigation, and transcript wheel responses per path at equal child geometry. Timing requires the shipping Rust optimization profile with all fixture controls retained; debug unit/role/probe coverage and feature-free shipping-artifact checks remain separate. Both paths retain matching complete 60-marker transcript baselines before sampling. Untimed provider setup acknowledges a complete literal editor echo before sending a separate Enter; the measured inputs keep their ordinary 30 ms cadence. Its incremental native Ghostty oracle matches completed semantic states, permits coalesced intermediate states, and rejects missing responses, one-second stalls, or resident p95 beyond direct solo plus the existing 16+33 ms input/presentation envelope. Direct target hashes come from the final semantic state in each ordinary inter-input interval, while timing uses its first observed occurrence, not a later settling delay or an early torn ConPTY hash. A fixed fixture role entry pins the exact addon for all detached roles, including Windows WMI launches that do not inherit the invoking shell's development override; production launch behavior is unchanged. Named pressure variants keep all turns unfinished while exercising hidden progress, repeated switching, navigation input, rename overlays, tab-local resize, a bounded 35 ms attach-receipt delay, and a 900 ms paused outer sink; every tab must retain partial animation/progress before settlement and then present its final output. A detached/reconnect variant releases all three unfinished turns after terminating the authenticated attach and exact self-reported server identity; child writes, PTY ingestion, and holder commits must continue before a replacement server and attach present every retained final output. Separate isolated exact-artifact control markers stop one target tab at engine delivery, physical child rendering, holder commit, or outer paint; each control requires its upstream stages and the other tabs to advance, then removes the stop and requires final output from all three tabs. The outer-paint control waits for a payload-free `fixture-stop-ack` from the sole writer before starting its scenario; that acknowledgement follows all earlier writes, and same-writer sequence ordering excludes only pre-acknowledgement paints from the zero-progress check. Any later paint still fails. A fifth control fragments a real owned-shell synchronized write into at most 64 physical writes without changing its bytes, records the DEC private mode 2026 transaction classification, and requires ordinary complete holder commits without deadline recovery before final output. This characterizes the built PTY/ConPTY coherence path; a passing fragmented run does not by itself assign the historical stall to that path without a failing live reproduction. The marker path is absent in ordinary sessions and is scrubbed before extensions or tools inherit the child environment, while observation callbacks remain passive and cannot select a control. CI runs the vendored Ghostty VT tests and both exact-artifact harnesses on Windows x64, Linux x64, and macOS arm64. Each platform's verdict stands alone.
|
|
254
264
|
|
|
255
265
|
### 7. Status and attention come from the tab bridge
|
|
256
266
|
|
|
@@ -325,7 +335,7 @@ Tab status is an A1-owned state machine fed by `bridge.status`:
|
|
|
325
335
|
|
|
326
336
|
- **Strip.** Row 0, drawn by the attach client, reproduces the approved v2 default appearance:
|
|
327
337
|
- normal chips use RGB background `38,38,38` with foreground `170,170,170`, hover uses `48,48,48`, and the viewed chip uses `55,55,55` with white text, plus v2's palette fallback;
|
|
328
|
-
- the empty state
|
|
338
|
+
- the empty state keeps the hoverable `+` control and the clock beside the landing, with no cursor;
|
|
329
339
|
- chips are at most 20 columns, clipped with `…` by grapheme width, with no separators or close glyph;
|
|
330
340
|
- a `…` overflow menu holds tabs that do not fit, and `+` is always reserved;
|
|
331
341
|
- these presentation choices are fixed, not settings, while each working spinner uses that tab's reported A1 accent.
|
|
@@ -337,6 +347,8 @@ Tab status is an A1-owned state machine fed by `bridge.status`:
|
|
|
337
347
|
| `Alt+W` | close |
|
|
338
348
|
| `F2` | rename |
|
|
339
349
|
| `Alt+,` | open the global Settings tab, or close it while it is viewed |
|
|
350
|
+
| `Alt+/` | open the command palette, or close it while it is open |
|
|
351
|
+
| unbound (`tabs.changelog`) | open the Changelog tab |
|
|
340
352
|
| `Alt+1`…`Alt+9`, `Alt+0` | jump to a tab |
|
|
341
353
|
| unbound (`tabs.next` / `tabs.previous`) | next / previous, wrapping |
|
|
342
354
|
| unbound (`tabs.moveRight` / `tabs.moveLeft`) | move the tab right / left |
|
|
@@ -347,24 +359,32 @@ Tab status is an A1-owned state machine fed by `bridge.status`:
|
|
|
347
359
|
- **`Ctrl+C` twice leaves, in every tab (user decision).** The client forwards the first `Ctrl+C` to the tab unchanged, so a single press still clears, copies, or interrupts. It consumes the second press within the existing clear/exit interval as the detach request and does not forward it. The tab therefore never receives a second `Ctrl+C` from the chord, which removes v2's bug where `Ctrl+C+C` killed the agent. The trade-off: a future CLI tab cannot receive two quick `Ctrl+C`s. Pressing them more slowly than the interval still reaches the program.
|
|
348
360
|
- **Mouse on row 0:**
|
|
349
361
|
- click activates a tab;
|
|
350
|
-
- right-click opens `Rename`/`Close` (`Close` only for Settings);
|
|
362
|
+
- right-click opens `Rename`/`Close` (`Close` only for Settings and Changelog);
|
|
363
|
+
- right-click on `+` opens a one-row `Configure` menu that opens the `Plus Menu` dialog over the dimmed active tab, without switching tabs;
|
|
351
364
|
- dragging reorders, with a `│` drop marker, after 250 ms or once the pointer moves;
|
|
352
|
-
- `+` opens a persistent dark v2 menu
|
|
365
|
+
- `+` follows the plus menu: with only Agent enabled it adds an Agent at once; with other kinds it opens a persistent dark v2 menu of the enabled kinds, `+ Agent` first and a separator before the `Settings` and `Changelog` toggles, each `✓` while its tab is open; with none the strip hides `+`. Hover and click remain active through the opening release.
|
|
353
366
|
- **Settings.** The strip owns one profile-global `Settings` tab. The grouped launcher creates or activates it; `Alt+,` does the same from another tab and closes it while it is viewed. Esc does not close the Settings tab; closing the tab removes it. Resident Agent autocomplete omits `/settings`, and manually entering it does not open a per-Agent screen. `a1 solo` and `a1 pi` retain their existing Settings routes.
|
|
354
|
-
- **Commands inside A1 tabs:**
|
|
367
|
+
- **Commands inside A1 tabs:** a tab's name and its session's name stay in sync. a strip rename or an automatic name sets the session name through the bridge, and a session name set otherwise publishes the session view at once, so the tab takes it as a user name; and a resumed session's name becomes the tab name. An automatic name never replaces a user name. Resident Agents also omit and refuse `/changelog`, which opens as its own tab, and `/name`, since renaming the tab (`F2`) renames the session; direct A1 keeps both. Milestone 5 adds no tab-management slash commands: `/new-tab`, `/close`, `/tabs`, `/quit-all`, and `/restart-tab` are not exposed.
|
|
355
368
|
- **Naming:**
|
|
356
369
|
- Default names are `agent` or `agent N`.
|
|
357
370
|
- When `tabsAutoName` is on (the default, by user decision), the tab's model proposes a hyphenated lowercase name of at most 16 characters after the first settled turn. It uses a small budget and one retry, with a fallback derived from the first prompt, and it runs inside the child.
|
|
358
371
|
- A user rename sets `nameSource=user`, and auto-naming never overrides it.
|
|
359
372
|
- An automatic rename uses v2's title transition: 600 ms moving rainbow, typing at 45 ms per character, then a 300 ms settle. Manual renames do not animate.
|
|
360
373
|
- Rename is inline in the chip: `Enter` commits, `Esc` or a click elsewhere cancels, and `Ctrl+Left` / `Ctrl+Right` move by word. The name is trimmed, must not be empty, is at most 64 characters, and has control characters stripped.
|
|
361
|
-
- **
|
|
374
|
+
- **Plus menu.** The A1 setting `tabBarPlusMenu`, shown as `Plus menu` in the `Tab Bar` Settings section, holds the kinds `+` offers, Agent and every utility kind, as comma-joined keys in that order and defaults to `agent`. The `Plus Menu` dialog lists every kind with `●` when enabled and `○` when not under `Choose what the + button in the tab bar can add.`, with `Space` to toggle, Up and Down to move, typing to filter, and `Esc` to close. `+`'s `Configure` opens it in the attach client as a modal over the dimmed active tab or landing, built on the command palette's list and renderer, with a reversed white search caret like owned inputs. The Settings `configure` entry opens the same modal: the Settings child sends a `configure-plus` bridge request as the tab's controlling client, which the server forwards to that client as `plus.configure`; only without a bridge does Settings open its own equivalent dialog. Each toggle is written at once: the attach client takes the `settings.json.lock` directory lock the TypeScript writer takes through `proper-lockfile`, rereads the document, replaces only `a1.tabBarPlusMenu` (removed at the default), and renames a temporary file over it. A refused write restores the stored menu. The attach client reads the setting live, and with no kind enabled the strip hides `+`. Because a modal's overlay saves and restores the cursor while faint is active, the strip starts every frame from reset attributes, so no chip stays faint after a modal closes.
|
|
375
|
+
- **Command palette.** `Alt+/` (`tabs.commands`) opens a centered box over the whole frame in the owned list style: an accent `Commands` title, a `> ` search row, `Agent`, `Settings`, and `Changelog` with their effective shortcuts right-aligned, and a key hint. Typing or pasting filters it, Up and Down move, `Enter` runs the selected entry through its shortcut action, and `Esc` or `Alt+/` closes it; another tab shortcut closes it and runs. While it is open no key, paste, or pointer event reaches the strip or a child, the cursor is hidden, and the composer redraws the strip and body with faint re-asserted after every SGR beneath the bright overlay.
|
|
376
|
+
- **Changelog.** The changelog opens as a singleton `changelog-` utility tab titled `Changelog`, from the palette or the unbound `tabs.changelog` action. The tab registry accepts the `agent`, `settings`, and `changelog` kinds. Like Settings it is never renamed, offers only `Close`, is placed last, does not count as an Agent, and is prewarmed and hidden: the attach client starts it with `tab.prewarm-changelog` once Settings is ready, and again after a Changelog tab closes, so opening it shows the running tab at once. Like a hidden Settings tab, a hidden Changelog never takes the view when the viewed tab closes, so closing the last Agent shows the landing. Its screen is the tab: it shows no `Esc close` hint, and neither `Esc` nor one `Ctrl+C` closes it.
|
|
377
|
+
- **Utility tab rules.** Settings and Changelog are kinds in one table, `UtilityKind` in the attach core and `UTILITY_KINDS` on the server, and every rule follows the kind rather than a per-kind path: a kind's toggle shows a tab already in the strip in its slot, reveals a hidden or absent one through `tab.open-<kind>`, which the server answers by starting it or placing the running one last, and hides the viewed one while its process keeps running; the client prewarms each kind with `tab.prewarm-<kind>` once the kind before it runs; and a hidden one never takes the view when another tab closes. In the child, the route matching a resident tab's id is that tab's pinned screen. Adding a kind is a table entry, its prewarm state, and its route.
|
|
378
|
+
- **Reveal.** Opening a tab draws it at once: a created Agent and a revealed Settings or Changelog tab both keep the current content, another tab or the landing, until their first drawn frame commits, a retained blank surface included, and a tab that never draws shows after the bounded first-draw wait. Only a resize fades (`roles/reveal.rs`): once the curtain lifts, the whole frame draws faint for one fade interval with no cursor, then at full intensity. A starting chip shows no spinner. One tab opens at a time: new-Agent input while a tab the client asked for is starting or not yet shown starts nothing.
|
|
379
|
+
- **Rename selection.** The inline rename draws a selection on the prompt editor's selection background (RGB `38,79,120`, palette `24`) and keeps the chip's text color, restoring the chip's background after it; only the caret is a reversed cell.
|
|
380
|
+
- **Close.** `Alt+W` and the right-click menu close a tab directly, including while it is working. Closing removes the chip without a confirmation, stopping/read-only frame, or close glyph, then gracefully stops the child under the existing bounded termination policy. Its session stays resumable. Closing the last tab leaves `+` and the clock beside the landing.
|
|
381
|
+
- **Landing.** While the attach client has no tab to show, the attach role renders the a1 landing as the frame's body. The launch cwd sits where an Agent tab's footer shows it, flush left on the second-to-last body row with the same `~` formatting and `dim` color, so the folder does not move when a tab opens or closes; `v<version>`, read once from the release root's `package.json`, sits below it and the three-row half-block `a1` wordmark in the profile's accent above it (omitted in Apple Terminal). The accent colors are a native table pinned by `landing_accents.json`, which a TypeScript test checks against `derivePiAccentProjection`. The body is painted with the canvas `backgroundStyle` gives Agent tabs. The landing holds no input: tab shortcuts, the launcher, strip mouse, and double-`Ctrl+C` behave as with tabs, and no cursor shows. A hidden Settings tab is prewarmed and kept running beside the landing, as beside Agents, so Settings opens without a start; it closes when the client detaches, and an attach treats a registry holding only Settings as empty, so it starts and views its first Agent. Adding a tab from the landing keeps it until the tab's first drawn frame replaces it at once; the landing has no animation of its own.
|
|
362
382
|
- **Detach.**
|
|
363
383
|
- `Ctrl+C` twice detaches from any tab and is handled by the attach client, so it works even when the bridge is down. Inside an A1 tab, `/quit` and empty-editor `Ctrl+D` send a detach request carrying the immutable command-origin controller identity and revision; stale, ambiguous or unavailable attribution keeps the tab running and directs the user to `Ctrl+C` twice.
|
|
364
384
|
- The client restores the terminal. When agents are still running, it prints `1 agent still running run a1 to return.` or `N agents still running run a1 to return.`; otherwise it prints the resume hint.
|
|
365
385
|
- **Launch and reattach:**
|
|
366
386
|
- Bare `a1` ensures the server and attaches.
|
|
367
|
-
- With zero tabs, it creates one A1 tab in the launch cwd.
|
|
387
|
+
- With zero tabs, it creates one A1 tab in the launch cwd; a registry holding only the hidden Settings tab counts as zero.
|
|
368
388
|
- Otherwise it views the last active tab from its retained surface without byte-history replay. Every holder keeps draining its PTY and updating its retained terminal independently while hidden; selecting a working tab requests its newest complete partial-response surface immediately and then continues live delivery. Measure retained first paint and first input separately from bootstrap; status for the remaining tabs arrives without subscribing to their surfaces.
|
|
369
389
|
- `a1 --session X` opens X in a new tab, or focuses the tab holding it.
|
|
370
390
|
- A new tab's cwd is the client's launch cwd.
|
|
@@ -589,6 +609,14 @@ The user made these decisions on 2026-09-24:
|
|
|
589
609
|
- `Ctrl+C` twice leaves `a1` from any tab while tabs keep running. There is no separate detach key.
|
|
590
610
|
- Reliability is a first-class, release-gating requirement (Decision 16).
|
|
591
611
|
|
|
612
|
+
On 2026-10-24 the user refined the tab bar:
|
|
613
|
+
|
|
614
|
+
- `+` adds an Agent by default; what it offers, Agent, Settings, and Changelog, is configurable from a right-click `Configure` modal over the active tab and from `Tab Bar` › `Plus menu` in Settings, where the `Tabs` section is renamed `Tab Bar`. With nothing enabled, `+` is hidden.
|
|
615
|
+
- `Alt+/` opens a keyboard-only command palette over a dimmed frame: `Agent`, `Settings`, and `Changelog`.
|
|
616
|
+
- The changelog opens at once as its own prewarmed singleton tab that `Esc` does not close, and leaves resident Agents' commands.
|
|
617
|
+
- Opening a tab, a new Agent or a revealed Settings or Changelog tab, draws it at once when its first frame is drawn, with no fade and no starting spinner; the current content, landing included, stays until then. Only a resize fades. The landing's wipe animation is removed.
|
|
618
|
+
- The tab rename selection matches the editor's selection style, and a tab's name and its session's name stay in sync.
|
|
619
|
+
|
|
592
620
|
On 2026-10-16 the `ci-critical-path` change replaced the Intel runner for the release builds:
|
|
593
621
|
|
|
594
622
|
- 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.
|
|
@@ -16,7 +16,7 @@ The source unit is from MIT-licensed repository <https://github.com/earendil-wor
|
|
|
16
16
|
|
|
17
17
|
One conversion is active per mounted tool. The waiting set is the newest current source set, not an unbounded queue of superseded snapshots. Hidden images and non-Kitty protocols do not start conversions. Work removed before codec entry is skipped. A public codec call already in progress cannot be cancelled; its eventual result is discarded if the source or mount is obsolete. This limitation is explicit, not claimed as cancellable worker execution.
|
|
18
18
|
|
|
19
|
-
Pending Kitty conversion has a visible `[Image converting: ...]` marker. Failure, rejection, or oversized converted bytes produces `[Image unavailable: ...]`, without exposing codec diagnostics or changing the operation outcome. Converted pixels never replace the original payload passed to extension renderers. PNG and iTerm presentation use the actual public `Image` component.
|
|
19
|
+
Pending Kitty conversion has a visible `[Image converting: ...]` marker. Failure, rejection, or oversized converted bytes produces `[Image unavailable: ...]`, without exposing codec diagnostics or changing the operation outcome. Converted pixels never replace the original payload passed to extension renderers. PNG and iTerm presentation use the actual public `Image` component. Explicitly hidden images retain pinned textual indicators. When image display is enabled but the terminal advertises no image protocol, bare A1 omits the non-actionable attachment fallback from user and tool transcript content; attachment bytes, metadata, model delivery, the explicit `a1 pi` route, and extension-owned image components remain unchanged.
|
|
20
20
|
|
|
21
21
|
Rebuild/disposal releases image ownership and invalidates the previous mount. Reentrant renderer invalidation is suppressed during synchronous reconstruction; valid asynchronous callbacks still request the existing scheduler and update presentation revision, not semantic revision.
|
|
22
22
|
|
|
@@ -31,6 +31,7 @@ This correction does not introduce an ANSI parser, change link detection, relax
|
|
|
31
31
|
- `transcript-image-conversion.test.ts`: independent pinned stale-cache baseline, JPEG replacement, PNG replacement, hidden/non-Kitty parity, failed-conversion fallback and real scheduled Kitty publication at unchanged semantic revision.
|
|
32
32
|
- `tool-image-presentation.test.ts`: current-source sharing, one active conversion, superseded bursts, removed/disposed callbacks, hidden/iTerm work avoidance, rejection and size fallback without retry loops.
|
|
33
33
|
- `tool-shell-parity.test.ts`: independent actual pinned read/bash/edit/write/grep/find/ls and generic rendering at widths 40/80/192, partial/final/error states, expansion, binary text, and extension default/self shells with original inputs and stable renderer state.
|
|
34
|
+
- `shell-components.test.ts`: unsupported user and tool transcript images produce no fallback row or spacer, while explicit hiding and capable-terminal unavailable diagnostics remain visible.
|
|
34
35
|
- Existing attachment-ownership, payload-limit, content-retention and shell tests remain non-regression gates. The wrapped-URL counterexample remains explicitly unresolved under issue #353.
|
|
35
36
|
|
|
36
37
|
These are automated content and ownership checks, not Windows Terminal visual acceptance. The broader change stays open until scheduled-producer evidence, required CI, and exact-candidate user review satisfy its remaining tasks.
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# Persistent prompt history
|
|
2
2
|
|
|
3
|
-
Bare A1 recalls recent prompts
|
|
4
|
-
|
|
3
|
+
Bare A1 recalls recent prompts within each agent session, including when that
|
|
4
|
+
session resumes. The optional Shared scope recalls prompts across agent sessions
|
|
5
|
+
and projects in the same A1 profile. It is input recall, not conversation recovery or agent memory. The Pi
|
|
5
6
|
comparison (`a1 pi`) retains its ordinary current-session history.
|
|
6
7
|
|
|
7
8
|
## Recall
|
|
@@ -36,16 +37,21 @@ imported automatically.
|
|
|
36
37
|
|
|
37
38
|
| Platform | Default durable location |
|
|
38
39
|
|---|---|
|
|
39
|
-
| Windows | `%USERPROFILE%/.a1/data/history/<
|
|
40
|
-
| Linux and macOS | `~/.a1/data/history/<
|
|
41
|
-
| Explicit override | `<A1_DATA_DIR>/history/<
|
|
40
|
+
| Windows | `%USERPROFILE%/.a1/data/history/<history-id>.sqlite3` |
|
|
41
|
+
| Linux and macOS | `~/.a1/data/history/<history-id>.sqlite3` |
|
|
42
|
+
| Explicit override | `<A1_DATA_DIR>/history/<history-id>.sqlite3` |
|
|
42
43
|
|
|
43
44
|
The default uses A1's effective profile home: `A1_PROFILE_HOME` when set, otherwise
|
|
44
45
|
the operating-system home. A custom agent-profile directory changes the history
|
|
45
46
|
identity, not this default root. `A1_DATA_DIR` takes precedence for storage;
|
|
46
47
|
`XDG_DATA_HOME` does not redirect default history. Control metadata, settings,
|
|
47
48
|
agent resources, releases, logs, runtime files, and caches keep their existing
|
|
48
|
-
locations.
|
|
49
|
+
locations. For repository-local `./scripts/dev` launches, history uses a stable
|
|
50
|
+
checkout-wide data root so two independent solo instances share the selected
|
|
51
|
+
Shared database, while control and runtime data remain isolated per launch.
|
|
52
|
+
The development-only `A1_DEV_HISTORY_DATA_DIR` selects that root; an explicit
|
|
53
|
+
`A1_DATA_DIR` still overrides its launcher default. Existing histories in old
|
|
54
|
+
per-instance development roots are not moved or combined.
|
|
49
55
|
|
|
50
56
|
**The home-based default starts fresh.** Previous versions used
|
|
51
57
|
`%LOCALAPPDATA%/a1/history` on Windows, or `$XDG_DATA_HOME/a1/history` /
|
|
@@ -53,12 +59,15 @@ locations.
|
|
|
53
59
|
fall back to those stores. Explicit `A1_DATA_DIR` selections still work normally.
|
|
54
60
|
Old and new releases using different roots do not synchronize history.
|
|
55
61
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
+
In Shared mode the filename is the original `a1-` SHA-256 profile identity,
|
|
63
|
+
independent of project, session, checkout, and release. In Unique mode (the
|
|
64
|
+
default), a domain-separated digest also includes the stable conversation session
|
|
65
|
+
ID; two solo launches or resident tabs therefore use different databases and
|
|
66
|
+
image sidecars. Resuming the same session reuses its store; starting or forking a
|
|
67
|
+
session selects a different one. Windows ordinary case and separator variants
|
|
68
|
+
normalize together. Moving a profile, unresolved symlink aliases, or selecting a
|
|
69
|
+
different data root can select a different history; A1 never silently merges or
|
|
70
|
+
imports it.
|
|
62
71
|
|
|
63
72
|
The database is separate from `control.sqlite3`, agent resources, and disposable
|
|
64
73
|
caches. Upgrades, ordinary npm uninstall/reinstall, release rollback, cache cleanup,
|
|
@@ -79,11 +88,19 @@ must not appear in history diagnostics or test evidence.
|
|
|
79
88
|
The History section in `/settings` provides:
|
|
80
89
|
|
|
81
90
|
- **Persistent history:** on by default; applies on the next start. Off disables
|
|
82
|
-
storage and
|
|
91
|
+
storage and durable recall for subsequent launches, not current-session
|
|
83
92
|
recovery. Existing enabled instances keep running until closed.
|
|
93
|
+
- **History scope:** Unique (default) keeps each agent session's durable prompts
|
|
94
|
+
separate across solo launches and resident tabs. Shared restores the former
|
|
95
|
+
profile-wide store. Switching applies to running agents, including other solo
|
|
96
|
+
processes and resident tabs, when their shared settings update arrives. New
|
|
97
|
+
prompts use the newly selected store; already-submitted writes finish in their
|
|
98
|
+
original store. Switching never transfers or deletes records, and it preserves
|
|
99
|
+
unsent drafts. Existing profiles without this setting default to Unique, so
|
|
100
|
+
old Shared entries remain intact but do not appear until Shared is selected.
|
|
84
101
|
- **History limit:** 10-100 in increments of 10, default 100; applies when a new
|
|
85
|
-
enabled instance starts. All writers
|
|
86
|
-
retention limit. Raising the limit cannot restore pruned entries.
|
|
102
|
+
enabled instance starts. All writers using that selected store then use its
|
|
103
|
+
updated retention limit. Raising the limit cannot restore pruned entries.
|
|
87
104
|
|
|
88
105
|
The settings use A1's existing settings document, not Pi's settings or a separate
|
|
89
106
|
history configuration file. Disabling persistence does not delete saved data;
|
|
@@ -134,14 +151,18 @@ placed beside history. For a history-only review, `NODE_DISABLE_COMPILE_CACHE=1`
|
|
|
134
151
|
also avoids creating compile-cache files under that explicitly overridden root.
|
|
135
152
|
The focused path/composition tests cover normal no-override selection. Test:
|
|
136
153
|
|
|
137
|
-
1. Submit two text prompts and one repeated prompt;
|
|
138
|
-
|
|
154
|
+
1. Submit two text prompts and one repeated prompt; resume the same session
|
|
155
|
+
after restart and check those prompts remain recallable. Start `/new` and
|
|
156
|
+
confirm its durable history is separate.
|
|
139
157
|
2. Browse multiline input with a draft present. Check v2 caret placement,
|
|
140
158
|
newest/oldest numbering, and draft restoration.
|
|
141
|
-
3. Submit from a second instance while the first browses.
|
|
142
|
-
|
|
159
|
+
3. Submit from a second instance while the first browses. Under Unique, the
|
|
160
|
+
first sees no cross-agent update. Switch to Shared without restarting; once
|
|
161
|
+
both agents apply the setting, the second instance's new prompt appears in
|
|
162
|
+
the first agent's shared history. Switching back restores each Unique store.
|
|
143
163
|
4. Recall pasted text after restart; image tokens must not promise reattachment.
|
|
144
|
-
5. Save
|
|
164
|
+
5. Save History scope and verify its immediate effective value. Restart only
|
|
165
|
+
for changes to Persistent history or History limit.
|
|
145
166
|
6. Use a second isolated effective profile and verify no sharing. Run
|
|
146
167
|
`./scripts/dev pi` and verify its ordinary history/editor remain unchanged.
|
|
147
168
|
|
package/docs/validation.md
CHANGED
|
@@ -6,11 +6,13 @@ Development validation computes one exact base/head selection with:
|
|
|
6
6
|
npm run select:validation-impact -- --base <full-base-sha> --head <full-head-sha> --output .artifacts/validation/impact.json
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
The versioned `config/validation-ownership.json` registry maps stable product and test path groups to a mandatory PR core,
|
|
9
|
+
The versioned `config/validation-ownership.json` registry maps stable product and test path groups to a mandatory PR core, an explicit critical floor per owner, changed and bounded transitive consumers, resource-sensitive tests, and integration owners. The coarse owners are UI/rendering, launch/startup, release/package/update, Pi, native containment, image/history, governance, and shared product inputs. Each integration owner in `config/integration-owners.json` declares `pull-request`, `direct-impact`, or `exhaustive` cadence. A changed pull-request test selects its owner; a changed exhaustive test selects focused deterministic contracts and records the exhaustive owner as cadence-deferred. Shared support selects every declared PR consumer and records affected exhaustive consumers; copies, renames, and deletions inspect both identities. Reasons and changed paths are recorded in `impact.json`.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Reviewed `refinements` can narrow a coarse owner's tests and integration links for known subareas: scrollbar/settings, clipboard/editor/repository commands, documentation/cleanup/delivery governance, and development-environment tooling. Each rule keeps an explicit minimum test set; a bounded relative-import graph across `test/`, `src/`, `scripts/`, and `bin/` adds at most 24 sorted transitive consumers per changed path, including consumers outside the coarse owner, and integration owners of those retained consumers. The graph is supplemental, not sole coverage authority. Unreviewed or absent source identities and an unavailable graph retain the coarse owner. Changed tests, shared support, and mixed source/support changes retain their coarse contracts; rename and deletion classification still includes both identities. Some UI changes deliberately select more tests than before because the graph exposes formerly missed shell consumers.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
Unknown operational inputs, unavailable comparison history, workflow/selector/suite/aggregate changes, and manual Development dispatch select every coarse owner's critical floor and only integration owners with `pull-request` cadence. Direct-impact owners require a changed declared test, support, or production surface; exhaustive owners remain deferred. Missing, malformed, or unknown cadence blocks instead of guessing. Documentation-only and version-only changes retain explicit exemptions. An implementation-bound association (an `openspec-implementation` link in the PR body) only disables those two exemptions: the PR core still runs, and owners are still selected by impact rather than forced to conservative coverage. Selection does not require whole-repository source parsing.
|
|
14
|
+
|
|
15
|
+
The `changes` job then derives the modular job matrix from that same selection through `scripts/release/validation-matrix.mjs`, which declares every Development modular job and returns only the entries the selection activates; GitHub never schedules an inactive entry, so an ordinary PR does not spend runner minutes on jobs that would resolve to nothing. The matrix uses fail-fast cancellation, so a decisive failure stops remaining cells while missing or cancelled selected evidence still blocks the aggregate. Each scheduled job still resolves its own owners from the uploaded impact artifact, and the aggregate still requires evidence for every selected owner, so an entry that should have run and did not fails the run.
|
|
14
16
|
|
|
15
17
|
## Commands and coverage levels
|
|
16
18
|
|
|
@@ -42,7 +44,7 @@ Preflight selects the committed merge-base diff with `origin/develop` exactly as
|
|
|
42
44
|
|
|
43
45
|
`test:pr-core` is not a replacement for `test:fast`: CI combines it with directly changed and coarse-owner tests from the exact impact selection. Selected resource-sensitive tests run independently on an isolated Windows runner. Selected package, startup, rendering, Pi, compatibility, and platform owners also run independently after their actual prerequisites.
|
|
44
46
|
|
|
45
|
-
Conservative Development retains every pull-request
|
|
47
|
+
Conservative Development retains every critical owner floor and the small set of `pull-request` integration owners, while scheduling only directly affected `direct-impact` owners and deferring all exhaustive owners. The complete regression implementation runs inside a PR only when base policy verifies an `openspec-ci` App-created repair, its generated candidate identity, and a failed Full regression source in `regression-provenance.json`. Publishing/prerequisite/validation-authority impact, shared inputs, unknown paths, labels, Publish-only repairs, and successful-run persistent-overrun candidates retain bounded ordinary validation. Planning-only generated repair drafts stay exempt; eligible implementation drafts get feedback without integration authority. The selected exact-head evidence must include Windows Node 22/24, Linux Node 24, and macOS Node 24. Full regression and nightly/stable release retain both cadence classes and the same coverage through the shared implementation. The real three-release `update-predecessor` exact-package oracle is exhaustive; deterministic predecessor command, fixture, materialization, warmup, package, and update contracts remain PR-eligible. Preview and release continue to consume exact candidate bytes under their channel-specific contracts. A standalone manual Full regression remains useful for diagnosis or an unselected candidate, but never replaces automatically selected current-PR evidence. The generated ownership ledger command is:
|
|
46
48
|
|
|
47
49
|
```bash
|
|
48
50
|
node scripts/release/generate-validation-ownership-ledger.mjs --output .artifacts/validation/ownership-ledger.json
|
|
@@ -54,6 +56,24 @@ The `terminal-host` owner is CI-only. A change under `native/terminal-host/` or
|
|
|
54
56
|
|
|
55
57
|
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. Fidelity, latency, chaos, and certification start roles as `process.execPath dist/foundation/resident-tabs/host-entry.js` with `A1_TERMINAL_HOST_PATH` naming the addon. Windows cleanup identifies those Node-hosted residents by command line; it never kills every `node.exe` by image name. 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. 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).
|
|
56
58
|
|
|
59
|
+
## Coverage-preserving efficiency
|
|
60
|
+
|
|
61
|
+
Complete regression selects the full rendering scope instead of also executing its superseded smoke files. Full captures call the same smoke assertions, including independent producer identity, readiness, semantic parity, synchronization, and bounded paint checks. Smoke-only corrupted-evidence controls live in an ordinary deterministic test. Scheduled content runs once in each gate through the same owned-versus-pinned helper. The retained full matrix has 11 streaming workloads across three producers and two modes, two scheduled-content workloads across two producers, and six input workloads batched across three producers: 73 matrix launches plus 10 protocol-failure launches. Standalone smoke retains 19 launches. Full regression avoids the additional 19 smoke launches; it does not remove a workload or platform lane.
|
|
62
|
+
|
|
63
|
+
The coverage ledger test reconstructs every full-plan invocation and requires each retained file exactly once, except the two smoke files whose executed replacement contracts it names explicitly. Unit, shell integration, source/emitted entries, independent pinned parity, security/race controls, and runtime-specific tests are not interchangeable merely because they touch the same module.
|
|
64
|
+
|
|
65
|
+
The package-command transcript fixture uses four file-owned producer processes (owned/pinned times color 0/1), rather than starting 22 equivalent loaders. Every request still runs the real command with its independent home/settings and fresh transcript; failed producers are never restarted. Fresh-home isolation and process disposal are tested. Cold-entry contracts elsewhere retain their cold process boundary. Git cleanup scenarios still initialize their own real repository/worktree, but write private Git configuration once instead of launching four config/remote commands. Release fixtures create baseline refs in one real atomic ref transaction. Setup remains inside the owning scenario and no mutable fixture repository is shared.
|
|
66
|
+
|
|
67
|
+
Exact-package identity walks hash each payload as it is read, retain only file digests, and parse the manifest from that same walk. They keep the original digest contract, all pre/post consumer verifications, fresh prefixes, and separate shard installations. This removes duplicate manifest reads and full-payload buffering; it does not cache verification results or promise a material improvement to npm installation time.
|
|
68
|
+
|
|
69
|
+
Generate a content-free per-file inventory and optional observed timing report from an existing Actions run and log:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
node scripts/release/validation-cost-report.mjs --run .artifacts/agent/completed-run.json --log .artifacts/agent/job.log --output .artifacts/validation/cost-report.json
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The run JSON is the output of `gh run view RUN --json jobs,headSha,createdAt,updatedAt`; the log is from that same run's chosen job. Omit both inputs for inventory only. Reports are observational, never validation receipts. They identify source drift, each retained file's coarse owner/execution class, job and step duration, total runner time, longest job, workflow elapsed time, and runner span. Queue and DAG critical-path timing remain unavailable rather than guessed. File timings preserve repeated observations; never add concurrent job/file durations and call the result elapsed savings. Compare comparable completed runs before claiming a hosted speedup. No passing outcomes, installed trees, or mutable fixture state may be cached by these optimizations.
|
|
76
|
+
|
|
57
77
|
## Windows Full regression shards
|
|
58
78
|
|
|
59
79
|
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.
|
|
@@ -132,4 +152,4 @@ Draft pull requests run no Development test suites. A ready version-3 PR whose b
|
|
|
132
152
|
|
|
133
153
|
To disable selective execution without reducing PR coverage, use manual Development dispatch or force conservative ownership selection. To restore the historical predecessor oracle to every applicable PR, change its cadence from `exhaustive` to `pull-request`; do not delete it from Full/nightly/release. To disable prior-attempt reuse, require all accepted attempts to equal the aggregate attempt; this must not remove attempt-qualified artifacts or failure visibility. To disable prerequisite reuse, unset readiness/tarball variables and receipt paths so tier orchestration rebuilds and repacks.
|
|
134
154
|
|
|
135
|
-
Rollback must retain
|
|
155
|
+
Rollback must retain complete Full/nightly/release test coverage, startup budgets, zero automatic retry policy, required release platform/runtime lanes, exact-package identity, resource isolation, and the single stable protected-branch aggregate. It must not restore owner-wide or unrelated exhaustive execution to ordinary pull requests.
|
package/package.json
CHANGED