@timurproko/a1 0.2.7-dev.810 → 0.2.7-dev.820
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/resident-tab-coordinator.d.ts +6 -9
- package/dist/app/session-shell/resident-tab-coordinator.js +9 -52
- package/dist/app/session-shell/session-shell-root.d.ts +1 -2
- package/dist/app/session-shell/session-shell.js +2 -2
- package/dist/app/session-shell/session-viewport-controller.js +6 -4
- package/dist/composition/owned-ui.js +1 -2
- package/dist/features/owned-ui/settings-app.js +1 -1
- package/dist/foundation/resident-tabs/bridge-client.d.ts +1 -1
- package/dist/foundation/resident-tabs/bridge-client.js +1 -1
- package/dist/foundation/resident-tabs/settings.d.ts +0 -1
- package/dist/foundation/resident-tabs/settings.js +0 -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/ui/components/dialog-panel.js +1 -1
- package/dist/ui/components/prompt-input.d.ts +2 -2
- package/dist/ui/components/prompt-input.js +2 -2
- package/dist/ui/components/submitted-prompt.d.ts +2 -2
- package/dist/ui/components/submitted-prompt.js +3 -3
- package/dist/ui/components/transcript-viewport.d.ts +1 -1
- package/dist/ui/components/transcript-viewport.js +141 -24
- package/dist/ui/settings/declarations.d.ts +13 -9
- package/dist/ui/settings/declarations.js +10 -7
- package/dist/ui/settings/migrations.js +7 -0
- package/docs/architecture/resident-tabs.md +4 -4
- package/package.json +1 -1
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
import type { OwnedUiEvent, PromptHistoryImageSidecarPort } from "../../contracts/owned-ui/index.js";
|
|
2
2
|
import type { SessionConcern } from "./session-concern.js";
|
|
3
3
|
import type { OwnedUiShellEngineOptions, OwnedUiSessionShellRoot } from "./session-shell-root.js";
|
|
4
|
-
import type { OwnedUiPresenterTerminal } from "./shell-host.js";
|
|
5
4
|
export type ResidentTabOptions = NonNullable<OwnedUiShellEngineOptions["residentTab"]>;
|
|
6
5
|
export interface ResidentTabCoordinatorPorts {
|
|
7
6
|
readonly root: Pick<OwnedUiSessionShellRoot, "appendWorkflowStatus" | "rehydrateHistoryText" | "editor">;
|
|
8
|
-
readonly terminal: Pick<OwnedUiPresenterTerminal, "requestRender">;
|
|
9
|
-
readonly disposed: () => boolean;
|
|
10
7
|
}
|
|
11
8
|
/**
|
|
12
|
-
* The session presenter's side of a resident tab: recovered input, the
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
9
|
+
* The session presenter's side of a resident tab: recovered input, the idle status it reports to
|
|
10
|
+
* the strip, prompt admission and correlation through the recovery journal, draft checkpoints,
|
|
11
|
+
* detach on quit, and the graceful stop when the presenter is released. Outside a resident tab
|
|
12
|
+
* every operation is a no-op.
|
|
16
13
|
*/
|
|
17
14
|
export declare class ResidentTabCoordinator implements SessionConcern {
|
|
18
15
|
#private;
|
|
@@ -23,8 +20,8 @@ export declare class ResidentTabCoordinator implements SessionConcern {
|
|
|
23
20
|
closeOwnedRoute(route: string): void;
|
|
24
21
|
/** Puts the prompts and draft a previous tab process left unfinished back into the editor. */
|
|
25
22
|
restoreInput(imageSidecar?: PromptHistoryImageSidecarPort): void;
|
|
26
|
-
/**
|
|
27
|
-
|
|
23
|
+
/** Reports idle to the strip whenever the session leaves its busy lifecycle. */
|
|
24
|
+
observeLifecycle(event: OwnedUiEvent): void;
|
|
28
25
|
/** Journals a prompt before it is sent; undefined outside a resident tab. Rejects when the journal cannot sync. */
|
|
29
26
|
admit(input: Parameters<NonNullable<ResidentTabOptions["admit"]>>[0]): Promise<{
|
|
30
27
|
readonly id: string;
|
|
@@ -1,18 +1,12 @@
|
|
|
1
|
-
const PROGRESS_EVENTS = new Set([
|
|
2
|
-
"transcript-block", "session-view", "assistant-message-completed", "agent-run-started",
|
|
3
|
-
]);
|
|
4
1
|
/**
|
|
5
|
-
* The session presenter's side of a resident tab: recovered input, the
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
2
|
+
* The session presenter's side of a resident tab: recovered input, the idle status it reports to
|
|
3
|
+
* the strip, prompt admission and correlation through the recovery journal, draft checkpoints,
|
|
4
|
+
* detach on quit, and the graceful stop when the presenter is released. Outside a resident tab
|
|
5
|
+
* every operation is a no-op.
|
|
9
6
|
*/
|
|
10
7
|
export class ResidentTabCoordinator {
|
|
11
8
|
#options;
|
|
12
9
|
#ports;
|
|
13
|
-
#progressTimer;
|
|
14
|
-
#working = false;
|
|
15
|
-
#stalled = false;
|
|
16
10
|
constructor(options, ports) {
|
|
17
11
|
this.#options = options;
|
|
18
12
|
this.#ports = ports;
|
|
@@ -51,40 +45,10 @@ export class ResidentTabCoordinator {
|
|
|
51
45
|
root.appendWorkflowStatus("Recovered the editor draft from the previous tab process.");
|
|
52
46
|
}
|
|
53
47
|
}
|
|
54
|
-
/**
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if (resident === undefined || seconds === undefined)
|
|
59
|
-
return;
|
|
60
|
-
if (event.type === "session-lifecycle") {
|
|
61
|
-
this.#working = event.lifecycle === "busy";
|
|
62
|
-
if (!this.#working) {
|
|
63
|
-
this.#clearTimer();
|
|
64
|
-
this.#stalled = false;
|
|
65
|
-
resident.reportStatus?.("idle");
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
else if (!this.#working || !PROGRESS_EVENTS.has(event.type)) {
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
if (this.#stalled) {
|
|
73
|
-
this.#stalled = false;
|
|
74
|
-
resident.reportStatus?.("working");
|
|
75
|
-
}
|
|
76
|
-
this.#clearTimer();
|
|
77
|
-
this.#progressTimer = setTimeout(() => {
|
|
78
|
-
this.#progressTimer = undefined;
|
|
79
|
-
if (this.#ports.disposed() || !this.#working)
|
|
80
|
-
return;
|
|
81
|
-
this.#stalled = true;
|
|
82
|
-
resident.reportStatus?.("stalled");
|
|
83
|
-
const minutes = Math.max(1, Math.round(seconds / 60));
|
|
84
|
-
this.#ports.root.appendWorkflowStatus(`No progress for ${minutes} min — Esc interrupts · use the tab restart action to restart`);
|
|
85
|
-
this.#ports.terminal.requestRender();
|
|
86
|
-
}, seconds * 1_000);
|
|
87
|
-
this.#progressTimer.unref?.();
|
|
48
|
+
/** Reports idle to the strip whenever the session leaves its busy lifecycle. */
|
|
49
|
+
observeLifecycle(event) {
|
|
50
|
+
if (event.type === "session-lifecycle" && event.lifecycle !== "busy")
|
|
51
|
+
this.#options?.reportStatus?.("idle");
|
|
88
52
|
}
|
|
89
53
|
/** Journals a prompt before it is sent; undefined outside a resident tab. Rejects when the journal cannot sync. */
|
|
90
54
|
admit(input) {
|
|
@@ -109,12 +73,5 @@ export class ResidentTabCoordinator {
|
|
|
109
73
|
gracefulStop(sessionFile) {
|
|
110
74
|
return this.#options?.gracefulStop?.(sessionFile) ?? Promise.resolve();
|
|
111
75
|
}
|
|
112
|
-
dispose() {
|
|
113
|
-
this.#clearTimer();
|
|
114
|
-
}
|
|
115
|
-
#clearTimer() {
|
|
116
|
-
if (this.#progressTimer !== undefined)
|
|
117
|
-
clearTimeout(this.#progressTimer);
|
|
118
|
-
this.#progressTimer = undefined;
|
|
119
|
-
}
|
|
76
|
+
dispose() { }
|
|
120
77
|
}
|
|
@@ -47,8 +47,7 @@ export interface OwnedUiShellEngineOptions {
|
|
|
47
47
|
readonly residentTab?: {
|
|
48
48
|
requestDetach(): Promise<boolean>;
|
|
49
49
|
closeOwnedRoute?(route: string): void;
|
|
50
|
-
reportStatus?(status: "working" | "idle"
|
|
51
|
-
readonly stallNoticeSeconds?: number;
|
|
50
|
+
reportStatus?(status: "working" | "idle"): void;
|
|
52
51
|
admit?(input: {
|
|
53
52
|
readonly sessionId: string;
|
|
54
53
|
readonly sessionFile: string | null;
|
|
@@ -251,7 +251,7 @@ export class OwnedUiSessionPresenter {
|
|
|
251
251
|
// Invariant: the presenter owns its root (theme subscription and helper clients) and disposes it once the
|
|
252
252
|
// terminal no longer renders it.
|
|
253
253
|
constructed.push(() => this.root.dispose());
|
|
254
|
-
this.#resident = new ResidentTabCoordinator(options.engine.residentTab, { root: this.root
|
|
254
|
+
this.#resident = new ResidentTabCoordinator(options.engine.residentTab, { root: this.root });
|
|
255
255
|
constructed.push(() => this.#resident.dispose());
|
|
256
256
|
const startupRoute = options.engine.startupRoute;
|
|
257
257
|
this.#dialogs = new SessionDialogCoordinator({
|
|
@@ -366,7 +366,7 @@ export class OwnedUiSessionPresenter {
|
|
|
366
366
|
this.#stop.settleAfter(() => this.dispose());
|
|
367
367
|
return;
|
|
368
368
|
}
|
|
369
|
-
this.#resident.
|
|
369
|
+
this.#resident.observeLifecycle(event);
|
|
370
370
|
// Performance: a streamed chunk names one block, and touching only that block is what keeps the
|
|
371
371
|
// cost of a chunk the same in a long session as in a new one. Everything else
|
|
372
372
|
// resynchronizes the view, which is cheap next to re-reading the transcript.
|
|
@@ -322,8 +322,8 @@ export class SessionViewportController {
|
|
|
322
322
|
this.#requestRender();
|
|
323
323
|
}
|
|
324
324
|
}
|
|
325
|
-
const previousPrompt =
|
|
326
|
-
if (editorActive && allowWheel && (previousPrompt ||
|
|
325
|
+
const previousPrompt = PROMPT_UP_INPUTS.has(data) || this.#editor.matchesTerminalKey(data, "alt+home");
|
|
326
|
+
if (editorActive && allowWheel && (previousPrompt || PROMPT_DOWN_INPUTS.has(data))) {
|
|
327
327
|
const scrolled = previousPrompt
|
|
328
328
|
? this.#viewport.scrollToPreviousPrompt(now)
|
|
329
329
|
: this.#viewport.scrollToNextPrompt(now);
|
|
@@ -670,5 +670,7 @@ export class SessionViewportController {
|
|
|
670
670
|
}
|
|
671
671
|
const SELECTION_AUTO_SCROLL_INTERVAL_MS = 30;
|
|
672
672
|
const EDITOR_LINK_DELETION_INPUTS = new Set(["\b", "\u007f", "\u001b[3~"]);
|
|
673
|
-
|
|
674
|
-
|
|
673
|
+
// Platform: Windows Terminal may consume Shift+Arrow for native selection. Ctrl+Arrow matches
|
|
674
|
+
// solo A1's Windows binding while retaining Shift+Arrow whenever the terminal delivers it.
|
|
675
|
+
const PROMPT_UP_INPUTS = new Set(["\u001b[1;2A", "\u001b[1;5A"]);
|
|
676
|
+
const PROMPT_DOWN_INPUTS = new Set(["\u001b[1;2B", "\u001b[1;5B"]);
|
|
@@ -50,7 +50,7 @@ export async function composeOwnedUi(options = {}) {
|
|
|
50
50
|
namespace: PRODUCT_IDENTITY.commandName,
|
|
51
51
|
legacyConfigDir: productPaths.configDir,
|
|
52
52
|
agentProvider: () => backend.settingsPort(),
|
|
53
|
-
...(options.hideTabSettings ? { hiddenOwnedSettingIds: ["tabsAutoName"] } : {}),
|
|
53
|
+
...(options.hideTabSettings ? { hiddenOwnedSettingIds: ["tabsAutoName", "tabsPosition"] } : {}),
|
|
54
54
|
hiddenAgentSettingIds: ["fullscreenWheelScrollLines"],
|
|
55
55
|
agentSettingLabelOverrides: { fullscreenCopyOnSelect: "Copy on select" },
|
|
56
56
|
});
|
|
@@ -221,7 +221,6 @@ export async function composeOwnedUi(options = {}) {
|
|
|
221
221
|
void tabBridge.requestClose();
|
|
222
222
|
},
|
|
223
223
|
reportStatus: status => tabBridge.status(status),
|
|
224
|
-
stallNoticeSeconds: 300,
|
|
225
224
|
admit: input => tabRecovery.admit({ ...input, incarnation: options.tab.incarnation }),
|
|
226
225
|
dispatched: () => tabRecovery.dispatched(),
|
|
227
226
|
correlated: (submissionId, entryId) => correlateResidentTabSubmission(tabRecovery, backend, submissionId, entryId),
|
|
@@ -1026,7 +1026,7 @@ export class SettingsApp {
|
|
|
1026
1026
|
const input = this.#filter;
|
|
1027
1027
|
if (input === null)
|
|
1028
1028
|
return [status];
|
|
1029
|
-
return [...renderInputRow(input, width, { placeholder: SEARCH_PLACEHOLDER, theme }).lines, status];
|
|
1029
|
+
return [...renderInputRow(input, width, { placeholder: SEARCH_PLACEHOLDER, promptGlyph: "", theme }).lines, status];
|
|
1030
1030
|
}
|
|
1031
1031
|
}
|
|
1032
1032
|
function modelLabelParts(label) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type ResidentTabStatus = "starting" | "working" | "needs-input" | "done-unseen" | "done" | "aborted" | "error" | "idle" | "unresponsive" | "
|
|
1
|
+
export type ResidentTabStatus = "starting" | "working" | "needs-input" | "done-unseen" | "done" | "aborted" | "error" | "idle" | "unresponsive" | "crashed" | "restarting" | "failed";
|
|
2
2
|
export interface TabBridgeCredentials {
|
|
3
3
|
readonly tabId: string;
|
|
4
4
|
readonly token: string;
|
|
@@ -80,7 +80,7 @@ export class TabBridgeClient {
|
|
|
80
80
|
}
|
|
81
81
|
status(state, degraded = false, pendingRequests = 0, queuedInput = 0) {
|
|
82
82
|
this.#latestStatus = { state, degraded, accent: this.#accent, pendingRequests, queuedInput };
|
|
83
|
-
const recoveryState = state === "unresponsive" || state === "
|
|
83
|
+
const recoveryState = state === "unresponsive" || state === "crashed" || state === "restarting" || state === "failed";
|
|
84
84
|
if (recoveryState && !this.#supports("recovery-status"))
|
|
85
85
|
return;
|
|
86
86
|
this.#send("bridge.status", {
|
|
@@ -7,7 +7,6 @@ export interface ResidentTabLaunchSettings {
|
|
|
7
7
|
readonly maxConcurrentStarts: number;
|
|
8
8
|
readonly prewarmCount: number;
|
|
9
9
|
readonly unresponsiveRestartSeconds: number;
|
|
10
|
-
readonly stallNoticeSeconds: number;
|
|
11
10
|
}
|
|
12
11
|
/** Read only resident-launch values before any UI or extension module is loaded. */
|
|
13
12
|
export declare function readResidentTabLaunchSettings(profileDir: string, namespace?: string, legacyConfigDir?: string, profileId?: string): Promise<ResidentTabLaunchSettings>;
|
|
@@ -11,7 +11,6 @@ const DEFAULTS = Object.freeze({
|
|
|
11
11
|
maxConcurrentStarts: RESIDENT_TABS_MAX_CONCURRENT_STARTS,
|
|
12
12
|
prewarmCount: RESIDENT_TABS_PREWARM_COUNT,
|
|
13
13
|
unresponsiveRestartSeconds: 120,
|
|
14
|
-
stallNoticeSeconds: 300,
|
|
15
14
|
});
|
|
16
15
|
/** Read only resident-launch values before any UI or extension module is loaded. */
|
|
17
16
|
export async function readResidentTabLaunchSettings(profileDir, namespace = "a1", legacyConfigDir, profileId = namespace) {
|
|
@@ -55,6 +54,5 @@ function resolveLaunchValues(document, migrations) {
|
|
|
55
54
|
maxConcurrentStarts: RESIDENT_TABS_MAX_CONCURRENT_STARTS,
|
|
56
55
|
prewarmCount: RESIDENT_TABS_PREWARM_COUNT,
|
|
57
56
|
unresponsiveRestartSeconds: DEFAULTS.unresponsiveRestartSeconds,
|
|
58
|
-
stallNoticeSeconds: DEFAULTS.stallNoticeSeconds,
|
|
59
57
|
};
|
|
60
58
|
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"platform": "darwin",
|
|
8
8
|
"architecture": "arm64",
|
|
9
9
|
"capability": "supported",
|
|
10
|
-
"builtAt": "2026-10-
|
|
10
|
+
"builtAt": "2026-10-24T04:45:15.222Z",
|
|
11
11
|
"artifact": {
|
|
12
12
|
"filename": "process-guardian.node",
|
|
13
13
|
"sha256": "a87eaaffcd986b624c8b044b756446cc1bdaf4334a1775b97820542fe34f9fb7",
|
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
"architecture": "arm64",
|
|
10
10
|
"target": "aarch64-apple-darwin",
|
|
11
11
|
"capability": "supported",
|
|
12
|
-
"builtAt": "2026-10-
|
|
12
|
+
"builtAt": "2026-10-24T04:48:05.486Z",
|
|
13
13
|
"artifact": {
|
|
14
14
|
"filename": "terminal-host.node",
|
|
15
|
-
"sha256": "
|
|
16
|
-
"size":
|
|
15
|
+
"sha256": "f7f580d5ded5e1efa26ae790488efc8284edd15fee620aed091564c0fc460d16",
|
|
16
|
+
"size": 5812800
|
|
17
17
|
},
|
|
18
18
|
"entry": {
|
|
19
19
|
"filename": "dist/foundation/resident-tabs/host-entry.js",
|
|
Binary file
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"platform": "darwin",
|
|
8
8
|
"architecture": "x64",
|
|
9
9
|
"capability": "supported",
|
|
10
|
-
"builtAt": "2026-10-
|
|
10
|
+
"builtAt": "2026-10-24T04:45:23.346Z",
|
|
11
11
|
"artifact": {
|
|
12
12
|
"filename": "process-guardian.node",
|
|
13
13
|
"sha256": "783a65f0de287a1c02cd5429dbe97bc8633f4cc8a7b92e3e961180b9bdcff814",
|
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
"architecture": "x64",
|
|
10
10
|
"target": "x86_64-apple-darwin",
|
|
11
11
|
"capability": "supported",
|
|
12
|
-
"builtAt": "2026-10-
|
|
12
|
+
"builtAt": "2026-10-24T04:50:05.116Z",
|
|
13
13
|
"artifact": {
|
|
14
14
|
"filename": "terminal-host.node",
|
|
15
|
-
"sha256": "
|
|
16
|
-
"size":
|
|
15
|
+
"sha256": "f7f2a1c09ebf4dbd5d28de747497564cb252d253ab7a9009b7aae1fe26bba4ff",
|
|
16
|
+
"size": 6340520
|
|
17
17
|
},
|
|
18
18
|
"entry": {
|
|
19
19
|
"filename": "dist/foundation/resident-tabs/host-entry.js",
|
|
Binary file
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"platform": "linux",
|
|
8
8
|
"architecture": "x64",
|
|
9
9
|
"capability": "supported",
|
|
10
|
-
"builtAt": "2026-10-
|
|
10
|
+
"builtAt": "2026-10-24T04:45:06.148Z",
|
|
11
11
|
"artifact": {
|
|
12
12
|
"filename": "process-guardian.node",
|
|
13
13
|
"sha256": "118e5954e3864672bd8482ab7edf419cb27dcdf2533720316979b7f5cc5a53a5",
|
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
"architecture": "x64",
|
|
10
10
|
"target": "x86_64-unknown-linux-gnu",
|
|
11
11
|
"capability": "supported",
|
|
12
|
-
"builtAt": "2026-10-
|
|
12
|
+
"builtAt": "2026-10-24T04:48:17.317Z",
|
|
13
13
|
"artifact": {
|
|
14
14
|
"filename": "terminal-host.node",
|
|
15
|
-
"sha256": "
|
|
16
|
-
"size":
|
|
15
|
+
"sha256": "3c2ed1d62404436ebe39000e93c5428dd9acff3f3158004b94af8a95e778e32a",
|
|
16
|
+
"size": 7069736
|
|
17
17
|
},
|
|
18
18
|
"entry": {
|
|
19
19
|
"filename": "dist/foundation/resident-tabs/host-entry.js",
|
|
Binary file
|
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
"platform": "win32",
|
|
8
8
|
"architecture": "x64",
|
|
9
9
|
"capability": "supported",
|
|
10
|
-
"builtAt": "2026-10-
|
|
10
|
+
"builtAt": "2026-10-24T04:45:48.535Z",
|
|
11
11
|
"artifact": {
|
|
12
12
|
"filename": "process-guardian.node",
|
|
13
|
-
"sha256": "
|
|
13
|
+
"sha256": "12f5a962bc427e82345170f1c4db2a875683d5cbb4e818bcc1a314a11c71bf31",
|
|
14
14
|
"size": 402432
|
|
15
15
|
},
|
|
16
16
|
"provenance": {
|
|
Binary file
|
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
"architecture": "x64",
|
|
10
10
|
"target": "x86_64-pc-windows-msvc",
|
|
11
11
|
"capability": "supported",
|
|
12
|
-
"builtAt": "2026-10-
|
|
12
|
+
"builtAt": "2026-10-24T04:48:40.147Z",
|
|
13
13
|
"artifact": {
|
|
14
14
|
"filename": "terminal-host.node",
|
|
15
|
-
"sha256": "
|
|
16
|
-
"size":
|
|
15
|
+
"sha256": "9beb6edeb62b28ff7e55d419d724c98f8645a73977d85fbb2c1bd85b7b324916",
|
|
16
|
+
"size": 6138880
|
|
17
17
|
},
|
|
18
18
|
"entry": {
|
|
19
19
|
"filename": "dist/foundation/resident-tabs/host-entry.js",
|
|
Binary file
|
|
@@ -69,7 +69,7 @@ export function renderSteppedDialogPanel(state, width, theme) {
|
|
|
69
69
|
});
|
|
70
70
|
const input = state.input === undefined
|
|
71
71
|
? []
|
|
72
|
-
: [...renderInputRow(state.input, width, { ruled: false, promptGlyph: "
|
|
72
|
+
: [...renderInputRow(state.input, width, { ruled: false, promptGlyph: "", theme }).lines, ""];
|
|
73
73
|
const step = `(step ${state.step}/${state.steps})`;
|
|
74
74
|
const title = `${state.title} ${step}`;
|
|
75
75
|
const paintedTitle = `${theme.bold(theme.fg("accent", state.title))} ${theme.fg("dim", step)}`;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type UiTheme } from "./theme.js";
|
|
2
|
-
export declare const PROMPT_GLYPH = "
|
|
3
|
-
export declare const PROMPT_PREFIX_WIDTH =
|
|
2
|
+
export declare const PROMPT_GLYPH = "";
|
|
3
|
+
export declare const PROMPT_PREFIX_WIDTH = 0;
|
|
4
4
|
type RuleTheme = Pick<UiTheme, "fg">;
|
|
5
5
|
export declare function promptRuleText(text: string, theme: RuleTheme): string;
|
|
6
6
|
export declare function promptRule(width: number, theme: RuleTheme): string;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { displayWidth, truncateToWidth } from "./text.js";
|
|
2
2
|
import { PLAIN_THEME } from "./theme.js";
|
|
3
|
-
export const PROMPT_GLYPH = "
|
|
4
|
-
export const PROMPT_PREFIX_WIDTH =
|
|
3
|
+
export const PROMPT_GLYPH = "";
|
|
4
|
+
export const PROMPT_PREFIX_WIDTH = 0;
|
|
5
5
|
export function promptRuleText(text, theme) {
|
|
6
6
|
return theme.fg("border", text);
|
|
7
7
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export type UiTimeFormat = "20:00" | "8:00 PM";
|
|
2
2
|
/** Deterministic local hour/minute presentation shared by A1-owned time labels. */
|
|
3
3
|
export declare function formatLocalTime(source: Date | number, format?: UiTimeFormat): string | null;
|
|
4
|
-
/** Visible width of the submitted-prompt prefix (
|
|
5
|
-
export declare const SUBMITTED_PROMPT_PREFIX_WIDTH =
|
|
4
|
+
/** Visible width of the submitted-prompt prefix (` ❯ `). */
|
|
5
|
+
export declare const SUBMITTED_PROMPT_PREFIX_WIDTH = 3;
|
|
6
6
|
export interface SubmittedPromptStyle {
|
|
7
7
|
readonly prefix?: (text: string) => string;
|
|
8
8
|
readonly timestamp?: (text: string) => string;
|
|
@@ -10,8 +10,8 @@ export function formatLocalTime(source, format = "8:00 PM") {
|
|
|
10
10
|
return `${String(hour).padStart(2, "0")}:${minute}`;
|
|
11
11
|
return `${hour % 12 || 12}:${minute} ${hour < 12 ? "AM" : "PM"}`;
|
|
12
12
|
}
|
|
13
|
-
/** Visible width of the submitted-prompt prefix (
|
|
14
|
-
export const SUBMITTED_PROMPT_PREFIX_WIDTH =
|
|
13
|
+
/** Visible width of the submitted-prompt prefix (` ❯ `). */
|
|
14
|
+
export const SUBMITTED_PROMPT_PREFIX_WIDTH = 3;
|
|
15
15
|
const TIMESTAMP_MARGIN = 3;
|
|
16
16
|
const MIN_USEFUL_CONTENT_WIDTH = 8;
|
|
17
17
|
/** Formats transcript metadata, never render time, in the active A1 time format. */
|
|
@@ -42,7 +42,7 @@ export function composeSubmittedPromptRows(contentRows, width, source, style = {
|
|
|
42
42
|
if (width <= 0)
|
|
43
43
|
return [];
|
|
44
44
|
const layout = submittedPromptLayout(width, source, format);
|
|
45
|
-
const prefix = (style.prefix ?? identity)("❯ ");
|
|
45
|
+
const prefix = (style.prefix ?? identity)(" ❯ ");
|
|
46
46
|
const rows = (contentRows.length === 0 ? [""] : contentRows).map((row, index) => {
|
|
47
47
|
const content = truncateToWidth(row, layout.contentWidth);
|
|
48
48
|
return `${index === 0 ? prefix : " ".repeat(SUBMITTED_PROMPT_PREFIX_WIDTH)}${content}`;
|
|
@@ -178,7 +178,7 @@ export declare class TranscriptViewport {
|
|
|
178
178
|
releaseSelection(): boolean;
|
|
179
179
|
clearSelection(): boolean;
|
|
180
180
|
selectedText(): string | null;
|
|
181
|
-
/** Captures
|
|
181
|
+
/** Captures selected text, including document rows of a visible selection scrolled off-screen. */
|
|
182
182
|
captureSelectedText(): SelectionCopySnapshot | null;
|
|
183
183
|
scrollBy(lines: number, now?: number): boolean;
|
|
184
184
|
scrollTo(position: number, now?: number): boolean;
|
|
@@ -15,6 +15,19 @@ const IDENTITY_ROW = (row) => row;
|
|
|
15
15
|
function documentSelectionAnchor(row, source, liveFromBottom) {
|
|
16
16
|
return { kind: "document", row, source, identity: stripAnsi(source).trimEnd(), liveFromBottom };
|
|
17
17
|
}
|
|
18
|
+
function sameSelectionRowAnchor(left, right) {
|
|
19
|
+
if (left.kind !== right.kind)
|
|
20
|
+
return false;
|
|
21
|
+
if (left.kind === "document" && right.kind === "document") {
|
|
22
|
+
return left.row === right.row && left.source === right.source && left.liveFromBottom === right.liveFromBottom;
|
|
23
|
+
}
|
|
24
|
+
if (left.kind === "screen" && right.kind === "screen") {
|
|
25
|
+
return left.row === right.row && left.source === right.source;
|
|
26
|
+
}
|
|
27
|
+
if (left.kind === "transient" && right.kind === "transient")
|
|
28
|
+
return left.fromBottom === right.fromBottom;
|
|
29
|
+
return left.kind === "dock" && right.kind === "dock" && left.fromBottom === right.fromBottom;
|
|
30
|
+
}
|
|
18
31
|
function viewportSelectionAnchor(row, source, selectableDocumentRowCount, documentRowCount) {
|
|
19
32
|
return row < selectableDocumentRowCount
|
|
20
33
|
? documentSelectionAnchor(row, source)
|
|
@@ -140,7 +153,12 @@ export class TranscriptViewport {
|
|
|
140
153
|
this.scrollBy(1, now);
|
|
141
154
|
else if (autoScroll && frameRow <= 1 && this.#scrollTop > 0)
|
|
142
155
|
this.scrollBy(-1, now);
|
|
143
|
-
const
|
|
156
|
+
const pointerLine = clamp(frameRow - 1, 0, this.#selectionRows.length - 1);
|
|
157
|
+
// Invariant: a pinned prompt alias is chrome; an upward drag reaching it keeps the pointer
|
|
158
|
+
// column on the first visible content row.
|
|
159
|
+
const line = pointerLine < this.#firstSelectableRow && pointerLine < selection.anchor.line
|
|
160
|
+
? this.#firstSelectableRow
|
|
161
|
+
: pointerLine;
|
|
144
162
|
const selectionWidth = line < (this.#frame?.hits.viewportHeight ?? 0)
|
|
145
163
|
? this.#contentWidth
|
|
146
164
|
: this.#selectionWidth;
|
|
@@ -151,12 +169,13 @@ export class TranscriptViewport {
|
|
|
151
169
|
? { line, column: targetColumn }
|
|
152
170
|
: textSelectionPointAt(line, targetColumn, this.#selectionRows[line] ?? "");
|
|
153
171
|
const next = extendTextSelection(selection, point);
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
172
|
+
const headAnchor = this.#selectionAnchorAt(line);
|
|
173
|
+
if (headAnchor === undefined || this.#selectionAnchors === undefined) {
|
|
174
|
+
this.clearSelection();
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
// Invariant: edge auto-scroll changes the document row under a still pointer; that advances the head.
|
|
178
|
+
if (next !== selection || !sameSelectionRowAnchor(this.#selectionAnchors.head, headAnchor)) {
|
|
160
179
|
this.#selection = next;
|
|
161
180
|
this.#selectionAnchors = { ...this.#selectionAnchors, head: headAnchor };
|
|
162
181
|
this.#selectionRevision += 1;
|
|
@@ -184,25 +203,66 @@ export class TranscriptViewport {
|
|
|
184
203
|
return true;
|
|
185
204
|
}
|
|
186
205
|
selectedText() {
|
|
187
|
-
const
|
|
188
|
-
if (
|
|
206
|
+
const visible = this.#visibleSelection();
|
|
207
|
+
if (visible === undefined)
|
|
189
208
|
return null;
|
|
190
|
-
const
|
|
209
|
+
const offscreen = offscreenSelectionRows(this.#selection, this.#selectionAnchors, this.#scrollTop, this.#firstSelectableRow, this.#viewportHeight, this.#documentRows.length);
|
|
210
|
+
// Rationale: edge auto-scroll grows a drag beyond one screen. Paint stays clipped to the
|
|
211
|
+
// frame, while copy reads the selected document rows past either viewport edge and beneath
|
|
212
|
+
// the floating bottom control.
|
|
213
|
+
const selectionRows = [];
|
|
214
|
+
const rowAnchors = [];
|
|
215
|
+
const controlLine = (this.#frame?.hits.bottom?.row ?? 0) - 1;
|
|
216
|
+
const pushDocumentRow = (row) => {
|
|
217
|
+
const source = this.#documentRows[row] ?? "";
|
|
218
|
+
selectionRows.push(truncateToWidth(source, this.#contentWidth));
|
|
219
|
+
rowAnchors.push(documentSelectionAnchor(row, source));
|
|
220
|
+
};
|
|
221
|
+
const pushBelow = () => {
|
|
222
|
+
for (let row = offscreen.below?.from ?? 0; row <= (offscreen.below?.to ?? -1); row += 1)
|
|
223
|
+
pushDocumentRow(row);
|
|
224
|
+
};
|
|
225
|
+
for (let row = offscreen.above?.from ?? 0; row <= (offscreen.above?.to ?? -1); row += 1)
|
|
226
|
+
pushDocumentRow(row);
|
|
227
|
+
if (offscreen.belowFirst)
|
|
228
|
+
pushBelow();
|
|
229
|
+
for (let line = visible.start.line; line <= visible.end.line; line += 1) {
|
|
191
230
|
const anchor = this.#selectionRowAnchors[line];
|
|
231
|
+
if (line === controlLine && anchor?.kind === "document")
|
|
232
|
+
pushDocumentRow(anchor.row);
|
|
233
|
+
else {
|
|
234
|
+
selectionRows.push(this.#selectionRows[line] ?? "");
|
|
235
|
+
rowAnchors.push(anchor);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (!offscreen.belowFirst)
|
|
239
|
+
pushBelow();
|
|
240
|
+
const offscreenStart = offscreen.above !== undefined || offscreen.belowFirst === true;
|
|
241
|
+
const offscreenEnd = offscreen.below !== undefined && !offscreen.belowFirst;
|
|
242
|
+
const selection = {
|
|
243
|
+
...visible,
|
|
244
|
+
start: { line: 0, column: offscreenStart ? offscreen.startColumn ?? 0 : visible.start.column },
|
|
245
|
+
end: {
|
|
246
|
+
line: selectionRows.length - 1,
|
|
247
|
+
column: offscreenEnd ? offscreen.endColumn ?? Number.MAX_SAFE_INTEGER : visible.end.column,
|
|
248
|
+
},
|
|
249
|
+
};
|
|
250
|
+
const copiedLines = Array.from({ length: Math.max(0, selection.end.line - selection.start.line + 1) }, (_value, index) => selection.start.line + index).filter(line => {
|
|
251
|
+
const anchor = rowAnchors[line];
|
|
192
252
|
return anchor?.kind === "document" && anchor.row < this.#selectableDocumentRowCount
|
|
193
253
|
|| anchor?.kind === "dock" && this.#copyableDockRows.has(anchor.fromBottom);
|
|
194
254
|
});
|
|
195
255
|
if (copiedLines.length === 0)
|
|
196
256
|
return null;
|
|
197
|
-
const transcriptLines = copiedLines.filter(line =>
|
|
257
|
+
const transcriptLines = copiedLines.filter(line => rowAnchors[line]?.kind === "document");
|
|
198
258
|
const prompt = transcriptLines.length === 0 ? undefined : this.#promptAnchors.find(candidate => transcriptLines.every(line => {
|
|
199
|
-
const anchor =
|
|
259
|
+
const anchor = rowAnchors[line];
|
|
200
260
|
return anchor?.kind === "document" && anchor.row >= candidate.firstRow && anchor.row <= candidate.lastRow;
|
|
201
261
|
}));
|
|
202
262
|
const included = new Set(copiedLines);
|
|
203
|
-
const text = textSelectionText(selection,
|
|
204
|
-
const row =
|
|
205
|
-
const anchor =
|
|
263
|
+
const text = textSelectionText(selection, selectionRows, line => {
|
|
264
|
+
const row = selectionRows[line] ?? "";
|
|
265
|
+
const anchor = rowAnchors[line];
|
|
206
266
|
const dock = anchor?.kind === "dock" ? this.#copyableDockRows.get(anchor.fromBottom) : undefined;
|
|
207
267
|
if (prompt !== undefined && anchor?.kind === "document") {
|
|
208
268
|
const first = anchor.row === prompt.firstRow;
|
|
@@ -211,11 +271,12 @@ export class TranscriptViewport {
|
|
|
211
271
|
: row;
|
|
212
272
|
return selectionCopyLineContent(copyRow, first ? "first" : "continuation");
|
|
213
273
|
}
|
|
214
|
-
|
|
274
|
+
// Invariant: the dock editor has no prefix; transcript prompts still carry chrome.
|
|
275
|
+
return dock?.prompt !== undefined ? usefulTextLineContent(row, 1) : usefulTextLineContent(row);
|
|
215
276
|
}, line => included.has(line));
|
|
216
277
|
return text.length === 0 ? null : text;
|
|
217
278
|
}
|
|
218
|
-
/** Captures
|
|
279
|
+
/** Captures selected text, including document rows of a visible selection scrolled off-screen. */
|
|
219
280
|
captureSelectedText() {
|
|
220
281
|
if (!this.#copyableSelection)
|
|
221
282
|
return null;
|
|
@@ -470,7 +531,7 @@ export class TranscriptViewport {
|
|
|
470
531
|
const bottomHovered = pointer !== undefined && pointer.row === bottomHit.row
|
|
471
532
|
&& pointer.column >= bottomHit.columnStart && pointer.column <= bottomHit.columnEnd;
|
|
472
533
|
control = { row, left, text: `${CONTROL_STYLE_RESET}${theme.bottomControl(label, bottomHovered)}` };
|
|
473
|
-
// Invariant: the control floats above selection;
|
|
534
|
+
// Invariant: the control floats above selection; copy restores the text beneath it.
|
|
474
535
|
selectionRows[row] = overlaySpan(padRowPreservingBackground(selectionRows[row] ?? "", width), left, left + labelWidth, " ".repeat(labelWidth));
|
|
475
536
|
}
|
|
476
537
|
}
|
|
@@ -701,7 +762,7 @@ export class TranscriptViewport {
|
|
|
701
762
|
const anchors = this.#selectionAnchors;
|
|
702
763
|
if (selection === undefined || anchors === undefined)
|
|
703
764
|
return;
|
|
704
|
-
const project = (anchor) => {
|
|
765
|
+
const project = (anchor, moving) => {
|
|
705
766
|
if (anchor.kind === "dock") {
|
|
706
767
|
const dockIndex = dockRows.length - anchor.fromBottom - 1;
|
|
707
768
|
return dockIndex < 0 || dockIndex >= dockRows.length
|
|
@@ -743,7 +804,7 @@ export class TranscriptViewport {
|
|
|
743
804
|
// Concurrency: a streaming chunk may replace or reflow the pointer's current live-tail
|
|
744
805
|
// row before the next motion report. During the held gesture only, retain its distance
|
|
745
806
|
// from the persistent tail; release restores identity-only projection.
|
|
746
|
-
if (selection.selecting && anchor.liveFromBottom !== undefined && this.#liveTailStartRow !== undefined) {
|
|
807
|
+
if (selection.selecting && moving && anchor.liveFromBottom !== undefined && this.#liveTailStartRow !== undefined) {
|
|
747
808
|
const documentRow = this.#selectableDocumentRowCount - anchor.liveFromBottom - 1;
|
|
748
809
|
if (documentRow >= this.#liveTailStartRow && documentRow < this.#selectableDocumentRowCount) {
|
|
749
810
|
const visible = this.#selectionRowAnchors.findIndex(candidate => candidate?.kind === "document" && candidate.row === documentRow);
|
|
@@ -755,10 +816,18 @@ export class TranscriptViewport {
|
|
|
755
816
|
}
|
|
756
817
|
}
|
|
757
818
|
}
|
|
819
|
+
// Concurrency: streaming may restyle a held endpoint's row; it keeps its document index.
|
|
820
|
+
// Following the tail would drag the press row toward the newest output.
|
|
821
|
+
if (selection.selecting && anchor.row >= 0 && anchor.row < documentRows.length) {
|
|
822
|
+
return {
|
|
823
|
+
line: anchor.row - this.#scrollTop,
|
|
824
|
+
anchor: documentSelectionAnchor(anchor.row, documentRows[anchor.row] ?? "", anchor.liveFromBottom),
|
|
825
|
+
};
|
|
826
|
+
}
|
|
758
827
|
return undefined;
|
|
759
828
|
};
|
|
760
|
-
const projectedAnchor = project(anchors.anchor);
|
|
761
|
-
const projectedHead = project(anchors.head);
|
|
829
|
+
const projectedAnchor = project(anchors.anchor, false);
|
|
830
|
+
const projectedHead = project(anchors.head, true);
|
|
762
831
|
if (projectedAnchor === undefined || projectedHead === undefined) {
|
|
763
832
|
this.clearSelection();
|
|
764
833
|
return;
|
|
@@ -778,11 +847,59 @@ export class TranscriptViewport {
|
|
|
778
847
|
// Invariant: mixed viewport/dock ranges project across the same complete frame regardless
|
|
779
848
|
// of origin. Document-only ranges remain clipped to the scrollable viewport when off-screen.
|
|
780
849
|
const rows = crossesDock ? this.#selectionRows.length : this.#viewportHeight;
|
|
781
|
-
|
|
850
|
+
const ordered = orderedTextSelection(dockOrderedSelection(this.#selection, anchors, this.#viewportHeight));
|
|
851
|
+
return visibleTextSelection(ordered, rows, this.#firstSelectableRow);
|
|
782
852
|
}
|
|
783
853
|
#updateCopyableSelection() {
|
|
784
|
-
|
|
854
|
+
// Performance: off-screen rows count as copyable by range alone; capture trims whitespace later.
|
|
855
|
+
const offscreen = offscreenSelectionRows(this.#selection, this.#selectionAnchors, this.#scrollTop, this.#firstSelectableRow, this.#viewportHeight, this.#documentRows.length);
|
|
856
|
+
const selectable = [offscreen.above, offscreen.below]
|
|
857
|
+
.some(range => range !== undefined && range.from < this.#selectableDocumentRowCount);
|
|
858
|
+
this.#copyableSelection = selectable && this.#visibleSelection() !== undefined || this.selectedText() !== null;
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
/**
|
|
862
|
+
* Invariant: dock rows follow the whole transcript, so a document endpoint scrolled below the
|
|
863
|
+
* viewport orders before every dock row instead of at its numeric frame line among them.
|
|
864
|
+
*/
|
|
865
|
+
function dockOrderedSelection(selection, anchors, viewportHeight) {
|
|
866
|
+
if (selection === undefined || anchors === undefined || anchors.anchor.kind !== "dock" && anchors.head.kind !== "dock") {
|
|
867
|
+
return selection;
|
|
785
868
|
}
|
|
869
|
+
const place = (point, kind) => kind === "document" && point.line >= viewportHeight ? { line: viewportHeight, column: 1, from: 0, to: 0 } : point;
|
|
870
|
+
return { ...selection, anchor: place(selection.anchor, anchors.anchor.kind), head: place(selection.head, anchors.head.kind) };
|
|
871
|
+
}
|
|
872
|
+
/** Document rows of a selection whose endpoints lie beyond the visible viewport edges. */
|
|
873
|
+
function offscreenSelectionRows(selection, anchors, scrollTop, firstSelectableRow, viewportHeight, documentRowCount) {
|
|
874
|
+
const ordered = orderedTextSelection(dockOrderedSelection(selection, anchors, viewportHeight));
|
|
875
|
+
if (selection === undefined || ordered === undefined || anchors === undefined)
|
|
876
|
+
return {};
|
|
877
|
+
const placed = dockOrderedSelection(selection, anchors, viewportHeight);
|
|
878
|
+
const anchorFirst = placed.anchor.line < placed.head.line
|
|
879
|
+
|| placed.anchor.line === placed.head.line && placed.anchor.column <= placed.head.column;
|
|
880
|
+
const top = anchorFirst ? anchors.anchor : anchors.head;
|
|
881
|
+
const bottom = anchorFirst ? anchors.head : anchors.anchor;
|
|
882
|
+
const topPoint = anchorFirst ? selection.anchor : selection.head;
|
|
883
|
+
const bottomPoint = anchorFirst ? selection.head : selection.anchor;
|
|
884
|
+
const firstVisibleRow = scrollTop + firstSelectableRow;
|
|
885
|
+
const pastVisibleRow = scrollTop + viewportHeight;
|
|
886
|
+
const columns = {
|
|
887
|
+
startColumn: selection.fullRow ? 0 : topPoint.from ?? Math.max(0, topPoint.column - 1),
|
|
888
|
+
endColumn: selection.fullRow ? Number.MAX_SAFE_INTEGER : bottomPoint.to ?? bottomPoint.column,
|
|
889
|
+
};
|
|
890
|
+
if (top.kind === "document" && bottom.kind === "dock" && top.row >= pastVisibleRow) {
|
|
891
|
+
return { ...columns, below: { from: top.row, to: documentRowCount - 1 }, belowFirst: true };
|
|
892
|
+
}
|
|
893
|
+
return {
|
|
894
|
+
...columns,
|
|
895
|
+
...(top.kind === "document" && top.row < firstVisibleRow && ordered.start.line < firstSelectableRow
|
|
896
|
+
? { above: { from: top.row, to: firstVisibleRow - 1 } }
|
|
897
|
+
: {}),
|
|
898
|
+
...(top.kind === "document" && bottom.kind === "document" && bottom.row >= pastVisibleRow
|
|
899
|
+
&& ordered.end.line >= viewportHeight
|
|
900
|
+
? { below: { from: pastVisibleRow, to: bottom.row } }
|
|
901
|
+
: {}),
|
|
902
|
+
};
|
|
786
903
|
}
|
|
787
904
|
function visibleTextSelection(selection, rowCount, firstRow) {
|
|
788
905
|
if (selection === undefined || rowCount <= firstRow || selection.end.line < firstRow || selection.start.line >= rowCount)
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
export declare const OWNED_UI_SETTINGS_VERSION =
|
|
1
|
+
export declare const OWNED_UI_SETTINGS_VERSION = 18;
|
|
2
2
|
export type OwnedUiSettingValue = string | number | boolean;
|
|
3
3
|
export type OwnedUiSettingApplication = "live" | "restart";
|
|
4
4
|
export interface OwnedUiSettingDeclaration {
|
|
5
5
|
readonly id: string;
|
|
6
|
-
/** Optional presentation label; the id remains the persistence key. */
|
|
7
6
|
readonly label?: string;
|
|
8
|
-
/** Optional settings-screen group for related owned controls. */
|
|
9
7
|
readonly section?: {
|
|
10
8
|
readonly id: string;
|
|
11
9
|
readonly title: string;
|
|
@@ -15,10 +13,6 @@ export interface OwnedUiSettingDeclaration {
|
|
|
15
13
|
readonly defaultValue: OwnedUiSettingValue;
|
|
16
14
|
readonly allowedValues: readonly OwnedUiSettingValue[];
|
|
17
15
|
}
|
|
18
|
-
/**
|
|
19
|
-
* Every A1 setting, keyed by its id. The table is the one declaration: ids, defaults, and allowed
|
|
20
|
-
* values are read from it, and `OwnedSettingValueOf` derives each getter's type from it.
|
|
21
|
-
*/
|
|
22
16
|
export declare const OWNED_SETTING_DECLARATIONS: Readonly<{
|
|
23
17
|
readonly updateCheck: Readonly<{
|
|
24
18
|
id: "updateCheck";
|
|
@@ -176,6 +170,18 @@ export declare const OWNED_SETTING_DECLARATIONS: Readonly<{
|
|
|
176
170
|
defaultValue: 8;
|
|
177
171
|
allowedValues: readonly [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
|
|
178
172
|
}>;
|
|
173
|
+
readonly tabsPosition: Readonly<{
|
|
174
|
+
id: "tabsPosition";
|
|
175
|
+
label: "Position";
|
|
176
|
+
section: Readonly<{
|
|
177
|
+
id: "tabs";
|
|
178
|
+
title: "Tabs";
|
|
179
|
+
}>;
|
|
180
|
+
description: "Place the resident tab bar at the top or bottom of the terminal.";
|
|
181
|
+
application: "live";
|
|
182
|
+
defaultValue: "top";
|
|
183
|
+
allowedValues: readonly ["top", "bottom"];
|
|
184
|
+
}>;
|
|
179
185
|
readonly tabsAutoName: Readonly<{
|
|
180
186
|
id: "tabsAutoName";
|
|
181
187
|
label: "Auto name";
|
|
@@ -190,9 +196,7 @@ export declare const OWNED_SETTING_DECLARATIONS: Readonly<{
|
|
|
190
196
|
}>;
|
|
191
197
|
}>;
|
|
192
198
|
export type OwnedSettingId = keyof typeof OWNED_SETTING_DECLARATIONS;
|
|
193
|
-
/** The value type a setting's declaration allows: a literal union for a choice, `number` for a stepped range. */
|
|
194
199
|
export type OwnedSettingValueOf<Id extends OwnedSettingId> = (typeof OWNED_SETTING_DECLARATIONS)[Id]["allowedValues"][number];
|
|
195
|
-
/** The declarations in declaration order, for resolution, persistence, and the settings screen. */
|
|
196
200
|
export declare const OWNED_UI_SETTING_DECLARATIONS: readonly OwnedUiSettingDeclaration[];
|
|
197
201
|
export declare function isOwnedSettingId(id: string): id is OwnedSettingId;
|
|
198
202
|
export declare function assertOwnedUiSettingDeclarations(declarations: readonly OwnedUiSettingDeclaration[]): void;
|
|
@@ -1,19 +1,14 @@
|
|
|
1
|
-
export const OWNED_UI_SETTINGS_VERSION =
|
|
1
|
+
export const OWNED_UI_SETTINGS_VERSION = 18;
|
|
2
2
|
const UI_ACCENT_COLOR_CHOICES = Object.freeze([
|
|
3
3
|
"purple", "blue", "cyan", "green", "orange", "pink",
|
|
4
4
|
]);
|
|
5
5
|
const MAX_ID_LENGTH = 64;
|
|
6
6
|
const ID_PATTERN = /^[a-z][a-z0-9]*(?:[A-Z][a-z0-9]*)*$/;
|
|
7
|
-
/** Declared first so the settings screen opens on it; sections follow first-declaration order. */
|
|
8
7
|
const GENERIC_SECTION = Object.freeze({ id: "generic", title: "Generic" });
|
|
9
8
|
const APPEARANCE_SECTION = Object.freeze({ id: "appearance", title: "Appearance" });
|
|
10
9
|
const SCROLL_SECTION = Object.freeze({ id: "scroll", title: "Scroll" });
|
|
11
10
|
const AGENT_SECTION = Object.freeze({ id: "agent", title: "Agent" });
|
|
12
11
|
const TABS_SECTION = Object.freeze({ id: "tabs", title: "Tabs" });
|
|
13
|
-
/**
|
|
14
|
-
* Every A1 setting, keyed by its id. The table is the one declaration: ids, defaults, and allowed
|
|
15
|
-
* values are read from it, and `OwnedSettingValueOf` derives each getter's type from it.
|
|
16
|
-
*/
|
|
17
12
|
export const OWNED_SETTING_DECLARATIONS = Object.freeze({
|
|
18
13
|
updateCheck: Object.freeze({
|
|
19
14
|
id: "updateCheck",
|
|
@@ -136,6 +131,15 @@ export const OWNED_SETTING_DECLARATIONS = Object.freeze({
|
|
|
136
131
|
defaultValue: 8,
|
|
137
132
|
allowedValues: Object.freeze([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]),
|
|
138
133
|
}),
|
|
134
|
+
tabsPosition: Object.freeze({
|
|
135
|
+
id: "tabsPosition",
|
|
136
|
+
label: "Position",
|
|
137
|
+
section: TABS_SECTION,
|
|
138
|
+
description: "Place the resident tab bar at the top or bottom of the terminal.",
|
|
139
|
+
application: "live",
|
|
140
|
+
defaultValue: "top",
|
|
141
|
+
allowedValues: Object.freeze(["top", "bottom"]),
|
|
142
|
+
}),
|
|
139
143
|
tabsAutoName: Object.freeze({
|
|
140
144
|
id: "tabsAutoName",
|
|
141
145
|
label: "Auto name",
|
|
@@ -146,7 +150,6 @@ export const OWNED_SETTING_DECLARATIONS = Object.freeze({
|
|
|
146
150
|
allowedValues: Object.freeze([true, false]),
|
|
147
151
|
}),
|
|
148
152
|
});
|
|
149
|
-
/** The declarations in declaration order, for resolution, persistence, and the settings screen. */
|
|
150
153
|
export const OWNED_UI_SETTING_DECLARATIONS = Object.freeze(Object.values(OWNED_SETTING_DECLARATIONS));
|
|
151
154
|
export function isOwnedSettingId(id) {
|
|
152
155
|
return Object.hasOwn(OWNED_SETTING_DECLARATIONS, id);
|
|
@@ -137,6 +137,13 @@ export const OWNED_UI_SETTINGS_MIGRATIONS = Object.freeze([
|
|
|
137
137
|
return { ...values };
|
|
138
138
|
},
|
|
139
139
|
}),
|
|
140
|
+
Object.freeze({
|
|
141
|
+
to: 18,
|
|
142
|
+
description: "Introduce the top resident tab-bar position default.",
|
|
143
|
+
migrate(values) {
|
|
144
|
+
return { ...values };
|
|
145
|
+
},
|
|
146
|
+
}),
|
|
140
147
|
]);
|
|
141
148
|
/** Shape-selected compatibility for the metadata-free product object. */
|
|
142
149
|
export function normalizeOwnedUiSettingsValues(source) {
|
|
@@ -303,7 +303,7 @@ Tab status is an A1-owned state machine fed by `bridge.status`:
|
|
|
303
303
|
| Tab child exits or crashes | holder sees the child exit | If the exit was not requested: `crashed`. The holder respawns `ui.js --tab --session <file>` with backoff 1 s, 5 s, 30 s. After 3 restarts in 10 min: `failed`, with a banner `✗ <reason> — [r] retry · [f] start fresh · [alt+w] close`. An interrupted prompt (reported via the bridge, or detected as a trailing user entry) is offered back into the editor and never resent. Child stderr, if separately available, is a private content-bearing recovery artifact, never an ordinary diagnostic log. |
|
|
304
304
|
| Holder crash | pipe EOF plus verified process identity/exit | Pseudoterminal loss is not proof that every descendant exited. Verify the old writer/tree is gone and acquire its writer lease before resuming in a new holder; otherwise keep the tab blocked without duplicate writers. |
|
|
305
305
|
| Holder hang | holder control heartbeat every 5 s, 3 missed over a healthy supervision path, then one heartbeat of grace for the holder-local watchdog record | Terminate only the verified holder/tree within bounded deadlines, then follow the lease-gated recovery path. |
|
|
306
|
-
| A1 child hang | missing child event-loop heartbeat over a healthy bridge path |
|
|
306
|
+
| A1 child hang | missing child event-loop heartbeat over a healthy bridge path | Record `unresponsive` at 30 s without changing the chip or painting a notice; restart at `tabsUnresponsiveRestartSeconds` (default 120), preserving journal/screen. A server/bridge transport outage alone is not proof of a child hang. |
|
|
307
307
|
| Server crash | clients and holders see the pipe EOF | Holders keep running. A client or holder starts a replacement, which acquires the released writer lease, increments the epoch, derives expected credentials, and re-admits identity-verified holders. A live but unresponsive recorded owner is terminated only after native identity verification; an unverifiable owner blocks replacement. After 3 starts in 60 s, clients show `✗ tab host stopped — [r] restart · [q] quit`. |
|
|
308
308
|
| Attach client crash or terminal close | the server sees the pipe EOF | Nothing happens to tabs. Pending requests stay `needs-input`. |
|
|
309
309
|
| Reboot or logout | the platform boot identity differs, or no verified holders exist for a new OS session | Tabs are not restored in this version. The server moves the previous tab set to registry history, and bare `a1` starts with one new tab. Every previous session stays resumable through the normal session picker or `a1 --session`. A pending journaled prompt is offered when its session is next opened in a tab, for up to seven days. Restoring tabs after reboot is a follow-up change. |
|
|
@@ -377,7 +377,7 @@ Tab status is an A1-owned state machine fed by `bridge.status`:
|
|
|
377
377
|
Internal resources remain bounded without imposing a user-visible tab-count quota.
|
|
378
378
|
|
|
379
379
|
- The only user-facing Tabs setting during resident launches is `tabsAutoName` (default on). Legacy `tabsMax` data is preserved but ignored. Tab enablement is not a setting. Direct `a1 solo` and `a1 pi` launches omit the Tabs section entirely.
|
|
380
|
-
- Unresponsive restart
|
|
380
|
+
- Unresponsive restart timing is internal fixed behavior at 120 seconds; there is no progress-stall notice; concurrent starts use internal default 2/hard cap 8 and prewarm uses internal count/hard cap 1.
|
|
381
381
|
- Every open tab keeps its holder and A1 process until explicit close. There is no idle-suspension setting, timer, lifecycle, or glyph.
|
|
382
382
|
- Scrollback is capped at 10 MiB per holder, and the replay after a server restart is the retained model itself.
|
|
383
383
|
- The server exits after 10 minutes with no attached client and no live user tab; the prewarmed standby does not keep it alive and is stopped first.
|
|
@@ -479,8 +479,8 @@ The v2 daemon was unreliable for structural reasons, and a forensic pass over it
|
|
|
479
479
|
**Agents that stop making progress.** Liveness and progress are separate signals:
|
|
480
480
|
|
|
481
481
|
- Holder heartbeat: a Rust main-loop tick every 5 s seen by the server; three missed ticks over an otherwise healthy supervision path plus one heartbeat of grace trigger verified holder recovery. The grace keeps external termination behind the holder's 15-second process-local watchdog so its own crash evidence wins despite scheduler jitter.
|
|
482
|
-
- A1 heartbeat: the child's Node event loop, seen through the bridge. After 30 s
|
|
483
|
-
- Agent progress: model or tool events
|
|
482
|
+
- A1 heartbeat: the child's Node event loop, seen through the bridge. After 30 s the server records `unresponsive` in the registry and counters, without changing the presented status, chip glyph, or colour and without a notice row or restart key; after `tabsUnresponsiveRestartSeconds` (default 120) the tab restarts from its session with its journal and last screen preserved. A missing server or bridge transport degrades status, not proof of a child event-loop stall; reacquire liveness evidence before destructive recovery.
|
|
483
|
+
- Agent progress: not supervised. A working tab without model or tool events keeps its working status indefinitely, gains no transcript notice, and is never auto-killed, because a legitimate tool can run for a long time.
|
|
484
484
|
|
|
485
485
|
**Data-loss envelope.** Distinguish process termination, power loss, and loss of both holder and child:
|
|
486
486
|
|
package/package.json
CHANGED