@timurproko/a1 0.2.7-dev.1221 → 0.2.7-dev.1225
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 +8 -6
- package/bin/ui.js +1 -0
- package/dist/app/session-shell/dock-chrome.js +1 -1
- package/dist/app/session-shell/exit-notice.d.ts +2 -2
- package/dist/app/session-shell/exit-notice.js +4 -4
- package/dist/app/session-shell/session-lifecycle.d.ts +2 -1
- package/dist/app/session-shell/session-lifecycle.js +4 -2
- package/dist/app/session-shell/session-shell-root.d.ts +2 -0
- package/dist/app/session-shell/session-shell-root.js +3 -2
- package/dist/app/session-shell/session-shell.js +9 -1
- package/dist/app/session-shell/session-viewport-controller.d.ts +2 -1
- package/dist/app/session-shell/session-viewport-controller.js +26 -37
- package/dist/app/session-shell/shell-editor-factory.js +4 -1
- package/dist/cli/dispatch.js +15 -2
- package/dist/composition/owned-ui.d.ts +2 -0
- package/dist/composition/owned-ui.js +43 -11
- package/dist/composition/reference-selection-options.d.ts +3 -0
- package/dist/composition/reference-selection-options.js +16 -0
- package/dist/composition/settings-route-host.d.ts +1 -2
- package/dist/composition/settings-route-host.js +19 -13
- package/dist/composition/settings-search-input.js +4 -1
- package/dist/composition/utility-tab.js +4 -2
- package/dist/features/launch/intent.js +2 -2
- package/dist/features/owned-ui/plus-menu-dialog.js +28 -20
- package/dist/features/owned-ui/reference-document-selection.d.ts +30 -0
- package/dist/features/owned-ui/reference-document-selection.js +179 -0
- package/dist/features/owned-ui/reference-screen-app.d.ts +4 -1
- package/dist/features/owned-ui/reference-screen-app.js +39 -8
- package/dist/features/owned-ui/settings-app.d.ts +1 -0
- package/dist/features/owned-ui/settings-app.js +23 -19
- package/dist/foundation/release/automatic-update.js +90 -22
- package/dist/foundation/release/bootstrap.js +2 -2
- package/dist/foundation/release/latest-release.d.ts +5 -1
- package/dist/foundation/release/latest-release.js +74 -31
- package/dist/integrations/pi/components/owned-editor-ux.js +48 -5
- package/dist/integrations/pi/engine/workflow-runner.js +1 -1
- package/dist/native/darwin-arm64/manifest.json +1 -1
- package/dist/native/darwin-arm64/terminal-host.manifest.json +3 -3
- package/dist/native/darwin-arm64/terminal-host.node +0 -0
- package/dist/native/darwin-x64/manifest.json +1 -1
- package/dist/native/darwin-x64/terminal-host.manifest.json +3 -3
- package/dist/native/darwin-x64/terminal-host.node +0 -0
- package/dist/native/linux-x64/manifest.json +1 -1
- package/dist/native/linux-x64/terminal-host.manifest.json +3 -3
- package/dist/native/linux-x64/terminal-host.node +0 -0
- package/dist/native/win32-x64/manifest.json +2 -2
- package/dist/native/win32-x64/process-guardian.node +0 -0
- package/dist/native/win32-x64/terminal-host.manifest.json +3 -3
- package/dist/native/win32-x64/terminal-host.node +0 -0
- package/dist/ui/components/dialog-panel.d.ts +17 -8
- package/dist/ui/components/dialog-panel.js +97 -26
- package/dist/ui/components/index.d.ts +3 -3
- package/dist/ui/components/index.js +3 -3
- package/dist/ui/components/scrollbar.d.ts +1 -1
- package/dist/ui/components/scrollbar.js +1 -1
- package/dist/ui/components/selection-copy.d.ts +2 -4
- package/dist/ui/components/selection-copy.js +4 -2
- package/dist/ui/components/text-selection.d.ts +1 -2
- package/dist/ui/components/text-selection.js +5 -2
- package/dist/ui/components/transcript-viewport.js +3 -3
- package/dist/ui/settings/declarations.d.ts +1 -1
- package/dist/ui/settings/declarations.js +1 -1
- package/docs/architecture/resident-tabs.md +5 -4
- package/docs/features/launch-profiles.md +7 -4
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -63,6 +63,8 @@ Launch A1 in single agent mode:
|
|
|
63
63
|
a1 solo
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
+
When a solo session exits, its printed `a1 solo --session <id>` command resumes that same conversation in solo mode; a custom session store is included in the printed command. Resident tabs are different: they keep running after detach, and bare `a1` returns to them.
|
|
67
|
+
|
|
66
68
|
Show all commands:
|
|
67
69
|
|
|
68
70
|
```text
|
|
@@ -92,7 +94,7 @@ a1 tabs
|
|
|
92
94
|
a1 tabs stop <id>
|
|
93
95
|
```
|
|
94
96
|
|
|
95
|
-
Stop every tab and the resident host
|
|
97
|
+
Stop every tab and the resident host when you are finished with them (not required to run `a1 solo`):
|
|
96
98
|
|
|
97
99
|
```text
|
|
98
100
|
a1 tabs host stop
|
|
@@ -145,13 +147,13 @@ Stuck on `0.2.2` and `a1 update` fails? Run the installer once, then use `a1 upd
|
|
|
145
147
|
npm x -y -- @timurproko/a1-install
|
|
146
148
|
```
|
|
147
149
|
|
|
148
|
-
The **Update** setting controls
|
|
150
|
+
The **Update** setting controls release discovery:
|
|
149
151
|
|
|
150
|
-
- **Auto** (default) silently installs a verified update
|
|
151
|
-
- **Notify** keeps the `Update Available` notice and leaves installation manual.
|
|
152
|
-
- **No** performs no
|
|
152
|
+
- **Auto** (default) checks the running release's stable or development channel at startup and every 15 minutes while open. It silently installs a verified update, then shows `Update installed · Restart to update`; existing sessions keep running until restarted.
|
|
153
|
+
- **Notify** checks at startup (at most daily), keeps the `Update Available` notice, and leaves installation manual.
|
|
154
|
+
- **No** performs no update check; updates remain available through the commands above.
|
|
153
155
|
|
|
154
|
-
`A1_SKIP_VERSION_CHECK=1`
|
|
156
|
+
`A1_SKIP_VERSION_CHECK=1` disables both Auto and Notify discovery. Manual stable, development, and exact-version update commands remain available in every mode.
|
|
155
157
|
|
|
156
158
|
<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>
|
|
157
159
|
|
package/bin/ui.js
CHANGED
|
@@ -103,6 +103,7 @@ if (utilityRoute !== null) {
|
|
|
103
103
|
ownedSurfaces,
|
|
104
104
|
notificationVersion: launchContext.updateVersion,
|
|
105
105
|
...(hidesTabSettings(launchContext) ? { hideTabSettings: true } : {}),
|
|
106
|
+
soloResumeHint: profile === "a1" && launchContext.launchMode === "solo",
|
|
106
107
|
...(processInspector === null ? {} : { inspectProcess: pid => processInspector.observe(pid) }),
|
|
107
108
|
projectTrustPrompt: createConsoleProjectTrustPrompt({ presentation: profile === "a1" ? "bare" : "comparison" }),
|
|
108
109
|
sessionForkPrompt: createConsoleSessionForkPrompt(),
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import { COPY_ACKNOWLEDGEMENT_DURATION_MS } from "../../ui/components/selection-copy.js";
|
|
1
2
|
import { renderPiShellCommandMessage, renderPiShellReleaseUpdateBanner, renderPiShellStatusText, } from "../../integrations/pi/components/shell-presenters-info.js";
|
|
2
3
|
import { PINNED_PI_LAYOUT, piTheme } from "../../integrations/pi/components/upstream/theme/theme.js";
|
|
3
4
|
import { piShellTruncateToWidth } from "../../integrations/pi/components/shell-shared-facade.js";
|
|
4
5
|
import { parseReleaseUpdateDiagnostic } from "./transcript-document.js";
|
|
5
|
-
const COPY_ACKNOWLEDGEMENT_DURATION_MS = 1_000;
|
|
6
6
|
/**
|
|
7
7
|
* The transient chrome around bare A1's dock: the one command notice above the editor, the release
|
|
8
8
|
* banner above Working with its close control, and the short copy acknowledgement.
|
|
@@ -13,6 +13,6 @@ export interface ExitNoticeSource {
|
|
|
13
13
|
export interface ArmedExitNotice {
|
|
14
14
|
clear(): void;
|
|
15
15
|
}
|
|
16
|
-
export declare function uncleanExitNoticeLines(resume: SessionResumeCommandMetadata | null): string[];
|
|
16
|
+
export declare function uncleanExitNoticeLines(resume: SessionResumeCommandMetadata | null, soloResumeHint?: boolean): string[];
|
|
17
17
|
/** Arms the notice and keeps its resume command following the bound session until cleared. */
|
|
18
|
-
export declare function armExitNotice(source: ExitNoticeSource, path: string): ArmedExitNotice;
|
|
18
|
+
export declare function armExitNotice(source: ExitNoticeSource, path: string, soloResumeHint?: boolean): ArmedExitNotice;
|
|
@@ -7,14 +7,14 @@
|
|
|
7
7
|
import { renameSync, rmSync, writeFileSync } from "node:fs";
|
|
8
8
|
import { PRODUCT_IDENTITY } from "../../product-identity.js";
|
|
9
9
|
import { formatSessionResumeCommand } from "./session-lifecycle.js";
|
|
10
|
-
export function uncleanExitNoticeLines(resume) {
|
|
10
|
+
export function uncleanExitNoticeLines(resume, soloResumeHint = false) {
|
|
11
11
|
return [
|
|
12
12
|
`${PRODUCT_IDENTITY.displayName} was stopped unexpectedly.`,
|
|
13
|
-
...(resume === null ? [] : [`To resume this session: ${formatSessionResumeCommand(resume)}`]),
|
|
13
|
+
...(resume === null ? [] : [`To resume this session: ${formatSessionResumeCommand(resume, soloResumeHint)}`]),
|
|
14
14
|
];
|
|
15
15
|
}
|
|
16
16
|
/** Arms the notice and keeps its resume command following the bound session until cleared. */
|
|
17
|
-
export function armExitNotice(source, path) {
|
|
17
|
+
export function armExitNotice(source, path, soloResumeHint = false) {
|
|
18
18
|
let current = null;
|
|
19
19
|
let cleared = false;
|
|
20
20
|
let generation = source.identity.sessionGeneration;
|
|
@@ -22,7 +22,7 @@ export function armExitNotice(source, path) {
|
|
|
22
22
|
if (cleared)
|
|
23
23
|
return;
|
|
24
24
|
// Protocol: plain text only; the guardian strips control characters, so styling would be lost.
|
|
25
|
-
const text = `${uncleanExitNoticeLines(source.identity.currentSessionResumeMetadata()).join("\n")}\n`;
|
|
25
|
+
const text = `${uncleanExitNoticeLines(source.identity.currentSessionResumeMetadata(), soloResumeHint).join("\n")}\n`;
|
|
26
26
|
if (text === current)
|
|
27
27
|
return;
|
|
28
28
|
try {
|
|
@@ -3,13 +3,14 @@ export interface SessionResumeCommandMetadata {
|
|
|
3
3
|
readonly sessionDir: string;
|
|
4
4
|
readonly usesDefaultSessionDir: boolean;
|
|
5
5
|
}
|
|
6
|
-
export declare function formatSessionResumeCommand(metadata: SessionResumeCommandMetadata): string;
|
|
6
|
+
export declare function formatSessionResumeCommand(metadata: SessionResumeCommandMetadata, solo?: boolean): string;
|
|
7
7
|
export declare function quoteCommandArgument(value: string): string;
|
|
8
8
|
export interface ExitTextInput {
|
|
9
9
|
readonly terminalMode: "regular" | "fullscreen";
|
|
10
10
|
readonly customViewport: boolean;
|
|
11
11
|
readonly exitMode: "transcript" | "resume-hint";
|
|
12
12
|
readonly resume: SessionResumeCommandMetadata | null;
|
|
13
|
+
readonly soloResumeHint?: boolean;
|
|
13
14
|
/** The styled transcript, rendered only when the pinned profile asks for it. */
|
|
14
15
|
readonly transcript: () => string;
|
|
15
16
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { PRODUCT_TEXT } from "../../product-identity.js";
|
|
2
|
-
export function formatSessionResumeCommand(metadata) {
|
|
2
|
+
export function formatSessionResumeCommand(metadata, solo = false) {
|
|
3
3
|
const args = [PRODUCT_TEXT.commandName];
|
|
4
|
+
if (solo)
|
|
5
|
+
args.push("solo");
|
|
4
6
|
if (!metadata.usesDefaultSessionDir)
|
|
5
7
|
args.push("--session-dir", quoteCommandArgument(metadata.sessionDir));
|
|
6
8
|
args.push("--session", metadata.sessionId);
|
|
@@ -18,7 +20,7 @@ export function quoteCommandArgument(value) {
|
|
|
18
20
|
export function composeExitText(input) {
|
|
19
21
|
if (input.terminalMode !== "fullscreen")
|
|
20
22
|
return "";
|
|
21
|
-
const resumeHint = input.resume === null ? "" : `${dim("To resume this session:")} ${formatSessionResumeCommand(input.resume)}`;
|
|
23
|
+
const resumeHint = input.resume === null ? "" : `${dim("To resume this session:")} ${formatSessionResumeCommand(input.resume, input.soloResumeHint)}`;
|
|
22
24
|
// Invariant: bare A1 leaves only the hint behind; the pinned comparison profile still
|
|
23
25
|
// honors fullscreenExitOutput, including the styled transcript.
|
|
24
26
|
if (input.customViewport || input.exitMode === "resume-hint")
|
|
@@ -73,6 +73,8 @@ export interface OwnedUiShellEngineOptions {
|
|
|
73
73
|
/** Terminal, startup, viewport, and presentation-cadence seams; production leaves most at their defaults. */
|
|
74
74
|
export interface OwnedUiShellPresentationOptions {
|
|
75
75
|
readonly terminal?: OwnedUiTerminalPort;
|
|
76
|
+
/** Explicit direct solo launch, not a resident fallback or comparison launch. */
|
|
77
|
+
readonly soloResumeHint?: boolean;
|
|
76
78
|
readonly startup?: OwnedUiStartupOptions;
|
|
77
79
|
/** Live profile-local settings, supplied only to the bare-A1 composition. */
|
|
78
80
|
readonly viewportSettings?: OwnedUiViewportSettingsPort;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { ImageAttachmentError } from "../../contracts/owned-ui/index.js";
|
|
2
2
|
import { promptArrow } from "../../ui/components/prompt-input.js";
|
|
3
3
|
import { composeSubmittedPromptRows, submittedPromptLayout, } from "../../ui/components/submitted-prompt.js";
|
|
4
|
-
import {
|
|
4
|
+
import { nativeHyperlinkStyle } from "../../ui/components/spans.js";
|
|
5
|
+
import { paintTextSelection } from "../../ui/components/text-selection.js";
|
|
5
6
|
import { progressStatusFrame, progressStatusText } from "../../ui/components/progress-status.js";
|
|
6
7
|
import { createPiQueuedInputStatus, createPiShellFooter, createPiShellHeader, createPiShellLoadedResources, createPiShellStatus, } from "../../integrations/pi/components/shell-footer-status.js";
|
|
7
8
|
import { createPiShellTranscriptComponent, } from "../../integrations/pi/components/shell-presenters-transcript.js";
|
|
@@ -847,5 +848,5 @@ const VIEWPORT_THEME = {
|
|
|
847
848
|
sticky: (text, hovered) => piTheme().bg(hovered ? "selectedBg" : "toolPendingBg", piTheme().fg("text", withoutTerminalBackground(text))),
|
|
848
849
|
quietSticky: text => `\u001b[2m${text.replace(/\u001b\[(?:0|22)m/g, "$&\u001b[2m")}\u001b[22m`,
|
|
849
850
|
bottomControl: (text, hovered) => piTheme().bg(hovered ? "selectedBg" : "toolPendingBg", piTheme().fg("text", text)),
|
|
850
|
-
selection:
|
|
851
|
+
selection: paintTextSelection,
|
|
851
852
|
};
|
|
@@ -14,6 +14,7 @@ import { promptSuggestionSurface, routeResponseToSuggestions, suggestionModelKey
|
|
|
14
14
|
import { CLEAR_EXIT_INTERVAL_MS, boundedCleanup } from "../../foundation/terminal-cleanup/terminal-reset.js";
|
|
15
15
|
import { OWNED_UI_HIDDEN_COMMAND_NAMES } from "../../contracts/owned-ui/index.js";
|
|
16
16
|
import { ImageAttachmentError } from "../../contracts/owned-ui/index.js";
|
|
17
|
+
import { copyAcknowledgementText } from "../../ui/components/selection-copy.js";
|
|
17
18
|
import { ContextualPromptSuggestionController } from "./prompt-suggestion-controller.js";
|
|
18
19
|
import { workflowCommandNames } from "../../integrations/pi/engine/workflows.js";
|
|
19
20
|
import { createPiExtensionUiBridge } from "../../integrations/pi/components/shell-extension-ui.js";
|
|
@@ -27,6 +28,8 @@ import { writeSystemClipboardText, } from "./system-clipboard.js";
|
|
|
27
28
|
import { OwnedUiSessionShellRoot, shellResourceEntries, } from "./session-shell-root.js";
|
|
28
29
|
// Invariant: a disposed shell leaves the engine a host that holds no reference back to it.
|
|
29
30
|
const DETACHED_WORKFLOW_INTERACTION = { prompt: async () => null, notify() { } };
|
|
31
|
+
// Invariant: the resume mode belongs to the presenter instance, not the persisted session or profile.
|
|
32
|
+
const SOLO_RESUME_HINTS = new WeakSet();
|
|
30
33
|
/** Resident Agents refuse the commands their tab bar serves instead, naming where they live. */
|
|
31
34
|
const RESIDENT_UTILITY_TAB_REFUSALS = Object.freeze({
|
|
32
35
|
settings: "Settings is available from the global Settings tab.",
|
|
@@ -108,6 +111,8 @@ export class OwnedUiSessionPresenter {
|
|
|
108
111
|
this.#sessionBindingGeneration = this.backend.identity.sessionBindingGeneration;
|
|
109
112
|
this.#suggestionModelKey = suggestionModelKey(this.backend.session.view());
|
|
110
113
|
this.#customViewport = sessionLayout === "custom-viewport";
|
|
114
|
+
if (options.presentation?.soloResumeHint)
|
|
115
|
+
SOLO_RESUME_HINTS.add(this);
|
|
111
116
|
this.#inputCoordination = inputPresentation?.coordination !== false;
|
|
112
117
|
this.#host = host;
|
|
113
118
|
this.#terminal = host.connect(this);
|
|
@@ -499,6 +504,8 @@ export class OwnedUiSessionPresenter {
|
|
|
499
504
|
const snapshot = routed.copySelection;
|
|
500
505
|
this.#trackCopy(this.#responseCopy?.submit(snapshot, this.#pendingClipboardWrite), copyCharacters(snapshot));
|
|
501
506
|
}
|
|
507
|
+
if (routed.copyLastMessage)
|
|
508
|
+
void this.runWorkflow({ command: "copy", argument: "" });
|
|
502
509
|
if (!routed.consumed)
|
|
503
510
|
return routed.data === data ? undefined : { data: routed.data };
|
|
504
511
|
return routed.data.length === 0 ? { consume: true } : { data: routed.data };
|
|
@@ -509,7 +516,7 @@ export class OwnedUiSessionPresenter {
|
|
|
509
516
|
if (count === undefined || this.#disposed || !this.#terminal.running || intent !== this.#copyIntentSequence
|
|
510
517
|
|| outcome !== "delivered" && outcome !== "submitted-unverified")
|
|
511
518
|
return;
|
|
512
|
-
this.root.showCopyAcknowledgement(
|
|
519
|
+
this.root.showCopyAcknowledgement(copyAcknowledgementText(count));
|
|
513
520
|
});
|
|
514
521
|
}
|
|
515
522
|
#promptSuggestionPresentationBlockReason(identity) {
|
|
@@ -1081,6 +1088,7 @@ export class OwnedUiSessionPresenter {
|
|
|
1081
1088
|
customViewport: this.#customViewport,
|
|
1082
1089
|
exitMode: this.backend.identity.disposed ? this.#fullscreenExitOutput : this.backend.settings.snapshot().fullscreenExitOutput,
|
|
1083
1090
|
resume: this.backend.identity.currentSessionResumeMetadata(),
|
|
1091
|
+
soloResumeHint: SOLO_RESUME_HINTS.has(this),
|
|
1084
1092
|
transcript: () => this.root.exitTranscript(terminal.columns),
|
|
1085
1093
|
});
|
|
1086
1094
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { PiTuiPointerSurface } from "../../integrations/pi/tui-runtime/contracts.js";
|
|
2
2
|
import type { OwnedUiViewportSettings } from "../../contracts/owned-ui/index.js";
|
|
3
3
|
import { type TranscriptViewportFrame, type TranscriptViewportFrameInput } from "../../ui/components/transcript-viewport.js";
|
|
4
|
-
import {
|
|
4
|
+
import type { SelectionCopySnapshot } from "../../ui/components/selection-copy.js";
|
|
5
5
|
import type { PiShellEditorPort } from "../../integrations/pi/components/shell-shared-facade.js";
|
|
6
6
|
import type { PasteEvent } from "./paste-protocol.js";
|
|
7
7
|
export interface SessionViewportControllerOptions {
|
|
@@ -26,6 +26,7 @@ export interface SessionViewportInputResult {
|
|
|
26
26
|
readonly data: string;
|
|
27
27
|
readonly consumed: boolean;
|
|
28
28
|
readonly copySelection?: SelectionCopySnapshot;
|
|
29
|
+
readonly copyLastMessage?: true;
|
|
29
30
|
}
|
|
30
31
|
/**
|
|
31
32
|
* Owns the stateful interaction policy of the bare-A1 session viewport. The
|
|
@@ -3,7 +3,6 @@ import { TranscriptViewport } from "../../ui/components/transcript-viewport.js";
|
|
|
3
3
|
import { readVisibleHyperlinks } from "../../ui/components/visible-hyperlinks.js";
|
|
4
4
|
import { routeMouseInput } from "../../ui/components/mouse.js";
|
|
5
5
|
import { scrollForTrackPage, scrollForThumbRow, scrollbarSelectionRows, scrollbarWheelRows } from "../../ui/components/scrollbar.js";
|
|
6
|
-
import { literalSelectionCopySnapshot } from "../../ui/components/selection-copy.js";
|
|
7
6
|
function withinTailControl(region, column, row) {
|
|
8
7
|
return row >= region.rowStart && row <= region.rowEnd && column >= region.columnStart && column <= region.columnEnd;
|
|
9
8
|
}
|
|
@@ -254,15 +253,13 @@ export class SessionViewportController {
|
|
|
254
253
|
pasteRequest = this.#nextPasteDiagnosticRequest();
|
|
255
254
|
this.#emitPasteRoute(pasteRequest, "shortcut-received");
|
|
256
255
|
}
|
|
257
|
-
//
|
|
258
|
-
// before the focused vanilla editor handles this input.
|
|
256
|
+
// Invariant: activate A1 aliases before editor input.
|
|
259
257
|
if (editorActive)
|
|
260
258
|
this.#editor.activateKeybindings();
|
|
261
259
|
// Protocol: bracketed paste is opaque input, even if its text resembles mouse reports.
|
|
262
260
|
if (data.startsWith("\u001b[200~"))
|
|
263
261
|
return { data, consumed: false };
|
|
264
|
-
// Platform:
|
|
265
|
-
// terminals vary between forwarding Ctrl+V and terminal-owned bracketed paste.
|
|
262
|
+
// Platform: Windows terminals may forward Ctrl+V or terminal-owned paste.
|
|
266
263
|
if (editorActive && this.#editor.matchesTerminalKey(data, "ctrl+v")) {
|
|
267
264
|
pasteRequest ??= this.#nextPasteDiagnosticRequest();
|
|
268
265
|
if (data !== "\u0016")
|
|
@@ -278,8 +275,7 @@ export class SessionViewportController {
|
|
|
278
275
|
this.#requestHyperlinkCleanup();
|
|
279
276
|
this.#requestRender();
|
|
280
277
|
}
|
|
281
|
-
// Invariant:
|
|
282
|
-
// or when scrolling is a no-op. Plain Home/End remain editor line navigation.
|
|
278
|
+
// Invariant: viewport shortcuts skip the prompt; Home/End do not.
|
|
283
279
|
if (editorActive && allowWheel && this.#editor.matchesTerminalKey(data, "ctrl+home")) {
|
|
284
280
|
if (this.#viewport.scrollTo(0, now)) {
|
|
285
281
|
this.#scheduleActivityExpiry();
|
|
@@ -295,6 +291,9 @@ export class SessionViewportController {
|
|
|
295
291
|
}
|
|
296
292
|
return { data: "", consumed: true };
|
|
297
293
|
}
|
|
294
|
+
if (data === "\u0018" && editorActive && !this.#viewport.hasSelection && !this.#editor.hasSelection()) {
|
|
295
|
+
return { data: "", consumed: true, copyLastMessage: true };
|
|
296
|
+
}
|
|
298
297
|
if (this.#viewport.frame === null) {
|
|
299
298
|
// Invariant: a not-yet-painted A1 screen is not Pi's selection surface.
|
|
300
299
|
return routeMouseInput(data, event => {
|
|
@@ -305,22 +304,15 @@ export class SessionViewportController {
|
|
|
305
304
|
return event.kind === "motion" || event.kind === "release" || (event.kind === "press" && event.button === 0);
|
|
306
305
|
});
|
|
307
306
|
}
|
|
308
|
-
if (data === "\u0003") {
|
|
309
|
-
// Invariant: the newest
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
? { data: "", consumed: true }
|
|
318
|
-
: { data: "", consumed: true, copySelection };
|
|
319
|
-
}
|
|
320
|
-
if (editorActive && this.#editor.hasSelection()) {
|
|
321
|
-
if (this.#viewport.clearSelection())
|
|
322
|
-
this.#requestRender();
|
|
323
|
-
}
|
|
307
|
+
if ((data === "\u0003" || data === "\u0018") && this.#viewport.hasSelection) {
|
|
308
|
+
// Invariant: the newest frame selection wins over an older editor selection.
|
|
309
|
+
const copySelection = this.#viewport.captureSelectedText();
|
|
310
|
+
this.#viewport.clearSelection();
|
|
311
|
+
this.#stopSelectionAutoScroll();
|
|
312
|
+
this.#requestRender();
|
|
313
|
+
return copySelection === null
|
|
314
|
+
? { data: "", consumed: true }
|
|
315
|
+
: { data: "", consumed: true, copySelection };
|
|
324
316
|
}
|
|
325
317
|
const previousPrompt = PROMPT_UP_INPUTS.has(data) || this.#editor.matchesTerminalKey(data, "alt+home");
|
|
326
318
|
if (editorActive && allowWheel && (previousPrompt || PROMPT_DOWN_INPUTS.has(data))) {
|
|
@@ -345,8 +337,7 @@ export class SessionViewportController {
|
|
|
345
337
|
const hits = frame.hits;
|
|
346
338
|
const overModal = this.#modalAt(event.column, event.row);
|
|
347
339
|
const wheel = event.kind === "wheel-up" || event.kind === "wheel-down";
|
|
348
|
-
// Invariant:
|
|
349
|
-
// fullscreen selection layer without ever invoking the hidden ordinary editor.
|
|
340
|
+
// Invariant: latch each gesture to one surface.
|
|
350
341
|
if (this.#pointerOwner === "drain" || !this.inputGeometryReady) {
|
|
351
342
|
if (event.kind === "release")
|
|
352
343
|
this.#pointerOwner = undefined;
|
|
@@ -387,8 +378,7 @@ export class SessionViewportController {
|
|
|
387
378
|
const wasOverBottom = hits.bottom !== null && previousPointer !== undefined
|
|
388
379
|
&& previousPointer.row === hits.bottom.row
|
|
389
380
|
&& previousPointer.column >= hits.bottom.columnStart && previousPointer.column <= hits.bottom.columnEnd;
|
|
390
|
-
// Invariant: every report
|
|
391
|
-
// Keep this location while the control is hidden; composition resolves its next hit region.
|
|
381
|
+
// Invariant: every report updates the pointer position.
|
|
392
382
|
this.#pointerPosition = { column: event.column, row: event.row };
|
|
393
383
|
repaint ||= wasOverBottom !== overBottom;
|
|
394
384
|
const wasOverTailControl = tailControl !== undefined && previousPointer !== undefined
|
|
@@ -407,7 +397,7 @@ export class SessionViewportController {
|
|
|
407
397
|
this.#viewport.setRailHovered(overRail);
|
|
408
398
|
this.#viewport.setStickyHovered(overSticky);
|
|
409
399
|
repaint = true;
|
|
410
|
-
if (editorActive && this.#
|
|
400
|
+
if (editorActive && this.#editorPointerSelecting && this.#editorPointerFrame !== undefined) {
|
|
411
401
|
this.#editor.handlePointer({
|
|
412
402
|
kind: "motion",
|
|
413
403
|
button: event.button,
|
|
@@ -423,6 +413,12 @@ export class SessionViewportController {
|
|
|
423
413
|
if (event.column === pendingEditorClick.column && event.row === pendingEditorClick.row)
|
|
424
414
|
return true;
|
|
425
415
|
this.#pendingEditorClick = undefined;
|
|
416
|
+
const editorFrame = editorActive ? this.#editorPointerFrame : undefined;
|
|
417
|
+
if (editorFrame !== undefined && this.#editor.handlePointer({ kind: "press", button: 0, column: pendingEditorClick.column, row: pendingEditorClick.row - editorFrame.rowStart + 1 })) {
|
|
418
|
+
this.#editorPointerSelecting = true;
|
|
419
|
+
this.#editor.handlePointer({ kind: "motion", button: event.button, column: event.column, row: event.row - editorFrame.rowStart + 1 });
|
|
420
|
+
return true;
|
|
421
|
+
}
|
|
426
422
|
this.#viewport.pressSelection(pendingEditorClick.column, pendingEditorClick.row, now, { continueMultiClick: false });
|
|
427
423
|
this.#viewport.extendSelection(event.column, event.row, now, false);
|
|
428
424
|
this.#updateSelectionAutoScroll(event.column, event.row, hits.viewportHeight, frame.rows.length);
|
|
@@ -513,8 +509,7 @@ export class SessionViewportController {
|
|
|
513
509
|
}
|
|
514
510
|
if (event.row >= 1 && event.row <= frame.rows.length && event.column <= frame.descriptor.width) {
|
|
515
511
|
if (editorFrame !== undefined && event.row >= editorFrame.rowStart && event.row <= editorFrame.rowEnd) {
|
|
516
|
-
// Invariant: no-drag
|
|
517
|
-
// motion promotes from this exact origin into complete-frame selection.
|
|
512
|
+
// Invariant: no-drag clicks remain editor-owned; first motion latches an editor drag.
|
|
518
513
|
this.#pendingEditorClick = { column: event.column, row: event.row };
|
|
519
514
|
}
|
|
520
515
|
else {
|
|
@@ -527,7 +522,7 @@ export class SessionViewportController {
|
|
|
527
522
|
return true;
|
|
528
523
|
}
|
|
529
524
|
if (event.kind === "release") {
|
|
530
|
-
if (editorActive && this.#
|
|
525
|
+
if (editorActive && this.#editorPointerSelecting && this.#editorPointerFrame !== undefined) {
|
|
531
526
|
const wasEditorSelecting = this.#editorPointerSelecting;
|
|
532
527
|
this.#editor.handlePointer({
|
|
533
528
|
kind: "release",
|
|
@@ -547,12 +542,6 @@ export class SessionViewportController {
|
|
|
547
542
|
const editorRow = pendingEditorClick.row - this.#editorPointerFrame.rowStart + 1;
|
|
548
543
|
this.#editor.handlePointer({ kind: "press", button: 0, column: pendingEditorClick.column, row: editorRow });
|
|
549
544
|
this.#editor.handlePointer({ kind: "release", button: 0, column: pendingEditorClick.column, row: editorRow });
|
|
550
|
-
if (this.#copyOnSelect) {
|
|
551
|
-
const selected = this.#editor.selectedText?.();
|
|
552
|
-
if (selected !== undefined) {
|
|
553
|
-
completedCopy = literalSelectionCopySnapshot(selected, this.#viewport.selectionRevision) ?? undefined;
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
545
|
}
|
|
557
546
|
forceRepaint = true;
|
|
558
547
|
repaint = true;
|
|
@@ -2,6 +2,7 @@ import { caretCell } from "../../ui/components/line-input.js";
|
|
|
2
2
|
import { PromptInput } from "../../ui/components/prompt-input.js";
|
|
3
3
|
import { backgroundSgrSpan, hyperlinkSgrSpan } from "../../ui/components/spans.js";
|
|
4
4
|
import { faint, stripAnsi } from "../../ui/components/text.js";
|
|
5
|
+
import { paintTextSelection } from "../../ui/components/text-selection.js";
|
|
5
6
|
import { createPiShellEditor } from "../../integrations/pi/components/shell-editor-autocomplete.js";
|
|
6
7
|
import { piTheme } from "../../integrations/pi/components/upstream/theme/theme.js";
|
|
7
8
|
import { piShellTruncateToWidth, piShellVisibleWidth, } from "../../integrations/pi/components/shell-shared-facade.js";
|
|
@@ -18,7 +19,9 @@ export function createShellEditor(ports) {
|
|
|
18
19
|
...handlers,
|
|
19
20
|
keybindingProfile: customViewport ? "a1" : "pi",
|
|
20
21
|
...(handlers.keybindings === undefined ? {} : { keybindings: handlers.keybindings }),
|
|
21
|
-
paintEditorSelection: (line, from, to, atomic) =>
|
|
22
|
+
paintEditorSelection: (line, from, to, atomic) => atomic
|
|
23
|
+
? backgroundSgrSpan(line, from, to, "\u001b[7m", "\u001b[27m", piShellVisibleWidth)
|
|
24
|
+
: paintTextSelection(line, from, to, piShellVisibleWidth, true),
|
|
22
25
|
transformPastedContent: content => {
|
|
23
26
|
try {
|
|
24
27
|
return promptChips.transformPastedContent(content, editor.getText());
|
package/dist/cli/dispatch.js
CHANGED
|
@@ -7,6 +7,8 @@ export function cliUsage(capabilities) {
|
|
|
7
7
|
"--session <path|id>",
|
|
8
8
|
"--session-dir <dir> --session <path|id>",
|
|
9
9
|
"solo",
|
|
10
|
+
"solo --session <path|id>",
|
|
11
|
+
"solo --session-dir <dir> --session <path|id>",
|
|
10
12
|
...(capabilities.developmentComparison ? ["pi"] : []),
|
|
11
13
|
"help",
|
|
12
14
|
"version",
|
|
@@ -36,6 +38,8 @@ export function cliHelp(capabilities) {
|
|
|
36
38
|
` ${command} --session <path|id>`,
|
|
37
39
|
` ${command} --session-dir <dir> --session <path|id>`,
|
|
38
40
|
` ${command} solo`,
|
|
41
|
+
` ${command} solo --session <path|id>`,
|
|
42
|
+
` ${command} solo --session-dir <dir> --session <path|id>`,
|
|
39
43
|
...(capabilities.developmentComparison ? [` ${command} pi`] : []),
|
|
40
44
|
` ${command} help`,
|
|
41
45
|
` ${command} version`,
|
|
@@ -145,8 +149,17 @@ export function parseCliCommand(arguments_, capabilities) {
|
|
|
145
149
|
return parsePackageCommand(rest, "pi");
|
|
146
150
|
return capabilities.developmentComparison ? { kind: "launch", profileId: "pi", launchMode: "solo" } : { kind: "noop" };
|
|
147
151
|
}
|
|
148
|
-
if (command === "solo")
|
|
149
|
-
|
|
152
|
+
if (command === "solo") {
|
|
153
|
+
if (rest.length === 0)
|
|
154
|
+
return { kind: "launch", profileId: "a1", launchMode: "solo" };
|
|
155
|
+
try {
|
|
156
|
+
const sessionSelection = parseSessionSelection(rest);
|
|
157
|
+
return { kind: "launch", profileId: "a1", launchMode: "solo", ...(sessionSelection === undefined ? {} : { sessionSelection }) };
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
return { kind: "error", message: PRODUCT_TEXT.diagnostic(`could not parse solo session launch: ${error instanceof Error ? error.message : String(error)}`) };
|
|
161
|
+
}
|
|
162
|
+
}
|
|
150
163
|
if (command === "update")
|
|
151
164
|
return parseUpdate(rest);
|
|
152
165
|
if (command === "session")
|
|
@@ -24,6 +24,8 @@ export interface OwnedUiCompositionOptions {
|
|
|
24
24
|
readonly profileId?: string;
|
|
25
25
|
/** Hide resident-only tab controls for the explicit direct single-agent launch. */
|
|
26
26
|
readonly hideTabSettings?: boolean;
|
|
27
|
+
/** The explicit solo launch owns a mode-specific resume hint; resident fallback keeps the tabbed form. */
|
|
28
|
+
readonly soloResumeHint?: boolean;
|
|
27
29
|
/** `off` withholds A1-specific surfaces from comparison profiles. */
|
|
28
30
|
readonly ownedSurfaces?: "on" | "off";
|
|
29
31
|
readonly projectTrustPrompt?: PiProjectTrustPreflightPrompt;
|
|
@@ -210,7 +210,7 @@ export async function composeOwnedUi(options = {}) {
|
|
|
210
210
|
},
|
|
211
211
|
presentation: {
|
|
212
212
|
...(options.terminal === undefined ? {} : { terminal: createPiTerminalBridge(options.terminal) }),
|
|
213
|
-
...residentStartupPresentation(tabBridge !== null, options.residentFallbackNotice),
|
|
213
|
+
...residentStartupPresentation(tabBridge !== null, options.residentFallbackNotice), soloResumeHint: options.soloResumeHint === true && tabBridge === null,
|
|
214
214
|
...(clipboardDiagnostics === null ? {} : { input: { onEvent: event => clipboardDiagnostics.runtime(event) } }),
|
|
215
215
|
...(viewportSettings === null ? {} : { viewportSettings }),
|
|
216
216
|
...(backgroundSettings === null ? {} : { backgroundSettings }),
|
|
@@ -286,7 +286,7 @@ export async function composeOwnedUi(options = {}) {
|
|
|
286
286
|
// Performance: the guardian notice loads beside first paint, never on the startup path.
|
|
287
287
|
if (exitNoticePath)
|
|
288
288
|
exitNotice = import("../app/session-shell/exit-notice.js")
|
|
289
|
-
.then(module => module.armExitNotice(backend, exitNoticePath), () => null);
|
|
289
|
+
.then(module => module.armExitNotice(backend, exitNoticePath, options.soloResumeHint === true && tabBridge === null), () => null);
|
|
290
290
|
presenter.start();
|
|
291
291
|
releaseUpdates.start();
|
|
292
292
|
},
|
|
@@ -345,17 +345,34 @@ function createPromptHistoryConfiguration(settings, backend, ownedSurfaces) {
|
|
|
345
345
|
}
|
|
346
346
|
function createStartupReleaseUpdateObservation(input) {
|
|
347
347
|
const controller = new AbortController();
|
|
348
|
+
let timer;
|
|
349
|
+
let announcedVersion = null;
|
|
350
|
+
let eligible = input.mode === "auto";
|
|
351
|
+
let cadence = null;
|
|
352
|
+
let nextDelay = null;
|
|
353
|
+
let started = false;
|
|
348
354
|
const observe = async () => {
|
|
349
|
-
|
|
350
|
-
|
|
355
|
+
// Performance: registry code and its locks stay outside the first-frame startup graph.
|
|
356
|
+
const releases = await import("../foundation/release/latest-release.js");
|
|
357
|
+
cadence = releases.UPDATE_CHECK_INTERVAL_MS;
|
|
358
|
+
nextDelay = cadence;
|
|
359
|
+
const check = input.checkForNewerRelease ?? releases.checkForNewerRelease;
|
|
351
360
|
const runningVersion = input.packageVersion ?? await readPackageVersion();
|
|
352
361
|
const discoveryVersion = input.mode === "notify" ? input.notificationVersion ?? runningVersion : runningVersion;
|
|
353
|
-
const
|
|
362
|
+
const options = {
|
|
354
363
|
runningVersion: discoveryVersion,
|
|
355
364
|
configDir: input.configDir,
|
|
356
365
|
interactive: process.stdout.isTTY === true,
|
|
357
366
|
settingMode: input.mode,
|
|
358
|
-
|
|
367
|
+
signal: controller.signal,
|
|
368
|
+
onNextCheckDelay: milliseconds => { nextDelay = milliseconds; },
|
|
369
|
+
};
|
|
370
|
+
// Invariant: opt-outs never arm a recurring timer, nor read the cache or probe the registry.
|
|
371
|
+
if (input.checkForNewerRelease === undefined && releases.startupReleaseCheckSkipReason(options) !== null) {
|
|
372
|
+
eligible = false;
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
const release = await check(options);
|
|
359
376
|
if (release === null || controller.signal.aborted)
|
|
360
377
|
return;
|
|
361
378
|
if (input.mode === "notify") {
|
|
@@ -365,14 +382,27 @@ function createStartupReleaseUpdateObservation(input) {
|
|
|
365
382
|
if (input.mode === "auto") {
|
|
366
383
|
const installed = await import("../foundation/release/automatic-update.js")
|
|
367
384
|
.then(module => module.requestAutomaticUpdate({ release, runningVersion, signal: controller.signal }));
|
|
368
|
-
if (installed !== null && !controller.signal.aborted) {
|
|
385
|
+
if (installed !== null && installed !== announcedVersion && !controller.signal.aborted) {
|
|
386
|
+
announcedVersion = installed;
|
|
369
387
|
input.backend.extensions.announceReleaseUpdate({ status: "installed", version: installed });
|
|
370
388
|
}
|
|
371
389
|
}
|
|
372
390
|
};
|
|
391
|
+
const run = async () => {
|
|
392
|
+
try {
|
|
393
|
+
await observe();
|
|
394
|
+
}
|
|
395
|
+
catch { /* Rationale: best effort; retry on the next interval. */ }
|
|
396
|
+
if (eligible && cadence !== null && !controller.signal.aborted) {
|
|
397
|
+
timer = setTimeout(() => { void run(); }, nextDelay ?? cadence);
|
|
398
|
+
timer.unref?.();
|
|
399
|
+
}
|
|
400
|
+
};
|
|
373
401
|
return {
|
|
374
|
-
start() {
|
|
375
|
-
|
|
402
|
+
start() { if (started || controller.signal.aborted)
|
|
403
|
+
return; started = true; void run(); },
|
|
404
|
+
stop() { controller.abort(); if (timer !== undefined)
|
|
405
|
+
clearTimeout(timer); },
|
|
376
406
|
};
|
|
377
407
|
}
|
|
378
408
|
export { watchSharedSettings } from "./owned-settings.js";
|
|
@@ -383,8 +413,10 @@ function settingsRouteOptions(tabBridge, keybindings, agentDir, cwd) {
|
|
|
383
413
|
agentDir,
|
|
384
414
|
cwd,
|
|
385
415
|
copyText: text => {
|
|
386
|
-
if (Buffer.byteLength(text)
|
|
387
|
-
|
|
416
|
+
if (Buffer.byteLength(text) > MAX_CLIPBOARD_TEXT_BYTES)
|
|
417
|
+
return false;
|
|
418
|
+
void writeSystemClipboardText(text).catch(() => { });
|
|
419
|
+
return true;
|
|
388
420
|
},
|
|
389
421
|
};
|
|
390
422
|
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ReferenceDocumentSelectionOptions } from "../features/owned-ui/reference-document-selection.js";
|
|
2
|
+
/** Builds the Changelog selection clipboard policy without loading Pi's engine in a utility tab. */
|
|
3
|
+
export declare function createReferenceSelectionOptions(settingsFile: string, copyText: ((text: string) => boolean | void) | undefined): ReferenceDocumentSelectionOptions;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
/** Builds the Changelog selection clipboard policy without loading Pi's engine in a utility tab. */
|
|
3
|
+
export function createReferenceSelectionOptions(settingsFile, copyText) {
|
|
4
|
+
return {
|
|
5
|
+
copyOnSelect: () => {
|
|
6
|
+
try {
|
|
7
|
+
const document = JSON.parse(readFileSync(settingsFile, "utf8"));
|
|
8
|
+
return typeof document.fullscreenCopyOnSelect === "boolean" ? document.fullscreenCopyOnSelect : true;
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
copyText: copyText ?? (() => false),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
@@ -2,7 +2,6 @@ import type { ReferenceDocumentProvider } from "../features/owned-ui/reference-s
|
|
|
2
2
|
import type { OwnedSettingsManager } from "../ui/settings/manager.js";
|
|
3
3
|
import type { UiRouteHost, UiRouteInput } from "../ui/apps/contracts.js";
|
|
4
4
|
import type { PiKeybindingsHost } from "../integrations/pi/components/shell-shared-facade.js";
|
|
5
|
-
/** Styled document rows for one content width. */
|
|
6
5
|
export type OwnedReferenceRows = NonNullable<ReferenceDocumentProvider["rows"]>;
|
|
7
6
|
/** Documents supplied by composition so the route host never reads shell data itself. */
|
|
8
7
|
export interface OwnedReferenceProviders {
|
|
@@ -22,7 +21,7 @@ export interface OwnedRouteHostOptions {
|
|
|
22
21
|
readonly keybindings?: PiKeybindingsHost;
|
|
23
22
|
readonly agentDir?: string;
|
|
24
23
|
readonly cwd?: string;
|
|
25
|
-
readonly copyText?: (text: string) => void;
|
|
24
|
+
readonly copyText?: (text: string) => boolean | void;
|
|
26
25
|
}
|
|
27
26
|
/** Declares settings and lazy document routes. */
|
|
28
27
|
export declare function createOwnedRouteHost(settings: OwnedSettingsManager, references?: OwnedReferenceProviders, options?: OwnedRouteHostOptions): UiRouteHost;
|