@timurproko/a1 0.1.8-dev.563 → 0.1.8-dev.572
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 +13 -11
- package/dist/app/session-shell/session-shell-root.js +6 -3
- package/dist/app/session-shell/session-shell.js +7 -1
- package/dist/app/session-shell/session-viewport-controller.js +48 -30
- package/dist/cli/dispatch.d.ts +3 -1
- package/dist/cli/dispatch.js +56 -56
- package/dist/cli/package-messages.d.ts +4 -3
- package/dist/cli/package-messages.js +23 -11
- package/dist/integrations/pi/components/shell-footer-status.d.ts +4 -3
- package/dist/integrations/pi/components/shell-footer-status.js +53 -15
- package/dist/integrations/pi/components/shell-shared-facade.d.ts +9 -1
- package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.d.ts +2 -2
- package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.js +3 -2
- package/dist/integrations/pi/engine/compaction-progress.d.ts +1 -1
- package/dist/integrations/pi/engine/compaction-progress.js +1 -0
- package/dist/integrations/pi/engine/repository-pr.d.ts +3 -3
- package/dist/integrations/pi/engine/repository-pr.js +6 -6
- package/dist/integrations/pi/engine/session-runtime.js +5 -4
- package/dist/integrations/pi/tui-runtime/adapter.d.ts +2 -0
- package/dist/integrations/pi/tui-runtime/adapter.js +5 -0
- 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 +3 -3
- package/dist/native/win32-x64/process-guardian.exe +0 -0
- package/dist/ui/components/index.d.ts +2 -1
- package/dist/ui/components/index.js +1 -1
- package/dist/ui/components/progress-status.d.ts +9 -2
- package/dist/ui/components/progress-status.js +24 -3
- package/dist/ui/components/transcript-viewport.d.ts +9 -3
- package/dist/ui/components/transcript-viewport.js +90 -132
- package/docs/architecture/ui-reference-provenance.md +8 -5
- package/docs/openspec-archive-automation.md +2 -2
- package/docs/validation.md +1 -1
- package/package.json +1 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { OwnedUiSessionViewModel } from "../../../contracts/owned-ui/index.js";
|
|
2
|
-
import { type
|
|
2
|
+
import { type KeybindingsConfig } from "./upstream/adjacent/core/keybindings.js";
|
|
3
|
+
import { type PiShellViewComponentPort, type PiShellStatusPort, type PiShellProgressStatusPresentation, type PiShellQueuedInputPort, type PiShellHeaderPort, type PiShellResourceEntry, type PiShellLoadedResourcesPort, type PiShellHeaderOptions, type PiShellEditorOptions } from "./shell-shared-facade.js";
|
|
3
4
|
export declare function createPiShellHeader(options?: PiShellHeaderOptions): PiShellHeaderPort;
|
|
4
5
|
export declare function createPiShellLoadedResources(resources: readonly PiShellResourceEntry[], initialExpanded?: boolean): PiShellLoadedResourcesPort;
|
|
5
|
-
export declare function createPiShellStatus(view: OwnedUiSessionViewModel,
|
|
6
|
+
export declare function createPiShellStatus(view: OwnedUiSessionViewModel, progressStatus: PiShellProgressStatusPresentation, runtime?: Pick<PiShellEditorOptions, "getColumns" | "getRows" | "requestRender">): PiShellStatusPort;
|
|
6
7
|
export declare function createPiShellFooter(view: OwnedUiSessionViewModel, cwd: string, profile?: "pi" | "a1", showLevel?: () => boolean): PiShellViewComponentPort;
|
|
7
|
-
export declare function createPiQueuedInputStatus(submissions: readonly string[], presentation?: "pinned" | "custom-viewport"): PiShellQueuedInputPort;
|
|
8
|
+
export declare function createPiQueuedInputStatus(submissions: readonly string[], presentation?: "pinned" | "custom-viewport", getKeybindings?: () => KeybindingsConfig): PiShellQueuedInputPort;
|
|
@@ -2,7 +2,7 @@ import { rawKeyHint, VERSION } from "../startup-public.js";
|
|
|
2
2
|
import { Spacer, Text, } from "@earendil-works/pi-tui";
|
|
3
3
|
import { SessionFooter } from "./upstream/components/session-footer.js";
|
|
4
4
|
import { KeybindingsManager } from "./upstream/adjacent/core/keybindings.js";
|
|
5
|
-
import { WorkingStatusIndicator } from "./upstream/components/status-indicator.js";
|
|
5
|
+
import { StatusIndicator, WorkingStatusIndicator } from "./upstream/components/status-indicator.js";
|
|
6
6
|
import { PINNED_PI_LAYOUT, piTheme, } from "./theme.js";
|
|
7
7
|
import { createTuiFacade, ensureTheme, } from "./shell-shared-facade.js";
|
|
8
8
|
export function createPiShellHeader(options = {}) {
|
|
@@ -70,15 +70,15 @@ export function createPiShellLoadedResources(resources, initialExpanded = false)
|
|
|
70
70
|
invalidate() { },
|
|
71
71
|
};
|
|
72
72
|
}
|
|
73
|
-
export function createPiShellStatus(view,
|
|
73
|
+
export function createPiShellStatus(view, progressStatus, runtime) {
|
|
74
74
|
ensureTheme();
|
|
75
75
|
const statusUi = createTuiFacade(runtime ?? { getColumns: () => 80, getRows: () => 24, requestRender() { } });
|
|
76
76
|
let workingOverride;
|
|
77
77
|
let outputPad = PINNED_PI_LAYOUT.outputPad;
|
|
78
78
|
let progressPresentation = "pinned";
|
|
79
79
|
let placement = statusPlacement(view, workingOverride);
|
|
80
|
-
const liveStatusText = () =>
|
|
81
|
-
let component = statusComponent(view, statusUi, outputPad, liveStatusText, placement);
|
|
80
|
+
const liveStatusText = () => progressStatus.text(liveWorkingText(view, workingOverride, progressPresentation), progressPresentation);
|
|
81
|
+
let component = statusComponent(view, statusUi, outputPad, liveStatusText, placement, progressPresentation, progressStatus);
|
|
82
82
|
let signature = statusSignature(view, workingOverride, outputPad, placement, progressPresentation);
|
|
83
83
|
const rebuild = () => {
|
|
84
84
|
const nextPlacement = statusPlacement(view, workingOverride);
|
|
@@ -86,7 +86,10 @@ export function createPiShellStatus(view, formatProgressStatus, runtime) {
|
|
|
86
86
|
if (nextSignature === signature)
|
|
87
87
|
return;
|
|
88
88
|
// Performance: progress ticks change only the live message; the spinner keeps its frame and timer.
|
|
89
|
-
|
|
89
|
+
const componentMatchesPresentation = progressPresentation === "custom-viewport"
|
|
90
|
+
? component instanceof OwnedWorkingStatusIndicator
|
|
91
|
+
: component instanceof WorkingStatusIndicator;
|
|
92
|
+
if (placement === "live" && nextPlacement === "live" && component instanceof StatusIndicator && componentMatchesPresentation
|
|
90
93
|
&& statusSignature(view, workingOverride, outputPad, nextPlacement, progressPresentation, false)
|
|
91
94
|
=== statusSignature(view, workingOverride, outputPad, placement, progressPresentation, false)) {
|
|
92
95
|
component.setMessage(liveStatusText());
|
|
@@ -97,7 +100,7 @@ export function createPiShellStatus(view, formatProgressStatus, runtime) {
|
|
|
97
100
|
component.dispose();
|
|
98
101
|
placement = nextPlacement;
|
|
99
102
|
signature = nextSignature;
|
|
100
|
-
component = statusComponent(view, statusUi, outputPad, liveStatusText, placement);
|
|
103
|
+
component = statusComponent(view, statusUi, outputPad, liveStatusText, placement, progressPresentation, progressStatus);
|
|
101
104
|
};
|
|
102
105
|
return {
|
|
103
106
|
render: width => component?.render(width) ?? [],
|
|
@@ -138,14 +141,27 @@ export function createPiShellFooter(view, cwd, profile = "pi", showLevel = () =>
|
|
|
138
141
|
dispose: () => footer.dispose(),
|
|
139
142
|
};
|
|
140
143
|
}
|
|
141
|
-
export function createPiQueuedInputStatus(submissions, presentation = "pinned") {
|
|
142
|
-
|
|
144
|
+
export function createPiQueuedInputStatus(submissions, presentation = "pinned", getKeybindings) {
|
|
145
|
+
let renderedText = queuedInputText(submissions, presentation, getKeybindings?.());
|
|
146
|
+
const text = new Text(renderedText, 1, 0);
|
|
147
|
+
const refresh = () => {
|
|
148
|
+
const next = queuedInputText(submissions, presentation, getKeybindings?.());
|
|
149
|
+
if (next === renderedText)
|
|
150
|
+
return;
|
|
151
|
+
renderedText = next;
|
|
152
|
+
text.setText(next);
|
|
153
|
+
};
|
|
143
154
|
return {
|
|
144
|
-
render
|
|
155
|
+
render(width) {
|
|
156
|
+
if (submissions.length === 0)
|
|
157
|
+
return [];
|
|
158
|
+
refresh();
|
|
159
|
+
return text.render(width);
|
|
160
|
+
},
|
|
145
161
|
invalidate: () => text.invalidate(),
|
|
146
162
|
update(next) {
|
|
147
163
|
submissions = next;
|
|
148
|
-
|
|
164
|
+
refresh();
|
|
149
165
|
},
|
|
150
166
|
};
|
|
151
167
|
}
|
|
@@ -168,9 +184,23 @@ function liveWorkingText(view, workingOverride, progressPresentation) {
|
|
|
168
184
|
? `${message}(${progress}%)`
|
|
169
185
|
: message;
|
|
170
186
|
}
|
|
171
|
-
|
|
187
|
+
class OwnedWorkingStatusIndicator extends StatusIndicator {
|
|
188
|
+
constructor(ui, message, frame) {
|
|
189
|
+
let phase = -1;
|
|
190
|
+
super("working", ui, spinner => {
|
|
191
|
+
phase += 1;
|
|
192
|
+
return piTheme().fg("accent", spinner);
|
|
193
|
+
}, text => frame(text, phase, {
|
|
194
|
+
muted: value => piTheme().fg("muted", value),
|
|
195
|
+
accent: value => piTheme().fg("accent", value),
|
|
196
|
+
}), message);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
function statusComponent(view, ui, outputPad, liveStatusText, placement, progressPresentation, progressStatus) {
|
|
172
200
|
if (placement === "live") {
|
|
173
|
-
return
|
|
201
|
+
return progressPresentation === "custom-viewport"
|
|
202
|
+
? new OwnedWorkingStatusIndicator(ui, liveStatusText(), progressStatus.frame)
|
|
203
|
+
: new WorkingStatusIndicator(ui, liveStatusText());
|
|
174
204
|
}
|
|
175
205
|
if (placement === "dock") {
|
|
176
206
|
if (view.lifecycle === "failed") {
|
|
@@ -184,7 +214,7 @@ function statusSignature(view, workingOverride, outputPad, placement, progressPr
|
|
|
184
214
|
const message = withMessage ? `${workingOverride ?? ""}\u0000${view.status.workingMessage ?? ""}\u0000${view.status.workingProgress ?? ""}` : "";
|
|
185
215
|
return `${placement}\u0000${outputPad}\u0000${view.lifecycle}\u0000${progressPresentation}\u0000${message}\u0000${view.status.diagnostics.at(-1) ?? ""}`;
|
|
186
216
|
}
|
|
187
|
-
function queuedInputText(submissions, presentation) {
|
|
217
|
+
function queuedInputText(submissions, presentation, bindings) {
|
|
188
218
|
if (submissions.length === 0)
|
|
189
219
|
return "";
|
|
190
220
|
const theme = piTheme();
|
|
@@ -192,11 +222,19 @@ function queuedInputText(submissions, presentation) {
|
|
|
192
222
|
return submissions.map(submission => theme.fg("muted", `Steering: ${submission.replaceAll("\n", " ⏎ ")}`)).join("\n");
|
|
193
223
|
}
|
|
194
224
|
const messages = submissions.map(submission => theme.fg("dim", `Steering: ${submission.replaceAll("\n", " ⏎ ")}`));
|
|
195
|
-
const
|
|
225
|
+
const keys = KeybindingsManager.fromOwnedBindings(bindings).getKeys("app.message.dequeue");
|
|
226
|
+
const label = keys.length === 0 ? "Unbound" : keys.map(displayKeybinding).join("/");
|
|
227
|
+
const dequeueHint = theme.fg("dim", `↳ ${label} to edit all queued messages`);
|
|
196
228
|
// Compatibility: the custom viewport matches Pi's interactive queue presentation while the
|
|
197
229
|
// comparison shell remains byte-for-byte compatible with its pinned fixture.
|
|
198
230
|
return ["", ...messages, dequeueHint].join("\n");
|
|
199
231
|
}
|
|
232
|
+
function displayKeybinding(key) {
|
|
233
|
+
return key.split("+").map(part => {
|
|
234
|
+
const display = process.platform === "darwin" && part.toLowerCase() === "alt" ? "option" : part;
|
|
235
|
+
return display.charAt(0).toUpperCase() + display.slice(1);
|
|
236
|
+
}).join("+");
|
|
237
|
+
}
|
|
200
238
|
function compactHeaderText() {
|
|
201
239
|
const theme = piTheme();
|
|
202
240
|
const instructions = [
|
|
@@ -231,7 +269,7 @@ function expandedHeaderText(bindings) {
|
|
|
231
269
|
rawKeyHint("!", "to run bash"),
|
|
232
270
|
rawKeyHint("!!", "to run bash (no context)"),
|
|
233
271
|
rawKeyHint("alt+enter", "to queue follow-up"),
|
|
234
|
-
rawKeyHint("alt+up", "to edit all queued messages"),
|
|
272
|
+
rawKeyHint(keys === undefined ? "alt+up" : keys.getKeys("app.message.dequeue").join("/") || "unbound", "to edit all queued messages"),
|
|
235
273
|
rawKeyHint(process.platform === "win32" ? "alt+v" : "ctrl+v", "to paste image (with text fallback)"),
|
|
236
274
|
rawKeyHint("drop files", "to attach"),
|
|
237
275
|
].join("\n");
|
|
@@ -80,11 +80,19 @@ export interface PiShellViewComponentPort extends PiShellComponentPort {
|
|
|
80
80
|
update(view: OwnedUiSessionViewModel): void;
|
|
81
81
|
}
|
|
82
82
|
export type PiShellStatusPlacement = "live" | "dock" | "hidden";
|
|
83
|
+
export type PiShellProgressPresentationMode = "pinned" | "custom-viewport";
|
|
84
|
+
export interface PiShellProgressStatusPresentation {
|
|
85
|
+
readonly text: (message: string, mode: PiShellProgressPresentationMode) => string;
|
|
86
|
+
readonly frame: (message: string, phase: number, styles: {
|
|
87
|
+
readonly muted: (text: string) => string;
|
|
88
|
+
readonly accent: (text: string) => string;
|
|
89
|
+
}) => string;
|
|
90
|
+
}
|
|
83
91
|
export interface PiShellStatusPort extends PiShellViewComponentPort {
|
|
84
92
|
setWorkingOverride(message: string | undefined): void;
|
|
85
93
|
setOutputPad(padding: 0 | 1): void;
|
|
86
94
|
/** Bare A1 shows engine-measured progress beside the working word; the pinned route keeps the bare word. */
|
|
87
|
-
setProgressPresentation(presentation:
|
|
95
|
+
setProgressPresentation(presentation: PiShellProgressPresentationMode): void;
|
|
88
96
|
/** Semantic row placement; callers must not inspect rendered text. */
|
|
89
97
|
placement(): PiShellStatusPlacement;
|
|
90
98
|
renderDock(width: number): readonly string[];
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Provenance: @earendil-works/pi-coding-agent 0.87.1 (MIT), commit f07218c4d4bbc12bef056a7058c3dd49dfe41abe,
|
|
3
3
|
* packages/coding-agent/src/core/keybindings.ts.
|
|
4
4
|
* Modifications: Mechanical source port with Node import prefixes, public package-root agent-directory
|
|
5
|
-
* resolution, and an opt-in bare-A1 input profile including Ctrl+L level cycling
|
|
6
|
-
* selection.
|
|
5
|
+
* resolution, and an opt-in bare-A1 input profile including Ctrl+L level cycling, unbound model
|
|
6
|
+
* selection, and cross-platform Alt+Up queued-message restoration.
|
|
7
7
|
* Deviations: keybindings-public-config-boundary, owned-level-cycle-shortcut,
|
|
8
8
|
* owned-input-keybinding-aliases.
|
|
9
9
|
*/
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Provenance: @earendil-works/pi-coding-agent 0.87.1 (MIT), commit f07218c4d4bbc12bef056a7058c3dd49dfe41abe,
|
|
3
3
|
* packages/coding-agent/src/core/keybindings.ts.
|
|
4
4
|
* Modifications: Mechanical source port with Node import prefixes, public package-root agent-directory
|
|
5
|
-
* resolution, and an opt-in bare-A1 input profile including Ctrl+L level cycling
|
|
6
|
-
* selection.
|
|
5
|
+
* resolution, and an opt-in bare-A1 input profile including Ctrl+L level cycling, unbound model
|
|
6
|
+
* selection, and cross-platform Alt+Up queued-message restoration.
|
|
7
7
|
* Deviations: keybindings-public-config-boundary, owned-level-cycle-shortcut,
|
|
8
8
|
* owned-input-keybinding-aliases.
|
|
9
9
|
*/
|
|
@@ -184,6 +184,7 @@ const OWNED_INPUT_KEYBINDINGS = {
|
|
|
184
184
|
...KEYBINDINGS,
|
|
185
185
|
"app.thinking.cycle": { ...KEYBINDINGS["app.thinking.cycle"], defaultKeys: "ctrl+l" },
|
|
186
186
|
"app.model.select": { ...KEYBINDINGS["app.model.select"], defaultKeys: [] },
|
|
187
|
+
"app.message.dequeue": { ...KEYBINDINGS["app.message.dequeue"], defaultKeys: "alt+up" },
|
|
187
188
|
"tui.editor.deleteWordBackward": {
|
|
188
189
|
...KEYBINDINGS["tui.editor.deleteWordBackward"],
|
|
189
190
|
defaultKeys: [...KEYBINDINGS["tui.editor.deleteWordBackward"].defaultKeys, "ctrl+backspace"],
|
|
@@ -10,7 +10,7 @@ export interface CompactionProgressSession {
|
|
|
10
10
|
} | undefined;
|
|
11
11
|
}
|
|
12
12
|
export interface CompactionProgressObserver {
|
|
13
|
-
/** Marks `compaction_start`: resets the
|
|
13
|
+
/** Marks `compaction_start`: resets the estimate and immediately reports zero percent. */
|
|
14
14
|
begin(): void;
|
|
15
15
|
/** Marks `compaction_end`: later stream chunks are ignored. */
|
|
16
16
|
end(): void;
|
|
@@ -3,6 +3,6 @@ export interface PiPullRequestIdentity {
|
|
|
3
3
|
readonly url: string;
|
|
4
4
|
}
|
|
5
5
|
export type PiPullRequestProbe = (cwd: string, branch: string, signal: AbortSignal) => Promise<PiPullRequestIdentity | null>;
|
|
6
|
-
/** Read
|
|
7
|
-
export declare function
|
|
8
|
-
export declare function
|
|
6
|
+
/** Read an exact branch's open or merged PR. */
|
|
7
|
+
export declare function readPullRequest(cwd: string, branch: string, signal: AbortSignal, environment?: NodeJS.ProcessEnv): Promise<PiPullRequestIdentity | null>;
|
|
8
|
+
export declare function parsePullRequest(stdout: string, branch: string): PiPullRequestIdentity | null;
|
|
@@ -2,19 +2,19 @@ import { execFile } from "node:child_process";
|
|
|
2
2
|
import { PRODUCT_IDENTITY } from "../../../product-identity.js";
|
|
3
3
|
const GH_TIMEOUT_MS = 5_000;
|
|
4
4
|
const GH_MAX_BUFFER_BYTES = 64 * 1024;
|
|
5
|
-
/** Read
|
|
6
|
-
export async function
|
|
5
|
+
/** Read an exact branch's open or merged PR. */
|
|
6
|
+
export async function readPullRequest(cwd, branch, signal, environment = process.env) {
|
|
7
7
|
if (branch.length === 0 || signal.aborted)
|
|
8
8
|
return null;
|
|
9
9
|
const preview = environment[PRODUCT_IDENTITY.environment.prFooterPreview];
|
|
10
10
|
if (preview !== undefined)
|
|
11
|
-
return
|
|
11
|
+
return parsePullRequest(preview, branch);
|
|
12
12
|
const stdout = await executeGh(cwd, signal);
|
|
13
13
|
if (stdout === null)
|
|
14
14
|
return null;
|
|
15
|
-
return
|
|
15
|
+
return parsePullRequest(stdout, branch);
|
|
16
16
|
}
|
|
17
|
-
export function
|
|
17
|
+
export function parsePullRequest(stdout, branch) {
|
|
18
18
|
let value;
|
|
19
19
|
try {
|
|
20
20
|
value = JSON.parse(stdout);
|
|
@@ -25,7 +25,7 @@ export function parseOpenPullRequest(stdout, branch) {
|
|
|
25
25
|
if (!isRecord(value)
|
|
26
26
|
|| !Number.isSafeInteger(value.number)
|
|
27
27
|
|| value.number <= 0
|
|
28
|
-
|| value.state !== "OPEN"
|
|
28
|
+
|| (value.state !== "OPEN" && value.state !== "MERGED")
|
|
29
29
|
|| value.headRefName !== branch
|
|
30
30
|
|| typeof value.url !== "string")
|
|
31
31
|
return null;
|
|
@@ -6,7 +6,7 @@ import { observeCompactionProgress } from "./compaction-progress.js";
|
|
|
6
6
|
import { createPiRuntimeIntegration } from "./runtime-integration.js";
|
|
7
7
|
import { PiSessionCommandIntegration } from "./session-integration.js";
|
|
8
8
|
import { DefaultPackageManager, VERSION } from "../startup-public.js";
|
|
9
|
-
import {
|
|
9
|
+
import { readPullRequest, } from "./repository-pr.js";
|
|
10
10
|
const execFileAsync = promisify(execFile);
|
|
11
11
|
const PULL_REQUEST_REFRESH_MS = 60_000;
|
|
12
12
|
const REPOSITORY_CONTEXT_POLL_MS = 5_000;
|
|
@@ -56,7 +56,7 @@ export class PiEngineRuntime {
|
|
|
56
56
|
?? (options.createRuntime
|
|
57
57
|
? async () => []
|
|
58
58
|
: settingsManager => checkDefaultPiPackageUpdates(this.#cwd, options.agentDir, settingsManager));
|
|
59
|
-
this.#pullRequestProbe = options.pullRequestProbe ??
|
|
59
|
+
this.#pullRequestProbe = options.pullRequestProbe ?? readPullRequest;
|
|
60
60
|
this.#pullRequestRefreshMs = options.pullRequestRefreshMs ?? PULL_REQUEST_REFRESH_MS;
|
|
61
61
|
this.#repositoryContextPollMs = options.repositoryContextPollMs ?? REPOSITORY_CONTEXT_POLL_MS;
|
|
62
62
|
this.#repositoryContextReader = options.repositoryContextReader ?? (async () => null);
|
|
@@ -393,6 +393,8 @@ function samePullRequest(left, right) {
|
|
|
393
393
|
return left?.number === right?.number && left?.url === right?.url;
|
|
394
394
|
}
|
|
395
395
|
async function readGitBranch(cwd, signal) {
|
|
396
|
+
if (!existsSync(cwd))
|
|
397
|
+
return null;
|
|
396
398
|
try {
|
|
397
399
|
const { stdout } = await execFileAsync("git", ["branch", "--show-current"], {
|
|
398
400
|
cwd,
|
|
@@ -402,8 +404,7 @@ async function readGitBranch(cwd, signal) {
|
|
|
402
404
|
signal,
|
|
403
405
|
encoding: "utf8",
|
|
404
406
|
});
|
|
405
|
-
|
|
406
|
-
return branch.length > 0 ? branch : null;
|
|
407
|
+
return stdout.trim() || null;
|
|
407
408
|
}
|
|
408
409
|
catch {
|
|
409
410
|
return null;
|
|
@@ -47,6 +47,8 @@ export declare class PiTuiRuntimeAdapter {
|
|
|
47
47
|
getClearOnShrink(): boolean;
|
|
48
48
|
setTerminalProgress(active: boolean): void;
|
|
49
49
|
setTitle(title: string): void;
|
|
50
|
+
/** Shows a bounded fullscreen acknowledgement without changing root layout geometry. */
|
|
51
|
+
showFlash(message: string, durationMs?: number): void;
|
|
50
52
|
writeAfterStop(text: string): void;
|
|
51
53
|
invalidate(): void;
|
|
52
54
|
requestRender(force?: boolean): void;
|
|
@@ -392,6 +392,11 @@ export class PiTuiRuntimeAdapter {
|
|
|
392
392
|
setTitle(title) {
|
|
393
393
|
this.#terminal.setTitle(title);
|
|
394
394
|
}
|
|
395
|
+
/** Shows a bounded fullscreen acknowledgement without changing root layout geometry. */
|
|
396
|
+
showFlash(message, durationMs) {
|
|
397
|
+
this.#assertRunning("flash");
|
|
398
|
+
this.#requireFullscreen().flash(message, durationMs);
|
|
399
|
+
}
|
|
395
400
|
writeAfterStop(text) {
|
|
396
401
|
if (this.#state !== "stopped")
|
|
397
402
|
throw new Error("terminal output requires a stopped Pi TUI runtime");
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"platform": "darwin",
|
|
6
6
|
"architecture": "arm64",
|
|
7
7
|
"capability": "supported",
|
|
8
|
-
"builtAt": "2026-09-
|
|
8
|
+
"builtAt": "2026-09-23T23:03:13.628Z",
|
|
9
9
|
"artifact": {
|
|
10
10
|
"filename": "process-guardian",
|
|
11
11
|
"sha256": "9db1726bbe3fc2e8292f2ead1e7e9d0fd4d7d0dc9217b372582bf354b0f565dc",
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"platform": "linux",
|
|
6
6
|
"architecture": "x64",
|
|
7
7
|
"capability": "supported",
|
|
8
|
-
"builtAt": "2026-09-
|
|
8
|
+
"builtAt": "2026-09-23T23:03:11.352Z",
|
|
9
9
|
"artifact": {
|
|
10
10
|
"filename": "process-guardian",
|
|
11
11
|
"sha256": "d8cda6b0c7cb36c0cc41e90802aceebf6c02eaebbc08a83d7d963d2e918dbd7a",
|
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
"platform": "win32",
|
|
6
6
|
"architecture": "x64",
|
|
7
7
|
"capability": "supported",
|
|
8
|
-
"builtAt": "2026-09-
|
|
8
|
+
"builtAt": "2026-09-23T23:03:54.826Z",
|
|
9
9
|
"artifact": {
|
|
10
10
|
"filename": "process-guardian.exe",
|
|
11
|
-
"sha256": "
|
|
12
|
-
"size":
|
|
11
|
+
"sha256": "6c21b828ef2eb284a783609ad28dee4744f81cf0d53a4de1e38bddba4da08079",
|
|
12
|
+
"size": 178176
|
|
13
13
|
},
|
|
14
14
|
"provenance": {
|
|
15
15
|
"language": "Rust",
|
|
Binary file
|
|
@@ -15,7 +15,8 @@ export { MOUSE_TRACKING_OFF, MOUSE_TRACKING_ON, parseMouseInput, routeMouseInput
|
|
|
15
15
|
export type { ParsedMouseInput, RoutedMouseInput } from "./mouse.js";
|
|
16
16
|
export { isInsidePane, toPaneLocalMouse } from "./pane.js";
|
|
17
17
|
export type { Pane, PaneInputResult, PaneMouseEvent } from "./pane.js";
|
|
18
|
-
export { progressStatusText } from "./progress-status.js";
|
|
18
|
+
export { progressStatusFrame, progressStatusText } from "./progress-status.js";
|
|
19
|
+
export type { ProgressStatusFrameStyles, ProgressStatusMarker } from "./progress-status.js";
|
|
19
20
|
export { FrameCache, RENDER_REVISION_KINDS, RenderRevisionTracker, ZERO_REVISIONS, normalizeRevisions, revisionsEqual } from "./revision.js";
|
|
20
21
|
export type { RenderCacheContract, RenderRevisionKind, RenderRevisions } from "./revision.js";
|
|
21
22
|
export { MAX_COPY_ROWS, MAX_COPY_SOURCE_UNITS, selectionCopyLineContent, selectionCopyRowText } from "./selection-copy.js";
|
|
@@ -7,7 +7,7 @@ export { blockJumpTarget, blockRowSpan, clampScroll, indexOfKey, layoutList, max
|
|
|
7
7
|
export { STEPPER_RESERVE, placementFor, regionAt, renderGroupHeader, renderListRow, renderNote, valueColumnFor } from "./list-view.js";
|
|
8
8
|
export { MOUSE_TRACKING_OFF, MOUSE_TRACKING_ON, parseMouseInput, routeMouseInput } from "./mouse.js";
|
|
9
9
|
export { isInsidePane, toPaneLocalMouse } from "./pane.js";
|
|
10
|
-
export { progressStatusText } from "./progress-status.js";
|
|
10
|
+
export { progressStatusFrame, progressStatusText } from "./progress-status.js";
|
|
11
11
|
export { FrameCache, RENDER_REVISION_KINDS, RenderRevisionTracker, ZERO_REVISIONS, normalizeRevisions, revisionsEqual } from "./revision.js";
|
|
12
12
|
export { MAX_COPY_ROWS, MAX_COPY_SOURCE_UNITS, selectionCopyLineContent, selectionCopyRowText } from "./selection-copy.js";
|
|
13
13
|
export { ScrollbarRails, isThumbRow, scrollForThumbRow, scrollForTrackPage, scrollbarGeometry, scrollbarPresentation, scrollbarReservesSpace, scrollbarSelectionRows, scrollbarWheelRows, } from "./scrollbar.js";
|
|
@@ -1,2 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
1
|
+
export type ProgressStatusMarker = "…" | "...";
|
|
2
|
+
export interface ProgressStatusFrameStyles {
|
|
3
|
+
readonly muted: (text: string) => string;
|
|
4
|
+
readonly accent: (text: string) => string;
|
|
5
|
+
}
|
|
6
|
+
/** Returns spinner-backed status text with the selected terminal progress marker. */
|
|
7
|
+
export declare function progressStatusText(message: string, marker?: ProgressStatusMarker): string;
|
|
8
|
+
/** Paints one width-stable, grapheme-safe frame of the quiet progress-label sweep. */
|
|
9
|
+
export declare function progressStatusFrame(message: string, phase: number, styles: ProgressStatusFrameStyles): string;
|
|
@@ -1,4 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
const GRAPHEMES = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
2
|
+
const HIGHLIGHT_WIDTH = 2;
|
|
3
|
+
const UPDATES_PER_STEP = 3;
|
|
4
|
+
/** Returns spinner-backed status text with the selected terminal progress marker. */
|
|
5
|
+
export function progressStatusText(message, marker = "…") {
|
|
6
|
+
return `${message.replace(/(?:…|\.+)$/u, "")}${marker}`;
|
|
7
|
+
}
|
|
8
|
+
/** Paints one width-stable, grapheme-safe frame of the quiet progress-label sweep. */
|
|
9
|
+
export function progressStatusFrame(message, phase, styles) {
|
|
10
|
+
const marker = message.endsWith("…") ? "…" : "";
|
|
11
|
+
const label = marker === "" ? message : message.slice(0, -marker.length);
|
|
12
|
+
const graphemes = [...GRAPHEMES.segment(label)].map(segment => segment.segment);
|
|
13
|
+
if (graphemes.length === 0)
|
|
14
|
+
return paint(marker, styles.muted);
|
|
15
|
+
const step = Math.floor(Math.max(0, phase) / UPDATES_PER_STEP) % (graphemes.length * 2);
|
|
16
|
+
if (step >= graphemes.length)
|
|
17
|
+
return paint(`${label}${marker}`, styles.muted);
|
|
18
|
+
const before = graphemes.slice(0, step).join("");
|
|
19
|
+
const highlight = graphemes.slice(step, step + HIGHLIGHT_WIDTH).join("");
|
|
20
|
+
const after = `${graphemes.slice(step + HIGHLIGHT_WIDTH).join("")}${marker}`;
|
|
21
|
+
return `${paint(before, styles.muted)}${paint(highlight, styles.accent)}${paint(after, styles.muted)}`;
|
|
22
|
+
}
|
|
23
|
+
function paint(text, style) {
|
|
24
|
+
return text === "" ? "" : style(text);
|
|
4
25
|
}
|
|
@@ -50,6 +50,12 @@ export interface TranscriptViewportFrameInput {
|
|
|
50
50
|
readonly column: number;
|
|
51
51
|
readonly row: number;
|
|
52
52
|
};
|
|
53
|
+
/** Base-frame cells hidden by modal/replacement surfaces and therefore excluded from visual copy. */
|
|
54
|
+
readonly coveredCells?: readonly {
|
|
55
|
+
readonly row: number;
|
|
56
|
+
readonly columnStart: number;
|
|
57
|
+
readonly columnEnd: number;
|
|
58
|
+
}[];
|
|
53
59
|
readonly now?: number;
|
|
54
60
|
readonly theme?: TranscriptViewportTheme;
|
|
55
61
|
}
|
|
@@ -150,12 +156,12 @@ export declare class TranscriptViewport {
|
|
|
150
156
|
setStickyHovered(hovered: boolean): void;
|
|
151
157
|
get selectionActive(): boolean;
|
|
152
158
|
get hasSelection(): boolean;
|
|
153
|
-
pressSelection(column: number,
|
|
154
|
-
extendSelection(column: number,
|
|
159
|
+
pressSelection(column: number, frameRow: number, now?: number): boolean;
|
|
160
|
+
extendSelection(column: number, frameRow: number, now?: number, autoScroll?: boolean): boolean;
|
|
155
161
|
releaseSelection(): boolean;
|
|
156
162
|
clearSelection(): boolean;
|
|
157
163
|
selectedText(): string | null;
|
|
158
|
-
/** Captures selected
|
|
164
|
+
/** Captures only the selected visible frame text, never off-screen transcript history. */
|
|
159
165
|
captureSelectedText(): SelectionCopySnapshot | null;
|
|
160
166
|
scrollBy(lines: number, now?: number): boolean;
|
|
161
167
|
scrollTo(position: number, now?: number): boolean;
|