@timurproko/a1 0.2.7-dev.820 → 0.2.7-dev.831
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 +7 -1
- package/bin/automatic-update.js +7 -0
- package/bin/ui.js +1 -0
- package/dist/app/session-shell/session-shell-root.d.ts +8 -1
- package/dist/app/session-shell/session-shell.js +68 -18
- package/dist/app/session-shell/transcript-document.js +1 -1
- package/dist/composition/owned-ui.d.ts +3 -11
- package/dist/composition/owned-ui.js +93 -42
- package/dist/composition/resident-tab-bridge.d.ts +10 -1
- package/dist/composition/resident-tab-bridge.js +21 -0
- package/dist/composition/settings-route-host.d.ts +12 -2
- package/dist/composition/settings-route-host.js +27 -9
- package/dist/composition/settings-search-input.d.ts +9 -0
- package/dist/composition/settings-search-input.js +58 -0
- package/dist/contracts/owned-ui/session-backend.d.ts +2 -0
- package/dist/contracts/owned-ui/session-workflows.d.ts +7 -3
- package/dist/contracts/presentation/index.d.ts +11 -0
- package/dist/contracts/presentation/index.js +12 -0
- package/dist/features/launch/development-launch.js +6 -0
- package/dist/features/launch/profile-paths.js +4 -0
- package/dist/features/owned-ui/plus-menu-dialog.d.ts +22 -0
- package/dist/features/owned-ui/plus-menu-dialog.js +76 -0
- package/dist/features/owned-ui/reference-screen-app.d.ts +2 -0
- package/dist/features/owned-ui/reference-screen-app.js +12 -2
- package/dist/features/owned-ui/settings-app.d.ts +10 -3
- package/dist/features/owned-ui/settings-app.js +98 -45
- package/dist/features/owned-ui/settings-search-input.d.ts +29 -0
- package/dist/features/owned-ui/settings-search-input.js +1 -0
- package/dist/features/prompt-history/index.d.ts +1 -1
- package/dist/features/prompt-history/paths.d.ts +2 -1
- package/dist/features/prompt-history/paths.js +10 -2
- package/dist/features/prompt-history/service.d.ts +5 -0
- package/dist/features/prompt-history/service.js +1 -1
- package/dist/features/resident-tab/shortcut-declarations.d.ts +1 -1
- package/dist/features/resident-tab/shortcut-declarations.js +2 -0
- package/dist/foundation/launch-context/index.d.ts +2 -0
- package/dist/foundation/launch-context/index.js +1 -0
- package/dist/foundation/release/automatic-update.d.ts +37 -0
- package/dist/foundation/release/automatic-update.js +265 -0
- package/dist/foundation/release/bootstrap.d.ts +1 -9
- package/dist/foundation/release/bootstrap.js +7 -3
- package/dist/foundation/release/index.d.ts +1 -1
- package/dist/foundation/release/index.js +1 -1
- package/dist/foundation/release/latest-release.d.ts +3 -2
- package/dist/foundation/release/latest-release.js +1 -1
- package/dist/foundation/release/update-execution-lease.d.ts +36 -0
- package/dist/foundation/release/update-execution-lease.js +145 -0
- package/dist/foundation/release/update.d.ts +8 -6
- package/dist/foundation/release/update.js +232 -167
- package/dist/foundation/resident-tabs/bridge-client.d.ts +8 -2
- package/dist/foundation/resident-tabs/bridge-client.js +13 -6
- package/dist/foundation/resident-tabs/routes.d.ts +5 -0
- package/dist/foundation/resident-tabs/routes.js +5 -0
- package/dist/integrations/pi/components/editor-interaction.d.ts +2 -0
- package/dist/integrations/pi/components/modal-frame.d.ts +17 -1
- package/dist/integrations/pi/components/modal-frame.js +48 -9
- package/dist/integrations/pi/components/models-dialog.js +4 -52
- package/dist/integrations/pi/components/owned-editor-ux.d.ts +3 -0
- package/dist/integrations/pi/components/owned-editor-ux.js +75 -4
- package/dist/integrations/pi/components/shell-editor-autocomplete.js +57 -37
- package/dist/integrations/pi/components/shell-footer-status.js +19 -3
- package/dist/integrations/pi/components/shell-presenters-transcript.js +4 -1
- package/dist/integrations/pi/components/shell-selectors-dialogs.js +4 -1
- package/dist/integrations/pi/components/shell-shared-facade.d.ts +9 -2
- package/dist/integrations/pi/components/skills-dialog.js +2 -2
- package/dist/integrations/pi/components/upstream/components/owned-editor.d.ts +7 -5
- package/dist/integrations/pi/components/upstream/components/owned-editor.js +15 -8
- package/dist/integrations/pi/components/upstream/components/scoped-models-selector.d.ts +3 -3
- package/dist/integrations/pi/components/upstream/components/scoped-models-selector.js +2 -2
- package/dist/integrations/pi/components/upstream/components/session-footer.js +26 -23
- package/dist/integrations/pi/components/upstream/components/session-selector.js +7 -6
- package/dist/integrations/pi/components/upstream/components/thinking-selector.d.ts +7 -6
- package/dist/integrations/pi/components/upstream/components/thinking-selector.js +9 -8
- package/dist/integrations/pi/components/upstream/components/tool-execution.js +6 -4
- package/dist/integrations/pi/components/upstream/components/tree-selector.d.ts +8 -8
- package/dist/integrations/pi/components/upstream/components/tree-selector.js +10 -10
- package/dist/integrations/pi/components/upstream/history/editor-core.js +2 -0
- package/dist/integrations/pi/engine/adapter.js +7 -2
- package/dist/integrations/pi/engine/ports.d.ts +1 -1
- package/dist/integrations/pi/engine/ports.js +4 -0
- package/dist/integrations/pi/engine/workflow-runner.js +9 -2
- 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/product-identity.d.ts +1 -1
- package/dist/product-identity.js +1 -1
- package/dist/product-identity.json +1 -0
- package/dist/ui/apps/host.d.ts +2 -0
- package/dist/ui/apps/host.js +7 -1
- package/dist/ui/components/dialog-panel.js +35 -30
- package/dist/ui/components/line-input.d.ts +3 -0
- package/dist/ui/components/line-input.js +3 -1
- package/dist/ui/components/prompt-input.d.ts +3 -2
- package/dist/ui/components/prompt-input.js +6 -4
- package/dist/ui/components/surface.js +4 -2
- package/dist/ui/settings/declarations.d.ts +54 -8
- package/dist/ui/settings/declarations.js +36 -7
- package/dist/ui/settings/manager.js +7 -3
- package/dist/ui/settings/migrations.js +15 -0
- package/dist/ui/settings/sections.d.ts +4 -0
- package/dist/ui/settings/sections.js +9 -3
- package/docs/architecture/resident-tabs.md +35 -7
- package/docs/architecture/tool-image-presentation.md +2 -1
- package/docs/features/prompt-history.md +41 -20
- package/package.json +1 -1
|
@@ -1,7 +1,47 @@
|
|
|
1
|
-
import { CancellableLoader, Container, Spacer, Text } from "@earendil-works/pi-tui";
|
|
1
|
+
import { CancellableLoader, Container, Input, Spacer, Text, } from "@earendil-works/pi-tui";
|
|
2
|
+
import { DIALOG_CONTENT_PADDING_X, dialogFrameGeometry, insetDialogContent } from "../../../contracts/presentation/index.js";
|
|
2
3
|
import { DynamicBorder } from "../startup-public.js";
|
|
3
4
|
import { DIALOG_CLOSE_SHORTCUT_HINT, paintPiBorder, piTheme, renderPiModalShortcutHints } from "./theme.js";
|
|
4
|
-
export const PI_MODAL_CONTENT_PADDING_X =
|
|
5
|
+
export const PI_MODAL_CONTENT_PADDING_X = DIALOG_CONTENT_PADDING_X;
|
|
6
|
+
/** Paints the standard Pi Input prompt as a live-themed dialog-search marker. */
|
|
7
|
+
export function renderPiModalSearchInput(input, width) {
|
|
8
|
+
return input.render(width).map(row => {
|
|
9
|
+
if (row.length === 0)
|
|
10
|
+
return row;
|
|
11
|
+
if (!row.startsWith(">"))
|
|
12
|
+
throw new TypeError("modal search input does not use the standard prompt");
|
|
13
|
+
return `${piTheme().fg("accent", ">")}${row.slice(1)}`;
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
/** Keeps Pi Input behavior and geometry while owning only its standard dialog-search presentation. */
|
|
17
|
+
export class PiModalSearchInput {
|
|
18
|
+
#input;
|
|
19
|
+
constructor(input) {
|
|
20
|
+
this.#input = input;
|
|
21
|
+
}
|
|
22
|
+
get focused() { return this.#input.focused; }
|
|
23
|
+
set focused(value) { this.#input.focused = value; }
|
|
24
|
+
invalidate() { this.#input.invalidate(); }
|
|
25
|
+
render(width) { return renderPiModalSearchInput(this.#input, width); }
|
|
26
|
+
handleMouse(event) { return this.#input.handleMouse(event); }
|
|
27
|
+
}
|
|
28
|
+
/** Replaces one explicitly identified direct Input child with the standard search presentation. */
|
|
29
|
+
export function adoptPiModalSearchInput(container, input) {
|
|
30
|
+
if (!(input instanceof Input))
|
|
31
|
+
throw new TypeError("modal search component is not an Input");
|
|
32
|
+
const index = container.children.indexOf(input);
|
|
33
|
+
if (index === -1)
|
|
34
|
+
throw new TypeError("modal search Input is not a direct child");
|
|
35
|
+
const presentation = new PiModalSearchInput(input);
|
|
36
|
+
container.children.splice(index, 1, presentation);
|
|
37
|
+
return presentation;
|
|
38
|
+
}
|
|
39
|
+
/** Adds one standard dialog-search presentation while callers retain the Input controller. */
|
|
40
|
+
export function addPiModalSearchInput(container, input) {
|
|
41
|
+
const presentation = new PiModalSearchInput(input);
|
|
42
|
+
container.addChild(presentation);
|
|
43
|
+
return presentation;
|
|
44
|
+
}
|
|
5
45
|
/** Adds the standard navigation/select/close footer before a dependency component's bottom rule. */
|
|
6
46
|
export function addPiModalSelectFooter(container) {
|
|
7
47
|
container.children.splice(container.children.length - 1, 0, new Text(renderPiModalShortcutHints([
|
|
@@ -67,21 +107,20 @@ export class PiModalFrame {
|
|
|
67
107
|
this.#bottomRule.invalidate();
|
|
68
108
|
}
|
|
69
109
|
render(width) {
|
|
70
|
-
const
|
|
71
|
-
const
|
|
72
|
-
const rows = [...this.#topRule.render(width)];
|
|
110
|
+
const geometry = dialogFrameGeometry(width);
|
|
111
|
+
const rows = [...this.#topRule.render(geometry.frameWidth)];
|
|
73
112
|
for (const component of this.#content) {
|
|
74
113
|
if (this.#fullWidthContent.has(component) || this.#preInsetContent.has(component)) {
|
|
75
|
-
rows.push(...component.render(
|
|
114
|
+
rows.push(...component.render(geometry.frameWidth));
|
|
76
115
|
}
|
|
77
116
|
else if (component instanceof Spacer) {
|
|
78
|
-
rows.push(...component.render(contentWidth));
|
|
117
|
+
rows.push(...component.render(geometry.contentWidth));
|
|
79
118
|
}
|
|
80
119
|
else {
|
|
81
|
-
rows.push(...component.render(contentWidth).map(row =>
|
|
120
|
+
rows.push(...component.render(geometry.contentWidth).map(row => insetDialogContent(row, geometry)));
|
|
82
121
|
}
|
|
83
122
|
}
|
|
84
|
-
rows.push(...this.#bottomRule.render(
|
|
123
|
+
rows.push(...this.#bottomRule.render(geometry.frameWidth));
|
|
85
124
|
return rows;
|
|
86
125
|
}
|
|
87
126
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Input, getKeybindings, matchesKey, truncateToWidth, visibleWidth, } from "@earendil-works/pi-tui";
|
|
2
2
|
import { DynamicBorder } from "../startup-public.js";
|
|
3
|
-
import { PiModalFrame } from "./modal-frame.js";
|
|
3
|
+
import { PiModalFrame, renderPiModalSearchInput } from "./modal-frame.js";
|
|
4
4
|
import { DIALOG_CLOSE_SHORTCUT_HINT, paintPiBorder, piTheme, renderPiModalListRow, renderPiModalShortcutHints, } from "./theme.js";
|
|
5
5
|
function renderHintsWithClose(entries, width) {
|
|
6
6
|
const body = renderPiModalShortcutHints(entries.slice(0, -1));
|
|
@@ -13,7 +13,6 @@ function renderHintsWithClose(entries, width) {
|
|
|
13
13
|
}
|
|
14
14
|
const MAX_VISIBLE_ROWS = 10;
|
|
15
15
|
const MODELS_TITLE = "Models";
|
|
16
|
-
const REFRESHING_TITLE_MIN_DURATION_MS = 1_000;
|
|
17
16
|
class ModalTitleLine {
|
|
18
17
|
#text = "";
|
|
19
18
|
setText(text) { this.#text = text; }
|
|
@@ -60,9 +59,6 @@ export class ModelsDialogComponent {
|
|
|
60
59
|
#selectedIndex = 0;
|
|
61
60
|
#preferredId;
|
|
62
61
|
#refreshStatus;
|
|
63
|
-
#refreshing = false;
|
|
64
|
-
#refreshStartedAt;
|
|
65
|
-
#refreshOutcomeTimer;
|
|
66
62
|
#disposed = false;
|
|
67
63
|
#focused = false;
|
|
68
64
|
constructor(config, callbacks) {
|
|
@@ -71,10 +67,6 @@ export class ModelsDialogComponent {
|
|
|
71
67
|
this.#scopeIds = [...config.scopeIds];
|
|
72
68
|
this.#savedScopeIds = [...config.savedScopeIds];
|
|
73
69
|
this.#filter = config.initialFilter ?? "all";
|
|
74
|
-
if (config.refreshStatus !== undefined) {
|
|
75
|
-
this.#refreshing = true;
|
|
76
|
-
this.#refreshStartedAt = Date.now();
|
|
77
|
-
}
|
|
78
70
|
if (config.initialQuery)
|
|
79
71
|
this.#input.setValue(config.initialQuery);
|
|
80
72
|
this.#replaceModels(config.models);
|
|
@@ -114,38 +106,10 @@ export class ModelsDialogComponent {
|
|
|
114
106
|
setRefreshStatus(message, kind) {
|
|
115
107
|
if (this.#disposed)
|
|
116
108
|
return;
|
|
117
|
-
|
|
118
|
-
this.#clearRefreshOutcome();
|
|
119
|
-
this.#refreshStatus = undefined;
|
|
120
|
-
this.#refreshing = true;
|
|
121
|
-
this.#refreshStartedAt = Date.now();
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
const visibleMs = this.#refreshStartedAt === undefined ? REFRESHING_TITLE_MIN_DURATION_MS : Date.now() - this.#refreshStartedAt;
|
|
125
|
-
const remainingMs = Math.max(0, REFRESHING_TITLE_MIN_DURATION_MS - visibleMs);
|
|
126
|
-
if (this.#refreshing && remainingMs > 0) {
|
|
127
|
-
this.#clearRefreshOutcome();
|
|
128
|
-
const timer = setTimeout(() => {
|
|
129
|
-
if (this.#refreshOutcomeTimer !== timer)
|
|
130
|
-
return;
|
|
131
|
-
this.#refreshOutcomeTimer = undefined;
|
|
132
|
-
if (this.#disposed)
|
|
133
|
-
return;
|
|
134
|
-
this.#applyRefreshOutcome(message, kind);
|
|
135
|
-
this.#callbacks.requestRender();
|
|
136
|
-
}, remainingMs);
|
|
137
|
-
timer.unref?.();
|
|
138
|
-
this.#refreshOutcomeTimer = timer;
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
this.#clearRefreshOutcome();
|
|
142
|
-
this.#applyRefreshOutcome(message, kind);
|
|
109
|
+
this.#refreshStatus = kind === "warning" ? { message, kind } : undefined;
|
|
143
110
|
}
|
|
144
111
|
dispose() {
|
|
145
112
|
this.#disposed = true;
|
|
146
|
-
this.#clearRefreshOutcome();
|
|
147
|
-
this.#refreshing = false;
|
|
148
|
-
this.#refreshStartedAt = undefined;
|
|
149
113
|
}
|
|
150
114
|
invalidate() {
|
|
151
115
|
this.#input.invalidate();
|
|
@@ -235,21 +199,9 @@ export class ModelsDialogComponent {
|
|
|
235
199
|
render(width) {
|
|
236
200
|
const theme = piTheme();
|
|
237
201
|
this.#title.setText(theme.fg("accent", theme.bold(MODELS_TITLE))
|
|
238
|
-
+ (this.dirty ? theme.fg("warning", " (unsaved)") : "")
|
|
239
|
-
+ (this.#refreshing ? theme.fg("muted", " (refreshing)") : ""));
|
|
202
|
+
+ (this.dirty ? theme.fg("warning", " (unsaved)") : ""));
|
|
240
203
|
return this.#frame.render(width);
|
|
241
204
|
}
|
|
242
|
-
#applyRefreshOutcome(message, kind) {
|
|
243
|
-
this.#refreshing = false;
|
|
244
|
-
this.#refreshStartedAt = undefined;
|
|
245
|
-
this.#refreshStatus = kind === "warning" ? { message, kind } : undefined;
|
|
246
|
-
}
|
|
247
|
-
#clearRefreshOutcome() {
|
|
248
|
-
if (this.#refreshOutcomeTimer === undefined)
|
|
249
|
-
return;
|
|
250
|
-
clearTimeout(this.#refreshOutcomeTimer);
|
|
251
|
-
this.#refreshOutcomeTimer = undefined;
|
|
252
|
-
}
|
|
253
205
|
#renderBody(width) {
|
|
254
206
|
const theme = piTheme();
|
|
255
207
|
const lines = [];
|
|
@@ -261,7 +213,7 @@ export class ModelsDialogComponent {
|
|
|
261
213
|
+ theme.fg("dim", " | ")
|
|
262
214
|
+ theme.fg(this.#filter === "scoped" ? "mdHeading" : "dim", "scoped"));
|
|
263
215
|
push();
|
|
264
|
-
for (const line of this.#input
|
|
216
|
+
for (const line of renderPiModalSearchInput(this.#input, width))
|
|
265
217
|
push(line);
|
|
266
218
|
push();
|
|
267
219
|
if (rows.length === 0) {
|
|
@@ -51,6 +51,8 @@ export interface PromptSelectionUxOptions {
|
|
|
51
51
|
readonly beginClipboardPaste?: () => PiShellPasteReservation;
|
|
52
52
|
readonly beginTextPaste?: (text: string) => PiShellPasteReservation;
|
|
53
53
|
readonly deferTextPaste?: (text: string) => boolean;
|
|
54
|
+
/** False consumes every paste transport without changing editor text. */
|
|
55
|
+
readonly pasteEnabled?: boolean;
|
|
54
56
|
readonly onPasteRejected?: (error: unknown) => void;
|
|
55
57
|
readonly onPasteInput?: (bytes: number, phase: "framing" | "inserting" | "settled") => void;
|
|
56
58
|
readonly transformPastedContent: (content: PiShellClipboardContent) => string;
|
|
@@ -72,5 +74,6 @@ export interface PromptSelectionUxOptions {
|
|
|
72
74
|
};
|
|
73
75
|
}
|
|
74
76
|
export declare function createPromptSelectionInterceptor(editor: Editor, keybindings: KeybindingsManager, options: PromptSelectionUxOptions): OwnedEditorUxInterceptor;
|
|
77
|
+
export declare function editorInsertedText(data: string): string | undefined;
|
|
75
78
|
/** Shares the existing atomic-aware body layout with prompt/menu composition. */
|
|
76
79
|
export declare function editorVisualLineCount(editor: Editor, width: number): number | undefined;
|
|
@@ -81,7 +81,7 @@ class PromptSelectionInterceptor {
|
|
|
81
81
|
installAtomicSegmentation(editor, options.atomicRanges, () => this.#wordDirection);
|
|
82
82
|
}
|
|
83
83
|
handleInput(data, next) {
|
|
84
|
-
if (this.#handleTerminalPaste(data, next))
|
|
84
|
+
if (this.options.pasteEnabled === false ? discardTerminalPaste(this, data, value => this.handleInput(value, next)) : this.#handleTerminalPaste(data, next))
|
|
85
85
|
return;
|
|
86
86
|
if (this.keybindings.matches(data, "owned.editor.selectAll")) {
|
|
87
87
|
this.#selectAll();
|
|
@@ -107,7 +107,8 @@ class PromptSelectionInterceptor {
|
|
|
107
107
|
return;
|
|
108
108
|
}
|
|
109
109
|
if (this.keybindings.matches(data, "owned.editor.paste")) {
|
|
110
|
-
this.
|
|
110
|
+
if (this.options.pasteEnabled !== false)
|
|
111
|
+
this.pasteClipboard();
|
|
111
112
|
return;
|
|
112
113
|
}
|
|
113
114
|
if (this.keybindings.matches(data, "owned.editor.redo")) {
|
|
@@ -132,7 +133,7 @@ class PromptSelectionInterceptor {
|
|
|
132
133
|
this.#deleteSelection();
|
|
133
134
|
return;
|
|
134
135
|
}
|
|
135
|
-
const inserted =
|
|
136
|
+
const inserted = editorInsertedText(data);
|
|
136
137
|
if (inserted !== undefined) {
|
|
137
138
|
this.#replaceSelection(inserted);
|
|
138
139
|
return;
|
|
@@ -280,11 +281,13 @@ class PromptSelectionInterceptor {
|
|
|
280
281
|
this.#pointerSelecting = false;
|
|
281
282
|
this.#lastClick = undefined;
|
|
282
283
|
this.#redoStack = [];
|
|
284
|
+
DISABLED_TERMINAL_PASTES.delete(this);
|
|
283
285
|
this.#selectionRevision += 1;
|
|
284
286
|
}
|
|
285
287
|
cancelPendingPastes() {
|
|
286
288
|
this.#pasteGeneration += 1;
|
|
287
289
|
this.#terminalPaste = undefined;
|
|
290
|
+
DISABLED_TERMINAL_PASTES.delete(this);
|
|
288
291
|
}
|
|
289
292
|
hasSelection() {
|
|
290
293
|
return this.#activeRange() !== undefined;
|
|
@@ -295,6 +298,8 @@ class PromptSelectionInterceptor {
|
|
|
295
298
|
: this.options.expandCopiedText(this.#selectedText());
|
|
296
299
|
}
|
|
297
300
|
pasteClipboard() {
|
|
301
|
+
if (this.options.pasteEnabled === false)
|
|
302
|
+
return false;
|
|
298
303
|
this.#pasteFromClipboard();
|
|
299
304
|
return true;
|
|
300
305
|
}
|
|
@@ -933,7 +938,73 @@ function isEditorSegment(value) {
|
|
|
933
938
|
const input = Reflect.get(value, "input");
|
|
934
939
|
return typeof segment === "string" && typeof index === "number" && typeof input === "string";
|
|
935
940
|
}
|
|
936
|
-
|
|
941
|
+
const DISABLED_TERMINAL_PASTES = new WeakMap();
|
|
942
|
+
function discardTerminalPaste(owner, data, forward) {
|
|
943
|
+
const startMarker = "\x1b[200~";
|
|
944
|
+
const endMarker = "\x1b[201~";
|
|
945
|
+
const state = DISABLED_TERMINAL_PASTES.get(owner) ?? { startTail: "", bodyTail: null };
|
|
946
|
+
if (state.bodyTail !== null) {
|
|
947
|
+
const framed = state.bodyTail + data;
|
|
948
|
+
const end = framed.indexOf(endMarker);
|
|
949
|
+
if (end < 0) {
|
|
950
|
+
state.bodyTail = delimiterTail(framed, endMarker);
|
|
951
|
+
DISABLED_TERMINAL_PASTES.set(owner, state);
|
|
952
|
+
return true;
|
|
953
|
+
}
|
|
954
|
+
DISABLED_TERMINAL_PASTES.delete(owner);
|
|
955
|
+
const remaining = framed.slice(end + endMarker.length);
|
|
956
|
+
if (remaining.length > 0)
|
|
957
|
+
forward(remaining);
|
|
958
|
+
return true;
|
|
959
|
+
}
|
|
960
|
+
const hadStartTail = state.startTail.length > 0;
|
|
961
|
+
const candidate = state.startTail + data;
|
|
962
|
+
state.startTail = "";
|
|
963
|
+
const start = candidate.indexOf(startMarker);
|
|
964
|
+
if (start >= 0) {
|
|
965
|
+
if (start > 0)
|
|
966
|
+
forward(candidate.slice(0, start));
|
|
967
|
+
const framed = candidate.slice(start + startMarker.length);
|
|
968
|
+
const end = framed.indexOf(endMarker);
|
|
969
|
+
if (end < 0) {
|
|
970
|
+
state.bodyTail = delimiterTail(framed, endMarker);
|
|
971
|
+
DISABLED_TERMINAL_PASTES.set(owner, state);
|
|
972
|
+
}
|
|
973
|
+
else {
|
|
974
|
+
DISABLED_TERMINAL_PASTES.delete(owner);
|
|
975
|
+
const remaining = framed.slice(end + endMarker.length);
|
|
976
|
+
if (remaining.length > 0)
|
|
977
|
+
forward(remaining);
|
|
978
|
+
}
|
|
979
|
+
return true;
|
|
980
|
+
}
|
|
981
|
+
const partial = delimiterTail(candidate, startMarker);
|
|
982
|
+
// Compatibility: standalone Escape still cancels; marker fragments stay pending.
|
|
983
|
+
if (partial.length > 1) {
|
|
984
|
+
const before = candidate.slice(0, candidate.length - partial.length);
|
|
985
|
+
if (before.length > 0)
|
|
986
|
+
forward(before);
|
|
987
|
+
state.startTail = partial;
|
|
988
|
+
DISABLED_TERMINAL_PASTES.set(owner, state);
|
|
989
|
+
return true;
|
|
990
|
+
}
|
|
991
|
+
DISABLED_TERMINAL_PASTES.delete(owner);
|
|
992
|
+
if (hadStartTail) {
|
|
993
|
+
forward(candidate);
|
|
994
|
+
return true;
|
|
995
|
+
}
|
|
996
|
+
return false;
|
|
997
|
+
}
|
|
998
|
+
function delimiterTail(text, delimiter) {
|
|
999
|
+
const limit = Math.min(text.length, delimiter.length - 1);
|
|
1000
|
+
for (let length = limit; length > 0; length -= 1) {
|
|
1001
|
+
const suffix = text.slice(text.length - length);
|
|
1002
|
+
if (delimiter.startsWith(suffix))
|
|
1003
|
+
return suffix;
|
|
1004
|
+
}
|
|
1005
|
+
return "";
|
|
1006
|
+
}
|
|
1007
|
+
export function editorInsertedText(data) {
|
|
937
1008
|
if (!data || data.includes("\u001b[200~"))
|
|
938
1009
|
return undefined;
|
|
939
1010
|
const kitty = decodeKittyPrintable(data);
|
|
@@ -5,6 +5,8 @@ import { OwnedEditorUxInterception, createPromptSelectionInterceptor, editorVisu
|
|
|
5
5
|
import { PINNED_PI_LAYOUT, getPiSelectListTheme, piTheme, renderPiListSelection, } from "./theme.js";
|
|
6
6
|
import { createPiKeybindingsHost, createTuiFacade, ensureTheme, isAutocompleteProvider, } from "./shell-shared-facade.js";
|
|
7
7
|
import { SKILLS_COMMAND_NAME, collapseSkillCommands, createSkillsTunnelProvider, } from "./skills-command.js";
|
|
8
|
+
/** The built-in commands resident A1 serves from its tab bar instead: utility tabs, and the tab rename for `/name`. */
|
|
9
|
+
const RESIDENT_UTILITY_TAB_COMMANDS = new Set(["settings", "changelog", "name"]);
|
|
8
10
|
/** A selected autocomplete row: its arrow/primary column, then the aligned description. */
|
|
9
11
|
const SELECTED_DESCRIBED_ROW = /^(→ .*?\S)(\s{2,}.*)$/u;
|
|
10
12
|
const INPUT_COUNTER_INSET = 3;
|
|
@@ -62,6 +64,8 @@ export function createPiShellEditor(options) {
|
|
|
62
64
|
ensureTheme();
|
|
63
65
|
const tui = createTuiFacade(options);
|
|
64
66
|
const inputPresentation = options.keybindingProfile === "a1" ? options.promptPresentation?.input : undefined;
|
|
67
|
+
const inputCapabilities = options.inputCapabilities ?? {};
|
|
68
|
+
const autocompleteEnabled = inputCapabilities.autocomplete !== false;
|
|
65
69
|
// Invariant: the process shares one manager; a standalone editor gets its own host for its profile.
|
|
66
70
|
const host = options.keybindings ?? createPiKeybindingsHost({
|
|
67
71
|
profile: options.keybindingProfile ?? "pi",
|
|
@@ -74,7 +78,7 @@ export function createPiShellEditor(options) {
|
|
|
74
78
|
}
|
|
75
79
|
const scrollInfo = { emitted: false, counter: undefined };
|
|
76
80
|
const selectListTheme = getPiSelectListTheme();
|
|
77
|
-
|
|
81
|
+
const autocomplete = { provider: emptyAutocompleteProvider(), skills: [] };
|
|
78
82
|
const EditorClass = options.keybindingProfile === "a1" && options.persistentHistory === true ? options.historyEditor : OwnedEditor;
|
|
79
83
|
const editor = new EditorClass(tui, {
|
|
80
84
|
borderColor: (value) => inputPresentation === undefined ? piTheme().fg("borderMuted", value) : inputPresentation.styleRule(value),
|
|
@@ -100,13 +104,14 @@ export function createPiShellEditor(options) {
|
|
|
100
104
|
...(options.keybindingProfile === "a1" ? {
|
|
101
105
|
terminalRows: options.getRows,
|
|
102
106
|
getVisualLineCount: (width) => editorVisualLineCount(editor, width),
|
|
103
|
-
clearCommandSearchOnEscape:
|
|
104
|
-
commandTunnels: () =>
|
|
107
|
+
clearCommandSearchOnEscape: autocompleteEnabled,
|
|
108
|
+
commandTunnels: () => !autocompleteEnabled || autocomplete.skills.length === 0 ? [] : [SKILLS_COMMAND_NAME],
|
|
105
109
|
} : {}),
|
|
106
110
|
...(options.keybindingProfile === "a1" && options.promptPresentation !== undefined ? {
|
|
107
111
|
...(inputPresentation === undefined ? {} : { inputPresentation }),
|
|
108
112
|
styleSuggestion: options.promptPresentation.styleSuggestion,
|
|
109
113
|
styleSuggestionCaret: options.promptPresentation.styleSuggestionCaret,
|
|
114
|
+
...(inputCapabilities.placeholder === undefined ? {} : { placeholder: inputCapabilities.placeholder }),
|
|
110
115
|
} : {}),
|
|
111
116
|
});
|
|
112
117
|
const editorUx = options.keybindingProfile === "a1"
|
|
@@ -117,6 +122,7 @@ export function createPiShellEditor(options) {
|
|
|
117
122
|
...(options.beginClipboardPaste === undefined ? {} : { beginClipboardPaste: options.beginClipboardPaste }),
|
|
118
123
|
...(options.beginTextPaste === undefined ? {} : { beginTextPaste: options.beginTextPaste }),
|
|
119
124
|
...(options.deferTextPaste === undefined ? {} : { deferTextPaste: options.deferTextPaste }),
|
|
125
|
+
pasteEnabled: inputCapabilities.paste !== false,
|
|
120
126
|
...(options.onPasteRejected === undefined ? {} : { onPasteRejected: options.onPasteRejected }),
|
|
121
127
|
...(options.onPasteInput === undefined ? {} : { onPasteInput: options.onPasteInput }),
|
|
122
128
|
transformPastedContent: options.transformPastedContent ?? (content => content.kind === "text" ? content.text : ""),
|
|
@@ -142,37 +148,7 @@ export function createPiShellEditor(options) {
|
|
|
142
148
|
: isBashMode ? piTheme().getBashModeBorderColor() : piTheme().getThinkingBorderColor(thinkingLevel);
|
|
143
149
|
tui.requestRender();
|
|
144
150
|
};
|
|
145
|
-
|
|
146
|
-
const builtInCatalog = (options.keybindingProfile === "a1" ? OWNED_BUILTIN_SLASH_COMMANDS : PINNED_PI_BUILTIN_SLASH_COMMANDS)
|
|
147
|
-
.filter(command => !options.hideSettingsCommand || command.name !== "settings");
|
|
148
|
-
const setAutocompleteCommands = (installed) => {
|
|
149
|
-
// Invariant: collapse is a bare-A1 presentation; the comparison profile installs the pinned list.
|
|
150
|
-
const collapsed = options.keybindingProfile === "a1" && options.skillsPresentation?.() === "collapse"
|
|
151
|
-
? collapseSkillCommands(installed)
|
|
152
|
-
: { commands: installed, skills: [] };
|
|
153
|
-
tunnelSkills = collapsed.skills;
|
|
154
|
-
const commands = collapsed.commands;
|
|
155
|
-
const additions = new Map(commands.map(command => [command.name, command]));
|
|
156
|
-
const builtInNames = new Set(builtInCatalog.map(command => command.name));
|
|
157
|
-
const builtIns = builtInCatalog.flatMap(command => [
|
|
158
|
-
autocompleteCommand(command, additions.get(command.name)),
|
|
159
|
-
// Rationale: the collapsed browser is a primary bare-A1 command, not a resource after quit.
|
|
160
|
-
...(command.name === "settings" && collapsed.collapsedCommand !== undefined
|
|
161
|
-
? [autocompleteCommand(collapsed.collapsedCommand)]
|
|
162
|
-
: []),
|
|
163
|
-
]);
|
|
164
|
-
// Invariant: built-in additions and the promoted collapsed browser never reappear as resources.
|
|
165
|
-
const resources = commands
|
|
166
|
-
.filter(command => command.name !== collapsed.collapsedCommand?.name && command.source !== "builtin" && !builtInNames.has(command.name))
|
|
167
|
-
.map(command => autocompleteCommand(command));
|
|
168
|
-
const catalog = [...builtIns, ...resources];
|
|
169
|
-
const combined = new CombinedAutocompleteProvider(catalog, options.cwd ?? process.cwd());
|
|
170
|
-
const tunnelAware = tunnelSkills.length === 0 ? combined : createSkillsTunnelProvider(combined, tunnelSkills);
|
|
171
|
-
autocompleteProvider = options.keybindingProfile === "a1"
|
|
172
|
-
? createDelimiterReadyCommandProvider(tunnelAware, catalog)
|
|
173
|
-
: tunnelAware;
|
|
174
|
-
editor.setAutocompleteProvider(autocompleteProvider);
|
|
175
|
-
};
|
|
151
|
+
const setAutocompleteCommands = autocompleteCommandSetter(options, editor, autocomplete, autocompleteEnabled);
|
|
176
152
|
setAutocompleteCommands(options.autocompleteCommands ?? []);
|
|
177
153
|
let submitHandler = options.onSubmit;
|
|
178
154
|
let interruptHandler = options.onInterrupt ?? (() => { });
|
|
@@ -259,6 +235,9 @@ export function createPiShellEditor(options) {
|
|
|
259
235
|
reloadKeybindings: () => host.reload(),
|
|
260
236
|
matchesTerminalKey: (data, key) => matchesKey(data, key),
|
|
261
237
|
handleInput: data => {
|
|
238
|
+
if (inputCapabilities.singleLine === true
|
|
239
|
+
&& (keybindings.matches(data, "tui.input.newLine") || keybindings.matches(data, "tui.input.submit")))
|
|
240
|
+
return;
|
|
262
241
|
// Compatibility: an unassigned reverse Tab must not clear selection or reach autocomplete fallback.
|
|
263
242
|
if (editorUx !== undefined && matchesKey(data, "shift+tab")
|
|
264
243
|
&& !Object.values(keybindings.getEffectiveConfig()).some(keys => (Array.isArray(keys) ? keys : [keys]).some(key => key !== undefined && matchesKey(data, key))))
|
|
@@ -316,13 +295,15 @@ export function createPiShellEditor(options) {
|
|
|
316
295
|
tui.requestRender();
|
|
317
296
|
},
|
|
318
297
|
addAutocompleteProvider(factory) {
|
|
298
|
+
if (!autocompleteEnabled)
|
|
299
|
+
return;
|
|
319
300
|
if (typeof factory !== "function")
|
|
320
301
|
throw new TypeError("extension autocomplete factory must be a function");
|
|
321
|
-
const candidate = Reflect.apply(factory, undefined, [
|
|
302
|
+
const candidate = Reflect.apply(factory, undefined, [autocomplete.provider]);
|
|
322
303
|
if (!isAutocompleteProvider(candidate))
|
|
323
304
|
throw new TypeError("extension autocomplete factory returned a malformed provider");
|
|
324
|
-
|
|
325
|
-
editor.setAutocompleteProvider(
|
|
305
|
+
autocomplete.provider = candidate;
|
|
306
|
+
editor.setAutocompleteProvider(autocomplete.provider);
|
|
326
307
|
},
|
|
327
308
|
setThinkingLevel(level) {
|
|
328
309
|
if (thinkingLevel === level)
|
|
@@ -354,6 +335,45 @@ export function createPiShellEditor(options) {
|
|
|
354
335
|
cancelPendingPastes: () => editorUx?.cancelPendingPastes(),
|
|
355
336
|
};
|
|
356
337
|
}
|
|
338
|
+
function autocompleteCommandSetter(options, editor, state, enabled) {
|
|
339
|
+
const builtInCatalog = (options.keybindingProfile === "a1" ? OWNED_BUILTIN_SLASH_COMMANDS : PINNED_PI_BUILTIN_SLASH_COMMANDS)
|
|
340
|
+
.filter(command => !options.hideUtilityTabCommands || !RESIDENT_UTILITY_TAB_COMMANDS.has(command.name));
|
|
341
|
+
return installed => {
|
|
342
|
+
if (!enabled) {
|
|
343
|
+
state.provider = emptyAutocompleteProvider();
|
|
344
|
+
editor.setAutocompleteProvider(state.provider);
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
// Invariant: collapse is a bare-A1 presentation; the comparison profile installs the pinned list.
|
|
348
|
+
const collapsed = options.keybindingProfile === "a1" && options.skillsPresentation?.() === "collapse"
|
|
349
|
+
? collapseSkillCommands(installed)
|
|
350
|
+
: { commands: installed, skills: [] };
|
|
351
|
+
state.skills = collapsed.skills;
|
|
352
|
+
const additions = new Map(collapsed.commands.map(command => [command.name, command]));
|
|
353
|
+
const builtInNames = new Set(builtInCatalog.map(command => command.name));
|
|
354
|
+
const builtIns = builtInCatalog.flatMap(command => [
|
|
355
|
+
autocompleteCommand(command, additions.get(command.name)),
|
|
356
|
+
// Rationale: the collapsed browser is a primary bare-A1 command, not a resource after quit.
|
|
357
|
+
...(command.name === "settings" && collapsed.collapsedCommand !== undefined
|
|
358
|
+
? [autocompleteCommand(collapsed.collapsedCommand)] : []),
|
|
359
|
+
]);
|
|
360
|
+
const resources = collapsed.commands
|
|
361
|
+
.filter(command => command.name !== collapsed.collapsedCommand?.name && command.source !== "builtin" && !builtInNames.has(command.name))
|
|
362
|
+
.map(command => autocompleteCommand(command));
|
|
363
|
+
const catalog = [...builtIns, ...resources];
|
|
364
|
+
const combined = new CombinedAutocompleteProvider(catalog, options.cwd ?? process.cwd());
|
|
365
|
+
const tunnelAware = state.skills.length === 0 ? combined : createSkillsTunnelProvider(combined, state.skills);
|
|
366
|
+
state.provider = options.keybindingProfile === "a1" ? createDelimiterReadyCommandProvider(tunnelAware, catalog) : tunnelAware;
|
|
367
|
+
editor.setAutocompleteProvider(state.provider);
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
function emptyAutocompleteProvider() {
|
|
371
|
+
return {
|
|
372
|
+
getSuggestions: async () => null,
|
|
373
|
+
applyCompletion: (lines, cursorLine, cursorCol) => ({ lines, cursorLine, cursorCol }),
|
|
374
|
+
shouldTriggerFileCompletion: () => false,
|
|
375
|
+
};
|
|
376
|
+
}
|
|
357
377
|
/** Bare A1 leaves an applied catalog command ready for `:`, a user-entered space, or submission. */
|
|
358
378
|
function createDelimiterReadyCommandProvider(base, commands) {
|
|
359
379
|
const commandNames = new Set(commands.map(command => command.name));
|
|
@@ -7,7 +7,7 @@ import { KeybindingsManager } from "./upstream/adjacent/core/keybindings.js";
|
|
|
7
7
|
import { StatusIndicator, WorkingStatusIndicator } from "./upstream/components/status-indicator.js";
|
|
8
8
|
import { protectPiPromptChipPresentation } from "./prompt-chip-presentation.js";
|
|
9
9
|
import { PINNED_PI_LAYOUT, piTheme, } from "./theme.js";
|
|
10
|
-
import { createTuiFacade, ensureTheme, } from "./shell-shared-facade.js";
|
|
10
|
+
import { createTuiFacade, ensureTheme, piShellTruncateToWidth, piShellVisibleWidth, } from "./shell-shared-facade.js";
|
|
11
11
|
export function createPiShellHeader(options = {}) {
|
|
12
12
|
ensureTheme();
|
|
13
13
|
let expanded = options.expanded ?? false;
|
|
@@ -107,9 +107,9 @@ export function createPiShellStatus(view, progressStatus, runtime) {
|
|
|
107
107
|
component = statusComponent(view, statusUi, outputPad, liveStatusText, placement, progressPresentation, progressStatus);
|
|
108
108
|
};
|
|
109
109
|
return {
|
|
110
|
-
render: width => component?.render(width) ?? [],
|
|
110
|
+
render: width => appendInstalledUpdateStatus(component?.render(width) ?? [], width, view),
|
|
111
111
|
renderDock: width => placement === "dock" ? component?.render(width) ?? [] : [],
|
|
112
|
-
renderLive: width => placement === "live" ? component?.render(width) ?? [] : [],
|
|
112
|
+
renderLive: width => appendInstalledUpdateStatus(placement === "live" ? component?.render(width) ?? [] : [], width, view),
|
|
113
113
|
placement: () => placement,
|
|
114
114
|
invalidate: () => component?.invalidate(),
|
|
115
115
|
update(next) {
|
|
@@ -228,6 +228,22 @@ function statusSignature(view, workingOverride, outputPad, placement, progressPr
|
|
|
228
228
|
const message = withMessage ? `${workingOverride ?? ""}\u0000${view.status.workingMessage ?? ""}\u0000${view.status.workingProgress ?? ""}` : "";
|
|
229
229
|
return `${placement}\u0000${outputPad}\u0000${view.lifecycle}\u0000${progressPresentation}\u0000${message}\u0000${view.status.diagnostics.at(-1) ?? ""}`;
|
|
230
230
|
}
|
|
231
|
+
function appendInstalledUpdateStatus(rows, width, view) {
|
|
232
|
+
if (width <= 0 || !view.diagnostics.some(diagnostic => diagnostic.code === "release-update-installed"))
|
|
233
|
+
return rows;
|
|
234
|
+
const theme = piTheme();
|
|
235
|
+
const full = `${theme.fg("success", "Update installed")} ${theme.fg("muted", "·")} ${theme.fg("accent", "Restart to update")}`;
|
|
236
|
+
const status = piShellTruncateToWidth(full, width);
|
|
237
|
+
const statusWidth = piShellVisibleWidth(status);
|
|
238
|
+
const target = rows.findLastIndex(row => piShellVisibleWidth(row.trimEnd()) > 0);
|
|
239
|
+
if (target < 0)
|
|
240
|
+
return [`${" ".repeat(Math.max(0, width - statusWidth))}${status}`];
|
|
241
|
+
const base = (rows[target] ?? "").trimEnd();
|
|
242
|
+
const gap = width - piShellVisibleWidth(base) - statusWidth;
|
|
243
|
+
if (gap >= 2)
|
|
244
|
+
return rows.map((row, index) => index === target ? `${base}${" ".repeat(gap)}${status}` : row);
|
|
245
|
+
return [...rows.slice(0, target + 1), `${" ".repeat(Math.max(0, width - statusWidth))}${status}`, ...rows.slice(target + 1)];
|
|
246
|
+
}
|
|
231
247
|
function queuedInputChipWrapping(text, presentation, width) {
|
|
232
248
|
return presentation === "custom-viewport"
|
|
233
249
|
? protectPiPromptChipPresentation(text, width)
|
|
@@ -4,7 +4,7 @@ import { SkillInvocationMessageComponent } from "./upstream/components/skill-inv
|
|
|
4
4
|
import { piToolArguments as toolArguments, updatePiToolResult as updateToolResult } from "./tool-result-adapter.js";
|
|
5
5
|
import { createTranscriptImageResolver } from "./transcript-image-resolver.js";
|
|
6
6
|
import { createMermaidMarkdownTransformer } from "./upstream/components/mermaid.js";
|
|
7
|
-
import { Container, Image, Spacer, Text, } from "@earendil-works/pi-tui";
|
|
7
|
+
import { Container, getCapabilities, Image, Spacer, Text, } from "@earendil-works/pi-tui";
|
|
8
8
|
import { createPiSubmittedPromptComponent, isPiPromptStyleCompaction, } from "./submitted-prompt-adapter.js";
|
|
9
9
|
export { isPiPromptStyleCompaction, paintPiSubmittedPromptTimestamp } from "./submitted-prompt-adapter.js";
|
|
10
10
|
import { getPiMarkdownTheme, PINNED_PI_LAYOUT, piTheme, } from "./theme.js";
|
|
@@ -127,7 +127,10 @@ function withTranscriptImages(component, block, assets, showImages, imageWidthCe
|
|
|
127
127
|
return component;
|
|
128
128
|
const container = new Container();
|
|
129
129
|
container.addChild(component);
|
|
130
|
+
const imageProtocolUnavailable = getCapabilities().images === null;
|
|
130
131
|
for (const reference of references) {
|
|
132
|
+
if (showImages && imageProtocolUnavailable)
|
|
133
|
+
continue;
|
|
131
134
|
container.addChild(new Spacer(1));
|
|
132
135
|
const asset = assets?.resolve(reference.assetId) ?? null;
|
|
133
136
|
if (!showImages) {
|
|
@@ -3,7 +3,7 @@ import { EarendilAnnouncementComponent } from "./upstream/components/earendil-an
|
|
|
3
3
|
import { ExtensionSelectorComponent } from "./upstream/components/extension-selector.js";
|
|
4
4
|
import { Box, Container, SelectList, Spacer, Text, } from "@earendil-works/pi-tui";
|
|
5
5
|
import { ScopedModelsSelectorComponent } from "./upstream/components/scoped-models-selector.js";
|
|
6
|
-
import { addPiModalSelectFooter, adoptPiModalFrame, adoptPiModalHeader } from "./modal-frame.js";
|
|
6
|
+
import { addPiModalSelectFooter, adoptPiModalFrame, adoptPiModalHeader, adoptPiModalSearchInput } from "./modal-frame.js";
|
|
7
7
|
import { ModelsDialogComponent } from "./models-dialog.js";
|
|
8
8
|
import { TrustSelectorComponent } from "./upstream/components/trust-selector.js";
|
|
9
9
|
import { canonicalizePiSettingsHint, DIALOG_CLOSE_SHORTCUT_HINT, PINNED_PI_LAYOUT, getPiSelectListTheme, paintPiBorder, piTheme, renderPiModalShortcutHints, } from "./theme.js";
|
|
@@ -96,6 +96,7 @@ export function createPiShellSettingsSelector(options) {
|
|
|
96
96
|
export function createPiShellModelSelector(options) {
|
|
97
97
|
ensureTheme();
|
|
98
98
|
const selector = new ModelSelectorComponent(createTuiFacade(options.runtime), options.currentModel, options.modelRuntime, options.scopedModels, options.onSelect, options.onCancel, options.initialSearchInput, options.onSelectAsDefault, options.defaultModel);
|
|
99
|
+
adoptPiModalSearchInput(selector, selector.getSearchInput());
|
|
99
100
|
adoptPiModalFrame(selector, { topIndex: 0, bottomIndex: selector.children.length - 1 });
|
|
100
101
|
return componentPort(selector);
|
|
101
102
|
}
|
|
@@ -235,6 +236,8 @@ export function createPiShellAuthProviderSelector(mode, providers, onSelect, onC
|
|
|
235
236
|
if (selected)
|
|
236
237
|
onSelect(selected.id);
|
|
237
238
|
}, onCancel, initialSearchInput);
|
|
239
|
+
// Provenance: OAuth selector slot is top rule, spacer, title, spacer, then its search Input.
|
|
240
|
+
adoptPiModalSearchInput(selector, selector.children[4]);
|
|
238
241
|
addPiModalSelectFooter(selector);
|
|
239
242
|
const preInsetContent = [selector.children[2], selector.children[6]];
|
|
240
243
|
const header = adoptPiModalHeader(selector, 0, 2);
|
|
@@ -161,11 +161,18 @@ export interface PiShellPasteReservation {
|
|
|
161
161
|
}
|
|
162
162
|
export interface PiShellEditorOptions {
|
|
163
163
|
readonly persistentHistory?: boolean;
|
|
164
|
+
/** Configures the shared interactive input for a caller narrower than the agent prompt. */
|
|
165
|
+
readonly inputCapabilities?: {
|
|
166
|
+
readonly singleLine?: boolean;
|
|
167
|
+
readonly paste?: boolean;
|
|
168
|
+
readonly autocomplete?: boolean;
|
|
169
|
+
readonly placeholder?: string;
|
|
170
|
+
};
|
|
164
171
|
readonly historyEditor?: HistoryEditorConstructor;
|
|
165
172
|
/** Bare A1 adds ergonomic aliases while comparison profiles retain Pi defaults. */
|
|
166
173
|
readonly keybindingProfile?: "pi" | "a1";
|
|
167
|
-
/** Resident Agents expose Settings
|
|
168
|
-
readonly
|
|
174
|
+
/** Resident Agents expose Settings and the changelog as utility tabs instead of slash commands. */
|
|
175
|
+
readonly hideUtilityTabCommands?: boolean;
|
|
169
176
|
/** The process's shared keybindings; its profile must match `keybindingProfile`. */
|
|
170
177
|
readonly keybindings?: PiKeybindingsHost;
|
|
171
178
|
readonly getColumns: () => number;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DynamicBorder } from "../startup-public.js";
|
|
2
2
|
import { Container, getKeybindings, Input, Spacer, Text } from "@earendil-works/pi-tui";
|
|
3
|
-
import { addPiModalHeader, adoptPiModalFrame } from "./modal-frame.js";
|
|
3
|
+
import { addPiModalHeader, addPiModalSearchInput, adoptPiModalFrame } from "./modal-frame.js";
|
|
4
4
|
import { DIALOG_CLOSE_SHORTCUT_HINT, paintPiBorder, PINNED_PI_LAYOUT, piTheme, renderPiModalListRow, renderPiModalShortcutHints, } from "./theme.js";
|
|
5
5
|
import { componentPort, ensureTheme, piShellTruncateToWidth, piShellVisibleWidth } from "./shell-shared-facade.js";
|
|
6
6
|
import { skillMatchesQuery } from "./skills-command.js";
|
|
@@ -50,7 +50,7 @@ class SkillsSelectorComponent extends Container {
|
|
|
50
50
|
this.#searchInput.onSubmit = () => this.#selectCurrent();
|
|
51
51
|
const header = addPiModalHeader(this, new DynamicBorder(paintPiBorder), new Text(piTheme().fg("accent", piTheme().bold("Skills")), 0, 0));
|
|
52
52
|
this.addChild(new Spacer(1));
|
|
53
|
-
|
|
53
|
+
addPiModalSearchInput(this, this.#searchInput);
|
|
54
54
|
this.addChild(new Spacer(1));
|
|
55
55
|
this.addChild(this.#listContainer);
|
|
56
56
|
this.addChild(new Spacer(1));
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
* packages/coding-agent/src/modes/interactive/components/custom-editor.ts.
|
|
4
4
|
* Modifications: A1-owned class name and synchronized A1 keybinding contract replace the nominal
|
|
5
5
|
* private upstream keybinding constructor dependency; bare A1 injects the shared Settings/agent input
|
|
6
|
-
* frame, transient contextual-suggestion branch, and
|
|
7
|
-
* above-prompt autocomplete with a one-cell outer gutter.
|
|
8
|
-
* slash-command search on Escape and completes a selected or
|
|
9
|
-
* and reopens the menu on its tunnel rows, and synchronously
|
|
10
|
-
* command menu after Tab applies its row.
|
|
6
|
+
* frame, configurable empty-input placeholder branch, transient contextual-suggestion branch, and
|
|
7
|
+
* explicit body geometry for selection and above-prompt autocomplete with a one-cell outer gutter.
|
|
8
|
+
* Bare A1 also clears a sole top-level slash-command search on Escape and completes a selected or
|
|
9
|
+
* exact completed tunnel command with `:` and reopens the menu on its tunnel rows, and synchronously
|
|
10
|
+
* retains then refreshes an exact top-level command menu after Tab applies its row.
|
|
11
11
|
* Deviations: owned-shared-input-frame, above-prompt-autocomplete-placement,
|
|
12
12
|
* clear-command-search-on-escape, command-tunnel-colon-completion, keep-command-menu-open-after-tab.
|
|
13
13
|
*/
|
|
@@ -21,6 +21,8 @@ export interface OwnedEditorOptions extends EditorOptions {
|
|
|
21
21
|
readonly inputPresentation?: PiShellPromptInputPresentation;
|
|
22
22
|
readonly styleSuggestion?: (text: string) => string;
|
|
23
23
|
readonly styleSuggestionCaret?: (text: string) => string;
|
|
24
|
+
/** Non-accepting empty-input guidance rendered through the shared suggestion presentation. */
|
|
25
|
+
readonly placeholder?: string;
|
|
24
26
|
readonly terminalRows?: () => number;
|
|
25
27
|
/** Reuses the editor's established atomic-aware visual layout when available. */
|
|
26
28
|
readonly getVisualLineCount?: (width: number) => number | undefined;
|