@timurproko/a1 0.1.8-dev.137 → 0.1.8-dev.151
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 +2 -4
- package/dist/cli/capabilities.d.ts +2 -11
- package/dist/cli/capabilities.js +1 -1
- package/dist/cli/dispatch.js +3 -6
- package/dist/composition/owned-ui.js +26 -5
- package/dist/composition/settings-route-host.d.ts +1 -1
- package/dist/composition/settings-route-host.js +1 -1
- package/dist/contracts/owned-ui/model.d.ts +28 -0
- package/dist/contracts/owned-ui/validation.js +14 -0
- package/dist/features/launch/development-launch.js +11 -3
- package/dist/features/launch/index.d.ts +0 -1
- package/dist/features/launch/index.js +0 -1
- package/dist/features/launch/intent.d.ts +3 -2
- package/dist/features/launch/intent.js +1 -2
- package/dist/features/launch/prepare-launch.d.ts +0 -2
- package/dist/features/launch/prepare-launch.js +2 -9
- package/dist/features/launch/profile-paths.d.ts +1 -2
- package/dist/features/launch/profile-paths.js +2 -8
- package/dist/features/launch/runtime-selection.d.ts +2 -5
- package/dist/features/owned-ui/settings-app.js +38 -14
- package/dist/foundation/lifecycle/model.d.ts +1 -1
- package/dist/foundation/lifecycle/model.js +1 -1
- package/dist/foundation/release/bootstrap.d.ts +1 -3
- package/dist/foundation/release/bootstrap.js +1 -1
- package/dist/foundation/storage/control-store.js +37 -3
- package/dist/integrations/pi/components/owned-editor-ux.d.ts +55 -0
- package/dist/integrations/pi/components/owned-editor-ux.js +778 -0
- package/dist/integrations/pi/components/shell-editor-autocomplete.js +44 -6
- package/dist/integrations/pi/components/shell-footer-status.d.ts +1 -1
- package/dist/integrations/pi/components/shell-footer-status.js +14 -5
- package/dist/integrations/pi/components/shell-presenters-transcript.d.ts +3 -2
- package/dist/integrations/pi/components/shell-presenters-transcript.js +9 -8
- package/dist/integrations/pi/components/shell-shared-facade.d.ts +41 -7
- package/dist/integrations/pi/components/shell-shared-facade.js +3 -1
- package/dist/integrations/pi/components/submitted-prompt-adapter.d.ts +13 -0
- package/dist/integrations/pi/components/submitted-prompt-adapter.js +27 -0
- package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.d.ts +10 -2
- package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.js +36 -3
- package/dist/integrations/pi/engine/adapter.js +21 -3
- package/dist/integrations/pi/engine/session-integration.d.ts +2 -1
- package/dist/integrations/pi/engine/session-integration.js +17 -3
- package/dist/integrations/pi/session-ui/prompt-chips.d.ts +23 -0
- package/dist/integrations/pi/session-ui/prompt-chips.js +242 -0
- package/dist/integrations/pi/session-ui/route-host.d.ts +5 -0
- package/dist/integrations/pi/{owned-ui → session-ui}/session-shell-root.d.ts +34 -5
- package/dist/integrations/pi/{owned-ui → session-ui}/session-shell-root.js +294 -45
- package/dist/integrations/pi/{owned-ui → session-ui}/session-shell.d.ts +1 -1
- package/dist/integrations/pi/{owned-ui → session-ui}/session-shell.js +167 -24
- package/dist/integrations/pi/session-ui/session-viewport-controller.d.ts +39 -0
- package/dist/integrations/pi/session-ui/session-viewport-controller.js +369 -0
- package/dist/integrations/pi/session-ui/system-clipboard.d.ts +11 -0
- package/dist/integrations/pi/session-ui/system-clipboard.js +101 -0
- package/dist/integrations/pi/tui-runtime/adapter.d.ts +2 -1
- package/dist/integrations/pi/tui-runtime/adapter.js +51 -2
- package/dist/integrations/pi/tui-runtime/contracts.d.ts +4 -6
- 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/product-identity.d.ts +2 -2
- package/dist/product-identity.js +3 -4
- package/dist/product-identity.json +3 -5
- package/dist/ui/apps/contracts.d.ts +23 -1
- package/dist/ui/apps/host.js +9 -1
- package/dist/ui/components/index.d.ts +3 -0
- package/dist/ui/components/index.js +3 -0
- package/dist/ui/components/mouse.d.ts +10 -0
- package/dist/ui/components/mouse.js +23 -0
- package/dist/ui/components/prompt-row.d.ts +14 -0
- package/dist/ui/components/prompt-row.js +20 -0
- package/dist/ui/components/scrollbar.d.ts +26 -0
- package/dist/ui/components/scrollbar.js +35 -0
- package/dist/ui/components/spans.d.ts +21 -0
- package/dist/ui/components/spans.js +178 -0
- package/dist/ui/components/submitted-prompt.d.ts +23 -0
- package/dist/ui/components/submitted-prompt.js +50 -0
- package/dist/ui/components/text-selection.d.ts +53 -0
- package/dist/ui/components/text-selection.js +168 -0
- package/dist/ui/components/text.d.ts +14 -0
- package/dist/ui/components/text.js +40 -0
- package/dist/ui/components/transcript-viewport.d.ts +104 -0
- package/dist/ui/components/transcript-viewport.js +381 -0
- package/dist/ui/settings/declarations.d.ts +8 -1
- package/dist/ui/settings/declarations.js +33 -5
- package/dist/ui/settings/migrations.d.ts +1 -1
- package/dist/ui/settings/migrations.js +21 -2
- package/dist/ui/settings/sections.js +15 -8
- package/dist/ui/settings/session.d.ts +2 -0
- package/dist/ui/settings/session.js +7 -5
- package/docs/architecture/boundaries.md +3 -3
- package/docs/architecture/project-structure.md +2 -2
- package/docs/architecture/resource-and-data-policy.md +1 -1
- package/docs/architecture/toolchain.md +5 -5
- package/docs/architecture/ui-reference-provenance.md +11 -2
- package/docs/features/launch-profiles.md +7 -14
- package/docs/manual-launch-instance-acceptance.md +12 -35
- package/docs/manual-owned-ui-checkpoint.md +2 -3
- package/docs/manual-pi-boundary-candidate.md +4 -5
- package/package.json +4 -2
- package/dist/features/launch/profiles.d.ts +0 -12
- package/dist/features/launch/profiles.js +0 -26
- package/dist/integrations/pi/owned-ui/route-host.d.ts +0 -29
- /package/dist/integrations/pi/{owned-ui → session-ui}/index.d.ts +0 -0
- /package/dist/integrations/pi/{owned-ui → session-ui}/index.js +0 -0
- /package/dist/integrations/pi/{owned-ui → session-ui}/route-host.js +0 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { getSelectListTheme } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { CombinedAutocompleteProvider, setKeybindings, } from "#pi-tui";
|
|
2
|
+
import { CombinedAutocompleteProvider, matchesKey, setKeybindings, } from "#pi-tui";
|
|
3
3
|
import { KeybindingsManager } from "./upstream/adjacent/core/keybindings.js";
|
|
4
4
|
import { OwnedEditor } from "./upstream/components/owned-editor.js";
|
|
5
|
+
import { OwnedEditorUxInterception, createPromptSelectionInterceptor, } from "./owned-editor-ux.js";
|
|
5
6
|
import { PINNED_PI_LAYOUT, piTheme, } from "./theme.js";
|
|
6
7
|
import { createTuiFacade, ensureTheme, isAutocompleteProvider, } from "./shell-shared-facade.js";
|
|
7
8
|
export const PINNED_PI_BUILTIN_SLASH_COMMANDS = [
|
|
@@ -31,7 +32,9 @@ export const PINNED_PI_BUILTIN_SLASH_COMMANDS = [
|
|
|
31
32
|
export function createPiShellEditor(options) {
|
|
32
33
|
ensureTheme();
|
|
33
34
|
const tui = createTuiFacade(options);
|
|
34
|
-
const keybindings =
|
|
35
|
+
const keybindings = options.keybindingProfile === "a1"
|
|
36
|
+
? KeybindingsManager.createForOwnedInput(options.agentDir)
|
|
37
|
+
: KeybindingsManager.create(options.agentDir);
|
|
35
38
|
setKeybindings(keybindings);
|
|
36
39
|
const editor = new OwnedEditor(tui, {
|
|
37
40
|
borderColor: (value) => piTheme().fg("borderMuted", value),
|
|
@@ -40,6 +43,24 @@ export function createPiShellEditor(options) {
|
|
|
40
43
|
paddingX: PINNED_PI_LAYOUT.editorPaddingX,
|
|
41
44
|
autocompleteMaxVisible: PINNED_PI_LAYOUT.autocompleteMaxVisible,
|
|
42
45
|
});
|
|
46
|
+
const editorUx = options.keybindingProfile === "a1"
|
|
47
|
+
? new OwnedEditorUxInterception([
|
|
48
|
+
createPromptSelectionInterceptor(editor, keybindings, {
|
|
49
|
+
copyText: options.onCopyText ?? (() => { }),
|
|
50
|
+
readClipboardContent: options.readClipboardContent ?? (async () => null),
|
|
51
|
+
transformPastedContent: options.transformPastedContent ?? (content => content.kind === "text" ? content.text : ""),
|
|
52
|
+
atomicRanges: options.editorAtomicRanges ?? (() => []),
|
|
53
|
+
expandCopiedText: options.expandCopiedEditorText ?? (text => text),
|
|
54
|
+
paintSelection: options.paintEditorSelection ?? (line => line),
|
|
55
|
+
decorateRow: options.decorateEditorRow ?? (row => row),
|
|
56
|
+
requestRender: options.requestRender,
|
|
57
|
+
getRows: options.getRows,
|
|
58
|
+
}),
|
|
59
|
+
], {
|
|
60
|
+
render: width => editor.render(width),
|
|
61
|
+
handleInput: data => editor.handleInput(data),
|
|
62
|
+
})
|
|
63
|
+
: undefined;
|
|
43
64
|
let thinkingLevel = "off";
|
|
44
65
|
let isBashMode = false;
|
|
45
66
|
const updateBorderColor = () => {
|
|
@@ -108,15 +129,28 @@ export function createPiShellEditor(options) {
|
|
|
108
129
|
if (options.onDequeue !== undefined)
|
|
109
130
|
editor.onAction("app.message.dequeue", options.onDequeue);
|
|
110
131
|
return {
|
|
111
|
-
render: width => editor.render(width),
|
|
112
|
-
|
|
132
|
+
render: width => editorUx?.render(width) ?? editor.render(width),
|
|
133
|
+
activateKeybindings: () => setKeybindings(keybindings),
|
|
134
|
+
matchesTerminalKey: (data, key) => matchesKey(data, key),
|
|
135
|
+
handleInput: data => {
|
|
136
|
+
if (editorUx === undefined)
|
|
137
|
+
editor.handleInput(data);
|
|
138
|
+
else
|
|
139
|
+
editorUx.handleInput(data);
|
|
140
|
+
},
|
|
113
141
|
invalidate: () => editor.invalidate(),
|
|
114
142
|
setFocused: focused => {
|
|
115
143
|
editor.focused = focused;
|
|
116
144
|
},
|
|
117
145
|
getText: () => editor.getExpandedText(),
|
|
118
|
-
setText: text =>
|
|
119
|
-
|
|
146
|
+
setText: text => {
|
|
147
|
+
editorUx?.reset();
|
|
148
|
+
editor.setText(text);
|
|
149
|
+
},
|
|
150
|
+
insertText: text => {
|
|
151
|
+
editorUx?.reset();
|
|
152
|
+
editor.insertTextAtCursor(text);
|
|
153
|
+
},
|
|
120
154
|
addToHistory: text => editor.addToHistory(text),
|
|
121
155
|
setSubmitEnabled: enabled => {
|
|
122
156
|
editor.disableSubmit = !enabled;
|
|
@@ -139,6 +173,10 @@ export function createPiShellEditor(options) {
|
|
|
139
173
|
thinkingLevel = level;
|
|
140
174
|
updateBorderColor();
|
|
141
175
|
},
|
|
176
|
+
hasSelection: () => editorUx?.hasSelection() ?? false,
|
|
177
|
+
ownsPointer: () => editorUx?.ownsPointer() ?? false,
|
|
178
|
+
handlePointer: event => editorUx?.handlePointer(event) ?? false,
|
|
179
|
+
pasteClipboard: () => editorUx?.pasteClipboard() ?? false,
|
|
142
180
|
};
|
|
143
181
|
}
|
|
144
182
|
function autocompleteCommand(command, addition) {
|
|
@@ -4,4 +4,4 @@ export declare function createPiShellHeader(options?: PiShellHeaderOptions): PiS
|
|
|
4
4
|
export declare function createPiShellLoadedResources(resources: readonly PiShellResourceEntry[], initialExpanded?: boolean): PiShellLoadedResourcesPort;
|
|
5
5
|
export declare function createPiShellStatus(view: OwnedUiSessionViewModel, runtime?: Pick<PiShellEditorOptions, "getColumns" | "getRows" | "requestRender">): PiShellStatusPort;
|
|
6
6
|
export declare function createPiShellFooter(view: OwnedUiSessionViewModel, cwd: string): PiShellViewComponentPort;
|
|
7
|
-
export declare function createPiQueuedInputStatus(submissions: readonly string[]): PiShellQueuedInputPort;
|
|
7
|
+
export declare function createPiQueuedInputStatus(submissions: readonly string[], presentation?: "pinned" | "custom-viewport"): PiShellQueuedInputPort;
|
|
@@ -110,14 +110,14 @@ export function createPiShellFooter(view, cwd) {
|
|
|
110
110
|
dispose: () => footer.dispose(),
|
|
111
111
|
};
|
|
112
112
|
}
|
|
113
|
-
export function createPiQueuedInputStatus(submissions) {
|
|
114
|
-
const text = new Text(queuedInputText(submissions), 1, 0);
|
|
113
|
+
export function createPiQueuedInputStatus(submissions, presentation = "pinned") {
|
|
114
|
+
const text = new Text(queuedInputText(submissions, presentation), 1, 0);
|
|
115
115
|
return {
|
|
116
116
|
render: width => submissions.length === 0 ? [] : text.render(width),
|
|
117
117
|
invalidate: () => text.invalidate(),
|
|
118
118
|
update(next) {
|
|
119
119
|
submissions = next;
|
|
120
|
-
text.setText(queuedInputText(next));
|
|
120
|
+
text.setText(queuedInputText(next, presentation));
|
|
121
121
|
},
|
|
122
122
|
};
|
|
123
123
|
}
|
|
@@ -135,9 +135,18 @@ function statusComponent(view, ui, workingOverride) {
|
|
|
135
135
|
function statusSignature(view, workingOverride) {
|
|
136
136
|
return `${view.lifecycle}\u0000${workingOverride ?? ""}\u0000${view.status.workingMessage ?? ""}\u0000${view.status.diagnostics.at(-1) ?? ""}`;
|
|
137
137
|
}
|
|
138
|
-
function queuedInputText(submissions) {
|
|
138
|
+
function queuedInputText(submissions, presentation) {
|
|
139
|
+
if (submissions.length === 0)
|
|
140
|
+
return "";
|
|
139
141
|
const theme = piTheme();
|
|
140
|
-
|
|
142
|
+
if (presentation === "pinned") {
|
|
143
|
+
return submissions.map(submission => theme.fg("muted", `Steering: ${submission.replaceAll("\n", " ⏎ ")}`)).join("\n");
|
|
144
|
+
}
|
|
145
|
+
const messages = submissions.map(submission => theme.fg("dim", `Steering: ${submission.replaceAll("\n", " ⏎ ")}`));
|
|
146
|
+
const dequeueHint = theme.fg("dim", "↳ Alt+Up to edit all queued messages");
|
|
147
|
+
// The custom viewport matches Pi's interactive queue presentation while the
|
|
148
|
+
// comparison shell remains byte-for-byte compatible with its pinned fixture.
|
|
149
|
+
return ["", ...messages, dequeueHint].join("\n");
|
|
141
150
|
}
|
|
142
151
|
function compactHeaderText() {
|
|
143
152
|
const theme = piTheme();
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { AssistantMessageComponent } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { OwnedUiTranscriptBlock } from "../../../contracts/owned-ui/index.js";
|
|
3
|
+
import { type PiShellSubmittedPromptComposer } from "./submitted-prompt-adapter.js";
|
|
4
|
+
export type { PiShellSubmittedPromptComposer } from "./submitted-prompt-adapter.js";
|
|
3
5
|
import { type PiShellComponentPort, type PiShellExtensionRendererResolver, type PiShellTranscriptComponentPort } from "./shell-shared-facade.js";
|
|
4
6
|
export interface PiShellSessionInfoPresentation {
|
|
5
7
|
readonly sessionName?: string;
|
|
@@ -35,7 +37,7 @@ export declare function renderPiShellStatusText(message: string, width: number):
|
|
|
35
37
|
export declare function createPiShellSessionInfo(presentation: PiShellSessionInfoPresentation): PiShellComponentPort;
|
|
36
38
|
export declare function createPiShellChangelog(markdown: string): PiShellComponentPort;
|
|
37
39
|
export declare function createPiShellHotkeys(): PiShellComponentPort;
|
|
38
|
-
export declare function createPiShellTranscriptComponent(initial: OwnedUiTranscriptBlock, cwd: string, extensions?: PiShellExtensionRendererResolver): PiShellTranscriptComponentPort;
|
|
40
|
+
export declare function createPiShellTranscriptComponent(initial: OwnedUiTranscriptBlock, cwd: string, extensions?: PiShellExtensionRendererResolver, submittedPrompt?: PiShellSubmittedPromptComposer): PiShellTranscriptComponentPort;
|
|
39
41
|
export declare function renderPiShellTranscriptBlock(block: OwnedUiTranscriptBlock, width: number, cwd: string): readonly string[];
|
|
40
42
|
/**
|
|
41
43
|
* Pinned Pi's CLI prints startup diagnostics with `reportDiagnostics` before
|
|
@@ -54,4 +56,3 @@ export declare function renderPiShellStartupDiagnostic(diagnostic: {
|
|
|
54
56
|
export declare function renderPiShellPackageUpdateNotice(packages: readonly string[], width: number): readonly string[];
|
|
55
57
|
type PiAssistantMessage = NonNullable<ConstructorParameters<typeof AssistantMessageComponent>[0]>;
|
|
56
58
|
export declare function validatedAssistantMessage(block: OwnedUiTranscriptBlock): PiAssistantMessage;
|
|
57
|
-
export {};
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { AssistantMessageComponent, BashExecutionComponent, CompactionSummaryMessageComponent, CustomMessageComponent, DynamicBorder, getMarkdownTheme, parseSkillBlock, ToolExecutionComponent, UserMessageComponent, } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { SkillInvocationMessageComponent
|
|
2
|
+
import { SkillInvocationMessageComponent } from "./upstream/components/skill-invocation-message.js";
|
|
3
3
|
import { Container, Markdown, Spacer, Text, } from "#pi-tui";
|
|
4
4
|
import { PRODUCT_TEXT } from "../../../product-identity.js";
|
|
5
|
-
import {
|
|
5
|
+
import { createPiSubmittedPromptComponent, } from "./submitted-prompt-adapter.js";
|
|
6
|
+
import { KeybindingsManager } from "./upstream/adjacent/core/keybindings.js";
|
|
6
7
|
import { PINNED_PI_LAYOUT, piTheme, } from "./theme.js";
|
|
7
8
|
import { componentPort, createTuiFacade, ensureTheme, formatSessionTokens, isRecord, } from "./shell-shared-facade.js";
|
|
8
9
|
export function renderPiShellStatusText(message, width) {
|
|
@@ -106,11 +107,11 @@ export function createPiShellHotkeys() {
|
|
|
106
107
|
container.addChild(new DynamicBorder());
|
|
107
108
|
return componentPort(container);
|
|
108
109
|
}
|
|
109
|
-
export function createPiShellTranscriptComponent(initial, cwd, extensions) {
|
|
110
|
+
export function createPiShellTranscriptComponent(initial, cwd, extensions, submittedPrompt) {
|
|
110
111
|
ensureTheme();
|
|
111
112
|
let block = initial;
|
|
112
113
|
let expanded = false;
|
|
113
|
-
let component = transcriptComponent(block, cwd, expanded, extensions);
|
|
114
|
+
let component = transcriptComponent(block, cwd, expanded, extensions, submittedPrompt);
|
|
114
115
|
return {
|
|
115
116
|
get id() { return block.id; },
|
|
116
117
|
get revision() { return block.revision; },
|
|
@@ -122,7 +123,7 @@ export function createPiShellTranscriptComponent(initial, cwd, extensions) {
|
|
|
122
123
|
const previous = block;
|
|
123
124
|
block = next;
|
|
124
125
|
if (!updateTranscriptComponent(component, previous, next, expanded)) {
|
|
125
|
-
component = transcriptComponent(block, cwd, expanded, extensions);
|
|
126
|
+
component = transcriptComponent(block, cwd, expanded, extensions, submittedPrompt);
|
|
126
127
|
}
|
|
127
128
|
},
|
|
128
129
|
setExpanded(next) {
|
|
@@ -133,7 +134,7 @@ export function createPiShellTranscriptComponent(initial, cwd, extensions) {
|
|
|
133
134
|
component.setExpanded(expanded);
|
|
134
135
|
}
|
|
135
136
|
else {
|
|
136
|
-
component = transcriptComponent(block, cwd, expanded, extensions);
|
|
137
|
+
component = transcriptComponent(block, cwd, expanded, extensions, submittedPrompt);
|
|
137
138
|
}
|
|
138
139
|
},
|
|
139
140
|
};
|
|
@@ -175,12 +176,12 @@ export function renderPiShellPackageUpdateNotice(packages, width) {
|
|
|
175
176
|
container.addChild(new DynamicBorder(text => theme.fg("warning", text)));
|
|
176
177
|
return container.render(width);
|
|
177
178
|
}
|
|
178
|
-
function transcriptComponent(block, cwd, expanded, extensions) {
|
|
179
|
+
function transcriptComponent(block, cwd, expanded, extensions, submittedPrompt) {
|
|
179
180
|
switch (block.kind) {
|
|
180
181
|
case "user": {
|
|
181
182
|
const skill = parseSkillBlock(block.text);
|
|
182
183
|
if (!skill)
|
|
183
|
-
return new UserMessageComponent(block.text);
|
|
184
|
+
return submittedPrompt ? createPiSubmittedPromptComponent(block, submittedPrompt) : new UserMessageComponent(block.text);
|
|
184
185
|
const invocation = new SkillInvocationMessageComponent(skill, getMarkdownTheme());
|
|
185
186
|
invocation.setExpanded(expanded);
|
|
186
187
|
if (!skill.userMessage)
|
|
@@ -1,13 +1,34 @@
|
|
|
1
|
-
import { type AutocompleteProvider, type Component, type TUI } from "#pi-tui";
|
|
1
|
+
import { visibleWidth, type AutocompleteProvider, type Component, type TUI } from "#pi-tui";
|
|
2
2
|
import type { OwnedUiSessionViewModel, OwnedUiThinkingLevel, OwnedUiTranscriptBlock } from "../../../contracts/owned-ui/index.js";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
import type { PresentationComponentPort } from "../../../contracts/presentation/index.js";
|
|
4
|
+
/** Terminal-cell width authority used by Pi-rendered component rows. */
|
|
5
|
+
export declare const piShellVisibleWidth: typeof visibleWidth;
|
|
6
|
+
export interface PiShellComponentPort extends PresentationComponentPort {
|
|
7
|
+
}
|
|
8
|
+
export type PiShellClipboardContent = {
|
|
9
|
+
readonly kind: "text";
|
|
10
|
+
readonly text: string;
|
|
11
|
+
} | {
|
|
12
|
+
readonly kind: "image";
|
|
13
|
+
readonly data: string;
|
|
14
|
+
readonly mimeType: string;
|
|
15
|
+
};
|
|
16
|
+
export interface PiShellEditorTextRange {
|
|
17
|
+
readonly start: number;
|
|
18
|
+
readonly end: number;
|
|
19
|
+
}
|
|
20
|
+
export interface PiShellEditorPointerEvent {
|
|
21
|
+
readonly kind: "press" | "motion" | "release";
|
|
22
|
+
readonly button: number;
|
|
23
|
+
readonly column: number;
|
|
24
|
+
/** One-based row relative to the top of the editor component. */
|
|
25
|
+
readonly row: number;
|
|
9
26
|
}
|
|
10
27
|
export interface PiShellEditorPort extends PiShellComponentPort {
|
|
28
|
+
/** Restores this editor's profile after another Pi component changed the global manager. */
|
|
29
|
+
activateKeybindings(): void;
|
|
30
|
+
/** Uses Pi's terminal decoder rather than assuming one terminal escape spelling. */
|
|
31
|
+
matchesTerminalKey(data: string, key: "home" | "end" | "ctrl+v"): boolean;
|
|
11
32
|
getText(): string;
|
|
12
33
|
setText(text: string): void;
|
|
13
34
|
insertText(text: string): void;
|
|
@@ -18,6 +39,10 @@ export interface PiShellEditorPort extends PiShellComponentPort {
|
|
|
18
39
|
setAutocompleteCommands(commands: readonly PiShellAutocompleteCommand[]): void;
|
|
19
40
|
addAutocompleteProvider(factory: unknown): void;
|
|
20
41
|
setThinkingLevel(level: OwnedUiThinkingLevel): void;
|
|
42
|
+
hasSelection(): boolean;
|
|
43
|
+
ownsPointer(): boolean;
|
|
44
|
+
handlePointer(event: PiShellEditorPointerEvent): boolean;
|
|
45
|
+
pasteClipboard(): boolean;
|
|
21
46
|
}
|
|
22
47
|
export interface PiShellAutocompleteCommand {
|
|
23
48
|
readonly name: string;
|
|
@@ -69,6 +94,8 @@ export interface PiShellHeaderOptions {
|
|
|
69
94
|
readonly resources?: readonly PiShellResourceEntry[];
|
|
70
95
|
}
|
|
71
96
|
export interface PiShellEditorOptions {
|
|
97
|
+
/** Bare A1 adds ergonomic aliases while comparison profiles retain Pi defaults. */
|
|
98
|
+
readonly keybindingProfile?: "pi" | "a1";
|
|
72
99
|
readonly getColumns: () => number;
|
|
73
100
|
readonly getRows: () => number;
|
|
74
101
|
readonly requestRender: () => void;
|
|
@@ -89,6 +116,13 @@ export interface PiShellEditorOptions {
|
|
|
89
116
|
readonly onMessageCopy?: (() => void) | undefined;
|
|
90
117
|
readonly onFollowUp?: (() => void) | undefined;
|
|
91
118
|
readonly onDequeue?: (() => void) | undefined;
|
|
119
|
+
readonly onCopyText?: (text: string) => void;
|
|
120
|
+
readonly readClipboardContent?: () => Promise<PiShellClipboardContent | null>;
|
|
121
|
+
readonly transformPastedContent?: (content: PiShellClipboardContent) => string;
|
|
122
|
+
readonly editorAtomicRanges?: (line: string) => readonly PiShellEditorTextRange[];
|
|
123
|
+
readonly expandCopiedEditorText?: (text: string) => string;
|
|
124
|
+
readonly paintEditorSelection?: (line: string, from: number, to: number, atomic: boolean) => string;
|
|
125
|
+
readonly decorateEditorRow?: (row: string, width: number) => string;
|
|
92
126
|
readonly cwd?: string;
|
|
93
127
|
readonly agentDir?: string;
|
|
94
128
|
readonly autocompleteCommands?: readonly PiShellAutocompleteCommand[];
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { setKeybindings, } from "#pi-tui";
|
|
1
|
+
import { setKeybindings, visibleWidth, } from "#pi-tui";
|
|
2
2
|
import { KeybindingsManager, } from "./upstream/adjacent/core/keybindings.js";
|
|
3
3
|
import { ensurePiTheme, } from "./theme.js";
|
|
4
|
+
/** Terminal-cell width authority used by Pi-rendered component rows. */
|
|
5
|
+
export const piShellVisibleWidth = visibleWidth;
|
|
4
6
|
// Pinned from packages/coding-agent/src/core/slash-commands.ts at 914cf14.
|
|
5
7
|
export function createTuiFacade(options) {
|
|
6
8
|
const children = [];
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type Component } from "#pi-tui";
|
|
2
|
+
import type { OwnedUiTranscriptBlock } from "../../../contracts/owned-ui/index.js";
|
|
3
|
+
export interface PiShellSubmittedPromptComposer {
|
|
4
|
+
readonly layout: (width: number, source?: Date | number | null) => {
|
|
5
|
+
readonly contentWidth: number;
|
|
6
|
+
};
|
|
7
|
+
readonly compose: (contentRows: readonly string[], width: number, source?: Date | number | null, style?: {
|
|
8
|
+
readonly prefix?: (text: string) => string;
|
|
9
|
+
readonly timestamp?: (text: string) => string;
|
|
10
|
+
}) => readonly string[];
|
|
11
|
+
}
|
|
12
|
+
/** Pi owns Markdown and theme adaptation; the injected composer owns neutral row geometry. */
|
|
13
|
+
export declare function createPiSubmittedPromptComponent(block: OwnedUiTranscriptBlock, composer: PiShellSubmittedPromptComposer): Component;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { getMarkdownTheme } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { Box, Markdown } from "#pi-tui";
|
|
3
|
+
import { piTheme } from "./theme.js";
|
|
4
|
+
/** Pi owns Markdown and theme adaptation; the injected composer owns neutral row geometry. */
|
|
5
|
+
export function createPiSubmittedPromptComponent(block, composer) {
|
|
6
|
+
const timestamp = numericTimestamp(block);
|
|
7
|
+
const markdown = new Markdown(block.text, 0, 0, getMarkdownTheme(), { color: content => piTheme().fg("userMessageText", content) }, { preserveOrderedListMarkers: true, preserveBackslashEscapes: true });
|
|
8
|
+
const content = {
|
|
9
|
+
render(width) {
|
|
10
|
+
const layout = composer.layout(width, timestamp);
|
|
11
|
+
return [...composer.compose(markdown.render(layout.contentWidth), width, timestamp, {
|
|
12
|
+
prefix: text => piTheme().fg("muted", text),
|
|
13
|
+
timestamp: text => piTheme().fg("dim", text),
|
|
14
|
+
})];
|
|
15
|
+
},
|
|
16
|
+
invalidate: () => markdown.invalidate(),
|
|
17
|
+
};
|
|
18
|
+
const box = new Box(0, 0, line => piTheme().bg("userMessageBg", line));
|
|
19
|
+
box.addChild(content);
|
|
20
|
+
return box;
|
|
21
|
+
}
|
|
22
|
+
function numericTimestamp(block) {
|
|
23
|
+
if (typeof block.payload !== "object" || block.payload === null)
|
|
24
|
+
return null;
|
|
25
|
+
const value = block.payload.timestamp;
|
|
26
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
27
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Source-synchronized from Pi 0.84.2 packages/coding-agent/src/core/keybindings.ts. */
|
|
2
|
-
import { type Keybinding, type KeybindingsConfig, type KeyId, KeybindingsManager as TuiKeybindingsManager } from "#pi-tui";
|
|
2
|
+
import { type Keybinding, type KeybindingDefinitions, type KeybindingsConfig, type KeyId, KeybindingsManager as TuiKeybindingsManager } from "#pi-tui";
|
|
3
3
|
export interface AppKeybindings {
|
|
4
4
|
"app.interrupt": true;
|
|
5
5
|
"app.clear": true;
|
|
@@ -43,6 +43,12 @@ export interface AppKeybindings {
|
|
|
43
43
|
"app.tree.filter.all": true;
|
|
44
44
|
"app.tree.filter.cycleForward": true;
|
|
45
45
|
"app.tree.filter.cycleBackward": true;
|
|
46
|
+
"owned.editor.selectAll": true;
|
|
47
|
+
"owned.editor.cut": true;
|
|
48
|
+
"owned.editor.paste": true;
|
|
49
|
+
"owned.editor.redo": true;
|
|
50
|
+
"owned.editor.extendLeft": true;
|
|
51
|
+
"owned.editor.extendRight": true;
|
|
46
52
|
}
|
|
47
53
|
export type AppKeybinding = keyof AppKeybindings;
|
|
48
54
|
declare module "#pi-tui" {
|
|
@@ -413,8 +419,10 @@ export declare function migrateKeybindingsConfig(rawConfig: Record<string, unkno
|
|
|
413
419
|
};
|
|
414
420
|
export declare class KeybindingsManager extends TuiKeybindingsManager {
|
|
415
421
|
private configPath;
|
|
416
|
-
constructor(userBindings?: KeybindingsConfig, configPath?: string);
|
|
422
|
+
constructor(userBindings?: KeybindingsConfig, configPath?: string, definitions?: KeybindingDefinitions);
|
|
417
423
|
static create(agentDir?: string): KeybindingsManager;
|
|
424
|
+
static createForOwnedInput(agentDir?: string): KeybindingsManager;
|
|
425
|
+
private static createWithDefinitions;
|
|
418
426
|
reload(): void;
|
|
419
427
|
getEffectiveConfig(): KeybindingsConfig;
|
|
420
428
|
private static loadFromFile;
|
|
@@ -147,6 +147,33 @@ export const KEYBINDINGS = {
|
|
|
147
147
|
description: "Tree filter: cycle backward",
|
|
148
148
|
},
|
|
149
149
|
};
|
|
150
|
+
/** Pi actions with bare-A1 aliases; the comparison profile keeps KEYBINDINGS unchanged. */
|
|
151
|
+
const OWNED_INPUT_KEYBINDINGS = {
|
|
152
|
+
...KEYBINDINGS,
|
|
153
|
+
"tui.editor.deleteWordBackward": {
|
|
154
|
+
...KEYBINDINGS["tui.editor.deleteWordBackward"],
|
|
155
|
+
defaultKeys: [...KEYBINDINGS["tui.editor.deleteWordBackward"].defaultKeys, "ctrl+backspace"],
|
|
156
|
+
},
|
|
157
|
+
"tui.editor.deleteWordForward": {
|
|
158
|
+
...KEYBINDINGS["tui.editor.deleteWordForward"],
|
|
159
|
+
defaultKeys: [...KEYBINDINGS["tui.editor.deleteWordForward"].defaultKeys, "ctrl+delete"],
|
|
160
|
+
},
|
|
161
|
+
"tui.editor.undo": {
|
|
162
|
+
...KEYBINDINGS["tui.editor.undo"],
|
|
163
|
+
defaultKeys: [KEYBINDINGS["tui.editor.undo"].defaultKeys, "ctrl+z"],
|
|
164
|
+
},
|
|
165
|
+
// Ctrl+Z edits the prompt in bare A1 instead of suspending the process.
|
|
166
|
+
"app.suspend": { ...KEYBINDINGS["app.suspend"], defaultKeys: [] },
|
|
167
|
+
// Owned prompt-selection actions are intercepted before vanilla Pi editor
|
|
168
|
+
// actions. Keeping them in the keybinding manager makes the UX declarative,
|
|
169
|
+
// configurable, and independent of terminal escape-sequence spellings.
|
|
170
|
+
"owned.editor.selectAll": { defaultKeys: "ctrl+a", description: "Select all prompt text" },
|
|
171
|
+
"owned.editor.cut": { defaultKeys: "ctrl+x", description: "Cut selected prompt text" },
|
|
172
|
+
"owned.editor.paste": { defaultKeys: "ctrl+v", description: "Paste clipboard text" },
|
|
173
|
+
"owned.editor.redo": { defaultKeys: "ctrl+y", description: "Redo prompt edit" },
|
|
174
|
+
"owned.editor.extendLeft": { defaultKeys: "shift+left", description: "Extend prompt selection left" },
|
|
175
|
+
"owned.editor.extendRight": { defaultKeys: "shift+right", description: "Extend prompt selection right" },
|
|
176
|
+
};
|
|
150
177
|
const KEYBINDING_NAME_MIGRATIONS = {
|
|
151
178
|
cursorUp: "tui.editor.cursorUp",
|
|
152
179
|
cursorDown: "tui.editor.cursorDown",
|
|
@@ -270,14 +297,20 @@ function loadRawConfig(path) {
|
|
|
270
297
|
}
|
|
271
298
|
export class KeybindingsManager extends TuiKeybindingsManager {
|
|
272
299
|
configPath;
|
|
273
|
-
constructor(userBindings = {}, configPath) {
|
|
274
|
-
super(
|
|
300
|
+
constructor(userBindings = {}, configPath, definitions = KEYBINDINGS) {
|
|
301
|
+
super(definitions, userBindings);
|
|
275
302
|
this.configPath = configPath;
|
|
276
303
|
}
|
|
277
304
|
static create(agentDir = getAgentDir()) {
|
|
305
|
+
return KeybindingsManager.createWithDefinitions(agentDir, KEYBINDINGS);
|
|
306
|
+
}
|
|
307
|
+
static createForOwnedInput(agentDir = getAgentDir()) {
|
|
308
|
+
return KeybindingsManager.createWithDefinitions(agentDir, OWNED_INPUT_KEYBINDINGS);
|
|
309
|
+
}
|
|
310
|
+
static createWithDefinitions(agentDir, definitions) {
|
|
278
311
|
const configPath = join(agentDir, "keybindings.json");
|
|
279
312
|
const userBindings = KeybindingsManager.loadFromFile(configPath);
|
|
280
|
-
return new KeybindingsManager(userBindings, configPath);
|
|
313
|
+
return new KeybindingsManager(userBindings, configPath, definitions);
|
|
281
314
|
}
|
|
282
315
|
reload() {
|
|
283
316
|
if (!this.configPath)
|
|
@@ -16,7 +16,10 @@ const execFileAsync = promisify(execFile);
|
|
|
16
16
|
* that a streaming burst never holds input, large enough that an ordinary turn is one
|
|
17
17
|
* batch.
|
|
18
18
|
*/
|
|
19
|
-
|
|
19
|
+
// Deliver at most one engine event per event-loop turn. Transcript updates can
|
|
20
|
+
// be expensive in long sessions; a larger synchronous batch starves terminal
|
|
21
|
+
// input and makes an in-progress mouse selection appear frozen.
|
|
22
|
+
const EVENT_DELIVERY_BATCH = 1;
|
|
20
23
|
const TOOL_UPDATE_COALESCE_MS = 50;
|
|
21
24
|
const DEFAULT_SURFACE = {
|
|
22
25
|
columns: 100,
|
|
@@ -1317,7 +1320,11 @@ export class PiEngineAdapter {
|
|
|
1317
1320
|
case "retry":
|
|
1318
1321
|
case "compact": {
|
|
1319
1322
|
const result = await this.#sessionCommands?.execute(command.type === "prompt" || command.type === "steer" || command.type === "follow-up"
|
|
1320
|
-
? {
|
|
1323
|
+
? {
|
|
1324
|
+
type: command.type,
|
|
1325
|
+
text: command.text,
|
|
1326
|
+
...(command.images === undefined ? {} : { images: [...command.images] }),
|
|
1327
|
+
}
|
|
1321
1328
|
: { type: command.type });
|
|
1322
1329
|
if (!result || result.outcome === "rejected" || result.outcome === "failed") {
|
|
1323
1330
|
throw new Error(command.type === "retry" ? "no previous prompt is available to retry" : `Pi session command failed: ${command.type}`);
|
|
@@ -1484,6 +1491,7 @@ export class PiEngineAdapter {
|
|
|
1484
1491
|
switch (event.type) {
|
|
1485
1492
|
case "agent_start":
|
|
1486
1493
|
this.#agentRunActive = true;
|
|
1494
|
+
this.#emitEvent({ type: "agent-run-started" });
|
|
1487
1495
|
this.#enterWorkState("working", "Working...");
|
|
1488
1496
|
return;
|
|
1489
1497
|
case "message_start":
|
|
@@ -1505,6 +1513,12 @@ export class PiEngineAdapter {
|
|
|
1505
1513
|
}
|
|
1506
1514
|
case "message_end":
|
|
1507
1515
|
this.#upsertMessageBlock(event.message, "finalized");
|
|
1516
|
+
// Preserve the same semantic boundary v2 counted. Transcript block
|
|
1517
|
+
// finalization is intentionally not a substitute: rebuilds, retries,
|
|
1518
|
+
// thinking parts, and tool rows can all finalize independently.
|
|
1519
|
+
if (isRecord(event.message) && event.message.role === "assistant") {
|
|
1520
|
+
this.#emitEvent({ type: "assistant-message-completed" });
|
|
1521
|
+
}
|
|
1508
1522
|
return;
|
|
1509
1523
|
case "turn_end":
|
|
1510
1524
|
this.#upsertMessageBlock(event.message, "finalized");
|
|
@@ -1688,7 +1702,11 @@ export class PiEngineAdapter {
|
|
|
1688
1702
|
revision: this.#nextBlockRevision(baseId),
|
|
1689
1703
|
title: "User",
|
|
1690
1704
|
text: textFromContent(message.content),
|
|
1691
|
-
payload: {
|
|
1705
|
+
payload: {
|
|
1706
|
+
role: "user",
|
|
1707
|
+
imageCount: contentImageCount(message.content),
|
|
1708
|
+
timestamp: typeof message.timestamp === "number" && Number.isFinite(message.timestamp) ? message.timestamp : null,
|
|
1709
|
+
},
|
|
1692
1710
|
}];
|
|
1693
1711
|
}
|
|
1694
1712
|
if (message.role === "bashExecution") {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentSession, AgentSessionEvent } from "@earendil-works/pi-coding-agent";
|
|
1
|
+
import type { AgentSession, AgentSessionEvent, PromptOptions } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { type AgentCommandOutcome, type AgentEvent } from "../../../contracts/agent-engine/index.js";
|
|
3
3
|
export interface PiDocumentedSessionCommands {
|
|
4
4
|
readonly isStreaming: AgentSession["isStreaming"];
|
|
@@ -18,6 +18,7 @@ export interface PiDocumentedSessionCommands {
|
|
|
18
18
|
export type PiSessionCommand = {
|
|
19
19
|
readonly type: "prompt" | "steer" | "follow-up";
|
|
20
20
|
readonly text: string;
|
|
21
|
+
readonly images?: NonNullable<PromptOptions["images"]>;
|
|
21
22
|
} | {
|
|
22
23
|
readonly type: "abort" | "retry" | "compact";
|
|
23
24
|
} | {
|
|
@@ -9,15 +9,29 @@ export class PiSessionCommandIntegration {
|
|
|
9
9
|
switch (command.type) {
|
|
10
10
|
case "prompt":
|
|
11
11
|
this.#lastPrompt = command.text;
|
|
12
|
-
await this.session.prompt(command.text,
|
|
12
|
+
await this.session.prompt(command.text, command.images === undefined
|
|
13
|
+
? this.session.isStreaming ? { streamingBehavior: "followUp" } : undefined
|
|
14
|
+
: {
|
|
15
|
+
...(this.session.isStreaming ? { streamingBehavior: "followUp" } : {}),
|
|
16
|
+
images: [...command.images],
|
|
17
|
+
});
|
|
13
18
|
return { outcome: "completed" };
|
|
14
19
|
case "steer":
|
|
15
20
|
this.#lastPrompt = command.text;
|
|
16
|
-
|
|
21
|
+
// Match interactive Pi: prompt() owns template/extension expansion and
|
|
22
|
+
// turns the accepted steering message into the visible user row while
|
|
23
|
+
// later messages remain in the pending queue.
|
|
24
|
+
await this.session.prompt(command.text, {
|
|
25
|
+
streamingBehavior: "steer",
|
|
26
|
+
...(command.images === undefined ? {} : { images: [...command.images] }),
|
|
27
|
+
});
|
|
17
28
|
return { outcome: "completed" };
|
|
18
29
|
case "follow-up":
|
|
19
30
|
this.#lastPrompt = command.text;
|
|
20
|
-
await this.session.
|
|
31
|
+
await this.session.prompt(command.text, {
|
|
32
|
+
streamingBehavior: "followUp",
|
|
33
|
+
...(command.images === undefined ? {} : { images: [...command.images] }),
|
|
34
|
+
});
|
|
21
35
|
return { outcome: "completed" };
|
|
22
36
|
case "abort":
|
|
23
37
|
if (this.session.isRetrying)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { PiShellClipboardContent, PiShellEditorTextRange } from "../components/index.js";
|
|
2
|
+
export interface PromptImageAttachment {
|
|
3
|
+
readonly type: "image";
|
|
4
|
+
readonly data: string;
|
|
5
|
+
readonly mimeType: string;
|
|
6
|
+
}
|
|
7
|
+
export interface PreparedPrompt {
|
|
8
|
+
readonly text: string;
|
|
9
|
+
readonly images: readonly PromptImageAttachment[];
|
|
10
|
+
}
|
|
11
|
+
/** Owns semantic clipboard records while the prompt displays compact chips. */
|
|
12
|
+
export declare class PromptChipStore {
|
|
13
|
+
#private;
|
|
14
|
+
transformPastedContent(content: PiShellClipboardContent): string;
|
|
15
|
+
atomicRanges(line: string): readonly PiShellEditorTextRange[];
|
|
16
|
+
hyperlinkRanges(text: string): readonly {
|
|
17
|
+
start: number;
|
|
18
|
+
end: number;
|
|
19
|
+
target: string;
|
|
20
|
+
}[];
|
|
21
|
+
expandCopiedText(text: string): string;
|
|
22
|
+
prepareSubmission(text: string): PreparedPrompt;
|
|
23
|
+
}
|