@timurproko/a1 0.2.7-dev.767 → 0.2.7-dev.772
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 +30 -0
- package/bin/cli.js +4 -0
- package/bin/tab-launch-context.d.ts +3 -0
- package/bin/tab-launch-context.js +52 -3
- package/bin/ui.js +6 -3
- package/dist/app/session-shell/session-shell-root.d.ts +0 -1
- package/dist/app/session-shell/session-shell-root.js +1 -1
- package/dist/app/session-shell/session-shell.js +3 -8
- package/dist/app/session-shell/shell-host.d.ts +2 -0
- package/dist/app/session-shell/shell-host.js +20 -1
- package/dist/cli/dispatch.d.ts +11 -1
- package/dist/cli/dispatch.js +79 -5
- package/dist/cli/tabs-request.d.ts +7 -0
- package/dist/cli/tabs-request.js +1 -0
- package/dist/cli/tabs.d.ts +14 -0
- package/dist/cli/tabs.js +140 -0
- package/dist/composition/owned-ui.d.ts +8 -2
- package/dist/composition/owned-ui.js +109 -12
- package/dist/features/launch/index.d.ts +1 -1
- package/dist/features/launch/intent.d.ts +3 -1
- package/dist/features/launch/intent.js +7 -4
- package/dist/features/resident-tab/auto-name.d.ts +11 -0
- package/dist/features/resident-tab/auto-name.js +42 -0
- package/dist/features/resident-tab/index.d.ts +3 -0
- package/dist/features/resident-tab/index.js +3 -0
- package/dist/features/resident-tab/shortcuts.d.ts +10 -0
- package/dist/features/resident-tab/shortcuts.js +35 -0
- package/dist/features/resident-tab/status-deriver.d.ts +9 -0
- package/dist/features/resident-tab/status-deriver.js +15 -0
- package/dist/foundation/launch-context/index.d.ts +4 -0
- package/dist/foundation/launch-context/index.js +10 -0
- package/dist/foundation/release/bootstrap.d.ts +4 -2
- package/dist/foundation/release/bootstrap.js +11 -8
- package/dist/foundation/release/cohort-state.d.ts +1 -1
- package/dist/foundation/release/cohort-state.js +1 -1
- package/dist/foundation/release/release-gc.d.ts +3 -0
- package/dist/foundation/release/release-gc.js +44 -8
- package/dist/foundation/resident-tabs/bounded-host-command.d.ts +16 -0
- package/dist/foundation/resident-tabs/bounded-host-command.js +35 -0
- package/dist/foundation/resident-tabs/bridge-client.d.ts +18 -3
- package/dist/foundation/resident-tabs/bridge-client.js +87 -7
- package/dist/foundation/resident-tabs/doctor.d.ts +29 -0
- package/dist/foundation/resident-tabs/doctor.js +166 -0
- package/dist/foundation/resident-tabs/ensure-terminal-host.d.ts +2 -1
- package/dist/foundation/resident-tabs/ensure-terminal-host.js +33 -7
- package/dist/foundation/resident-tabs/index.d.ts +7 -1
- package/dist/foundation/resident-tabs/index.js +4 -1
- package/dist/foundation/resident-tabs/maintenance.d.ts +42 -0
- package/dist/foundation/resident-tabs/maintenance.js +78 -0
- package/dist/foundation/resident-tabs/release-holds.d.ts +27 -0
- package/dist/foundation/resident-tabs/release-holds.js +77 -0
- package/dist/foundation/resident-tabs/routing.d.ts +4 -3
- package/dist/foundation/resident-tabs/routing.js +6 -4
- package/dist/foundation/resident-tabs/settings.d.ts +7 -2
- package/dist/foundation/resident-tabs/settings.js +17 -16
- package/dist/foundation/resident-tabs/terminal-host-artifact.d.ts +18 -0
- package/dist/foundation/resident-tabs/terminal-host-artifact.js +53 -0
- package/dist/foundation/resident-tabs/zip-archive.d.ts +9 -0
- package/dist/foundation/resident-tabs/zip-archive.js +56 -0
- package/dist/foundation/session-writer-lock/index.d.ts +5 -0
- package/dist/foundation/session-writer-lock/index.js +13 -9
- package/dist/integrations/pi/components/shell-hotkey-sections.js +1 -1
- package/dist/integrations/pi/components/shell-presenters-info.d.ts +4 -2
- package/dist/integrations/pi/components/shell-presenters-info.js +38 -4
- package/dist/integrations/pi/engine/adapter.d.ts +1 -0
- package/dist/integrations/pi/engine/adapter.js +3 -0
- package/dist/integrations/pi/engine/prompt-suggestions.d.ts +1 -0
- package/dist/integrations/pi/engine/prompt-suggestions.js +40 -0
- package/dist/native/darwin-arm64/a1_session_lock.node +0 -0
- package/dist/native/darwin-arm64/manifest.json +1 -1
- package/dist/native/darwin-arm64/terminal-host +0 -0
- package/dist/native/darwin-arm64/terminal-host-THIRD-PARTY-NOTICES.txt +10500 -0
- package/dist/native/darwin-arm64/terminal-host.manifest.json +44 -0
- package/dist/native/darwin-x64/a1_session_lock.node +0 -0
- package/dist/native/darwin-x64/manifest.json +20 -0
- package/dist/native/darwin-x64/process-guardian +0 -0
- package/dist/native/darwin-x64/terminal-host +0 -0
- package/dist/native/darwin-x64/terminal-host-THIRD-PARTY-NOTICES.txt +10737 -0
- package/dist/native/darwin-x64/terminal-host.manifest.json +44 -0
- package/dist/native/linux-x64/a1_session_lock.node +0 -0
- package/dist/native/linux-x64/manifest.json +1 -1
- package/dist/native/linux-x64/terminal-host +0 -0
- package/dist/native/linux-x64/terminal-host-THIRD-PARTY-NOTICES.txt +11226 -0
- package/dist/native/linux-x64/terminal-host.manifest.json +44 -0
- package/dist/native/win32-x64/a1_session_lock.node +0 -0
- package/dist/native/win32-x64/manifest.json +2 -2
- package/dist/native/win32-x64/process-guardian.exe +0 -0
- package/dist/native/win32-x64/terminal-host-THIRD-PARTY-NOTICES.txt +10757 -0
- package/dist/native/win32-x64/terminal-host.exe +0 -0
- package/dist/native/win32-x64/terminal-host.manifest.json +44 -0
- package/dist/ui/settings/declarations.d.ts +19 -43
- package/dist/ui/settings/declarations.js +19 -35
- package/dist/ui/settings/index.d.ts +1 -1
- package/dist/ui/settings/index.js +1 -1
- package/dist/ui/settings/manager.d.ts +1 -0
- package/dist/ui/settings/manager.js +9 -1
- package/dist/ui/settings/migrations.js +12 -0
- package/dist/ui/settings/resolution.d.ts +0 -2
- package/dist/ui/settings/resolution.js +1 -13
- package/dist/ui/settings/sections.js +0 -4
- package/docs/architecture/evidence/resident-tabs/linux-x64.md +37 -0
- package/docs/architecture/evidence/resident-tabs/macos-arm64.md +36 -0
- package/docs/architecture/evidence/resident-tabs/macos-x64.md +35 -0
- package/docs/architecture/evidence/resident-tabs/windows-x64.md +36 -0
- package/docs/architecture/internal-naming.md +2 -0
- package/docs/architecture/process-guardian-provenance.md +2 -0
- package/docs/architecture/resident-tabs-requirements.md +1 -563
- package/docs/architecture/resident-tabs.md +166 -94
- package/docs/architecture/terminal-host-provenance.md +13 -3
- package/docs/architecture/toolchain.md +1 -1
- package/docs/ci-release-runbook.md +2 -2
- package/docs/features/launch-profiles.md +11 -8
- package/docs/manual-resident-tabs-acceptance.md +51 -0
- package/docs/validation.md +9 -1
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -57,6 +57,12 @@ Launch A1 (profile: `~/.a1/agent`):
|
|
|
57
57
|
a1
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
+
Launch A1 in single agent mode:
|
|
61
|
+
|
|
62
|
+
```text
|
|
63
|
+
a1 solo
|
|
64
|
+
```
|
|
65
|
+
|
|
60
66
|
Show all commands:
|
|
61
67
|
|
|
62
68
|
```text
|
|
@@ -77,6 +83,30 @@ a1 pi
|
|
|
77
83
|
|
|
78
84
|
<p align="center"><picture><source media="(prefers-color-scheme: dark)" srcset="docs/assets/readme/separator-dark.svg"><img src="docs/assets/readme/separator.svg" alt="" width="280"></picture></p>
|
|
79
85
|
|
|
86
|
+
## Tabs
|
|
87
|
+
|
|
88
|
+
Tabs keep running after their terminal closes. List them, or stop one; its session stays resumable:
|
|
89
|
+
|
|
90
|
+
```text
|
|
91
|
+
a1 tabs
|
|
92
|
+
a1 tabs stop <id>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Stop every tab and the resident host, for example before going back to single agent mode with `a1 solo`:
|
|
96
|
+
|
|
97
|
+
```text
|
|
98
|
+
a1 tabs host stop
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Show host health, or write a redacted diagnostic bundle:
|
|
102
|
+
|
|
103
|
+
```text
|
|
104
|
+
a1 tabs host status
|
|
105
|
+
a1 tabs doctor
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
<p align="center"><picture><source media="(prefers-color-scheme: dark)" srcset="docs/assets/readme/separator-dark.svg"><img src="docs/assets/readme/separator.svg" alt="" width="280"></picture></p>
|
|
109
|
+
|
|
80
110
|
## Update
|
|
81
111
|
|
|
82
112
|
Install the release:
|
package/bin/cli.js
CHANGED
|
@@ -69,6 +69,10 @@ process.exitCode = await dispatchCli(process.argv.slice(2), {
|
|
|
69
69
|
},
|
|
70
70
|
});
|
|
71
71
|
},
|
|
72
|
+
tabs: async request => {
|
|
73
|
+
const { runTabsCommand } = await import("../dist/cli/tabs.js");
|
|
74
|
+
return await runTabsCommand(request, { packageRoot: fileURLToPath(packageRoot) });
|
|
75
|
+
},
|
|
72
76
|
}, {
|
|
73
77
|
stdout: message => process.stdout.write(message),
|
|
74
78
|
stderr: message => process.stderr.write(message),
|
|
@@ -12,6 +12,8 @@ export function assignFreshSessionRuntimeId(
|
|
|
12
12
|
createId: () => string,
|
|
13
13
|
): string;
|
|
14
14
|
|
|
15
|
+
export function waitForStandbyPromotion(input: NodeJS.ReadStream, promotionFile?: string): Promise<void>;
|
|
16
|
+
|
|
15
17
|
export function consumeTabLaunchContext(
|
|
16
18
|
arguments_: readonly string[],
|
|
17
19
|
environment: Record<string, string | undefined>,
|
|
@@ -20,4 +22,5 @@ export function consumeTabLaunchContext(
|
|
|
20
22
|
readonly tabCredentials: TabLaunchCredentials | undefined;
|
|
21
23
|
readonly tabDataDir?: string;
|
|
22
24
|
readonly tabIncarnation?: number;
|
|
25
|
+
readonly tabStandby: boolean;
|
|
23
26
|
};
|
|
@@ -1,8 +1,14 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
|
|
1
3
|
const TAB_ENVIRONMENT_NAMES = ["A1_TAB_ID", "A1_TAB_TOKEN", "A1_TAB_ENDPOINT"];
|
|
2
4
|
|
|
3
5
|
/** @param {string} reason */
|
|
4
6
|
export function formatResidentFallbackNotice(reason) {
|
|
5
|
-
|
|
7
|
+
// Protocol: a host of another generation is never replaced automatically; only the user stops it.
|
|
8
|
+
if (reason === "incompatible generation") {
|
|
9
|
+
return "A1 normal mode unavailable (running tabs use another version); running in single agent mode. Run `a1 tabs host stop` to restart them on this version.";
|
|
10
|
+
}
|
|
11
|
+
return `A1 normal mode unavailable (${reason}); running in single agent mode.`;
|
|
6
12
|
}
|
|
7
13
|
|
|
8
14
|
/**
|
|
@@ -17,6 +23,46 @@ export function assignFreshSessionRuntimeId(environment, name, createId) {
|
|
|
17
23
|
return id;
|
|
18
24
|
}
|
|
19
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Keep the standby below the session/runtime boundary until its authenticated holder promotes it.
|
|
28
|
+
* Platform: Windows ConPTY may drop the in-band marker, so the holder also creates the promotion
|
|
29
|
+
* file inside the tab's private data directory; whichever arrives first promotes the standby.
|
|
30
|
+
* @param {NodeJS.ReadStream} input
|
|
31
|
+
* @param {string} [promotionFile]
|
|
32
|
+
*/
|
|
33
|
+
export function waitForStandbyPromotion(input, promotionFile) {
|
|
34
|
+
return /** @type {Promise<void>} */ (new Promise((resolve, reject) => {
|
|
35
|
+
const marker = Buffer.from("\x1b]a1;standby-promote\x07");
|
|
36
|
+
let buffered = Buffer.alloc(0);
|
|
37
|
+
/** @type {NodeJS.Timeout | undefined} */
|
|
38
|
+
let poll;
|
|
39
|
+
const cleanup = () => {
|
|
40
|
+
if (poll !== undefined) clearInterval(poll);
|
|
41
|
+
input.off("data", onData);
|
|
42
|
+
input.off("error", onError);
|
|
43
|
+
input.pause();
|
|
44
|
+
if (input.isTTY) input.setRawMode(false);
|
|
45
|
+
};
|
|
46
|
+
/** @param {Error} error */
|
|
47
|
+
const onError = error => { cleanup(); reject(error); };
|
|
48
|
+
/** @param {Buffer | string} chunk */
|
|
49
|
+
const onData = chunk => {
|
|
50
|
+
const bytes = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
|
|
51
|
+
buffered = Buffer.concat([buffered, bytes]).subarray(-marker.length);
|
|
52
|
+
if (buffered.equals(marker)) { cleanup(); resolve(undefined); }
|
|
53
|
+
};
|
|
54
|
+
if (input.isTTY) input.setRawMode(true);
|
|
55
|
+
input.on("data", onData);
|
|
56
|
+
input.on("error", onError);
|
|
57
|
+
input.resume();
|
|
58
|
+
if (promotionFile !== undefined) {
|
|
59
|
+
poll = setInterval(() => {
|
|
60
|
+
if (existsSync(promotionFile)) { cleanup(); resolve(undefined); }
|
|
61
|
+
}, 10);
|
|
62
|
+
}
|
|
63
|
+
}));
|
|
64
|
+
}
|
|
65
|
+
|
|
20
66
|
/**
|
|
21
67
|
* Security: consume bridge credentials into caller-private memory and erase their inheritable
|
|
22
68
|
* environment entries before any extension-capable module loads.
|
|
@@ -28,6 +74,9 @@ export function consumeTabLaunchContext(arguments_, environment) {
|
|
|
28
74
|
const tabArgument = uiArguments.indexOf("--tab");
|
|
29
75
|
const tabMode = tabArgument !== -1;
|
|
30
76
|
if (tabMode) uiArguments.splice(tabArgument, 1);
|
|
77
|
+
const standbyArgument = uiArguments.indexOf("--tab-standby");
|
|
78
|
+
const tabStandby = standbyArgument !== -1;
|
|
79
|
+
if (tabStandby) uiArguments.splice(standbyArgument, 1);
|
|
31
80
|
const dataArgument = uiArguments.indexOf("--tab-data-dir");
|
|
32
81
|
const tabDataDir = dataArgument === -1 ? undefined : uiArguments[dataArgument + 1];
|
|
33
82
|
if (dataArgument !== -1) uiArguments.splice(dataArgument, 2);
|
|
@@ -36,10 +85,10 @@ export function consumeTabLaunchContext(arguments_, environment) {
|
|
|
36
85
|
if (incarnationArgument !== -1) uiArguments.splice(incarnationArgument, 2);
|
|
37
86
|
const [tabId, token, endpoint] = TAB_ENVIRONMENT_NAMES.map(name => environment[name]);
|
|
38
87
|
for (const name of TAB_ENVIRONMENT_NAMES) delete environment[name];
|
|
39
|
-
if (!tabMode) return { uiArguments, tabCredentials: undefined };
|
|
88
|
+
if (!tabMode) return { uiArguments, tabCredentials: undefined, tabStandby: false };
|
|
40
89
|
if (!tabId || !token || !endpoint || !tabDataDir || tabDataDir.includes("\0")
|
|
41
90
|
|| typeof tabIncarnation !== "number" || !Number.isSafeInteger(tabIncarnation) || tabIncarnation < 1) {
|
|
42
91
|
throw new Error("A1 tab mode requires bridge credentials, recovery directory, and incarnation");
|
|
43
92
|
}
|
|
44
|
-
return { uiArguments, tabCredentials: { tabId, token, endpoint }, tabDataDir, tabIncarnation };
|
|
93
|
+
return { uiArguments, tabCredentials: { tabId, token, endpoint }, tabDataDir, tabIncarnation, tabStandby };
|
|
45
94
|
}
|
package/bin/ui.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// Security: this helper has no A1, Pi, tool, or extension dependency and erases credentials before those modules load.
|
|
4
|
-
const { assignFreshSessionRuntimeId, consumeTabLaunchContext, formatResidentFallbackNotice } = await import("./tab-launch-context.js");
|
|
5
|
-
const { uiArguments, tabCredentials, tabDataDir, tabIncarnation } = consumeTabLaunchContext(process.argv.slice(2), process.env);
|
|
4
|
+
const { assignFreshSessionRuntimeId, consumeTabLaunchContext, formatResidentFallbackNotice, waitForStandbyPromotion } = await import("./tab-launch-context.js");
|
|
5
|
+
const { uiArguments, tabCredentials, tabDataDir, tabIncarnation, tabStandby } = consumeTabLaunchContext(process.argv.slice(2), process.env);
|
|
6
6
|
|
|
7
7
|
// Invariant: one terminal module identity per process, decided before any A1 or Pi module loads.
|
|
8
8
|
const { installPinnedPiTuiResolver } = await import("./module-resolver.js");
|
|
9
9
|
installPinnedPiTuiResolver(new URL("..", import.meta.url).pathname.replace(/^\/(.:)/, "$1").replace(/\/$/, ""));
|
|
10
10
|
const startup = await import("../dist/foundation/startup/startup-runtime.js");
|
|
11
11
|
startup.enableEnvironmentCompileCache(process.env);
|
|
12
|
-
const { readLaunchContext } = await import("../dist/foundation/launch-context/index.js");
|
|
12
|
+
const { hidesTabSettings, readLaunchContext } = await import("../dist/foundation/launch-context/index.js");
|
|
13
13
|
const launchContext = readLaunchContext(process.env, "profile");
|
|
14
14
|
const profile = launchContext.launchProfile;
|
|
15
15
|
if (profile === undefined) throw new Error("A1 launch profile is missing");
|
|
@@ -52,6 +52,8 @@ const [
|
|
|
52
52
|
{ composeOwnedUi },
|
|
53
53
|
] = await modules;
|
|
54
54
|
|
|
55
|
+
if (tabStandby) await waitForStandbyPromotion(process.stdin, tabDataDir === undefined ? undefined : join(tabDataDir, "standby-promote"));
|
|
56
|
+
|
|
55
57
|
// Compatibility: before the composition uses pinned Pi's terminal stack: confirm A1 and Pi
|
|
56
58
|
// resolve it to the same copy, so extensions and the owned UI share one module identity.
|
|
57
59
|
identity.assertSinglePiTuiModuleAtLaunch(packageRootPath, (/** @type {string} */ message) => process.stderr.write(message));
|
|
@@ -76,6 +78,7 @@ Promise.resolve().then(async () => {
|
|
|
76
78
|
cwd: process.cwd(),
|
|
77
79
|
profileId,
|
|
78
80
|
ownedSurfaces,
|
|
81
|
+
...(hidesTabSettings(launchContext) ? { hideTabSettings: true } : {}),
|
|
79
82
|
...(processInspector === null ? {} : { inspectProcess: pid => processInspector.observe(pid) }),
|
|
80
83
|
projectTrustPrompt: createConsoleProjectTrustPrompt({ presentation: profile === "a1" ? "bare" : "comparison" }),
|
|
81
84
|
sessionForkPrompt: createConsoleSessionForkPrompt(),
|
|
@@ -46,7 +46,6 @@ export interface OwnedUiShellEngineOptions {
|
|
|
46
46
|
/** Resident tab quit routes detach the outer client without stopping this engine session. */
|
|
47
47
|
readonly residentTab?: {
|
|
48
48
|
requestDetach(): Promise<boolean>;
|
|
49
|
-
requestRestart?(): Promise<boolean>;
|
|
50
49
|
reportStatus?(status: "working" | "idle" | "stalled"): void;
|
|
51
50
|
readonly stallNoticeSeconds?: number;
|
|
52
51
|
admit?(input: {
|
|
@@ -916,7 +916,7 @@ export class OwnedUiSessionShellRoot {
|
|
|
916
916
|
if (result.command === "hotkeys" && result.outcome === "completed") {
|
|
917
917
|
this.#lastWorkflowStatusId = undefined;
|
|
918
918
|
const presentation = this.hotkeysPresentation();
|
|
919
|
-
const hotkeys = createPiShellHotkeys(presentation.bindings, presentation.getShortcuts, presentation.profile);
|
|
919
|
+
const hotkeys = createPiShellHotkeys(presentation.bindings, presentation.getShortcuts, presentation.profile, presentation.residentTabs);
|
|
920
920
|
this.#appendAnchoredWorkflowComponent(width => hotkeys.render(width), () => hotkeys.dispose?.());
|
|
921
921
|
return;
|
|
922
922
|
}
|
|
@@ -1276,7 +1276,7 @@ export class OwnedUiSessionPresenter {
|
|
|
1276
1276
|
this.#residentStalled = true;
|
|
1277
1277
|
resident.reportStatus?.("stalled");
|
|
1278
1278
|
const minutes = Math.max(1, Math.round(seconds / 60));
|
|
1279
|
-
this.root.appendWorkflowStatus(`No progress for ${minutes} min — Esc interrupts ·
|
|
1279
|
+
this.root.appendWorkflowStatus(`No progress for ${minutes} min — Esc interrupts · use the tab restart action to restart`);
|
|
1280
1280
|
this.#terminal.requestRender();
|
|
1281
1281
|
}, seconds * 1_000);
|
|
1282
1282
|
this.#residentProgressTimer.unref?.();
|
|
@@ -1759,7 +1759,8 @@ export class OwnedUiSessionPresenter {
|
|
|
1759
1759
|
this.#dialogId = dialog.id;
|
|
1760
1760
|
}
|
|
1761
1761
|
hotkeysPresentation() {
|
|
1762
|
-
|
|
1762
|
+
const presentation = this.root.hotkeysPresentation();
|
|
1763
|
+
return this.#residentTabOptions === undefined ? presentation : { ...presentation, residentTabs: true };
|
|
1763
1764
|
}
|
|
1764
1765
|
async #slashCommand(text) {
|
|
1765
1766
|
const body = text.slice(1).trim();
|
|
@@ -1768,12 +1769,6 @@ export class OwnedUiSessionPresenter {
|
|
|
1768
1769
|
const argument = separator < 0 ? "" : body.slice(separator + 1).trimStart();
|
|
1769
1770
|
if (this.#routeHost?.claims(name))
|
|
1770
1771
|
return this.#openOwnedRoute(name);
|
|
1771
|
-
if (name === "restart-tab" && this.#residentTabOptions?.requestRestart !== undefined) {
|
|
1772
|
-
const accepted = await this.#residentTabOptions.requestRestart().catch(() => false);
|
|
1773
|
-
this.root.appendWorkflowStatus(accepted ? "Restarting resident tab…" : "Resident tab restart is unavailable.");
|
|
1774
|
-
this.#terminal.requestRender();
|
|
1775
|
-
return accepted ? { outcome: "completed", diagnostic: null } : rejected("resident tab restart is unavailable");
|
|
1776
|
-
}
|
|
1777
1772
|
if (this.#isWorkflowRoute(name))
|
|
1778
1773
|
return this.runWorkflow({ command: name, argument });
|
|
1779
1774
|
if (this.#skillsCollapsed()) {
|
|
@@ -98,6 +98,8 @@ export declare class OwnedUiTerminalHost implements OwnedUiTerminalHostContract<
|
|
|
98
98
|
active(): OwnedUiHostedPresenter | null;
|
|
99
99
|
requestRender(from: OwnedUiHostedPresenter, force?: boolean): void;
|
|
100
100
|
start(): void;
|
|
101
|
+
/** Hidden resident tabs retain engine/status work but stop producing terminal animation frames. */
|
|
102
|
+
setPresentationVisible(visible: boolean): void;
|
|
101
103
|
/** Closes one presenter. Closing the last one ends the terminal. */
|
|
102
104
|
close(presenter: OwnedUiHostedPresenter): Promise<void>;
|
|
103
105
|
dispose(): Promise<void>;
|
|
@@ -42,6 +42,8 @@ export class OwnedUiTerminalHost {
|
|
|
42
42
|
#focused = false;
|
|
43
43
|
#pointerReporting = false;
|
|
44
44
|
#started = false;
|
|
45
|
+
#presentationVisible = true;
|
|
46
|
+
#releaseVisibilityHold;
|
|
45
47
|
#ending;
|
|
46
48
|
constructor(options) {
|
|
47
49
|
this.#customViewport = options.layout === "custom-viewport";
|
|
@@ -147,7 +149,7 @@ export class OwnedUiTerminalHost {
|
|
|
147
149
|
return this.#active;
|
|
148
150
|
}
|
|
149
151
|
requestRender(from, force = false) {
|
|
150
|
-
if (from === this.#active)
|
|
152
|
+
if (this.#presentationVisible && from === this.#active)
|
|
151
153
|
this.runtime.requestRender(force);
|
|
152
154
|
}
|
|
153
155
|
start() {
|
|
@@ -155,9 +157,26 @@ export class OwnedUiTerminalHost {
|
|
|
155
157
|
return;
|
|
156
158
|
this.#started = true;
|
|
157
159
|
this.runtime.start();
|
|
160
|
+
if (!this.#presentationVisible)
|
|
161
|
+
this.#releaseVisibilityHold = this.runtime.beginPresentationHold();
|
|
158
162
|
if (this.#customViewport)
|
|
159
163
|
this.#setPointerReporting(true);
|
|
160
164
|
}
|
|
165
|
+
/** Hidden resident tabs retain engine/status work but stop producing terminal animation frames. */
|
|
166
|
+
setPresentationVisible(visible) {
|
|
167
|
+
if (this.#presentationVisible === visible)
|
|
168
|
+
return;
|
|
169
|
+
this.#presentationVisible = visible;
|
|
170
|
+
if (!this.#started || this.#ending !== undefined)
|
|
171
|
+
return;
|
|
172
|
+
if (visible) {
|
|
173
|
+
this.#releaseVisibilityHold?.();
|
|
174
|
+
this.#releaseVisibilityHold = undefined;
|
|
175
|
+
}
|
|
176
|
+
else if (this.#releaseVisibilityHold === undefined) {
|
|
177
|
+
this.#releaseVisibilityHold = this.runtime.beginPresentationHold();
|
|
178
|
+
}
|
|
179
|
+
}
|
|
161
180
|
/** Closes one presenter. Closing the last one ends the terminal. */
|
|
162
181
|
close(presenter) {
|
|
163
182
|
if (this.#ending !== undefined || !this.#presenters.has(presenter))
|
package/dist/cli/dispatch.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { type InteractiveLaunchIntent, type LaunchProfileId, type SessionSelection } from "../features/launch/index.js";
|
|
1
|
+
import { type InteractiveLaunchIntent, type InteractiveLaunchMode, type LaunchProfileId, type SessionSelection } from "../features/launch/index.js";
|
|
2
2
|
import type { CliCapabilities } from "./capabilities.js";
|
|
3
3
|
import type { PackageCommandRequest } from "./packages.js";
|
|
4
4
|
import type { SessionContextRequest } from "./session-context.js";
|
|
5
|
+
import type { TabsRequest } from "./tabs-request.js";
|
|
5
6
|
import { type PackageCliVerb, type PackageCommandNamespace, type PackageSyntaxDiagnostic } from "./package-messages.js";
|
|
6
7
|
export type UpdateChannel = "stable" | "next";
|
|
7
8
|
export interface CliHandlers {
|
|
@@ -10,6 +11,7 @@ export interface CliHandlers {
|
|
|
10
11
|
readonly update: (channel: UpdateChannel, target?: string) => Promise<number>;
|
|
11
12
|
readonly packages: (request: PackageCommandRequest) => Promise<number>;
|
|
12
13
|
readonly sessionContext: (request: SessionContextRequest) => Promise<number>;
|
|
14
|
+
readonly tabs: (request: TabsRequest) => Promise<number>;
|
|
13
15
|
}
|
|
14
16
|
export interface CliOutput {
|
|
15
17
|
readonly stdout: (message: string) => void;
|
|
@@ -17,6 +19,8 @@ export interface CliOutput {
|
|
|
17
19
|
}
|
|
18
20
|
export declare function cliUsage(capabilities: CliCapabilities): string;
|
|
19
21
|
export declare function cliHelp(capabilities: CliCapabilities): string;
|
|
22
|
+
/** Focused help for the resident-tab maintenance forms; printing it contacts no host. */
|
|
23
|
+
export declare function tabsHelp(): string;
|
|
20
24
|
export declare function dispatchCli(arguments_: readonly string[], handlers: CliHandlers, output: CliOutput, capabilities: CliCapabilities): Promise<number>;
|
|
21
25
|
export type CliCommand = {
|
|
22
26
|
readonly kind: "noop";
|
|
@@ -33,6 +37,7 @@ export type CliCommand = {
|
|
|
33
37
|
} | {
|
|
34
38
|
readonly kind: "launch";
|
|
35
39
|
readonly profileId: LaunchProfileId;
|
|
40
|
+
readonly launchMode: InteractiveLaunchMode;
|
|
36
41
|
readonly sessionSelection?: SessionSelection;
|
|
37
42
|
} | {
|
|
38
43
|
readonly kind: "version";
|
|
@@ -46,6 +51,11 @@ export type CliCommand = {
|
|
|
46
51
|
} | {
|
|
47
52
|
readonly kind: "session-context";
|
|
48
53
|
readonly request: SessionContextRequest;
|
|
54
|
+
} | {
|
|
55
|
+
readonly kind: "tabs";
|
|
56
|
+
readonly request: TabsRequest;
|
|
57
|
+
} | {
|
|
58
|
+
readonly kind: "tabs-help";
|
|
49
59
|
} | {
|
|
50
60
|
readonly kind: "error";
|
|
51
61
|
readonly message: string;
|
package/dist/cli/dispatch.js
CHANGED
|
@@ -6,6 +6,7 @@ export function cliUsage(capabilities) {
|
|
|
6
6
|
"",
|
|
7
7
|
"--session <path|id>",
|
|
8
8
|
"--session-dir <dir> --session <path|id>",
|
|
9
|
+
"solo",
|
|
9
10
|
...(capabilities.developmentComparison ? ["pi"] : []),
|
|
10
11
|
"help",
|
|
11
12
|
"version",
|
|
@@ -21,6 +22,10 @@ export function cliUsage(capabilities) {
|
|
|
21
22
|
"session worktrees",
|
|
22
23
|
"session link-worktree <path>",
|
|
23
24
|
"session unlink-worktree",
|
|
25
|
+
"tabs",
|
|
26
|
+
"tabs stop <id>|--all",
|
|
27
|
+
"tabs host status|stop",
|
|
28
|
+
"tabs doctor",
|
|
24
29
|
]);
|
|
25
30
|
}
|
|
26
31
|
export function cliHelp(capabilities) {
|
|
@@ -30,6 +35,7 @@ export function cliHelp(capabilities) {
|
|
|
30
35
|
` ${command}`,
|
|
31
36
|
` ${command} --session <path|id>`,
|
|
32
37
|
` ${command} --session-dir <dir> --session <path|id>`,
|
|
38
|
+
` ${command} solo`,
|
|
33
39
|
...(capabilities.developmentComparison ? [` ${command} pi`] : []),
|
|
34
40
|
` ${command} help`,
|
|
35
41
|
` ${command} version`,
|
|
@@ -52,9 +58,32 @@ export function cliHelp(capabilities) {
|
|
|
52
58
|
` ${command} session link-worktree <path>`,
|
|
53
59
|
` ${command} session unlink-worktree`,
|
|
54
60
|
"",
|
|
61
|
+
"Tabs:",
|
|
62
|
+
` ${command} tabs`,
|
|
63
|
+
` ${command} tabs stop <id>|--all`,
|
|
64
|
+
` ${command} tabs host status|stop`,
|
|
65
|
+
` ${command} tabs doctor`,
|
|
66
|
+
"",
|
|
67
|
+
].join("\n");
|
|
68
|
+
}
|
|
69
|
+
/** Focused help for the resident-tab maintenance forms; printing it contacts no host. */
|
|
70
|
+
export function tabsHelp() {
|
|
71
|
+
const command = PRODUCT_TEXT.commandName;
|
|
72
|
+
return [
|
|
73
|
+
"Resident tabs:",
|
|
74
|
+
` ${command} tabs List running tabs`,
|
|
75
|
+
` ${command} tabs stop <id> Stop one tab; its session stays resumable`,
|
|
76
|
+
` ${command} tabs stop --all Stop every tab and keep the host`,
|
|
77
|
+
` ${command} tabs host status Show host identity, limits, and health counters`,
|
|
78
|
+
` ${command} tabs host stop Stop every tab and the host`,
|
|
79
|
+
` ${command} tabs doctor Write a redacted diagnostic bundle`,
|
|
80
|
+
"",
|
|
81
|
+
"Options:",
|
|
82
|
+
" --json Print the versioned machine-readable report",
|
|
83
|
+
"",
|
|
55
84
|
].join("\n");
|
|
56
85
|
}
|
|
57
|
-
const PROFILE_WORDS = new Set(["pi"]);
|
|
86
|
+
const PROFILE_WORDS = new Set(["pi", "solo"]);
|
|
58
87
|
export async function dispatchCli(arguments_, handlers, output, capabilities) {
|
|
59
88
|
const command = parseCliCommand(arguments_, capabilities);
|
|
60
89
|
if (command.kind === "noop")
|
|
@@ -76,23 +105,29 @@ export async function dispatchCli(arguments_, handlers, output, capabilities) {
|
|
|
76
105
|
return 2;
|
|
77
106
|
}
|
|
78
107
|
if (command.kind === "launch")
|
|
79
|
-
return await handlers.launch(interactiveLaunchIntent(command.profileId, command.sessionSelection));
|
|
108
|
+
return await handlers.launch(interactiveLaunchIntent(command.profileId, command.sessionSelection, command.launchMode));
|
|
80
109
|
if (command.kind === "version")
|
|
81
110
|
return await handlers.version();
|
|
82
111
|
if (command.kind === "packages")
|
|
83
112
|
return await handlers.packages(command.request);
|
|
84
113
|
if (command.kind === "session-context")
|
|
85
114
|
return await handlers.sessionContext(command.request);
|
|
115
|
+
if (command.kind === "tabs-help") {
|
|
116
|
+
output.stdout(tabsHelp());
|
|
117
|
+
return 0;
|
|
118
|
+
}
|
|
119
|
+
if (command.kind === "tabs")
|
|
120
|
+
return await handlers.tabs(command.request);
|
|
86
121
|
return await handlers.update(command.channel, command.target);
|
|
87
122
|
}
|
|
88
123
|
export function parseCliCommand(arguments_, capabilities) {
|
|
89
124
|
if (arguments_.length === 0)
|
|
90
|
-
return { kind: "launch", profileId: "a1" };
|
|
125
|
+
return { kind: "launch", profileId: "a1", launchMode: "tabs" };
|
|
91
126
|
const [command, ...rest] = arguments_;
|
|
92
127
|
if (command === "--session" || command === "--session-dir") {
|
|
93
128
|
try {
|
|
94
129
|
const sessionSelection = parseSessionSelection(arguments_);
|
|
95
|
-
return { kind: "launch", profileId: "a1", ...(sessionSelection === undefined ? {} : { sessionSelection }) };
|
|
130
|
+
return { kind: "launch", profileId: "a1", launchMode: "tabs", ...(sessionSelection === undefined ? {} : { sessionSelection }) };
|
|
96
131
|
}
|
|
97
132
|
catch (error) {
|
|
98
133
|
return { kind: "error", message: PRODUCT_TEXT.diagnostic(`could not parse session launch: ${error instanceof Error ? error.message : String(error)}`) };
|
|
@@ -108,18 +143,57 @@ export function parseCliCommand(arguments_, capabilities) {
|
|
|
108
143
|
if (command === "pi") {
|
|
109
144
|
if (rest.length > 0)
|
|
110
145
|
return parsePackageCommand(rest, "pi");
|
|
111
|
-
return capabilities.developmentComparison ? { kind: "launch", profileId: "pi" } : { kind: "noop" };
|
|
146
|
+
return capabilities.developmentComparison ? { kind: "launch", profileId: "pi", launchMode: "solo" } : { kind: "noop" };
|
|
112
147
|
}
|
|
148
|
+
if (command === "solo")
|
|
149
|
+
return withoutArguments(rest, { kind: "launch", profileId: "a1", launchMode: "solo" });
|
|
113
150
|
if (command === "update")
|
|
114
151
|
return parseUpdate(rest);
|
|
115
152
|
if (command === "session")
|
|
116
153
|
return parseSessionContext(rest);
|
|
154
|
+
if (command === "tabs")
|
|
155
|
+
return parseTabs(rest);
|
|
117
156
|
if (command?.startsWith("update:"))
|
|
118
157
|
return { kind: "noop" };
|
|
119
158
|
// Rationale: unsupported and reserved command spaces are deliberately quiet. Help is
|
|
120
159
|
// explicit, and a typo must never start an interactive or maintenance path.
|
|
121
160
|
return { kind: "noop" };
|
|
122
161
|
}
|
|
162
|
+
/** Parses `a1 tabs` before any I/O; a malformed form fails with one line and contacts no host. */
|
|
163
|
+
function parseTabs(rest) {
|
|
164
|
+
if (rest.some(value => value === "--help" || value === "-h"))
|
|
165
|
+
return { kind: "tabs-help" };
|
|
166
|
+
const json = rest.includes("--json");
|
|
167
|
+
const words = rest.filter(value => value !== "--json");
|
|
168
|
+
const fail = (detail) => ({ kind: "error", message: PRODUCT_TEXT.diagnostic(`could not parse tabs: ${detail}`) });
|
|
169
|
+
const [action, value, extra] = words;
|
|
170
|
+
if (action === undefined)
|
|
171
|
+
return { kind: "tabs", request: { action: "list", json } };
|
|
172
|
+
if (action === "stop") {
|
|
173
|
+
if (value === undefined)
|
|
174
|
+
return fail("stop requires a tab id or --all.");
|
|
175
|
+
if (extra !== undefined)
|
|
176
|
+
return fail("stop accepts one tab id or --all.");
|
|
177
|
+
if (value === "--all")
|
|
178
|
+
return { kind: "tabs", request: { action: "stop-all", json } };
|
|
179
|
+
if (value.startsWith("-"))
|
|
180
|
+
return fail(`unknown option ${value}.`);
|
|
181
|
+
return { kind: "tabs", request: { action: "stop", tabId: value, json } };
|
|
182
|
+
}
|
|
183
|
+
if (action === "host") {
|
|
184
|
+
if (extra !== undefined || (value !== "status" && value !== "stop"))
|
|
185
|
+
return fail("host accepts status or stop.");
|
|
186
|
+
return { kind: "tabs", request: { action: value === "status" ? "host-status" : "host-stop", json } };
|
|
187
|
+
}
|
|
188
|
+
if (action === "doctor") {
|
|
189
|
+
if (value !== undefined)
|
|
190
|
+
return fail("doctor does not accept arguments.");
|
|
191
|
+
return { kind: "tabs", request: { action: "doctor", json } };
|
|
192
|
+
}
|
|
193
|
+
if (action.startsWith("-"))
|
|
194
|
+
return fail(`unknown option ${action}.`);
|
|
195
|
+
return fail(`unknown subcommand ${action}; use list, stop, host, or doctor.`);
|
|
196
|
+
}
|
|
123
197
|
function parseSessionContext(rest) {
|
|
124
198
|
const [action, value, extra] = rest;
|
|
125
199
|
if (action === "link-worktree") {
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type TabsAction = "list" | "stop" | "stop-all" | "host-status" | "host-stop" | "doctor";
|
|
2
|
+
/** One parsed `a1 tabs` form. Parsing finishes before any host is contacted. */
|
|
3
|
+
export interface TabsRequest {
|
|
4
|
+
readonly action: TabsAction;
|
|
5
|
+
readonly tabId?: string;
|
|
6
|
+
readonly json: boolean;
|
|
7
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { runTerminalHostMaintenance, writeDoctorBundle } from "../foundation/resident-tabs/index.js";
|
|
2
|
+
import type { TabsRequest } from "./tabs-request.js";
|
|
3
|
+
export type { TabsRequest } from "./tabs-request.js";
|
|
4
|
+
export interface TabsCommandOptions {
|
|
5
|
+
/** The running package root; its terminal host is a candidate after the active release. */
|
|
6
|
+
readonly packageRoot?: string;
|
|
7
|
+
readonly environment?: NodeJS.ProcessEnv;
|
|
8
|
+
readonly stdout?: (message: string) => void;
|
|
9
|
+
readonly stderr?: (message: string) => void;
|
|
10
|
+
readonly maintenance?: typeof runTerminalHostMaintenance;
|
|
11
|
+
readonly doctor?: typeof writeDoctorBundle;
|
|
12
|
+
}
|
|
13
|
+
/** `a1 tabs`: inspect or stop resident tabs without attaching, and never start a host to report state. */
|
|
14
|
+
export declare function runTabsCommand(request: TabsRequest, options?: TabsCommandOptions): Promise<number>;
|