@timurproko/a1 0.1.8-dev.332 → 0.1.8-dev.368
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 +40 -11
- package/dist/composition/owned-ui.d.ts +2 -0
- package/dist/composition/owned-ui.js +20 -3
- package/dist/contracts/owned-ui/index.d.ts +2 -0
- package/dist/contracts/owned-ui/index.js +2 -0
- package/dist/contracts/owned-ui/model.d.ts +38 -3
- package/dist/contracts/owned-ui/prompt-history.d.ts +18 -0
- package/dist/contracts/owned-ui/prompt-suggestions.d.ts +1 -1
- package/dist/contracts/owned-ui/prompt-suggestions.js +4 -1
- package/dist/contracts/owned-ui/suggestion-diagnostics.d.ts +22 -0
- package/dist/contracts/owned-ui/suggestion-diagnostics.js +9 -0
- package/dist/contracts/owned-ui/transcript-lifecycle.d.ts +5 -0
- package/dist/contracts/owned-ui/transcript-lifecycle.js +33 -0
- package/dist/contracts/owned-ui/validation.js +45 -2
- package/dist/contracts/presentation/index.d.ts +8 -0
- package/dist/features/owned-ui/settings-app.js +1 -1
- package/dist/features/prompt-history/image-sidecar.d.ts +32 -0
- package/dist/features/prompt-history/image-sidecar.js +130 -0
- package/dist/features/prompt-history/index.d.ts +2 -0
- package/dist/features/prompt-history/index.js +1 -0
- package/dist/features/prompt-history/paths.d.ts +1 -0
- package/dist/features/prompt-history/paths.js +2 -1
- package/dist/features/prompt-history/store.d.ts +1 -1
- package/dist/features/prompt-history/store.js +46 -2
- package/dist/features/prompt-history/worker.js +1 -1
- package/dist/features/prompt-suggestions/diagnostics.d.ts +18 -0
- package/dist/features/prompt-suggestions/diagnostics.js +82 -0
- package/dist/features/prompt-suggestions/index.d.ts +1 -0
- package/dist/features/prompt-suggestions/index.js +1 -0
- package/dist/foundation/release/bootstrap.d.ts +2 -0
- package/dist/foundation/release/bootstrap.js +54 -18
- package/dist/foundation/release/index.d.ts +1 -0
- package/dist/foundation/release/index.js +1 -0
- package/dist/foundation/release/update-launch.d.ts +6 -0
- package/dist/foundation/release/update-launch.js +17 -0
- package/dist/foundation/release/update.d.ts +2 -0
- package/dist/foundation/release/update.js +20 -21
- package/dist/foundation/supervision/main.js +5 -2
- package/dist/foundation/supervision/server.js +40 -13
- package/dist/integrations/pi/components/owned-editor-ux.d.ts +9 -1
- package/dist/integrations/pi/components/owned-editor-ux.js +11 -8
- package/dist/integrations/pi/components/prompt-input-port.d.ts +18 -0
- package/dist/integrations/pi/components/prompt-input-port.js +1 -0
- package/dist/integrations/pi/components/shell-editor-autocomplete.js +66 -11
- package/dist/integrations/pi/components/shell-footer-status.d.ts +1 -1
- package/dist/integrations/pi/components/shell-footer-status.js +9 -5
- package/dist/integrations/pi/components/shell-presenters-info.js +7 -2
- package/dist/integrations/pi/components/shell-presenters-transcript.d.ts +6 -2
- package/dist/integrations/pi/components/shell-presenters-transcript.js +81 -49
- package/dist/integrations/pi/components/shell-shared-facade.d.ts +17 -4
- package/dist/integrations/pi/components/shell-shared-facade.js +2 -1
- package/dist/integrations/pi/components/submitted-prompt-adapter.d.ts +3 -1
- package/dist/integrations/pi/components/submitted-prompt-adapter.js +20 -4
- package/dist/integrations/pi/components/tool-result-adapter.d.ts +6 -0
- package/dist/integrations/pi/components/tool-result-adapter.js +27 -0
- package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.d.ts +1 -0
- package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.js +5 -0
- package/dist/integrations/pi/components/upstream/components/owned-editor.d.ts +7 -2
- package/dist/integrations/pi/components/upstream/components/owned-editor.js +41 -33
- package/dist/integrations/pi/components/upstream/components/session-footer.d.ts +2 -1
- package/dist/integrations/pi/components/upstream/components/session-footer.js +12 -7
- package/dist/integrations/pi/components/upstream/history/editor-core.js +1 -1
- package/dist/integrations/pi/engine/adapter.d.ts +2 -1
- package/dist/integrations/pi/engine/adapter.js +181 -84
- package/dist/integrations/pi/engine/tool-rendering.d.ts +13 -0
- package/dist/integrations/pi/engine/tool-rendering.js +122 -0
- package/dist/integrations/pi/session-ui/prompt-chips.d.ts +30 -0
- package/dist/integrations/pi/session-ui/prompt-chips.js +128 -1
- package/dist/integrations/pi/session-ui/prompt-history-controller.d.ts +8 -0
- package/dist/integrations/pi/session-ui/prompt-history-controller.js +30 -2
- package/dist/integrations/pi/session-ui/prompt-suggestion-controller.d.ts +11 -3
- package/dist/integrations/pi/session-ui/prompt-suggestion-controller.js +126 -51
- package/dist/integrations/pi/session-ui/session-shell-root.d.ts +24 -2
- package/dist/integrations/pi/session-ui/session-shell-root.js +108 -38
- package/dist/integrations/pi/session-ui/session-shell.js +51 -31
- package/dist/integrations/pi/session-ui/session-viewport-controller.d.ts +7 -1
- package/dist/integrations/pi/session-ui/session-viewport-controller.js +106 -16
- package/dist/integrations/pi/tui-runtime/adapter.d.ts +2 -0
- package/dist/integrations/pi/tui-runtime/adapter.js +35 -11
- package/dist/integrations/pi/tui-runtime/contracts.d.ts +4 -1
- package/dist/integrations/pi/tui-runtime/index.d.ts +1 -0
- package/dist/integrations/pi/tui-runtime/index.js +1 -0
- package/dist/integrations/pi/tui-runtime/mouse-report-input.d.ts +13 -0
- package/dist/integrations/pi/tui-runtime/mouse-report-input.js +59 -0
- package/dist/integrations/pi/tui-runtime/overlay-geometry.d.ts +32 -0
- package/dist/integrations/pi/tui-runtime/overlay-geometry.js +159 -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 +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 +1 -0
- package/dist/ui/components/index.d.ts +1 -0
- package/dist/ui/components/index.js +1 -0
- package/dist/ui/components/line-input.d.ts +2 -3
- package/dist/ui/components/line-input.js +4 -20
- package/dist/ui/components/mouse.d.ts +1 -1
- package/dist/ui/components/mouse.js +4 -3
- package/dist/ui/components/prompt-input.d.ts +32 -0
- package/dist/ui/components/prompt-input.js +54 -0
- package/docs/architecture/boundaries.md +1 -0
- package/docs/architecture/history-editor-provenance.md +5 -1
- package/docs/architecture/project-structure.md +1 -0
- package/docs/architecture/prompt-suggestions.md +68 -0
- package/docs/architecture/toolchain.md +1 -1
- package/docs/architecture/ui-reference-provenance.md +23 -0
- package/docs/ci-release-runbook.md +45 -7
- package/docs/features/modal-content-interaction.md +37 -0
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { chmod, lstat, mkdir, rename, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { chmod, lstat, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { createConnection, createServer } from "node:net";
|
|
4
4
|
import { platform } from "node:os";
|
|
5
5
|
import { dirname } from "node:path";
|
|
@@ -54,6 +54,7 @@ export class SupervisorServer {
|
|
|
54
54
|
startedAt = new Date().toISOString();
|
|
55
55
|
paths;
|
|
56
56
|
#server = null;
|
|
57
|
+
#ownsEndpoint = false;
|
|
57
58
|
#instances = new Map();
|
|
58
59
|
#instanceOwners = new Map();
|
|
59
60
|
#clientIds = new Map();
|
|
@@ -104,15 +105,24 @@ export class SupervisorServer {
|
|
|
104
105
|
if (platform() !== "win32") {
|
|
105
106
|
if (this.paths.endpointDirectory)
|
|
106
107
|
await ensureManagedEndpointDirectory(dirname(this.paths.endpoint));
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
108
|
+
const existing = await lstat(this.paths.endpoint).catch(error => {
|
|
109
|
+
if (error.code === "ENOENT")
|
|
110
|
+
return null;
|
|
111
|
+
throw error;
|
|
112
|
+
});
|
|
113
|
+
if (existing) {
|
|
114
|
+
if (await endpointIsLive(this.paths.endpoint)) {
|
|
115
|
+
throw Object.assign(new Error(PRODUCT_TEXT.diagnostic(`supervisor already owns ${this.paths.endpoint}`)), { code: "EADDRINUSE" });
|
|
116
|
+
}
|
|
117
|
+
await rm(this.paths.endpoint, { force: true });
|
|
118
|
+
}
|
|
110
119
|
}
|
|
111
120
|
const server = createServer(socket => this.#attach(socket));
|
|
112
121
|
this.#server = server;
|
|
113
122
|
await new Promise((resolve, reject) => {
|
|
114
123
|
server.once("error", reject);
|
|
115
124
|
server.listen(this.paths.endpoint, () => {
|
|
125
|
+
this.#ownsEndpoint = true;
|
|
116
126
|
server.off("error", reject);
|
|
117
127
|
resolve();
|
|
118
128
|
});
|
|
@@ -146,7 +156,9 @@ export class SupervisorServer {
|
|
|
146
156
|
await this.close(false);
|
|
147
157
|
}
|
|
148
158
|
async close(stopAgents = false) {
|
|
149
|
-
if (
|
|
159
|
+
if (this.#closing)
|
|
160
|
+
return;
|
|
161
|
+
if (stopAgents && this.#ownsEndpoint && !await this.#drainInstances("update", this.shutdownDeadlineMs)) {
|
|
150
162
|
throw new Error("active launch instances did not release ownership within the shutdown deadline");
|
|
151
163
|
}
|
|
152
164
|
this.#closing = true;
|
|
@@ -155,14 +167,27 @@ export class SupervisorServer {
|
|
|
155
167
|
this.#supersededPoll = null;
|
|
156
168
|
for (const client of this.#clients)
|
|
157
169
|
client.destroy();
|
|
158
|
-
|
|
159
|
-
await
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
170
|
+
try {
|
|
171
|
+
await this.#metadataWrites.catch(() => undefined);
|
|
172
|
+
if (this.#ownsEndpoint) {
|
|
173
|
+
// Concurrency: remove only our publication, before releasing the listening endpoint.
|
|
174
|
+
// A contender that failed to bind must never erase the successful owner's discovery file.
|
|
175
|
+
const metadata = await readFile(this.paths.endpointMetadataPath, "utf8").then(source => JSON.parse(source)).catch(() => null);
|
|
176
|
+
if (metadata?.supervisorId === this.id && metadata.bootNonce === this.bootNonce) {
|
|
177
|
+
await rm(this.paths.endpointMetadataPath, { force: true });
|
|
178
|
+
}
|
|
179
|
+
// Platform: Node unlinks its Unix socket when the server closes; unlinking it early would
|
|
180
|
+
// let a replacement bind before the old server finishes releasing that path.
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
finally {
|
|
184
|
+
// Platform: a metadata sharing error must not leave a bound but undiscoverable listener.
|
|
185
|
+
if (this.#server)
|
|
186
|
+
await new Promise(resolve => this.#server?.close(() => resolve()));
|
|
187
|
+
this.#server = null;
|
|
188
|
+
this.#ownsEndpoint = false;
|
|
189
|
+
this.store.close();
|
|
190
|
+
}
|
|
166
191
|
}
|
|
167
192
|
async closeForReleaseReplacement(stopAgents) {
|
|
168
193
|
await this.close(stopAgents);
|
|
@@ -473,6 +498,8 @@ export class SupervisorServer {
|
|
|
473
498
|
return [...this.#instances.keys()];
|
|
474
499
|
}
|
|
475
500
|
#writeEndpointMetadata() {
|
|
501
|
+
if (this.#closing)
|
|
502
|
+
return Promise.resolve();
|
|
476
503
|
const liveInstanceIds = this.#liveInstanceIds();
|
|
477
504
|
const metadata = {
|
|
478
505
|
schema: PRODUCT_IDENTITY.protocol.supervisorSchema,
|
|
@@ -59,6 +59,14 @@ export interface PromptSelectionUxOptions {
|
|
|
59
59
|
readonly requestRender: () => void;
|
|
60
60
|
readonly getRows: () => number;
|
|
61
61
|
/** Presentation-only columns reserved before semantic editor text. */
|
|
62
|
-
readonly
|
|
62
|
+
readonly promptGeometry?: (width: number, padding: number) => {
|
|
63
|
+
readonly prefixWidth: number;
|
|
64
|
+
readonly innerWidth: number;
|
|
65
|
+
readonly paddingX: number;
|
|
66
|
+
readonly contentWidth: number;
|
|
67
|
+
readonly layoutWidth: number;
|
|
68
|
+
};
|
|
63
69
|
}
|
|
64
70
|
export declare function createPromptSelectionInterceptor(editor: Editor, keybindings: KeybindingsManager, options: PromptSelectionUxOptions): OwnedEditorUxInterceptor;
|
|
71
|
+
/** Shares the existing atomic-aware body layout with prompt/menu composition. */
|
|
72
|
+
export declare function editorVisualLineCount(editor: Editor, width: number): number | undefined;
|
|
@@ -230,18 +230,17 @@ class PromptSelectionInterceptor {
|
|
|
230
230
|
}
|
|
231
231
|
#renderVisible(width, next) {
|
|
232
232
|
const rows = next().map(row => row.replaceAll(ATOMIC_SPACE_SENTINEL, " "));
|
|
233
|
-
const
|
|
234
|
-
const padding = Math.min(this.editor.getPaddingX(),
|
|
235
|
-
const prefixWidth =
|
|
236
|
-
const
|
|
237
|
-
const
|
|
238
|
-
const layoutWidth = Math.max(1, contentWidth - (padding ? 0 : 1));
|
|
233
|
+
const shared = this.options.promptGeometry?.(width, this.editor.getPaddingX());
|
|
234
|
+
const padding = shared?.paddingX ?? Math.min(this.editor.getPaddingX(), Math.max(0, Math.floor((width - 1) / 2)));
|
|
235
|
+
const prefixWidth = shared?.prefixWidth ?? 0;
|
|
236
|
+
const contentWidth = shared?.contentWidth ?? Math.max(1, width - padding * 2);
|
|
237
|
+
const layoutWidth = shared?.layoutWidth ?? Math.max(1, contentWidth - (padding ? 0 : 1));
|
|
239
238
|
const visualLines = editorVisualLineMap(this.editor, layoutWidth)
|
|
240
239
|
?? buildVisualLineMap(editorState(this.editor).lines, layoutWidth);
|
|
241
240
|
const scrollOffset = this.editor.interaction?.scrollOffset() ?? numericProperty(this.editor, "scrollOffset");
|
|
242
241
|
const maxVisibleLines = Math.max(5, Math.floor(this.options.getRows() * 0.3));
|
|
243
242
|
const textRows = Math.max(0, Math.min(visualLines.length - scrollOffset, maxVisibleLines, rows.length - 2));
|
|
244
|
-
this.#geometry = { width, padding, layoutWidth, visualLines, scrollOffset, textRows, hiddenRanges: [] };
|
|
243
|
+
this.#geometry = { width, padding, prefixWidth, layoutWidth, visualLines, scrollOffset, textRows, hiddenRanges: [] };
|
|
245
244
|
if (this.#atomicFocus() !== undefined) {
|
|
246
245
|
for (let row = 0; row < rows.length; row += 1) {
|
|
247
246
|
rows[row] = (rows[row] ?? "").replaceAll(CURSOR_MARKER, "");
|
|
@@ -376,7 +375,7 @@ class PromptSelectionInterceptor {
|
|
|
376
375
|
return undefined;
|
|
377
376
|
const line = (geometry.lines ?? editorState(this.editor).lines)[visual.logicalLine] ?? "";
|
|
378
377
|
const segment = line.slice(visual.startCol, visual.startCol + visual.length);
|
|
379
|
-
const displayColumn = Math.max(0, column - 1 - geometry.padding -
|
|
378
|
+
const displayColumn = Math.max(0, column - 1 - geometry.padding - geometry.prefixWidth);
|
|
380
379
|
return {
|
|
381
380
|
line: visual.logicalLine,
|
|
382
381
|
col: restoreColumn(visual.startCol + indexAtDisplayWidth(segment, displayColumn), geometry.hiddenRanges[visual.logicalLine] ?? []),
|
|
@@ -900,6 +899,10 @@ function numericProperty(target, key) {
|
|
|
900
899
|
const value = Object.getOwnPropertyDescriptor(target, key)?.value;
|
|
901
900
|
return typeof value === "number" ? value : 0;
|
|
902
901
|
}
|
|
902
|
+
/** Shares the existing atomic-aware body layout with prompt/menu composition. */
|
|
903
|
+
export function editorVisualLineCount(editor, width) {
|
|
904
|
+
return editorVisualLineMap(editor, width)?.length;
|
|
905
|
+
}
|
|
903
906
|
function editorVisualLineMap(editor, width) {
|
|
904
907
|
if (editor.interaction !== undefined)
|
|
905
908
|
return editor.interaction.visualLines(width);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface PiShellPromptInputGeometry {
|
|
2
|
+
readonly prefixWidth: number;
|
|
3
|
+
readonly innerWidth: number;
|
|
4
|
+
readonly paddingX: number;
|
|
5
|
+
readonly contentWidth: number;
|
|
6
|
+
readonly layoutWidth: number;
|
|
7
|
+
}
|
|
8
|
+
export interface PiShellPromptInputBody {
|
|
9
|
+
readonly rows: readonly string[];
|
|
10
|
+
readonly topRule?: string | undefined;
|
|
11
|
+
readonly bottomRule?: string | undefined;
|
|
12
|
+
readonly after?: readonly string[];
|
|
13
|
+
}
|
|
14
|
+
export interface PiShellPromptInputPresentation {
|
|
15
|
+
geometry(width: number, padding?: number): PiShellPromptInputGeometry;
|
|
16
|
+
styleRule(text: string): string;
|
|
17
|
+
render(width: number, body: (innerWidth: number) => PiShellPromptInputBody, ruled?: boolean, padding?: number): string[];
|
|
18
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { getSelectListTheme } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { CombinedAutocompleteProvider, matchesKey, setKeybindings, } from "#pi-tui";
|
|
2
|
+
import { CombinedAutocompleteProvider, matchesKey, setKeybindings, visibleWidth, } from "#pi-tui";
|
|
3
3
|
import { PROMPT_HISTORY_EDITOR_REPLACEMENT } from "../../../contracts/owned-ui/index.js";
|
|
4
4
|
import { KeybindingsManager } from "./upstream/adjacent/core/keybindings.js";
|
|
5
5
|
import { OwnedEditor } from "./upstream/components/owned-editor.js";
|
|
6
|
-
import { OwnedEditorUxInterception, createPromptSelectionInterceptor, } from "./owned-editor-ux.js";
|
|
6
|
+
import { OwnedEditorUxInterception, createPromptSelectionInterceptor, editorVisualLineCount, } from "./owned-editor-ux.js";
|
|
7
7
|
import { PINNED_PI_LAYOUT, piTheme, } from "./theme.js";
|
|
8
8
|
import { createTuiFacade, ensureTheme, isAutocompleteProvider, } from "./shell-shared-facade.js";
|
|
9
9
|
export const PINNED_PI_BUILTIN_SLASH_COMMANDS = [
|
|
@@ -33,6 +33,7 @@ export const PINNED_PI_BUILTIN_SLASH_COMMANDS = [
|
|
|
33
33
|
export function createPiShellEditor(options) {
|
|
34
34
|
ensureTheme();
|
|
35
35
|
const tui = createTuiFacade(options);
|
|
36
|
+
const inputPresentation = options.keybindingProfile === "a1" ? options.promptPresentation?.input : undefined;
|
|
36
37
|
const keybindings = options.keybindingProfile === "a1"
|
|
37
38
|
? KeybindingsManager.createForOwnedInput(options.agentDir)
|
|
38
39
|
: KeybindingsManager.create(options.agentDir);
|
|
@@ -40,20 +41,33 @@ export function createPiShellEditor(options) {
|
|
|
40
41
|
if (options.persistentHistory === true && options.keybindingProfile === "a1" && options.historyEditor === undefined) {
|
|
41
42
|
throw new Error("Persistent history requires the loaded owned editor");
|
|
42
43
|
}
|
|
44
|
+
const scrollInfo = { emitted: false, counter: undefined };
|
|
45
|
+
const selectListTheme = getSelectListTheme();
|
|
43
46
|
const EditorClass = options.keybindingProfile === "a1" && options.persistentHistory === true ? options.historyEditor : OwnedEditor;
|
|
44
47
|
const editor = new EditorClass(tui, {
|
|
45
|
-
borderColor: (value) => piTheme().fg("borderMuted", value),
|
|
46
|
-
selectList:
|
|
48
|
+
borderColor: (value) => inputPresentation === undefined ? piTheme().fg("borderMuted", value) : inputPresentation.styleRule(value),
|
|
49
|
+
selectList: options.keybindingProfile !== "a1" ? selectListTheme : {
|
|
50
|
+
...selectListTheme,
|
|
51
|
+
scrollInfo: text => {
|
|
52
|
+
// Protocol: the pinned list emits its final counter row through this callback.
|
|
53
|
+
scrollInfo.emitted = true;
|
|
54
|
+
scrollInfo.counter = /^ \((\d+\/\d+)\)$/u.exec(text)?.[1];
|
|
55
|
+
return selectListTheme.scrollInfo(text);
|
|
56
|
+
},
|
|
57
|
+
},
|
|
47
58
|
}, keybindings, {
|
|
48
59
|
paddingX: PINNED_PI_LAYOUT.editorPaddingX,
|
|
49
60
|
autocompleteMaxVisible: PINNED_PI_LAYOUT.autocompleteMaxVisible,
|
|
50
61
|
persistentHistory: options.persistentHistory === true,
|
|
51
62
|
styleHistoryLabel: text => piTheme().fg("dim", text),
|
|
63
|
+
...(options.keybindingProfile === "a1" ? {
|
|
64
|
+
terminalRows: options.getRows,
|
|
65
|
+
getVisualLineCount: (width) => editorVisualLineCount(editor, width),
|
|
66
|
+
} : {}),
|
|
52
67
|
...(options.keybindingProfile === "a1" && options.promptPresentation !== undefined ? {
|
|
53
|
-
|
|
68
|
+
...(inputPresentation === undefined ? {} : { inputPresentation }),
|
|
54
69
|
styleSuggestion: options.promptPresentation.styleSuggestion,
|
|
55
70
|
styleSuggestionCaret: options.promptPresentation.styleSuggestionCaret,
|
|
56
|
-
terminalRows: options.getRows,
|
|
57
71
|
} : {}),
|
|
58
72
|
});
|
|
59
73
|
const editorUx = options.keybindingProfile === "a1"
|
|
@@ -70,7 +84,7 @@ export function createPiShellEditor(options) {
|
|
|
70
84
|
decorateRow: options.decorateEditorRow ?? (row => row),
|
|
71
85
|
requestRender: options.requestRender,
|
|
72
86
|
getRows: options.getRows,
|
|
73
|
-
...(
|
|
87
|
+
...(inputPresentation === undefined ? {} : { promptGeometry: (width, padding) => inputPresentation.geometry(width, padding) }),
|
|
74
88
|
}),
|
|
75
89
|
], {
|
|
76
90
|
render: width => editor.render(width),
|
|
@@ -80,9 +94,9 @@ export function createPiShellEditor(options) {
|
|
|
80
94
|
let thinkingLevel = "off";
|
|
81
95
|
let isBashMode = false;
|
|
82
96
|
const updateBorderColor = () => {
|
|
83
|
-
editor.borderColor =
|
|
84
|
-
?
|
|
85
|
-
: piTheme().getThinkingBorderColor(thinkingLevel);
|
|
97
|
+
editor.borderColor = inputPresentation !== undefined
|
|
98
|
+
? text => inputPresentation.styleRule(text)
|
|
99
|
+
: isBashMode ? piTheme().getBashModeBorderColor() : piTheme().getThinkingBorderColor(thinkingLevel);
|
|
86
100
|
tui.requestRender();
|
|
87
101
|
};
|
|
88
102
|
let autocompleteProvider;
|
|
@@ -147,13 +161,43 @@ export function createPiShellEditor(options) {
|
|
|
147
161
|
if (options.onPromptSuggestionAccepted !== undefined) {
|
|
148
162
|
editor.onPromptSuggestionAccepted = options.onPromptSuggestionAccepted;
|
|
149
163
|
}
|
|
164
|
+
let bodyGeometry = { rowOffset: 0, rowCount: 0 };
|
|
150
165
|
return {
|
|
151
|
-
|
|
166
|
+
...(editorUx === undefined ? {} : { bodyGeometry: () => bodyGeometry }),
|
|
167
|
+
render: width => {
|
|
168
|
+
scrollInfo.emitted = false;
|
|
169
|
+
scrollInfo.counter = undefined;
|
|
170
|
+
const rows = editorUx?.render(width) ?? editor.render(width);
|
|
171
|
+
if (editorUx === undefined)
|
|
172
|
+
return rows;
|
|
173
|
+
const rowCount = editor.getRenderedBodyRowCount();
|
|
174
|
+
const menu = rows.slice(rowCount, scrollInfo.emitted ? -1 : undefined);
|
|
175
|
+
const label = scrollInfo.counter === undefined ? "" : `${scrollInfo.counter} `;
|
|
176
|
+
// Compatibility: match the history border's four-cell inset and dim label style.
|
|
177
|
+
const counterBorder = label.length > 0 && 4 + visibleWidth(label) <= width
|
|
178
|
+
? editor.borderColor("─── ") + piTheme().fg("dim", label)
|
|
179
|
+
+ editor.borderColor("─".repeat(width - 4 - visibleWidth(label)))
|
|
180
|
+
: editor.borderColor("─".repeat(width));
|
|
181
|
+
// Rationale: when the menu is open, drop the plain top border above the
|
|
182
|
+
// menu and instead show the counter on the border directly above the input
|
|
183
|
+
// prompt (the body's own top border row), mirroring the history header.
|
|
184
|
+
const bodyRows = menu.length === 0
|
|
185
|
+
? rows.slice(0, rowCount)
|
|
186
|
+
: [counterBorder, ...rows.slice(1, rowCount)];
|
|
187
|
+
bodyGeometry = { rowOffset: menu.length, rowCount };
|
|
188
|
+
// Invariant: decorate/select in body coordinates first, then move the whole menu.
|
|
189
|
+
// Completion state, sizing, styles, and pagination still belong to the editor.
|
|
190
|
+
return [...menu, ...bodyRows];
|
|
191
|
+
},
|
|
152
192
|
activateKeybindings: () => setKeybindings(keybindings),
|
|
153
193
|
keybindingConfig: () => keybindings.getEffectiveConfig(),
|
|
154
194
|
reloadKeybindings: () => { keybindings.reload(); setKeybindings(keybindings); },
|
|
155
195
|
matchesTerminalKey: (data, key) => matchesKey(data, key),
|
|
156
196
|
handleInput: data => {
|
|
197
|
+
// Compatibility: an unassigned reverse Tab must not clear selection or reach autocomplete fallback.
|
|
198
|
+
if (editorUx !== undefined && matchesKey(data, "shift+tab")
|
|
199
|
+
&& !Object.values(keybindings.getEffectiveConfig()).some(keys => (Array.isArray(keys) ? keys : [keys]).some(key => key !== undefined && matchesKey(data, key))))
|
|
200
|
+
return;
|
|
157
201
|
if (editorUx === undefined)
|
|
158
202
|
editor.handleInput(data);
|
|
159
203
|
else
|
|
@@ -210,6 +254,17 @@ export function createPiShellEditor(options) {
|
|
|
210
254
|
editor.invalidate();
|
|
211
255
|
},
|
|
212
256
|
canPresentPromptSuggestion: () => editor.canPresentPromptSuggestion(),
|
|
257
|
+
promptSuggestionBlockReason: () => {
|
|
258
|
+
if (!editor.focused)
|
|
259
|
+
return "not-focused";
|
|
260
|
+
if (editor.disableSubmit)
|
|
261
|
+
return "not-ready";
|
|
262
|
+
if (editor.getText().length > 0)
|
|
263
|
+
return "draft";
|
|
264
|
+
if (editor.isShowingAutocomplete())
|
|
265
|
+
return "autocomplete";
|
|
266
|
+
return editor.canPresentPromptSuggestion() ? null : "prompt-mode";
|
|
267
|
+
},
|
|
213
268
|
hasSelection: () => editorUx?.hasSelection() ?? false,
|
|
214
269
|
ownsPointer: () => editorUx?.ownsPointer() ?? false,
|
|
215
270
|
handlePointer: event => editorUx?.handlePointer(event) ?? false,
|
|
@@ -3,5 +3,5 @@ import { type PiShellViewComponentPort, type PiShellStatusPort, type PiShellQueu
|
|
|
3
3
|
export declare function createPiShellHeader(options?: PiShellHeaderOptions): PiShellHeaderPort;
|
|
4
4
|
export declare function createPiShellLoadedResources(resources: readonly PiShellResourceEntry[], initialExpanded?: boolean): PiShellLoadedResourcesPort;
|
|
5
5
|
export declare function createPiShellStatus(view: OwnedUiSessionViewModel, formatProgressStatus: (message: string) => string, runtime?: Pick<PiShellEditorOptions, "getColumns" | "getRows" | "requestRender">): PiShellStatusPort;
|
|
6
|
-
export declare function createPiShellFooter(view: OwnedUiSessionViewModel, cwd: string): PiShellViewComponentPort;
|
|
6
|
+
export declare function createPiShellFooter(view: OwnedUiSessionViewModel, cwd: string, profile?: "pi" | "a1"): PiShellViewComponentPort;
|
|
7
7
|
export declare function createPiQueuedInputStatus(submissions: readonly string[], presentation?: "pinned" | "custom-viewport"): PiShellQueuedInputPort;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { rawKeyHint, VERSION } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { Spacer, Text, } from "#pi-tui";
|
|
3
3
|
import { SessionFooter } from "./upstream/components/session-footer.js";
|
|
4
|
+
import { KeybindingsManager } from "./upstream/adjacent/core/keybindings.js";
|
|
4
5
|
import { WorkingStatusIndicator } from "./upstream/components/status-indicator.js";
|
|
5
6
|
import { PINNED_PI_LAYOUT, piTheme, } from "./theme.js";
|
|
6
7
|
import { createTuiFacade, ensureTheme, } from "./shell-shared-facade.js";
|
|
@@ -16,6 +17,8 @@ export function createPiShellHeader(options = {}) {
|
|
|
16
17
|
render(width) {
|
|
17
18
|
if (options.quiet)
|
|
18
19
|
return [];
|
|
20
|
+
if (expanded && options.getKeybindings !== undefined)
|
|
21
|
+
full.setText(expandedHeaderText(options.getKeybindings()));
|
|
19
22
|
return [
|
|
20
23
|
...new Spacer(1).render(width),
|
|
21
24
|
...(expanded ? full : compact).render(width),
|
|
@@ -111,9 +114,9 @@ export function createPiShellStatus(view, formatProgressStatus, runtime) {
|
|
|
111
114
|
},
|
|
112
115
|
};
|
|
113
116
|
}
|
|
114
|
-
export function createPiShellFooter(view, cwd) {
|
|
117
|
+
export function createPiShellFooter(view, cwd, profile = "pi") {
|
|
115
118
|
ensureTheme();
|
|
116
|
-
const footer = new SessionFooter(() => view, cwd);
|
|
119
|
+
const footer = new SessionFooter(() => view, cwd, profile);
|
|
117
120
|
return {
|
|
118
121
|
render: width => footer.render(width),
|
|
119
122
|
invalidate: () => footer.invalidate(),
|
|
@@ -184,7 +187,8 @@ function compactHeaderText() {
|
|
|
184
187
|
const onboarding = theme.fg("dim", "Pi can explain its own features and look up its docs. Ask it how to use or extend Pi.");
|
|
185
188
|
return `${logo}\n${instructions}\n${compactOnboarding}\n\n${onboarding}`;
|
|
186
189
|
}
|
|
187
|
-
function expandedHeaderText() {
|
|
190
|
+
function expandedHeaderText(bindings) {
|
|
191
|
+
const keys = bindings === undefined ? undefined : KeybindingsManager.fromOwnedBindings(bindings);
|
|
188
192
|
const instructions = [
|
|
189
193
|
rawKeyHint("escape", "to interrupt"),
|
|
190
194
|
rawKeyHint("ctrl+c", "to clear"),
|
|
@@ -192,9 +196,9 @@ function expandedHeaderText() {
|
|
|
192
196
|
rawKeyHint("ctrl+d", "to exit (empty)"),
|
|
193
197
|
rawKeyHint(process.platform === "win32" ? "" : "ctrl+z", "to suspend"),
|
|
194
198
|
rawKeyHint("ctrl+k", "to delete to end"),
|
|
195
|
-
rawKeyHint("shift+tab", "to cycle thinking level"),
|
|
199
|
+
rawKeyHint(keys?.getKeys("app.thinking.cycle").join("/") ?? "shift+tab", "to cycle thinking level"),
|
|
196
200
|
rawKeyHint("ctrl+p/shift+ctrl+p", "to cycle models"),
|
|
197
|
-
rawKeyHint("ctrl+l", "to select model"),
|
|
201
|
+
rawKeyHint(keys === undefined ? "ctrl+l" : keys.getKeys("app.model.select").join("/") || "/model", "to select model"),
|
|
198
202
|
rawKeyHint("ctrl+o", "to expand tools"),
|
|
199
203
|
rawKeyHint("ctrl+t", "to expand thinking"),
|
|
200
204
|
rawKeyHint("ctrl+g", "for external editor"),
|
|
@@ -86,12 +86,17 @@ function shortcutDisplay(key) {
|
|
|
86
86
|
}
|
|
87
87
|
export function createPiShellHotkeys(bindings, getShortcuts = () => [], profile = "pi") {
|
|
88
88
|
ensureTheme();
|
|
89
|
-
const keys = new KeybindingsManager(bindings);
|
|
89
|
+
const keys = profile === "a1" ? KeybindingsManager.fromOwnedBindings(bindings) : new KeybindingsManager(bindings);
|
|
90
90
|
// Compatibility: the owned viewport consumes these physical chords before editor actions.
|
|
91
91
|
const display = (action) => keys.getKeys(action)
|
|
92
92
|
.filter(key => profile !== "a1" || (key !== "ctrl+home" && key !== "ctrl+end"))
|
|
93
93
|
.map(shortcutDisplay).join("/");
|
|
94
|
-
const row = (actions, description) => `| ${actions.map(action =>
|
|
94
|
+
const row = (actions, description) => `| ${actions.map(action => {
|
|
95
|
+
const label = display(action);
|
|
96
|
+
return profile === "a1" && label.length === 0
|
|
97
|
+
? action === "app.model.select" ? "Unbound (`/model`)" : "Unbound"
|
|
98
|
+
: `\`${label}\``;
|
|
99
|
+
}).join(" / ")} | ${description} |`;
|
|
95
100
|
let markdown = ["**Navigation**", "| Key | Action |", "|-----|--------|", row(["tui.editor.cursorUp", "tui.editor.cursorDown", "tui.editor.cursorLeft", "tui.editor.cursorRight"], "Move cursor / browse history"), row(["tui.editor.cursorWordLeft", "tui.editor.cursorWordRight"], "Move by word"), row(["tui.editor.cursorLineStart"], profile === "a1" ? "Start of prompt line" : "Start of line"), row(["tui.editor.cursorLineEnd"], profile === "a1" ? "End of prompt line" : "End of line"), ...(profile === "a1" ? ["| `Ctrl+Home` | Start of content |", "| `Ctrl+End` | End of content / follow output |"] : []), row(["tui.editor.jumpForward"], "Jump forward to character"), row(["tui.editor.jumpBackward"], "Jump backward to character"), row(["tui.editor.pageUp", "tui.editor.pageDown"], "Scroll by page"), "", "**Editing**", "| Key | Action |", "|-----|--------|", row(["tui.input.submit"], "Send message"), row(["tui.input.newLine"], `New line${process.platform === "win32" ? " (Ctrl+Enter on Windows Terminal)" : ""}`), row(["tui.editor.deleteWordBackward"], "Delete word backwards"), row(["tui.editor.deleteWordForward"], "Delete word forwards"), row(["tui.editor.deleteToLineStart"], "Delete to start of line"), row(["tui.editor.deleteToLineEnd"], "Delete to end of line"), row(["tui.editor.yank"], "Paste the most-recently-deleted text"), row(["tui.editor.yankPop"], "Cycle through the deleted text after pasting"), row(["tui.editor.undo"], "Undo"), "", "**Other**", "| Key | Action |", "|-----|--------|", row(["tui.input.tab"], "Path completion / accept autocomplete"), row(["app.interrupt"], "Cancel autocomplete / abort streaming"), row(["app.clear"], "Clear editor (first) / exit (second)"), row(["app.exit"], "Exit (when editor is empty)"), row(["app.suspend"], "Suspend to background"), row(["app.thinking.cycle"], "Cycle thinking level"), row(["app.model.cycleForward", "app.model.cycleBackward"], "Cycle models"), row(["app.model.select"], "Open model selector"), row(["app.tools.expand"], "Toggle tool output expansion"), row(["app.thinking.toggle"], "Toggle thinking block visibility"), row(["app.editor.external"], "Edit message in external editor"), row(["app.message.copy"], "Copy last assistant message"), row(["app.message.followUp"], "Queue follow-up message"), row(["app.message.dequeue"], "Restore queued messages"), row(["app.clipboard.pasteImage"], "Paste image or text from clipboard"), "| `/` | Slash commands |", "| `!` | Run bash command |", "| `!!` | Run bash command (excluded from context) |"].join("\n");
|
|
96
101
|
const shortcuts = getShortcuts(bindings ?? keys.getEffectiveConfig());
|
|
97
102
|
if (shortcuts.length > 0) {
|
|
@@ -2,9 +2,13 @@ import { AssistantMessageComponent } from "@earendil-works/pi-coding-agent";
|
|
|
2
2
|
import { type MermaidRenderingMode } from "./upstream/components/mermaid.js";
|
|
3
3
|
import type { OwnedUiTranscriptBlock } from "../../../contracts/owned-ui/index.js";
|
|
4
4
|
import { type PiShellSubmittedPromptComposer } from "./submitted-prompt-adapter.js";
|
|
5
|
-
export type
|
|
5
|
+
export { isPiPromptStyleCompaction, type PiShellSubmittedPromptComposer } from "./submitted-prompt-adapter.js";
|
|
6
6
|
import { type PiShellExtensionRendererResolver, type PiShellImageAssetResolver, type PiShellTranscriptComponentPort } from "./shell-shared-facade.js";
|
|
7
|
-
export declare function createPiShellTranscriptComponent(initial: OwnedUiTranscriptBlock, cwd: string, extensions?: PiShellExtensionRendererResolver, submittedPrompt?: PiShellSubmittedPromptComposer, initialOutputPad?: 0 | 1, initialHideThinkingBlock?: boolean, initialMermaidRenderingMode?: MermaidRenderingMode, initialShowImages?: boolean, initialImageWidthCells?: number, imageAssets?: PiShellImageAssetResolver
|
|
7
|
+
export declare function createPiShellTranscriptComponent(initial: OwnedUiTranscriptBlock, cwd: string, extensions?: PiShellExtensionRendererResolver, submittedPrompt?: PiShellSubmittedPromptComposer, initialOutputPad?: 0 | 1, initialHideThinkingBlock?: boolean, initialMermaidRenderingMode?: MermaidRenderingMode, initialShowImages?: boolean, initialImageWidthCells?: number, imageAssets?: PiShellImageAssetResolver, presentation?: {
|
|
8
|
+
readonly getColumns: () => number;
|
|
9
|
+
readonly getRows: () => number;
|
|
10
|
+
readonly changed: () => void;
|
|
11
|
+
}): PiShellTranscriptComponentPort;
|
|
8
12
|
export declare function renderPiShellTranscriptBlock(block: OwnedUiTranscriptBlock, width: number, cwd: string): readonly string[];
|
|
9
13
|
/**
|
|
10
14
|
* Pinned Pi's CLI prints startup diagnostics with `reportDiagnostics` before
|