@timurproko/a1 0.2.7-dev.754 → 0.2.7-dev.767
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/bin/guardian.js +5 -0
- package/bin/tab-launch-context.d.ts +23 -0
- package/bin/tab-launch-context.js +45 -0
- package/bin/ui.js +20 -5
- 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 +29 -1
- package/dist/app/session-shell/session-shell-root.js +3 -2
- package/dist/app/session-shell/session-shell.js +153 -30
- 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/composition/owned-ui.d.ts +20 -0
- package/dist/composition/owned-ui.js +76 -3
- package/dist/contracts/owned-ui/session-backend.d.ts +1 -0
- package/dist/features/owned-ui/settings-app.js +8 -35
- package/dist/foundation/launch-context/index.d.ts +6 -0
- package/dist/foundation/launch-context/index.js +15 -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 +4 -0
- package/dist/foundation/release/bootstrap.js +44 -6
- 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/resident-tabs/bridge-client.d.ts +41 -0
- package/dist/foundation/resident-tabs/bridge-client.js +199 -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 +30 -0
- package/dist/foundation/resident-tabs/ensure-terminal-host.js +121 -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 +6 -0
- package/dist/foundation/resident-tabs/index.js +3 -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/routing.d.ts +10 -0
- package/dist/foundation/resident-tabs/routing.js +8 -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 +9 -0
- package/dist/foundation/resident-tabs/settings.js +65 -0
- package/dist/foundation/session-writer-lock/index.d.ts +27 -0
- package/dist/foundation/session-writer-lock/index.js +197 -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-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.js +1 -0
- package/dist/integrations/pi/engine/pi-settings-metadata.json +2 -2
- 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/manifest.json +1 -1
- package/dist/native/linux-x64/manifest.json +1 -1
- package/dist/native/win32-x64/manifest.json +2 -2
- package/dist/native/win32-x64/process-guardian.exe +0 -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 +49 -1
- package/dist/ui/settings/declarations.js +37 -1
- package/dist/ui/settings/index.d.ts +5 -3
- package/dist/ui/settings/index.js +3 -2
- package/dist/ui/settings/manager.d.ts +9 -3
- package/dist/ui/settings/manager.js +81 -29
- package/dist/ui/settings/migrations.d.ts +6 -0
- package/dist/ui/settings/migrations.js +32 -0
- package/dist/ui/settings/profile-document.d.ts +26 -0
- package/dist/ui/settings/profile-document.js +164 -0
- package/dist/ui/settings/resolution.d.ts +2 -0
- package/dist/ui/settings/resolution.js +13 -1
- package/dist/ui/settings/sections.js +4 -0
- package/docs/architecture/internal-naming.md +1 -1
- package/docs/architecture/resident-tabs-requirements.md +580 -0
- package/docs/architecture/resident-tabs.md +522 -0
- package/docs/architecture/terminal-host-proof-gate.md +2 -0
- package/docs/openspec-archive-automation.md +17 -16
- package/docs/validation.md +1 -1
- package/package.json +6 -4
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,23 @@
|
|
|
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 consumeTabLaunchContext(
|
|
16
|
+
arguments_: readonly string[],
|
|
17
|
+
environment: Record<string, string | undefined>,
|
|
18
|
+
): {
|
|
19
|
+
readonly uiArguments: string[];
|
|
20
|
+
readonly tabCredentials: TabLaunchCredentials | undefined;
|
|
21
|
+
readonly tabDataDir?: string;
|
|
22
|
+
readonly tabIncarnation?: number;
|
|
23
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const TAB_ENVIRONMENT_NAMES = ["A1_TAB_ID", "A1_TAB_TOKEN", "A1_TAB_ENDPOINT"];
|
|
2
|
+
|
|
3
|
+
/** @param {string} reason */
|
|
4
|
+
export function formatResidentFallbackNotice(reason) {
|
|
5
|
+
return `Resident tabs unavailable (${reason}); running a single agent.`;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {Record<string, string | undefined>} environment
|
|
10
|
+
* @param {string} name
|
|
11
|
+
* @param {() => string} createId
|
|
12
|
+
*/
|
|
13
|
+
export function assignFreshSessionRuntimeId(environment, name, createId) {
|
|
14
|
+
const id = createId();
|
|
15
|
+
if (typeof id !== "string" || id.length === 0 || id.includes("\0")) throw new Error("Session runtime identity generation failed");
|
|
16
|
+
environment[name] = id;
|
|
17
|
+
return id;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Security: consume bridge credentials into caller-private memory and erase their inheritable
|
|
22
|
+
* environment entries before any extension-capable module loads.
|
|
23
|
+
* @param {readonly string[]} arguments_
|
|
24
|
+
* @param {Record<string, string | undefined>} environment
|
|
25
|
+
*/
|
|
26
|
+
export function consumeTabLaunchContext(arguments_, environment) {
|
|
27
|
+
const uiArguments = [...arguments_];
|
|
28
|
+
const tabArgument = uiArguments.indexOf("--tab");
|
|
29
|
+
const tabMode = tabArgument !== -1;
|
|
30
|
+
if (tabMode) uiArguments.splice(tabArgument, 1);
|
|
31
|
+
const dataArgument = uiArguments.indexOf("--tab-data-dir");
|
|
32
|
+
const tabDataDir = dataArgument === -1 ? undefined : uiArguments[dataArgument + 1];
|
|
33
|
+
if (dataArgument !== -1) uiArguments.splice(dataArgument, 2);
|
|
34
|
+
const incarnationArgument = uiArguments.indexOf("--tab-incarnation");
|
|
35
|
+
const tabIncarnation = incarnationArgument === -1 ? undefined : Number(uiArguments[incarnationArgument + 1]);
|
|
36
|
+
if (incarnationArgument !== -1) uiArguments.splice(incarnationArgument, 2);
|
|
37
|
+
const [tabId, token, endpoint] = TAB_ENVIRONMENT_NAMES.map(name => environment[name]);
|
|
38
|
+
for (const name of TAB_ENVIRONMENT_NAMES) delete environment[name];
|
|
39
|
+
if (!tabMode) return { uiArguments, tabCredentials: undefined };
|
|
40
|
+
if (!tabId || !token || !endpoint || !tabDataDir || tabDataDir.includes("\0")
|
|
41
|
+
|| typeof tabIncarnation !== "number" || !Number.isSafeInteger(tabIncarnation) || tabIncarnation < 1) {
|
|
42
|
+
throw new Error("A1 tab mode requires bridge credentials, recovery directory, and incarnation");
|
|
43
|
+
}
|
|
44
|
+
return { uiArguments, tabCredentials: { tabId, token, endpoint }, tabDataDir, tabIncarnation };
|
|
45
|
+
}
|
package/bin/ui.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
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 } = await import("./tab-launch-context.js");
|
|
5
|
+
const { uiArguments, tabCredentials, tabDataDir, tabIncarnation } = 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(/\/$/, ""));
|
|
@@ -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;
|
|
@@ -51,8 +57,13 @@ const [
|
|
|
51
57
|
identity.assertSinglePiTuiModuleAtLaunch(packageRootPath, (/** @type {string} */ message) => process.stderr.write(message));
|
|
52
58
|
await startup.markStartupPhase(process.env, "ui-modules-loaded");
|
|
53
59
|
|
|
54
|
-
const sessionSelection = parseSessionSelection(
|
|
55
|
-
Promise.resolve().then(() => {
|
|
60
|
+
const sessionSelection = parseSessionSelection(uiArguments);
|
|
61
|
+
Promise.resolve().then(async () => {
|
|
62
|
+
const tab = tabCredentials === undefined ? undefined : {
|
|
63
|
+
bridge: new (await import("../dist/foundation/resident-tabs/bridge-client.js")).TabBridgeClient({ credentials: tabCredentials }),
|
|
64
|
+
dataDir: /** @type {string} */ (tabDataDir),
|
|
65
|
+
incarnation: /** @type {number} */ (tabIncarnation),
|
|
66
|
+
};
|
|
56
67
|
if (sessionSelection && profile !== "a1") throw new Error("session selection requires the normal A1 profile");
|
|
57
68
|
return runSelectedInteractiveRuntime(profile, {
|
|
58
69
|
ownedUi: async (profileId, ownedSurfaces) => {
|
|
@@ -69,6 +80,10 @@ Promise.resolve().then(() => {
|
|
|
69
80
|
projectTrustPrompt: createConsoleProjectTrustPrompt({ presentation: profile === "a1" ? "bare" : "comparison" }),
|
|
70
81
|
sessionForkPrompt: createConsoleSessionForkPrompt(),
|
|
71
82
|
...(sessionSelection === undefined ? {} : { sessionSelection }),
|
|
83
|
+
...(tab === undefined ? {} : { tab }),
|
|
84
|
+
...(launchContext.residentFallback === undefined ? {} : {
|
|
85
|
+
residentFallbackNotice: formatResidentFallbackNotice(launchContext.residentFallback),
|
|
86
|
+
}),
|
|
72
87
|
});
|
|
73
88
|
runningApplication = application;
|
|
74
89
|
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,34 @@ 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
|
+
requestRestart?(): Promise<boolean>;
|
|
50
|
+
reportStatus?(status: "working" | "idle" | "stalled"): void;
|
|
51
|
+
readonly stallNoticeSeconds?: number;
|
|
52
|
+
admit?(input: {
|
|
53
|
+
readonly sessionId: string;
|
|
54
|
+
readonly sessionFile: string | null;
|
|
55
|
+
readonly kind: "prompt" | "steer" | "follow-up";
|
|
56
|
+
readonly text: string;
|
|
57
|
+
readonly images?: readonly unknown[];
|
|
58
|
+
}): Promise<{
|
|
59
|
+
readonly id: string;
|
|
60
|
+
}>;
|
|
61
|
+
dispatched?(): void;
|
|
62
|
+
correlated?(submissionId: string, entryId: string): Promise<void>;
|
|
63
|
+
checkpoint?(text: string): void;
|
|
64
|
+
gracefulStop?(sessionFile: string | null): Promise<void>;
|
|
65
|
+
readonly recovered?: {
|
|
66
|
+
readonly prompts: readonly {
|
|
67
|
+
readonly id: string;
|
|
68
|
+
readonly text: string;
|
|
69
|
+
}[];
|
|
70
|
+
readonly draft: string | null;
|
|
71
|
+
};
|
|
72
|
+
readonly unavailableNotice?: string;
|
|
73
|
+
};
|
|
46
74
|
}
|
|
47
75
|
/** Terminal, startup, viewport, and presentation-cadence seams; production leaves most at their defaults. */
|
|
48
76
|
export interface OwnedUiShellPresentationOptions {
|
|
@@ -182,7 +210,7 @@ export declare class OwnedUiSessionShellRoot implements PiTuiComponentPort {
|
|
|
182
210
|
readonly onPromptSuggestionAccepted?: (text: string) => void;
|
|
183
211
|
readonly onInputSurfaceChanged?: () => void;
|
|
184
212
|
readonly onPromptSuggestionEligibilityChanged?: () => void;
|
|
185
|
-
readonly onCopyText?: (text: string) => void;
|
|
213
|
+
readonly onCopyText?: (text: string, cut: boolean) => void;
|
|
186
214
|
readonly readClipboardContent?: (signal?: AbortSignal) => Promise<PiShellClipboardContent | null>;
|
|
187
215
|
readonly captureClipboardPaste?: () => PasteSource;
|
|
188
216
|
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) {
|
|
@@ -3,7 +3,7 @@ import { createResponseCopyExecutor, hasAsyncClipboardOutput, responseCopyDestin
|
|
|
3
3
|
import { MAX_COPY_CONTROL_BYTES } from "./response-copy-protocol.js";
|
|
4
4
|
import { PromptHistoryController } from "./prompt-history-controller.js";
|
|
5
5
|
import { PRODUCT_TEXT } from "../../product-identity.js";
|
|
6
|
-
import { boundedCleanup } from "../../foundation/terminal-cleanup/terminal-reset.js";
|
|
6
|
+
import { CLEAR_EXIT_INTERVAL_MS, boundedCleanup } from "../../foundation/terminal-cleanup/terminal-reset.js";
|
|
7
7
|
import { assertOwnedUiCommand, OWNED_UI_HIDDEN_COMMAND_NAMES } from "../../contracts/owned-ui/index.js";
|
|
8
8
|
import { assertPromptImages, ImageAttachmentError } from "../../contracts/owned-ui/index.js";
|
|
9
9
|
import { ContextualPromptSuggestionController } from "./prompt-suggestion-controller.js";
|
|
@@ -34,6 +34,7 @@ export class OwnedUiSessionPresenter {
|
|
|
34
34
|
#host;
|
|
35
35
|
#terminal;
|
|
36
36
|
#cwd;
|
|
37
|
+
#residentTabOptions;
|
|
37
38
|
#listeners = new Set();
|
|
38
39
|
#unsubscribe;
|
|
39
40
|
#unsubscribePromptSuggestions;
|
|
@@ -53,6 +54,9 @@ export class OwnedUiSessionPresenter {
|
|
|
53
54
|
#routeHost;
|
|
54
55
|
#startupRoute;
|
|
55
56
|
#startupRouteTimer;
|
|
57
|
+
#residentProgressTimer;
|
|
58
|
+
#residentWorking = false;
|
|
59
|
+
#residentStalled = false;
|
|
56
60
|
#dialogHandle;
|
|
57
61
|
#sequence = 0;
|
|
58
62
|
#editorRevision = 0;
|
|
@@ -104,6 +108,7 @@ export class OwnedUiSessionPresenter {
|
|
|
104
108
|
this.#sessionBindingGeneration = this.backend.identity.sessionBindingGeneration;
|
|
105
109
|
this.#suggestionModelKey = modelKey(this.backend.session.view());
|
|
106
110
|
this.#cwd = cwd;
|
|
111
|
+
this.#residentTabOptions = options.engine.residentTab;
|
|
107
112
|
this.#routeHost = routeHost ?? null;
|
|
108
113
|
this.#startupRoute = options.engine.startupRoute;
|
|
109
114
|
this.#customViewport = sessionLayout === "custom-viewport";
|
|
@@ -202,6 +207,7 @@ export class OwnedUiSessionPresenter {
|
|
|
202
207
|
onDequeue: () => this.restoreQueuedInput(),
|
|
203
208
|
onEditorChange: text => {
|
|
204
209
|
this.#editorRevision++;
|
|
210
|
+
this.#residentTabOptions?.checkpoint?.(text);
|
|
205
211
|
promptSuggestionController?.abortPending();
|
|
206
212
|
if (text.length === 0) {
|
|
207
213
|
// Concurrency: the editor synchronizes deletion-owned autocomplete after onChange.
|
|
@@ -218,9 +224,9 @@ export class OwnedUiSessionPresenter {
|
|
|
218
224
|
promptSuggestionController?.invalidate();
|
|
219
225
|
this.#promptHistory?.synchronize();
|
|
220
226
|
},
|
|
221
|
-
onCopyText: text => {
|
|
227
|
+
onCopyText: (text, cut) => {
|
|
222
228
|
if (this.#responseCopy !== null) {
|
|
223
|
-
|
|
229
|
+
this.#trackCopy(this.#responseCopy.submitText(text), cut ? undefined : text.length);
|
|
224
230
|
return;
|
|
225
231
|
}
|
|
226
232
|
const write = () => {
|
|
@@ -263,6 +269,7 @@ export class OwnedUiSessionPresenter {
|
|
|
263
269
|
// Invariant: the presenter owns its root (theme subscription and helper clients) and disposes it once the
|
|
264
270
|
// terminal no longer renders it.
|
|
265
271
|
constructed.push(() => this.root.dispose());
|
|
272
|
+
this.#restoreResidentInput();
|
|
266
273
|
const initialPiSettings = this.backend.settings.snapshot();
|
|
267
274
|
this.#programStatus = new ProgramStatusReporter(status => this.#terminal.setProgramStatus(status), () => this.view().status.footer?.sessionName ?? undefined);
|
|
268
275
|
this.#reloadPresentation = reloadPresentation;
|
|
@@ -474,6 +481,7 @@ export class OwnedUiSessionPresenter {
|
|
|
474
481
|
this.#settleStoppedLifecycle();
|
|
475
482
|
return;
|
|
476
483
|
}
|
|
484
|
+
this.#observeResidentProgress(event);
|
|
477
485
|
// Performance: a streamed chunk names one block, and touching only that block is what keeps the
|
|
478
486
|
// cost of a chunk the same in a long session as in a new one. Everything else
|
|
479
487
|
// resynchronizes the view, which is cheap next to re-reading the transcript.
|
|
@@ -626,21 +634,21 @@ export class OwnedUiSessionPresenter {
|
|
|
626
634
|
const routed = this.root.handleViewportPreInput(data, true, Date.now(), this.root.usesDefaultInputSurface() && !this.#terminal.hasFocusedOverlay());
|
|
627
635
|
if (routed.copySelection !== undefined) {
|
|
628
636
|
const snapshot = routed.copySelection;
|
|
629
|
-
|
|
630
|
-
const intent = ++this.#copyIntentSequence;
|
|
631
|
-
void this.#responseCopy?.submit(snapshot, this.#pendingClipboardWrite).then(result => {
|
|
632
|
-
if (this.#disposed || !this.#terminal.running || intent !== this.#copyIntentSequence
|
|
633
|
-
|| result.outcome !== "delivered" && result.outcome !== "submitted-unverified")
|
|
634
|
-
return;
|
|
635
|
-
if (acknowledgement.hasNonWhitespace) {
|
|
636
|
-
this.root.showCopyAcknowledgement(`copied ${acknowledgement.characters} chars to clipboard`);
|
|
637
|
-
}
|
|
638
|
-
});
|
|
637
|
+
this.#trackCopy(this.#responseCopy?.submit(snapshot, this.#pendingClipboardWrite), copyCharacters(snapshot));
|
|
639
638
|
}
|
|
640
639
|
if (!routed.consumed)
|
|
641
640
|
return routed.data === data ? undefined : { data: routed.data };
|
|
642
641
|
return routed.data.length === 0 ? { consume: true } : { data: routed.data };
|
|
643
642
|
}
|
|
643
|
+
#trackCopy(delivery, count) {
|
|
644
|
+
const intent = ++this.#copyIntentSequence;
|
|
645
|
+
void delivery?.then(({ outcome }) => {
|
|
646
|
+
if (count === undefined || this.#disposed || !this.#terminal.running || intent !== this.#copyIntentSequence
|
|
647
|
+
|| outcome !== "delivered" && outcome !== "submitted-unverified")
|
|
648
|
+
return;
|
|
649
|
+
this.root.showCopyAcknowledgement(`copied ${count} chars to clipboard`);
|
|
650
|
+
});
|
|
651
|
+
}
|
|
644
652
|
#promptSuggestionPresentationBlockReason(identity) {
|
|
645
653
|
const view = this.view();
|
|
646
654
|
if (this.#disposed)
|
|
@@ -779,15 +787,38 @@ export class OwnedUiSessionPresenter {
|
|
|
779
787
|
const type = this.view().lifecycle === "busy" ? "steer" : "prompt";
|
|
780
788
|
this.#rememberInput(this.root.omitUnsentPromptImages(displayInput).trim(), type);
|
|
781
789
|
this.root.resumeViewportFollowing();
|
|
782
|
-
const
|
|
790
|
+
const command = {
|
|
791
|
+
type,
|
|
792
|
+
correlationId: this.#correlation(type),
|
|
793
|
+
sessionId: this.backend.identity.sessionId,
|
|
794
|
+
text: input,
|
|
795
|
+
...(prepared.images.length === 0 ? {} : { images: prepared.images }),
|
|
796
|
+
};
|
|
797
|
+
let submission;
|
|
783
798
|
try {
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
799
|
+
submission = await this.#residentTabOptions?.admit?.({
|
|
800
|
+
sessionId: command.sessionId,
|
|
801
|
+
sessionFile: this.#currentSessionFile(),
|
|
802
|
+
kind: type,
|
|
788
803
|
text: input,
|
|
789
804
|
...(prepared.images.length === 0 ? {} : { images: prepared.images }),
|
|
790
|
-
}
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
catch {
|
|
808
|
+
if (this.root.editor.getText().length === 0)
|
|
809
|
+
this.root.editor.setText(displayInput);
|
|
810
|
+
const message = "Prompt was not sent because its recovery journal could not be synchronized.";
|
|
811
|
+
this.#reportSubmissionError(undefined, message);
|
|
812
|
+
return rejected(message);
|
|
813
|
+
}
|
|
814
|
+
const finishSending = prepared.images.length === 0 ? () => { } : this.root.beginImageSubmissionStatus();
|
|
815
|
+
try {
|
|
816
|
+
const result = await this.#execute(command, displayInput);
|
|
817
|
+
if (submission !== undefined && result.outcome !== "rejected") {
|
|
818
|
+
this.#residentTabOptions?.dispatched?.();
|
|
819
|
+
await this.#residentTabOptions?.correlated?.(submission.id, command.correlationId).catch(() => undefined);
|
|
820
|
+
}
|
|
821
|
+
return result;
|
|
791
822
|
}
|
|
792
823
|
finally {
|
|
793
824
|
finishSending();
|
|
@@ -795,7 +826,7 @@ export class OwnedUiSessionPresenter {
|
|
|
795
826
|
}
|
|
796
827
|
async clearOrExit(now = Date.now()) {
|
|
797
828
|
this.#promptSuggestions?.abortPending();
|
|
798
|
-
if (now - this.#lastClearTime <
|
|
829
|
+
if (now - this.#lastClearTime < CLEAR_EXIT_INTERVAL_MS)
|
|
799
830
|
return this.shutdown();
|
|
800
831
|
this.root.editor.setText("");
|
|
801
832
|
this.#lastClearTime = now;
|
|
@@ -1149,10 +1180,23 @@ export class OwnedUiSessionPresenter {
|
|
|
1149
1180
|
this.#presenters.openSettingsSelector(this.#dialogHost);
|
|
1150
1181
|
}
|
|
1151
1182
|
shutdown() {
|
|
1152
|
-
this.#shutdownPromise ??= this.#shutdown()
|
|
1183
|
+
this.#shutdownPromise ??= this.#shutdown().finally(() => {
|
|
1184
|
+
// Invariant: a rejected resident detach is retryable; ordinary shutdown has disposed the shell already.
|
|
1185
|
+
if (!this.#disposed)
|
|
1186
|
+
this.#shutdownPromise = undefined;
|
|
1187
|
+
});
|
|
1153
1188
|
return this.#shutdownPromise;
|
|
1154
1189
|
}
|
|
1155
1190
|
async #shutdown() {
|
|
1191
|
+
const residentTab = this.#residentTab();
|
|
1192
|
+
if (residentTab !== undefined) {
|
|
1193
|
+
const detached = await residentTab.requestDetach().catch(() => false);
|
|
1194
|
+
if (detached)
|
|
1195
|
+
return { outcome: "completed", diagnostic: null };
|
|
1196
|
+
this.root.appendWorkflowStatus(residentTab.unavailableNotice ?? "Press Ctrl+C twice to leave; this agent keeps running.");
|
|
1197
|
+
this.#terminal.requestRender();
|
|
1198
|
+
return rejected("resident tab detach is unavailable");
|
|
1199
|
+
}
|
|
1156
1200
|
try {
|
|
1157
1201
|
const quit = this.backend.workflows.executeWorkflow({ command: "quit", argument: "" });
|
|
1158
1202
|
// Invariant: a slow or hung engine quit never holds the terminal past the cleanup deadline. The
|
|
@@ -1168,6 +1212,75 @@ export class OwnedUiSessionPresenter {
|
|
|
1168
1212
|
this.#resolveStoppedLifecycle();
|
|
1169
1213
|
}
|
|
1170
1214
|
}
|
|
1215
|
+
#restoreResidentInput() {
|
|
1216
|
+
const recovered = this.#residentTabOptions?.recovered;
|
|
1217
|
+
if (recovered === undefined)
|
|
1218
|
+
return;
|
|
1219
|
+
const prompts = recovered.prompts.map(prompt => prompt.text).filter(Boolean);
|
|
1220
|
+
if (prompts.length > 0) {
|
|
1221
|
+
this.root.editor.setText(prompts[0]);
|
|
1222
|
+
if (recovered.draft)
|
|
1223
|
+
this.root.editor.addToHistory(recovered.draft);
|
|
1224
|
+
for (const additional of prompts.slice(1))
|
|
1225
|
+
this.root.editor.addToHistory(additional);
|
|
1226
|
+
this.root.appendWorkflowStatus(recovered.draft
|
|
1227
|
+
? `Recovered an unfinished prompt; the prior draft and ${Math.max(0, prompts.length - 1)} additional prompt${prompts.length === 2 ? "" : "s"} are in editor history.`
|
|
1228
|
+
: `Recovered an unfinished prompt${prompts.length > 1 ? `; ${prompts.length - 1} additional prompts are in editor history` : ""}.`);
|
|
1229
|
+
}
|
|
1230
|
+
else if (recovered.draft) {
|
|
1231
|
+
this.root.editor.setText(recovered.draft);
|
|
1232
|
+
this.root.appendWorkflowStatus("Recovered the editor draft from the previous tab process.");
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
#currentSessionFile() {
|
|
1236
|
+
const direct = this.backend.identity.currentSessionFile?.();
|
|
1237
|
+
if (direct !== undefined && direct !== null)
|
|
1238
|
+
return direct;
|
|
1239
|
+
const resume = this.backend.identity.currentSessionResumeMetadata();
|
|
1240
|
+
return resume === null ? null : `${resume.sessionDir}/${resume.sessionId}`;
|
|
1241
|
+
}
|
|
1242
|
+
#residentTab() {
|
|
1243
|
+
return this.#residentTabOptions;
|
|
1244
|
+
}
|
|
1245
|
+
#observeResidentProgress(event) {
|
|
1246
|
+
const resident = this.#residentTabOptions;
|
|
1247
|
+
const seconds = resident?.stallNoticeSeconds;
|
|
1248
|
+
if (resident === undefined || seconds === undefined)
|
|
1249
|
+
return;
|
|
1250
|
+
if (event.type === "session-lifecycle") {
|
|
1251
|
+
this.#residentWorking = event.lifecycle === "busy";
|
|
1252
|
+
if (!this.#residentWorking) {
|
|
1253
|
+
if (this.#residentProgressTimer !== undefined)
|
|
1254
|
+
clearTimeout(this.#residentProgressTimer);
|
|
1255
|
+
this.#residentProgressTimer = undefined;
|
|
1256
|
+
this.#residentStalled = false;
|
|
1257
|
+
resident.reportStatus?.("idle");
|
|
1258
|
+
return;
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
else if (!this.#residentWorking || ![
|
|
1262
|
+
"transcript-block", "session-view", "assistant-message-completed", "agent-run-started",
|
|
1263
|
+
].includes(event.type)) {
|
|
1264
|
+
return;
|
|
1265
|
+
}
|
|
1266
|
+
if (this.#residentStalled) {
|
|
1267
|
+
this.#residentStalled = false;
|
|
1268
|
+
resident.reportStatus?.("working");
|
|
1269
|
+
}
|
|
1270
|
+
if (this.#residentProgressTimer !== undefined)
|
|
1271
|
+
clearTimeout(this.#residentProgressTimer);
|
|
1272
|
+
this.#residentProgressTimer = setTimeout(() => {
|
|
1273
|
+
this.#residentProgressTimer = undefined;
|
|
1274
|
+
if (this.#disposed || !this.#residentWorking)
|
|
1275
|
+
return;
|
|
1276
|
+
this.#residentStalled = true;
|
|
1277
|
+
resident.reportStatus?.("stalled");
|
|
1278
|
+
const minutes = Math.max(1, Math.round(seconds / 60));
|
|
1279
|
+
this.root.appendWorkflowStatus(`No progress for ${minutes} min — Esc interrupts · /restart-tab restarts`);
|
|
1280
|
+
this.#terminal.requestRender();
|
|
1281
|
+
}, seconds * 1_000);
|
|
1282
|
+
this.#residentProgressTimer.unref?.();
|
|
1283
|
+
}
|
|
1171
1284
|
async runWorkflow(request) {
|
|
1172
1285
|
if (request.command === "quit")
|
|
1173
1286
|
return this.shutdown();
|
|
@@ -1425,6 +1538,9 @@ export class OwnedUiSessionPresenter {
|
|
|
1425
1538
|
if (this.#startupRouteTimer !== undefined)
|
|
1426
1539
|
clearTimeout(this.#startupRouteTimer);
|
|
1427
1540
|
this.#startupRouteTimer = undefined;
|
|
1541
|
+
if (this.#residentProgressTimer !== undefined)
|
|
1542
|
+
clearTimeout(this.#residentProgressTimer);
|
|
1543
|
+
this.#residentProgressTimer = undefined;
|
|
1428
1544
|
});
|
|
1429
1545
|
attempt(() => this.#promptSuggestions?.dispose());
|
|
1430
1546
|
attempt(() => this.#unsubscribePromptSuggestions());
|
|
@@ -1461,6 +1577,8 @@ export class OwnedUiSessionPresenter {
|
|
|
1461
1577
|
}
|
|
1462
1578
|
await historyCleanup.catch(() => false); // Security: background durability outcomes never enter terminal output.
|
|
1463
1579
|
await boundedCleanup(() => pasteCleanup).catch(error => settled.push(error));
|
|
1580
|
+
await boundedCleanup(() => this.#residentTabOptions?.gracefulStop?.(this.#currentSessionFile()) ?? Promise.resolve())
|
|
1581
|
+
.catch(error => settled.push(error));
|
|
1464
1582
|
await boundedCleanup(() => this.backend.extensions.unbindExtensionUi()).catch(error => settled.push(error));
|
|
1465
1583
|
return settled;
|
|
1466
1584
|
};
|
|
@@ -1650,6 +1768,12 @@ export class OwnedUiSessionPresenter {
|
|
|
1650
1768
|
const argument = separator < 0 ? "" : body.slice(separator + 1).trimStart();
|
|
1651
1769
|
if (this.#routeHost?.claims(name))
|
|
1652
1770
|
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
|
+
}
|
|
1653
1777
|
if (this.#isWorkflowRoute(name))
|
|
1654
1778
|
return this.runWorkflow({ command: name, argument });
|
|
1655
1779
|
if (this.#skillsCollapsed()) {
|
|
@@ -1920,16 +2044,15 @@ export class OwnedUiSessionShell extends OwnedUiSessionPresenter {
|
|
|
1920
2044
|
function isCoalescedStreamBlock(kind) {
|
|
1921
2045
|
return kind === "assistant" || kind === "thinking" || kind === "tool-call" || kind === "tool-result";
|
|
1922
2046
|
}
|
|
1923
|
-
function
|
|
2047
|
+
function copyCharacters(snapshot) {
|
|
1924
2048
|
if (snapshot.literal !== true)
|
|
1925
|
-
return
|
|
1926
|
-
let
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
return { characters, hasNonWhitespace };
|
|
2049
|
+
return snapshot.sourceUnits;
|
|
2050
|
+
let count = Math.max(0, snapshot.rows.length - 1), hasContent = false;
|
|
2051
|
+
for (const { text } of snapshot.rows) {
|
|
2052
|
+
count += text.length;
|
|
2053
|
+
hasContent ||= /\S/u.test(text);
|
|
2054
|
+
}
|
|
2055
|
+
return hasContent ? count : undefined;
|
|
1933
2056
|
}
|
|
1934
2057
|
export function formatSessionResumeCommand(metadata) {
|
|
1935
2058
|
const args = [PRODUCT_TEXT.commandName];
|
|
@@ -64,7 +64,7 @@ export declare class SessionViewportController {
|
|
|
64
64
|
setOverlaySurfaces(surfaces: readonly PiTuiPointerSurface[] | null): void;
|
|
65
65
|
get inputGeometryReady(): boolean;
|
|
66
66
|
compose(input: Omit<TranscriptViewportFrameInput, "pointerPosition">): TranscriptViewportFrame;
|
|
67
|
-
composeDockOnly(dockRows: readonly string[], width: number, height: number): TranscriptViewportFrame | null;
|
|
67
|
+
composeDockOnly(dockRows: readonly string[], width: number, height: number, explicitCopyDockRows?: number): TranscriptViewportFrame | null;
|
|
68
68
|
/** Returns true when document wrapping may have changed. */
|
|
69
69
|
setConfig(config: OwnedUiViewportSettings): boolean;
|
|
70
70
|
resumeFollowing(): void;
|
|
@@ -165,8 +165,8 @@ export class SessionViewportController {
|
|
|
165
165
|
}
|
|
166
166
|
return frame;
|
|
167
167
|
}
|
|
168
|
-
composeDockOnly(dockRows, width, height) {
|
|
169
|
-
return this.#viewport.composeDockOnly(dockRows, width, height);
|
|
168
|
+
composeDockOnly(dockRows, width, height, explicitCopyDockRows = 0) {
|
|
169
|
+
return this.#viewport.composeDockOnly(dockRows, width, height, explicitCopyDockRows);
|
|
170
170
|
}
|
|
171
171
|
/** Returns true when document wrapping may have changed. */
|
|
172
172
|
setConfig(config) {
|
|
@@ -307,7 +307,7 @@ export class SessionViewportController {
|
|
|
307
307
|
this.#requestRender();
|
|
308
308
|
}
|
|
309
309
|
else if (this.#viewport.hasSelection) {
|
|
310
|
-
const copySelection = this.#viewport.captureSelectedText();
|
|
310
|
+
const copySelection = this.#viewport.captureSelectedText("explicit");
|
|
311
311
|
this.#viewport.clearSelection();
|
|
312
312
|
this.#stopSelectionAutoScroll();
|
|
313
313
|
this.#requestRender();
|
|
@@ -10,6 +10,8 @@ import type { OwnedUiApplicationPort, PresentationTerminalPort } from "../contra
|
|
|
10
10
|
import type { AgentSettingsPort } from "../contracts/agent-engine/index.js";
|
|
11
11
|
import type { OwnedUiPromptSuggestionGeneratorPort, UiAccentColor } from "../contracts/owned-ui/index.js";
|
|
12
12
|
import type { ReleaseNoteCatalog } from "../features/owned-ui/release-notes.js";
|
|
13
|
+
import type { TabBridgeClient } from "../foundation/resident-tabs/bridge-client.js";
|
|
14
|
+
import { ResidentTabRecovery } from "../foundation/resident-tabs/recovery.js";
|
|
13
15
|
/**
|
|
14
16
|
* The session backend composition wires: the owned-UI port, the prompt-suggestion generator, and
|
|
15
17
|
* the agent settings port. `settingsPort` stays outside the owned-UI contract because its type
|
|
@@ -55,11 +57,29 @@ export interface OwnedUiCompositionOptions {
|
|
|
55
57
|
readonly checkForNewerRelease?: (options: StartupReleaseCheckOptions) => Promise<AvailableRelease | null>;
|
|
56
58
|
/** Exact native process inspection injected by the shipped entry; absent SDK seams do not publish claims. */
|
|
57
59
|
readonly inspectProcess?: (pid: number) => Promise<NativeProcessIdentity | null>;
|
|
60
|
+
/** Private credentials were consumed by the entry before composition or extensions loaded. */
|
|
61
|
+
readonly tab?: {
|
|
62
|
+
readonly bridge: TabBridgeClient;
|
|
63
|
+
readonly dataDir: string;
|
|
64
|
+
readonly incarnation: number;
|
|
65
|
+
};
|
|
66
|
+
/** One prompt-adjacent notice explaining why an opted-in resident launch fell back. */
|
|
67
|
+
readonly residentFallbackNotice?: string;
|
|
58
68
|
}
|
|
59
69
|
export interface OwnedUiComposition {
|
|
60
70
|
readonly application: OwnedUiApplicationPort;
|
|
61
71
|
/** Present when a profile was supplied, so the caller can resolve settings before start. */
|
|
62
72
|
readonly settings: OwnedSettingsManager | null;
|
|
63
73
|
}
|
|
74
|
+
export declare function residentStartupPresentation(resident: boolean, fallbackNotice?: string): {
|
|
75
|
+
readonly startup?: {
|
|
76
|
+
readonly quiet?: boolean;
|
|
77
|
+
readonly notices?: readonly [{
|
|
78
|
+
readonly kind: "warning";
|
|
79
|
+
readonly message: string;
|
|
80
|
+
}];
|
|
81
|
+
};
|
|
82
|
+
};
|
|
64
83
|
export declare function composeOwnedUiApplication(options?: OwnedUiCompositionOptions): Promise<OwnedUiApplicationPort>;
|
|
65
84
|
export declare function composeOwnedUi(options?: OwnedUiCompositionOptions): Promise<OwnedUiComposition>;
|
|
85
|
+
export declare function bindResidentTabBridge(tabBridge: TabBridgeClient, backend: Pick<ComposedSessionBackend, "identity" | "session">, recovery?: Pick<ResidentTabRecovery, "settled"> | null): Promise<() => void>;
|