@timurproko/a1 0.2.7-dev.754 → 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/guardian.js +5 -0
- package/bin/tab-launch-context.d.ts +26 -0
- package/bin/tab-launch-context.js +94 -0
- package/bin/ui.js +24 -6
- package/dist/app/session-shell/helper-pool.d.ts +3 -5
- package/dist/app/session-shell/helper-pool.js +3 -5
- package/dist/app/session-shell/session-shell-root.d.ts +28 -1
- package/dist/app/session-shell/session-shell-root.js +4 -3
- package/dist/app/session-shell/session-shell.js +149 -31
- package/dist/app/session-shell/session-viewport-controller.d.ts +1 -1
- package/dist/app/session-shell/session-viewport-controller.js +3 -3
- 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 +26 -0
- package/dist/composition/owned-ui.js +176 -6
- package/dist/contracts/owned-ui/session-backend.d.ts +1 -0
- 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/owned-ui/settings-app.js +8 -35
- 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 +10 -0
- package/dist/foundation/launch-context/index.js +25 -0
- package/dist/foundation/launch-guardian/main.d.ts +5 -0
- package/dist/foundation/launch-guardian/main.js +2 -1
- package/dist/foundation/release/bootstrap.d.ts +6 -0
- package/dist/foundation/release/bootstrap.js +51 -10
- package/dist/foundation/release/cohort-state.d.ts +1 -1
- package/dist/foundation/release/cohort-state.js +1 -1
- package/dist/foundation/release/dependency-certification.js +54 -19
- package/dist/foundation/release/progress-palette.generated.d.ts +2 -2
- package/dist/foundation/release/progress-palette.generated.js +2 -2
- 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 +56 -0
- package/dist/foundation/resident-tabs/bridge-client.js +279 -0
- package/dist/foundation/resident-tabs/doctor.d.ts +29 -0
- package/dist/foundation/resident-tabs/doctor.js +166 -0
- package/dist/foundation/resident-tabs/draft-checkpoint.d.ts +27 -0
- package/dist/foundation/resident-tabs/draft-checkpoint.js +145 -0
- package/dist/foundation/resident-tabs/ensure-terminal-host.d.ts +31 -0
- package/dist/foundation/resident-tabs/ensure-terminal-host.js +147 -0
- package/dist/foundation/resident-tabs/fault-injection.d.ts +8 -0
- package/dist/foundation/resident-tabs/fault-injection.js +26 -0
- package/dist/foundation/resident-tabs/index.d.ts +12 -0
- package/dist/foundation/resident-tabs/index.js +6 -0
- package/dist/foundation/resident-tabs/maintenance.d.ts +42 -0
- package/dist/foundation/resident-tabs/maintenance.js +78 -0
- package/dist/foundation/resident-tabs/prompt-journal.d.ts +65 -0
- package/dist/foundation/resident-tabs/prompt-journal.js +214 -0
- package/dist/foundation/resident-tabs/protocol.d.ts +12 -0
- package/dist/foundation/resident-tabs/protocol.js +195 -0
- package/dist/foundation/resident-tabs/recovery.d.ts +26 -0
- package/dist/foundation/resident-tabs/recovery.js +205 -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 +11 -0
- package/dist/foundation/resident-tabs/routing.js +10 -0
- package/dist/foundation/resident-tabs/session-sync.d.ts +14 -0
- package/dist/foundation/resident-tabs/session-sync.js +37 -0
- package/dist/foundation/resident-tabs/settings.d.ts +14 -0
- package/dist/foundation/resident-tabs/settings.js +66 -0
- 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 +32 -0
- package/dist/foundation/session-writer-lock/index.js +201 -0
- package/dist/foundation/startup/startup-descriptor.js +2 -2
- package/dist/foundation/terminal-cleanup/index.d.ts +1 -1
- package/dist/foundation/terminal-cleanup/index.js +1 -1
- package/dist/foundation/terminal-cleanup/terminal-reset.d.ts +1 -0
- package/dist/foundation/terminal-cleanup/terminal-reset.js +1 -0
- package/dist/integrations/pi/components/owned-editor-ux.d.ts +1 -1
- package/dist/integrations/pi/components/owned-editor-ux.js +2 -2
- package/dist/integrations/pi/components/resources/builtin-themes.d.ts +4 -4
- package/dist/integrations/pi/components/resources/builtin-themes.js +4 -4
- 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/components/shell-shared-facade.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.js +17 -4
- package/dist/integrations/pi/components/upstream/assets/earendil-image.json +2 -2
- package/dist/integrations/pi/components/upstream/components/countdown-timer.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/earendil-announcement.js +1 -1
- package/dist/integrations/pi/components/upstream/components/extension-editor.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/extension-editor.js +1 -1
- package/dist/integrations/pi/components/upstream/components/extension-input.js +1 -1
- package/dist/integrations/pi/components/upstream/components/extension-selector.js +1 -1
- package/dist/integrations/pi/components/upstream/components/mermaid.js +1 -1
- package/dist/integrations/pi/components/upstream/components/owned-editor.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/owned-editor.js +1 -1
- package/dist/integrations/pi/components/upstream/components/pi-logo.js +1 -1
- package/dist/integrations/pi/components/upstream/components/scoped-models-selector.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/session-footer.js +1 -1
- package/dist/integrations/pi/components/upstream/components/session-selector-search.js +1 -1
- package/dist/integrations/pi/components/upstream/components/session-selector.js +1 -1
- package/dist/integrations/pi/components/upstream/components/skill-invocation-message.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/skill-invocation-message.js +1 -1
- package/dist/integrations/pi/components/upstream/components/status-indicator.d.ts +2 -2
- package/dist/integrations/pi/components/upstream/components/status-indicator.js +3 -3
- package/dist/integrations/pi/components/upstream/components/themed-text.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/themed-text.js +1 -1
- package/dist/integrations/pi/components/upstream/components/thinking-selector.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/thinking-selector.js +1 -1
- package/dist/integrations/pi/components/upstream/components/tool-execution.d.ts +0 -2
- package/dist/integrations/pi/components/upstream/components/tool-execution.js +41 -20
- package/dist/integrations/pi/components/upstream/components/tree-selector.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/tree-selector.js +1 -1
- package/dist/integrations/pi/components/upstream/components/trust-selector.js +1 -1
- package/dist/integrations/pi/components/upstream/external-editor.js +1 -1
- package/dist/integrations/pi/components/upstream/history/editor-core.d.ts +2 -2
- package/dist/integrations/pi/components/upstream/history/editor-core.js +5 -5
- package/dist/integrations/pi/components/upstream/history/kill-ring.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/history/kill-ring.js +1 -1
- package/dist/integrations/pi/components/upstream/history/printable-key.js +1 -1
- package/dist/integrations/pi/components/upstream/history/text-helpers.js +1 -1
- package/dist/integrations/pi/components/upstream/history/undo-stack.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/history/undo-stack.js +1 -1
- package/dist/integrations/pi/components/upstream/history/word-navigation.js +1 -1
- package/dist/integrations/pi/components/upstream/model-search.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/program-status-reporter.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/theme/theme-controller.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/theme/theme.d.ts +3 -0
- package/dist/integrations/pi/components/upstream/theme/theme.js +24 -1
- package/dist/integrations/pi/engine/adapter.d.ts +1 -0
- package/dist/integrations/pi/engine/adapter.js +4 -0
- package/dist/integrations/pi/engine/pi-settings-metadata.json +2 -2
- package/dist/integrations/pi/engine/prompt-suggestions.d.ts +1 -0
- package/dist/integrations/pi/engine/prompt-suggestions.js +40 -0
- package/dist/integrations/pi/engine/resources/changelog.json +4 -4
- package/dist/integrations/pi/engine/runtime-integration.js +102 -29
- package/dist/integrations/pi/engine/session-runtime.d.ts +0 -6
- package/dist/integrations/pi/engine/session-runtime.js +8 -3
- package/dist/integrations/pi/engine/session-selection.d.ts +3 -0
- package/dist/integrations/pi/engine/session-selection.js +45 -9
- package/dist/integrations/pi/engine/workflow-contexts.js +10 -1
- package/dist/integrations/pi/startup-public.js +152 -222
- package/dist/integrations/pi/startup-public.manifest.json +266 -177
- package/dist/integrations/pi/tui-runtime/damage-aware-terminal.d.ts +1 -1
- package/dist/integrations/pi/tui-runtime/damage-aware-terminal.js +1 -1
- 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/product-identity.d.ts +1 -1
- package/dist/product-identity.js +1 -1
- package/dist/product-identity.json +5 -0
- package/dist/runtime-payload-inventory.json +5349 -6035
- package/dist/ui/components/line-input.d.ts +1 -1
- package/dist/ui/components/prompt-input.d.ts +7 -5
- package/dist/ui/components/prompt-input.js +7 -7
- package/dist/ui/components/transcript-viewport.d.ts +6 -3
- package/dist/ui/components/transcript-viewport.js +13 -15
- package/dist/ui/settings/declarations.d.ts +25 -1
- package/dist/ui/settings/declarations.js +21 -1
- package/dist/ui/settings/index.d.ts +4 -2
- package/dist/ui/settings/index.js +2 -1
- package/dist/ui/settings/manager.d.ts +10 -3
- package/dist/ui/settings/manager.js +90 -30
- package/dist/ui/settings/migrations.d.ts +6 -0
- package/dist/ui/settings/migrations.js +44 -0
- package/dist/ui/settings/profile-document.d.ts +26 -0
- package/dist/ui/settings/profile-document.js +164 -0
- 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 +3 -1
- package/docs/architecture/process-guardian-provenance.md +2 -0
- package/docs/architecture/resident-tabs-requirements.md +18 -0
- package/docs/architecture/resident-tabs.md +594 -0
- package/docs/architecture/terminal-host-proof-gate.md +2 -0
- 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/openspec-archive-automation.md +17 -16
- package/docs/validation.md +9 -1
- package/package.json +10 -4
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),
|
package/bin/guardian.js
CHANGED
|
@@ -16,8 +16,13 @@ const profileId = readLaunchContext(process.env, "profile").launchProfile;
|
|
|
16
16
|
if (releaseRoot === undefined || profileId === undefined) throw new Error("A1 launch context is incomplete");
|
|
17
17
|
|
|
18
18
|
const sessionSelection = parseSessionSelection(process.argv.slice(2));
|
|
19
|
+
const rootCommand = launchContext.residentRootExecutable === undefined ? undefined : {
|
|
20
|
+
executable: launchContext.residentRootExecutable,
|
|
21
|
+
arguments: JSON.parse(launchContext.residentRootArguments ?? "[]"),
|
|
22
|
+
};
|
|
19
23
|
Promise.resolve().then(() => runLaunchGuardian({
|
|
20
24
|
...(sessionSelection === undefined ? {} : { sessionSelection }),
|
|
25
|
+
...(rootCommand === undefined ? {} : { rootCommand }),
|
|
21
26
|
profileId,
|
|
22
27
|
releaseRoot,
|
|
23
28
|
uiEntry: resolve(releaseRoot, "bin", "ui.js"),
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface TabLaunchCredentials {
|
|
2
|
+
readonly tabId: string;
|
|
3
|
+
readonly token: string;
|
|
4
|
+
readonly endpoint: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function formatResidentFallbackNotice(reason: string): string;
|
|
8
|
+
|
|
9
|
+
export function assignFreshSessionRuntimeId(
|
|
10
|
+
environment: Record<string, string | undefined>,
|
|
11
|
+
name: string,
|
|
12
|
+
createId: () => string,
|
|
13
|
+
): string;
|
|
14
|
+
|
|
15
|
+
export function waitForStandbyPromotion(input: NodeJS.ReadStream, promotionFile?: string): Promise<void>;
|
|
16
|
+
|
|
17
|
+
export function consumeTabLaunchContext(
|
|
18
|
+
arguments_: readonly string[],
|
|
19
|
+
environment: Record<string, string | undefined>,
|
|
20
|
+
): {
|
|
21
|
+
readonly uiArguments: string[];
|
|
22
|
+
readonly tabCredentials: TabLaunchCredentials | undefined;
|
|
23
|
+
readonly tabDataDir?: string;
|
|
24
|
+
readonly tabIncarnation?: number;
|
|
25
|
+
readonly tabStandby: boolean;
|
|
26
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
|
|
3
|
+
const TAB_ENVIRONMENT_NAMES = ["A1_TAB_ID", "A1_TAB_TOKEN", "A1_TAB_ENDPOINT"];
|
|
4
|
+
|
|
5
|
+
/** @param {string} reason */
|
|
6
|
+
export function formatResidentFallbackNotice(reason) {
|
|
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.`;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {Record<string, string | undefined>} environment
|
|
16
|
+
* @param {string} name
|
|
17
|
+
* @param {() => string} createId
|
|
18
|
+
*/
|
|
19
|
+
export function assignFreshSessionRuntimeId(environment, name, createId) {
|
|
20
|
+
const id = createId();
|
|
21
|
+
if (typeof id !== "string" || id.length === 0 || id.includes("\0")) throw new Error("Session runtime identity generation failed");
|
|
22
|
+
environment[name] = id;
|
|
23
|
+
return id;
|
|
24
|
+
}
|
|
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
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Security: consume bridge credentials into caller-private memory and erase their inheritable
|
|
68
|
+
* environment entries before any extension-capable module loads.
|
|
69
|
+
* @param {readonly string[]} arguments_
|
|
70
|
+
* @param {Record<string, string | undefined>} environment
|
|
71
|
+
*/
|
|
72
|
+
export function consumeTabLaunchContext(arguments_, environment) {
|
|
73
|
+
const uiArguments = [...arguments_];
|
|
74
|
+
const tabArgument = uiArguments.indexOf("--tab");
|
|
75
|
+
const tabMode = tabArgument !== -1;
|
|
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);
|
|
80
|
+
const dataArgument = uiArguments.indexOf("--tab-data-dir");
|
|
81
|
+
const tabDataDir = dataArgument === -1 ? undefined : uiArguments[dataArgument + 1];
|
|
82
|
+
if (dataArgument !== -1) uiArguments.splice(dataArgument, 2);
|
|
83
|
+
const incarnationArgument = uiArguments.indexOf("--tab-incarnation");
|
|
84
|
+
const tabIncarnation = incarnationArgument === -1 ? undefined : Number(uiArguments[incarnationArgument + 1]);
|
|
85
|
+
if (incarnationArgument !== -1) uiArguments.splice(incarnationArgument, 2);
|
|
86
|
+
const [tabId, token, endpoint] = TAB_ENVIRONMENT_NAMES.map(name => environment[name]);
|
|
87
|
+
for (const name of TAB_ENVIRONMENT_NAMES) delete environment[name];
|
|
88
|
+
if (!tabMode) return { uiArguments, tabCredentials: undefined, tabStandby: false };
|
|
89
|
+
if (!tabId || !token || !endpoint || !tabDataDir || tabDataDir.includes("\0")
|
|
90
|
+
|| typeof tabIncarnation !== "number" || !Number.isSafeInteger(tabIncarnation) || tabIncarnation < 1) {
|
|
91
|
+
throw new Error("A1 tab mode requires bridge credentials, recovery directory, and incarnation");
|
|
92
|
+
}
|
|
93
|
+
return { uiArguments, tabCredentials: { tabId, token, endpoint }, tabDataDir, tabIncarnation, tabStandby };
|
|
94
|
+
}
|
package/bin/ui.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
// Security: this helper has no A1, Pi, tool, or extension dependency and erases credentials before those modules load.
|
|
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
|
+
|
|
3
7
|
// Invariant: one terminal module identity per process, decided before any A1 or Pi module loads.
|
|
4
8
|
const { installPinnedPiTuiResolver } = await import("./module-resolver.js");
|
|
5
9
|
installPinnedPiTuiResolver(new URL("..", import.meta.url).pathname.replace(/^\/(.:)/, "$1").replace(/\/$/, ""));
|
|
6
10
|
const startup = await import("../dist/foundation/startup/startup-runtime.js");
|
|
7
11
|
startup.enableEnvironmentCompileCache(process.env);
|
|
8
|
-
const { readLaunchContext } = await import("../dist/foundation/launch-context/index.js");
|
|
12
|
+
const { hidesTabSettings, readLaunchContext } = await import("../dist/foundation/launch-context/index.js");
|
|
9
13
|
const launchContext = readLaunchContext(process.env, "profile");
|
|
10
14
|
const profile = launchContext.launchProfile;
|
|
11
15
|
if (profile === undefined) throw new Error("A1 launch profile is missing");
|
|
@@ -14,15 +18,17 @@ if (profile === "a1") {
|
|
|
14
18
|
import("node:crypto"),
|
|
15
19
|
import("../dist/product-identity.js"),
|
|
16
20
|
]);
|
|
17
|
-
process.env
|
|
21
|
+
assignFreshSessionRuntimeId(process.env, PRODUCT_IDENTITY.environment.sessionRuntimeId, randomUUID);
|
|
18
22
|
}
|
|
19
23
|
const { installFatalExit } = await import("../dist/foundation/terminal-cleanup/fatal-exit.js");
|
|
20
24
|
const { resolveProductPaths } = await import("../dist/foundation/lifecycle/paths.js");
|
|
21
|
-
const { join } = await import("node:path");
|
|
25
|
+
const { dirname, join } = await import("node:path");
|
|
22
26
|
/** @type {{ dispose(): void | Promise<unknown> } | undefined} */
|
|
23
27
|
let runningApplication;
|
|
24
28
|
const fatal = profile === "a1" ? installFatalExit({
|
|
25
|
-
directory:
|
|
29
|
+
directory: tabDataDir === undefined
|
|
30
|
+
? join(resolveProductPaths().runtimeDir, "crashes")
|
|
31
|
+
: join(dirname(tabDataDir), "diagnostics"),
|
|
26
32
|
...(launchContext.releaseId === undefined ? {} : { releaseId: launchContext.releaseId }),
|
|
27
33
|
dispose: () => runningApplication?.dispose(),
|
|
28
34
|
}) : undefined;
|
|
@@ -46,13 +52,20 @@ const [
|
|
|
46
52
|
{ composeOwnedUi },
|
|
47
53
|
] = await modules;
|
|
48
54
|
|
|
55
|
+
if (tabStandby) await waitForStandbyPromotion(process.stdin, tabDataDir === undefined ? undefined : join(tabDataDir, "standby-promote"));
|
|
56
|
+
|
|
49
57
|
// Compatibility: before the composition uses pinned Pi's terminal stack: confirm A1 and Pi
|
|
50
58
|
// resolve it to the same copy, so extensions and the owned UI share one module identity.
|
|
51
59
|
identity.assertSinglePiTuiModuleAtLaunch(packageRootPath, (/** @type {string} */ message) => process.stderr.write(message));
|
|
52
60
|
await startup.markStartupPhase(process.env, "ui-modules-loaded");
|
|
53
61
|
|
|
54
|
-
const sessionSelection = parseSessionSelection(
|
|
55
|
-
Promise.resolve().then(() => {
|
|
62
|
+
const sessionSelection = parseSessionSelection(uiArguments);
|
|
63
|
+
Promise.resolve().then(async () => {
|
|
64
|
+
const tab = tabCredentials === undefined ? undefined : {
|
|
65
|
+
bridge: new (await import("../dist/foundation/resident-tabs/bridge-client.js")).TabBridgeClient({ credentials: tabCredentials }),
|
|
66
|
+
dataDir: /** @type {string} */ (tabDataDir),
|
|
67
|
+
incarnation: /** @type {number} */ (tabIncarnation),
|
|
68
|
+
};
|
|
56
69
|
if (sessionSelection && profile !== "a1") throw new Error("session selection requires the normal A1 profile");
|
|
57
70
|
return runSelectedInteractiveRuntime(profile, {
|
|
58
71
|
ownedUi: async (profileId, ownedSurfaces) => {
|
|
@@ -65,10 +78,15 @@ Promise.resolve().then(() => {
|
|
|
65
78
|
cwd: process.cwd(),
|
|
66
79
|
profileId,
|
|
67
80
|
ownedSurfaces,
|
|
81
|
+
...(hidesTabSettings(launchContext) ? { hideTabSettings: true } : {}),
|
|
68
82
|
...(processInspector === null ? {} : { inspectProcess: pid => processInspector.observe(pid) }),
|
|
69
83
|
projectTrustPrompt: createConsoleProjectTrustPrompt({ presentation: profile === "a1" ? "bare" : "comparison" }),
|
|
70
84
|
sessionForkPrompt: createConsoleSessionForkPrompt(),
|
|
71
85
|
...(sessionSelection === undefined ? {} : { sessionSelection }),
|
|
86
|
+
...(tab === undefined ? {} : { tab }),
|
|
87
|
+
...(launchContext.residentFallback === undefined ? {} : {
|
|
88
|
+
residentFallbackNotice: formatResidentFallbackNotice(launchContext.residentFallback),
|
|
89
|
+
}),
|
|
72
90
|
});
|
|
73
91
|
runningApplication = application;
|
|
74
92
|
return await runOwnedUi({ application, ...(settings === null ? {} : { settings }) });
|
|
@@ -12,16 +12,14 @@ export interface HelperPoolOptions {
|
|
|
12
12
|
readonly stop: (child: ChildProcess) => void;
|
|
13
13
|
readonly idleMs?: number;
|
|
14
14
|
}
|
|
15
|
-
/**
|
|
16
|
-
* Keeps one forked IPC helper ready so a request takes a live child instead of paying fork and module
|
|
17
|
-
* load on its own critical path. The spare stays idle on its channel until it is taken; a spare that
|
|
18
|
-
* exits or idles too long is dropped, and the next request forks cold and warms a replacement afterwards.
|
|
19
|
-
*/
|
|
15
|
+
/** Holds one idle IPC helper; requests take it, and exits, expiry, or use trigger replacement. */
|
|
20
16
|
export declare class HelperPool {
|
|
21
17
|
#private;
|
|
22
18
|
constructor(options: HelperPoolOptions);
|
|
23
19
|
/** Whether a spare is currently held (ready or still starting). */
|
|
24
20
|
get warmed(): boolean;
|
|
21
|
+
/** Whether the held spare has announced that module loading is complete. */
|
|
22
|
+
get ready(): boolean;
|
|
25
23
|
/** Opts the pool in and forks the first spare; from here on `replenish` keeps one ready after each request. */
|
|
26
24
|
warm(): void;
|
|
27
25
|
/** Forks the next spare after a request released its child; a no-op until the owner warmed the pool. A fork failure leaves the pool empty. */
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
/** How long an unused spare helper may idle before it is stopped so a background session holds no extra process. */
|
|
2
2
|
export const HELPER_SPARE_IDLE_MS = 5 * 60_000;
|
|
3
|
-
/**
|
|
4
|
-
* Keeps one forked IPC helper ready so a request takes a live child instead of paying fork and module
|
|
5
|
-
* load on its own critical path. The spare stays idle on its channel until it is taken; a spare that
|
|
6
|
-
* exits or idles too long is dropped, and the next request forks cold and warms a replacement afterwards.
|
|
7
|
-
*/
|
|
3
|
+
/** Holds one idle IPC helper; requests take it, and exits, expiry, or use trigger replacement. */
|
|
8
4
|
export class HelperPool {
|
|
9
5
|
#fork;
|
|
10
6
|
#stop;
|
|
@@ -19,6 +15,8 @@ export class HelperPool {
|
|
|
19
15
|
}
|
|
20
16
|
/** Whether a spare is currently held (ready or still starting). */
|
|
21
17
|
get warmed() { return this.#spare !== undefined; }
|
|
18
|
+
/** Whether the held spare has announced that module loading is complete. */
|
|
19
|
+
get ready() { return this.#spare?.ready === true; }
|
|
22
20
|
/** Opts the pool in and forks the first spare; from here on `replenish` keeps one ready after each request. */
|
|
23
21
|
warm() {
|
|
24
22
|
if (this.#disposed)
|
|
@@ -43,6 +43,33 @@ export interface OwnedUiShellEngineOptions {
|
|
|
43
43
|
};
|
|
44
44
|
/** Bare A1 selects the owned bounded viewport; comparison profiles stay pinned. */
|
|
45
45
|
readonly sessionLayout?: "pinned" | "custom-viewport";
|
|
46
|
+
/** Resident tab quit routes detach the outer client without stopping this engine session. */
|
|
47
|
+
readonly residentTab?: {
|
|
48
|
+
requestDetach(): Promise<boolean>;
|
|
49
|
+
reportStatus?(status: "working" | "idle" | "stalled"): void;
|
|
50
|
+
readonly stallNoticeSeconds?: number;
|
|
51
|
+
admit?(input: {
|
|
52
|
+
readonly sessionId: string;
|
|
53
|
+
readonly sessionFile: string | null;
|
|
54
|
+
readonly kind: "prompt" | "steer" | "follow-up";
|
|
55
|
+
readonly text: string;
|
|
56
|
+
readonly images?: readonly unknown[];
|
|
57
|
+
}): Promise<{
|
|
58
|
+
readonly id: string;
|
|
59
|
+
}>;
|
|
60
|
+
dispatched?(): void;
|
|
61
|
+
correlated?(submissionId: string, entryId: string): Promise<void>;
|
|
62
|
+
checkpoint?(text: string): void;
|
|
63
|
+
gracefulStop?(sessionFile: string | null): Promise<void>;
|
|
64
|
+
readonly recovered?: {
|
|
65
|
+
readonly prompts: readonly {
|
|
66
|
+
readonly id: string;
|
|
67
|
+
readonly text: string;
|
|
68
|
+
}[];
|
|
69
|
+
readonly draft: string | null;
|
|
70
|
+
};
|
|
71
|
+
readonly unavailableNotice?: string;
|
|
72
|
+
};
|
|
46
73
|
}
|
|
47
74
|
/** Terminal, startup, viewport, and presentation-cadence seams; production leaves most at their defaults. */
|
|
48
75
|
export interface OwnedUiShellPresentationOptions {
|
|
@@ -182,7 +209,7 @@ export declare class OwnedUiSessionShellRoot implements PiTuiComponentPort {
|
|
|
182
209
|
readonly onPromptSuggestionAccepted?: (text: string) => void;
|
|
183
210
|
readonly onInputSurfaceChanged?: () => void;
|
|
184
211
|
readonly onPromptSuggestionEligibilityChanged?: () => void;
|
|
185
|
-
readonly onCopyText?: (text: string) => void;
|
|
212
|
+
readonly onCopyText?: (text: string, cut: boolean) => void;
|
|
186
213
|
readonly readClipboardContent?: (signal?: AbortSignal) => Promise<PiShellClipboardContent | null>;
|
|
187
214
|
readonly captureClipboardPaste?: () => PasteSource;
|
|
188
215
|
readonly pasteDiagnostics?: (event: PasteEvent) => void;
|
|
@@ -458,7 +458,7 @@ export class OwnedUiSessionShellRoot {
|
|
|
458
458
|
&& snapshot.inputSurface === inputSurface
|
|
459
459
|
&& snapshot.viewportRevision === viewportRevision
|
|
460
460
|
&& snapshot.selectionRevision === selectionRevision
|
|
461
|
-
? this.#viewportController.composeDockOnly(dockRows, width, height)
|
|
461
|
+
? this.#viewportController.composeDockOnly(dockRows, width, height, dock.footerRows)
|
|
462
462
|
: null;
|
|
463
463
|
if (frame === null) {
|
|
464
464
|
frame = this.#viewportController.compose({
|
|
@@ -474,6 +474,7 @@ export class OwnedUiSessionShellRoot {
|
|
|
474
474
|
...(document.liveTailStartRow === undefined ? {} : { liveTailStartRow: document.liveTailStartRow }),
|
|
475
475
|
bottomAlignedTailRowCount: steeringRows.length + statusRows.length,
|
|
476
476
|
dockRows,
|
|
477
|
+
explicitCopyDockRows: dock.footerRows,
|
|
477
478
|
promptAnchors: document.promptAnchors,
|
|
478
479
|
width,
|
|
479
480
|
height,
|
|
@@ -603,7 +604,6 @@ export class OwnedUiSessionShellRoot {
|
|
|
603
604
|
const transientRows = [...queued, ...statusRows, ...this.#renderDockNotice(width)];
|
|
604
605
|
const aboveWidgets = this.#renderWidgets("aboveEditor", width);
|
|
605
606
|
const input = this.#inputSurface.render(width);
|
|
606
|
-
// Invariant: pointer rows describe the body, not the autocomplete block now preceding it.
|
|
607
607
|
const body = this.usesDefaultInputSurface() ? this.editor.bodyGeometry?.() : undefined;
|
|
608
608
|
const belowWidgets = this.#renderWidgets("belowEditor", width);
|
|
609
609
|
const footer = this.#renderFooter(width);
|
|
@@ -614,6 +614,7 @@ export class OwnedUiSessionShellRoot {
|
|
|
614
614
|
inputOffset,
|
|
615
615
|
editorOffset: inputOffset + (body?.rowOffset ?? 0),
|
|
616
616
|
inputRows: body?.rowCount ?? input.length,
|
|
617
|
+
footerRows: footer.length,
|
|
617
618
|
};
|
|
618
619
|
}
|
|
619
620
|
#renderCopyAcknowledgement(width) {
|
|
@@ -915,7 +916,7 @@ export class OwnedUiSessionShellRoot {
|
|
|
915
916
|
if (result.command === "hotkeys" && result.outcome === "completed") {
|
|
916
917
|
this.#lastWorkflowStatusId = undefined;
|
|
917
918
|
const presentation = this.hotkeysPresentation();
|
|
918
|
-
const hotkeys = createPiShellHotkeys(presentation.bindings, presentation.getShortcuts, presentation.profile);
|
|
919
|
+
const hotkeys = createPiShellHotkeys(presentation.bindings, presentation.getShortcuts, presentation.profile, presentation.residentTabs);
|
|
919
920
|
this.#appendAnchoredWorkflowComponent(width => hotkeys.render(width), () => hotkeys.dispose?.());
|
|
920
921
|
return;
|
|
921
922
|
}
|