@selesai/code 0.8.2 → 0.8.3

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.
Files changed (42) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +4 -2
  3. package/dist/cli/args.d.ts +2 -0
  4. package/dist/cli/args.js +18 -0
  5. package/dist/cli/config-selector.js +2 -2
  6. package/dist/cli/startup-ui.d.ts +1 -1
  7. package/dist/cli/startup-ui.js +2 -2
  8. package/dist/core/keybindings.d.ts +44 -4
  9. package/dist/core/llama/provider.js +22 -7
  10. package/dist/core/model-resolver.js +2 -0
  11. package/dist/core/model-runtime.d.ts +4 -2
  12. package/dist/core/model-runtime.js +23 -2
  13. package/dist/core/remote-catalog-provider.js +72 -67
  14. package/dist/core/settings-manager.d.ts +8 -0
  15. package/dist/core/settings-manager.js +18 -0
  16. package/dist/extensions/pi-powerline-footer/CHANGELOG.md +2 -1
  17. package/dist/extensions/pi-powerline-footer/index.ts +3 -272
  18. package/dist/extensions/pi-powerline-footer/package.json +4 -5
  19. package/dist/extensions/pi-powerline-footer/powerline-config.ts +0 -6
  20. package/dist/extensions/pi-powerline-footer/tests/fixed-editor-border.test.ts +2 -2
  21. package/dist/extensions/pi-powerline-footer/tests/guide.test.ts +1 -1
  22. package/dist/extensions/pi-powerline-footer/tsconfig.json +1 -1
  23. package/dist/index.d.ts +1 -1
  24. package/dist/main.js +1 -0
  25. package/dist/modes/interactive/components/settings-selector.d.ts +6 -2
  26. package/dist/modes/interactive/components/settings-selector.js +20 -0
  27. package/dist/modes/interactive/interactive-mode.d.ts +28 -0
  28. package/dist/modes/interactive/interactive-mode.js +214 -28
  29. package/dist/modes/interactive/theme/dark.json +1 -0
  30. package/dist/modes/interactive/theme/light.json +1 -0
  31. package/dist/modes/interactive/theme/theme-controller.d.ts +3 -0
  32. package/dist/modes/interactive/theme/theme-controller.js +10 -1
  33. package/dist/modes/interactive/theme/theme.d.ts +2 -2
  34. package/dist/modes/interactive/theme/theme.js +24 -6
  35. package/docs/keybindings.md +35 -7
  36. package/docs/settings.md +16 -14
  37. package/docs/usage.md +5 -0
  38. package/package.json +5 -5
  39. package/dist/extensions/pi-powerline-footer/fixed-editor/cluster.ts +0 -113
  40. package/dist/extensions/pi-powerline-footer/fixed-editor/terminal-split.ts +0 -1080
  41. package/dist/extensions/pi-powerline-footer/tests/fixed-editor.test.ts +0 -1452
  42. package/dist/extensions/pi-powerline-footer/tests/jump-shortcuts.test.ts +0 -49
@@ -70,8 +70,6 @@ import {
70
70
  } from "./working-vibes.ts";
71
71
  import { setupTpsTracker } from "./tps.ts";
72
72
  import { readAsyncSubagentUsage } from "./session-usage.ts";
73
- import { renderFixedEditorCluster } from "./fixed-editor/cluster.ts";
74
- import { emergencyTerminalModeReset, TerminalSplitCompositor } from "./fixed-editor/terminal-split.ts";
75
73
  import { PowerlineQueueStore, currentQueueContext, formatIdeaIssuePrompt, formatQueueDeliveryText, parseCompactQueuedPrompt, parseSigilIdeaCapture, parseTargetPrefix, targetForIdea } from "./queue/store.ts";
76
74
  import type { PowerlineQueueItem, QueueContext, QueueIntent, QueueTarget } from "./queue/types.ts";
77
75
 
@@ -82,8 +80,6 @@ import type { PowerlineQueueItem, QueueContext, QueueIntent, QueueTarget } from
82
80
  let config: PowerlineConfig = {
83
81
  preset: "default",
84
82
  customItems: [],
85
- mouseScroll: true,
86
- fixedEditor: true,
87
83
  fixedEditorPromptGlyph: "",
88
84
  disabledSegments: [],
89
85
  invalidDisabledSegments: [],
@@ -109,38 +105,18 @@ export interface PowerlineShortcuts {
109
105
  cutEditor: ShortcutBinding;
110
106
  ideaCapture: ShortcutBinding;
111
107
  queueOpen: ShortcutBinding;
112
- jumpPreviousUserMessage: ShortcutBinding;
113
- jumpNextUserMessage: ShortcutBinding;
114
- jumpPreviousLlmMessage: ShortcutBinding;
115
- jumpNextLlmMessage: ShortcutBinding;
116
- jumpChatBottom: ShortcutBinding;
117
- scrollChatUp: ShortcutBinding;
118
- scrollChatDown: ShortcutBinding;
119
108
  editorStart: ShortcutBinding;
120
109
  editorEnd: ShortcutBinding;
121
110
  }
122
111
 
123
112
  type PowerlineShortcutKey = keyof PowerlineShortcuts;
124
- type ChatJumpShortcutKey = Extract<PowerlineShortcutKey,
125
- | "jumpPreviousUserMessage"
126
- | "jumpNextUserMessage"
127
- | "jumpPreviousLlmMessage"
128
- | "jumpNextLlmMessage"
129
- | "jumpChatBottom"
130
- >;
131
- type ChatJumpRole = "user" | "assistant";
132
- type ChatJumpDirection = "previous" | "next";
133
- type ChatJumpShortcutAction =
134
- | { kind: "message"; role: ChatJumpRole; direction: ChatJumpDirection }
135
- | { kind: "bottom" };
136
113
  type PowerlineShortcutAction =
137
114
  | { kind: "stashHistory" }
138
115
  | { kind: "copyEditor" }
139
116
  | { kind: "cutEditor" }
140
117
  | { kind: "ideaCapture" }
141
118
  | { kind: "queueOpen" }
142
- | { kind: "bashMode" }
143
- | { kind: "chat"; action: ChatJumpShortcutAction };
119
+ | { kind: "bashMode" };
144
120
  const STASH_HISTORY_LIMIT = 12;
145
121
  const PROJECT_PROMPT_HISTORY_LIMIT = 50;
146
122
  const STASH_PREVIEW_WIDTH = 72;
@@ -150,13 +126,6 @@ const DEFAULT_SHORTCUTS: PowerlineShortcuts = {
150
126
  cutEditor: "ctrl+alt+x",
151
127
  ideaCapture: null,
152
128
  queueOpen: "ctrl+alt+q",
153
- jumpPreviousUserMessage: "ctrl+shift+u",
154
- jumpNextUserMessage: "ctrl+shift+i",
155
- jumpPreviousLlmMessage: "ctrl+alt+,",
156
- jumpNextLlmMessage: "ctrl+alt+.",
157
- jumpChatBottom: "ctrl+shift+g",
158
- scrollChatUp: "super+up",
159
- scrollChatDown: "super+down",
160
129
  editorStart: "super+shift+up",
161
130
  editorEnd: "super+shift+down",
162
131
  };
@@ -165,38 +134,7 @@ const DEFAULT_BASH_MODE_SETTINGS = {
165
134
  transcriptMaxLines: 2000,
166
135
  transcriptMaxBytes: 512 * 1024,
167
136
  } as const satisfies BashModeSettings;
168
- const SHORTCUT_KEYS: PowerlineShortcutKey[] = ["stashHistory", "copyEditor", "cutEditor", "ideaCapture", "queueOpen", "jumpPreviousUserMessage", "jumpNextUserMessage", "jumpPreviousLlmMessage", "jumpNextLlmMessage", "jumpChatBottom", "scrollChatUp", "scrollChatDown", "editorStart", "editorEnd"];
169
- const CHAT_JUMP_SHORTCUTS: Array<{
170
- shortcutKey: ChatJumpShortcutKey;
171
- description: string;
172
- action: ChatJumpShortcutAction;
173
- }> = [
174
- {
175
- shortcutKey: "jumpPreviousUserMessage",
176
- description: "Jump to previous user message",
177
- action: { kind: "message", role: "user", direction: "previous" },
178
- },
179
- {
180
- shortcutKey: "jumpNextUserMessage",
181
- description: "Jump to next user message",
182
- action: { kind: "message", role: "user", direction: "next" },
183
- },
184
- {
185
- shortcutKey: "jumpPreviousLlmMessage",
186
- description: "Jump to previous LLM message",
187
- action: { kind: "message", role: "assistant", direction: "previous" },
188
- },
189
- {
190
- shortcutKey: "jumpNextLlmMessage",
191
- description: "Jump to next LLM message",
192
- action: { kind: "message", role: "assistant", direction: "next" },
193
- },
194
- {
195
- shortcutKey: "jumpChatBottom",
196
- description: "Jump chat to bottom",
197
- action: { kind: "bottom" },
198
- },
199
- ];
137
+ const SHORTCUT_KEYS: PowerlineShortcutKey[] = ["stashHistory", "copyEditor", "cutEditor", "ideaCapture", "queueOpen", "editorStart", "editorEnd"];
200
138
  const APP_RESERVED_SHORTCUTS = [
201
139
  "escape",
202
140
  "ctrl+c",
@@ -1070,11 +1008,6 @@ export default function powerlineFooter(pi: ExtensionAPI) {
1070
1008
  let bashTranscript = new BashTranscriptStore(bashModeSettings);
1071
1009
  let bashCompletionEngine = new BashCompletionEngine();
1072
1010
  let shellSession: ManagedShellSession | null = null;
1073
- let fixedEditorCompositor: TerminalSplitCompositor | null = null;
1074
- let fixedStatusContainer: any = null;
1075
- let fixedEditorContainer: any = null;
1076
- let fixedWidgetContainerAbove: any = null;
1077
- let fixedWidgetContainerBelow: any = null;
1078
1011
  const queueStore = new PowerlineQueueStore();
1079
1012
  let powerlineCompacting = false;
1080
1013
  let deliverAfterRetrySettles = false;
@@ -2100,12 +2033,7 @@ export default function powerlineFooter(pi: ExtensionAPI) {
2100
2033
  return { kind: "bashMode" };
2101
2034
  }
2102
2035
 
2103
- const chatJumpAction = getChatJumpShortcutAction(data);
2104
- return chatJumpAction ? { kind: "chat", action: chatJumpAction } : null;
2105
- }
2106
-
2107
- function getChatJumpShortcutAction(data: string): ChatJumpShortcutAction | null {
2108
- return CHAT_JUMP_SHORTCUTS.find(({ shortcutKey }) => matchesConfiguredShortcut(data, resolvedShortcuts[shortcutKey]))?.action ?? null;
2036
+ return null;
2109
2037
  }
2110
2038
 
2111
2039
  function runPowerlineShortcut(ctx: any, action: PowerlineShortcutAction): void {
@@ -2146,13 +2074,6 @@ export default function powerlineFooter(pi: ExtensionAPI) {
2146
2074
  void setBashModeActive(!bashModeActive, ctx);
2147
2075
  return;
2148
2076
  }
2149
-
2150
- if (action.action.kind === "bottom") {
2151
- jumpChatToBottom(ctx);
2152
- return;
2153
- }
2154
-
2155
- jumpToChatMessage(ctx, action.action.role, action.action.direction);
2156
2077
  }
2157
2078
 
2158
2079
  function stashOrRestoreEditorText(ctx: any): void {
@@ -2988,192 +2909,6 @@ export default function powerlineFooter(pi: ExtensionAPI) {
2988
2909
  return [truncateToWidth(line, width, "…")];
2989
2910
  }
2990
2911
 
2991
- function teardownFixedEditorCompositor(options?: { resetExtendedKeyboardModes?: boolean }) {
2992
- const hadCompositor = fixedEditorCompositor !== null;
2993
- fixedEditorCompositor?.dispose(options);
2994
- if (!hadCompositor && options?.resetExtendedKeyboardModes) {
2995
- try {
2996
- process.stdout.write(emergencyTerminalModeReset());
2997
- } catch {
2998
- // Shutdown cleanup cannot surface useful terminal write failures.
2999
- }
3000
- }
3001
- fixedEditorCompositor = null;
3002
- fixedStatusContainer = null;
3003
- fixedEditorContainer = null;
3004
- fixedWidgetContainerAbove = null;
3005
- fixedWidgetContainerBelow = null;
3006
- }
3007
-
3008
- function findContainerWithChild(tui: any, child: any): { container: any; index: number } | null {
3009
- const children = Array.isArray(tui?.children) ? tui.children : [];
3010
- const index = children.findIndex((candidate: any) => Array.isArray(candidate?.children) && candidate.children.includes(child));
3011
- if (index === -1) return null;
3012
-
3013
- return { container: children[index], index };
3014
- }
3015
-
3016
- function installFixedEditorCompositor(ctx: any, tui: any) {
3017
- teardownFixedEditorCompositor();
3018
-
3019
- if (!ctx.hasUI || !config.fixedEditor) return;
3020
- if (!tui?.terminal || typeof tui.terminal.write !== "function") {
3021
- throw new Error("[powerline-footer] Fixed editor compositor could not find tui.terminal.write()");
3022
- }
3023
- if (!currentEditor) {
3024
- throw new Error("[powerline-footer] Fixed editor compositor expected the custom editor to be installed first");
3025
- }
3026
-
3027
- const editorContainerMatch = findContainerWithChild(tui, currentEditor);
3028
- if (!editorContainerMatch) {
3029
- throw new Error("[powerline-footer] Fixed editor compositor could not find the editor container in TUI children");
3030
- }
3031
-
3032
- const tuiChildren = Array.isArray(tui.children) ? tui.children : [];
3033
- fixedEditorContainer = editorContainerMatch.container;
3034
- const statusContainerCandidate = tuiChildren[editorContainerMatch.index - 2] ?? null;
3035
- fixedStatusContainer = statusContainerCandidate && typeof statusContainerCandidate.render === "function"
3036
- ? statusContainerCandidate
3037
- : null;
3038
- fixedWidgetContainerAbove = tuiChildren[editorContainerMatch.index - 1] ?? null;
3039
- fixedWidgetContainerBelow = tuiChildren[editorContainerMatch.index + 1] ?? null;
3040
-
3041
- let compositor: TerminalSplitCompositor;
3042
- compositor = new TerminalSplitCompositor({
3043
- tui,
3044
- terminal: tui.terminal,
3045
- mouseScroll: config.mouseScroll,
3046
- keyboardScrollShortcuts: {
3047
- up: resolvedShortcuts.scrollChatUp ?? "super+up",
3048
- down: resolvedShortcuts.scrollChatDown ?? "super+down",
3049
- },
3050
- shouldCaptureKeyboardScroll: () => Reflect.get(tui, "focusedComponent") === currentEditor,
3051
- onCopySelection: (text) => copyTextToClipboard(ctx, text),
3052
- getShowHardwareCursor: () => typeof tui.getShowHardwareCursor === "function" && tui.getShowHardwareCursor(),
3053
- renderCluster: (width, terminalRows) => {
3054
- const theme = currentCtx?.ui?.theme ?? ctx.ui.theme;
3055
- const statusContainerLines = fixedStatusContainer
3056
- ? compositor.renderHidden(fixedStatusContainer, width).filter((line) => visibleWidth(line) > 0)
3057
- : [];
3058
- const aboveWidgetLines = fixedWidgetContainerAbove ? compositor.renderHidden(fixedWidgetContainerAbove, width) : [];
3059
- const belowWidgetLines = fixedWidgetContainerBelow ? compositor.renderHidden(fixedWidgetContainerBelow, width) : [];
3060
- return renderFixedEditorCluster({
3061
- width,
3062
- terminalRows,
3063
- statusLines: [...aboveWidgetLines, ...renderPowerlineStatusLines(width), ...statusContainerLines],
3064
- topLines: renderPowerlinePrimaryLines(width, theme),
3065
- editorLines: fixedEditorContainer ? compositor.renderHidden(fixedEditorContainer, width) : [],
3066
- secondaryLines: [...renderPowerlineSecondaryLines(width, theme), ...belowWidgetLines],
3067
- transcriptLines: renderBashTranscriptLines(width, theme),
3068
- lastPromptLines: renderLastPromptLines(width),
3069
- });
3070
- },
3071
- });
3072
-
3073
- fixedEditorCompositor = compositor;
3074
- if (fixedStatusContainer?.render) compositor.hideRenderable(fixedStatusContainer);
3075
- if (fixedWidgetContainerAbove?.render) compositor.hideRenderable(fixedWidgetContainerAbove);
3076
- compositor.hideRenderable(fixedEditorContainer);
3077
- if (fixedWidgetContainerBelow?.render) compositor.hideRenderable(fixedWidgetContainerBelow);
3078
- compositor.install();
3079
- tui.requestRender(true);
3080
- }
3081
-
3082
- function isChatMessageComponentForRole(component: unknown, role: ChatJumpRole): boolean {
3083
- const componentName = typeof component === "object" && component !== null ? component.constructor?.name : undefined;
3084
- if (role === "assistant") {
3085
- return componentName === "AssistantMessageComponent";
3086
- }
3087
-
3088
- return componentName === "UserMessageComponent" || componentName === "SkillInvocationMessageComponent";
3089
- }
3090
-
3091
- function renderLineCount(component: unknown, width: number): number {
3092
- if (typeof component !== "object" || component === null) return 0;
3093
-
3094
- const render = Reflect.get(component, "render");
3095
- if (typeof render !== "function") return 0;
3096
-
3097
- const lines = render.call(component, width);
3098
- return Array.isArray(lines) ? lines.length : 0;
3099
- }
3100
-
3101
- function collectMessageStartLines(component: unknown, width: number, role: ChatJumpRole, offset: number): {
3102
- targets: number[];
3103
- lineCount: number;
3104
- } {
3105
- const lineCount = renderLineCount(component, width);
3106
- if (isChatMessageComponentForRole(component, role)) {
3107
- return { targets: [offset], lineCount };
3108
- }
3109
-
3110
- const children = typeof component === "object" && component !== null ? Reflect.get(component, "children") : null;
3111
- if (!Array.isArray(children) || children.length === 0) {
3112
- return { targets: [], lineCount };
3113
- }
3114
-
3115
- const targets: number[] = [];
3116
- let childOffset = offset;
3117
- let childrenLineCount = 0;
3118
- for (const child of children) {
3119
- const result = collectMessageStartLines(child, width, role, childOffset);
3120
- targets.push(...result.targets);
3121
- childOffset += result.lineCount;
3122
- childrenLineCount += result.lineCount;
3123
- }
3124
-
3125
- return { targets, lineCount: Math.max(lineCount, childrenLineCount) };
3126
- }
3127
-
3128
- function collectChatMessageStartLines(role: ChatJumpRole): number[] {
3129
- const children = Array.isArray(tuiRef?.children) ? tuiRef.children : [];
3130
- const width = Math.max(1, tuiRef?.terminal?.columns ?? 80);
3131
- const targets: number[] = [];
3132
- let offset = 0;
3133
-
3134
- for (const child of children) {
3135
- const result = collectMessageStartLines(child, width, role, offset);
3136
- targets.push(...result.targets);
3137
- offset += result.lineCount;
3138
- }
3139
-
3140
- return [...new Set(targets)].sort((a, b) => a - b);
3141
- }
3142
-
3143
- function jumpToChatMessage(ctx: any, role: ChatJumpRole, direction: ChatJumpDirection): void {
3144
- if (!fixedEditorCompositor) {
3145
- ctx.ui.notify("Chat message jumps require /powerline fixed-editor on", "warning");
3146
- return;
3147
- }
3148
-
3149
- const targets = collectChatMessageStartLines(role);
3150
- const label = role === "assistant" ? "LLM" : "user";
3151
- if (targets.length === 0) {
3152
- ctx.ui.notify(`No ${label} messages found`, "info");
3153
- return;
3154
- }
3155
-
3156
- const jumped = direction === "previous"
3157
- ? fixedEditorCompositor.jumpToPreviousRootTarget(targets)
3158
- : fixedEditorCompositor.jumpToNextRootTarget(targets);
3159
- if (!jumped) {
3160
- ctx.ui.notify(`No ${direction} ${label} message`, "info");
3161
- }
3162
- }
3163
-
3164
- function jumpChatToBottom(ctx: any): void {
3165
- if (!fixedEditorCompositor) {
3166
- ctx.ui.notify("Chat bottom jump requires /powerline fixed-editor on", "warning");
3167
- return;
3168
- }
3169
-
3170
- fixedEditorCompositor.jumpToRootBottom();
3171
- }
3172
-
3173
- function followSubmittedEditorToBottom(): void {
3174
- fixedEditorCompositor?.jumpToRootBottom();
3175
- }
3176
-
3177
2912
  function installPowerlineWidgets(ctx: any) {
3178
2913
  ctx.ui.setWidget("powerline-status", () => ({
3179
2914
  dispose() {},
@@ -3424,12 +3159,8 @@ export default function powerlineFooter(pi: ExtensionAPI) {
3424
3159
  }
3425
3160
 
3426
3161
  attachAutocompleteProvider();
3427
- const followUpText = keybindings.matches(data, "app.message.followUp") ? getCurrentEditorText(ctx, editor) : "";
3428
3162
  scheduleDismissWelcome(ctx);
3429
3163
  originalHandleInput(data);
3430
- if (hasNonWhitespaceText(followUpText) && !hasNonWhitespaceText(getCurrentEditorText(ctx, editor))) {
3431
- followSubmittedEditorToBottom();
3432
- }
3433
3164
  };
3434
3165
 
3435
3166
  const originalRender = editor.render.bind(editor);
@@ -9,7 +9,6 @@
9
9
  "*.json",
10
10
  "!progress.md",
11
11
  "bash-mode/**/*.ts",
12
- "fixed-editor/**/*.ts",
13
12
  "queue/**/*.ts"
14
13
  ],
15
14
  "keywords": [
@@ -31,14 +30,14 @@
31
30
  "test": "node --experimental-strip-types --test tests/**/*.test.ts"
32
31
  },
33
32
  "peerDependencies": {
34
- "@earendil-works/pi-ai": ">=0.81.0 <0.84.0",
33
+ "@earendil-works/pi-ai": ">=0.84.1 <0.85.0",
35
34
  "@selesai/code": "*",
36
- "@earendil-works/pi-tui": ">=0.81.0 <0.84.0"
35
+ "@earendil-works/pi-tui": ">=0.84.1 <0.85.0"
37
36
  },
38
37
  "devDependencies": {
39
- "@earendil-works/pi-ai": "^0.83.0",
38
+ "@earendil-works/pi-ai": "^0.84.1",
40
39
  "@selesai/code": "^0.1.0",
41
- "@earendil-works/pi-tui": "^0.83.0",
40
+ "@earendil-works/pi-tui": "^0.84.1",
42
41
  "@types/node": "24.13.3",
43
42
  "typescript": "5.9.3"
44
43
  },
@@ -6,8 +6,6 @@ import type { ColorValue, CustomItemPosition, CustomStatusItem, PowerlinePlaceme
6
6
  export interface PowerlineConfig {
7
7
  preset: StatusLinePreset;
8
8
  customItems: CustomStatusItem[];
9
- mouseScroll: boolean;
10
- fixedEditor: boolean;
11
9
  fixedEditorPromptGlyph: string;
12
10
  disabledSegments: StatusLineSegmentId[];
13
11
  invalidDisabledSegments: string[];
@@ -305,8 +303,6 @@ export function parsePowerlineConfig(value: unknown, presets: readonly StatusLin
305
303
  const defaultConfig: PowerlineConfig = {
306
304
  preset: "default",
307
305
  customItems: [],
308
- mouseScroll: true,
309
- fixedEditor: true,
310
306
  fixedEditorPromptGlyph: "",
311
307
  disabledSegments: [],
312
308
  invalidDisabledSegments: [],
@@ -337,8 +333,6 @@ export function parsePowerlineConfig(value: unknown, presets: readonly StatusLin
337
333
  return {
338
334
  preset: normalizePreset(value.preset, presets) ?? defaultConfig.preset,
339
335
  customItems,
340
- mouseScroll: value.mouseScroll !== false,
341
- fixedEditor: value.fixedEditor !== false,
342
336
  fixedEditorPromptGlyph: typeof value.fixedEditorPromptGlyph === "string" ? value.fixedEditorPromptGlyph : defaultConfig.fixedEditorPromptGlyph,
343
337
  disabledSegments,
344
338
  invalidDisabledSegments,
@@ -2,7 +2,7 @@ import { describe, expect, it } from "vitest";
2
2
  import { initTheme, theme } from "../../../modes/interactive/theme/theme.ts";
3
3
 
4
4
  /**
5
- * The powerline fixed-editor render draws its own border. It must respect the
5
+ * The powerline editor render draws its own border. It must respect the
6
6
  * editor's `borderColor` (set by interactive-mode to the bash-mode / thinking
7
7
  * color) instead of always using the gray "sep" color, so typing `!` visibly
8
8
  * switches the input field to bash mode.
@@ -13,7 +13,7 @@ function powerlineBorderColor(editor: { borderColor: unknown }, s: string): stri
13
13
  : `\x1b[38;5;244m${s}\x1b[0m`;
14
14
  }
15
15
 
16
- describe("powerline fixed-editor border respects editor.borderColor", () => {
16
+ describe("powerline editor border respects editor.borderColor", () => {
17
17
  it("uses the bash-mode color when interactive-mode sets it (typing !)", () => {
18
18
  initTheme("dark");
19
19
  const editorWithBashBorder = {
@@ -36,7 +36,7 @@ test("guide dismissal and compact preferences change automatic display", () => {
36
36
  assert.equal(resolveGuideDisplayMode({ reason: "startup", hasUI: false, guideMode: "full" }), "none");
37
37
  });
38
38
 
39
- test("guide defaults to a compact header so fixed-editor mode stays active", () => {
39
+ test("guide defaults to a compact header so the native dock stays uncluttered", () => {
40
40
  const preferences = getGuidePreferences({});
41
41
  assert.equal(preferences.mode, "compact");
42
42
  assert.equal(resolveGuideDisplayMode({ reason: "startup", hasUI: true, guideMode: preferences.mode }), "compact");
@@ -9,5 +9,5 @@
9
9
  "types": ["node"],
10
10
  "allowImportingTsExtensions": true
11
11
  },
12
- "include": ["*.ts", "bash-mode/**/*.ts", "fixed-editor/**/*.ts", "queue/**/*.ts"]
12
+ "include": ["*.ts", "bash-mode/**/*.ts", "queue/**/*.ts"]
13
13
  }
package/dist/index.d.ts CHANGED
@@ -18,7 +18,7 @@ export type { ResourceCollision, ResourceDiagnostic, ResourceLoader } from "./co
18
18
  export { DefaultResourceLoader, loadProjectContextFiles } from "./core/resource-loader.ts";
19
19
  export { AgentSessionRuntime, type AgentSessionRuntimeDiagnostic, type AgentSessionServices, type CreateAgentSessionFromServicesOptions, type CreateAgentSessionOptions, type CreateAgentSessionResult, type CreateAgentSessionRuntimeFactory, type CreateAgentSessionRuntimeResult, type CreateAgentSessionServicesOptions, createAgentSession, createAgentSessionFromServices, createAgentSessionRuntime, createAgentSessionServices, createBashTool, createCodingTools, createEditTool, createFindTool, createGrepTool, createLsTool, createReadOnlyTools, createReadTool, createWriteTool, type PromptTemplate, } from "./core/sdk.ts";
20
20
  export { type BranchSummaryEntry, buildContextEntries, buildSessionContext, type CompactionEntry, CURRENT_SESSION_VERSION, type CustomEntry, type CustomMessageEntry, type FileEntry, getLatestCompactionEntry, type ModelChangeEntry, migrateSessionEntries, type NewSessionOptions, parseSessionEntries, type SessionContext, type SessionEntry, type SessionEntryBase, type SessionHeader, type SessionInfo, type SessionInfoEntry, SessionManager, type SessionMessageEntry, type SessionTreeNode, sessionEntryToContextMessages, type ThinkingLevelChangeEntry, } from "./core/session-manager.ts";
21
- export { type AutoHandoffSettings, type CompactionSettings, type DefaultProjectTrust, type ImageSettings, type PackageSource, type RetrySettings, SettingsManager, type SettingsManagerCreateOptions, } from "./core/settings-manager.ts";
21
+ export { type AutoHandoffSettings, type CompactionSettings, type DefaultProjectTrust, type ImageSettings, type PackageSource, type RetrySettings, SettingsManager, type SettingsManagerCreateOptions, type TuiMode, } from "./core/settings-manager.ts";
22
22
  export { formatSkillsForPrompt, type LoadSkillsFromDirOptions, type LoadSkillsResult, loadSkills, loadSkillsFromDir, type Skill, type SkillFrontmatter, } from "./core/skills.ts";
23
23
  export { createSyntheticSourceInfo } from "./core/source-info.ts";
24
24
  export { type EditDiffResult, generateDiffString, generateUnifiedPatch } from "./core/tools/edit-diff.ts";
package/dist/main.js CHANGED
@@ -722,6 +722,7 @@ export async function main(args, options) {
722
722
  initialImages,
723
723
  initialMessages: parsed.messages,
724
724
  verbose: parsed.verbose,
725
+ tuiMode: parsed.tuiMode,
725
726
  });
726
727
  if (startupBenchmark) {
727
728
  await interactiveMode.init();
@@ -1,7 +1,7 @@
1
1
  import type { ThinkingLevel } from "@earendil-works/pi-agent-core";
2
2
  import type { Transport } from "@earendil-works/pi-ai";
3
- import { Container, SettingsList } from "@earendil-works/pi-tui";
4
- import type { DefaultProjectTrust, WarningSettings } from "../../../core/settings-manager.ts";
3
+ import { Container, type ScrollViewScrollbar, SettingsList } from "@earendil-works/pi-tui";
4
+ import type { DefaultProjectTrust, TuiMode, WarningSettings } from "../../../core/settings-manager.ts";
5
5
  import { type TerminalTheme } from "../theme/theme.ts";
6
6
  export interface SettingsConfig {
7
7
  autoCompact: boolean;
@@ -34,6 +34,8 @@ export interface SettingsConfig {
34
34
  defaultProjectTrust: DefaultProjectTrust;
35
35
  clearOnShrink: boolean;
36
36
  showTerminalProgress: boolean;
37
+ tuiMode: TuiMode;
38
+ fullscreenScrollbar: ScrollViewScrollbar;
37
39
  warnings: WarningSettings;
38
40
  }
39
41
  export interface SettingsCallbacks {
@@ -65,6 +67,8 @@ export interface SettingsCallbacks {
65
67
  onDefaultProjectTrustChange: (defaultProjectTrust: DefaultProjectTrust) => void;
66
68
  onClearOnShrinkChange: (enabled: boolean) => void;
67
69
  onShowTerminalProgressChange: (enabled: boolean) => void;
70
+ onTuiModeChange: (mode: TuiMode) => void;
71
+ onFullscreenScrollbarChange: (mode: ScrollViewScrollbar) => void;
68
72
  onWarningsChange: (warnings: WarningSettings) => void;
69
73
  onCancel: () => void;
70
74
  }
@@ -402,6 +402,20 @@ export class SettingsSelectorComponent extends Container {
402
402
  done(value);
403
403
  }, () => done()),
404
404
  },
405
+ {
406
+ id: "tui-mode",
407
+ label: "TUI mode",
408
+ description: "Switches immediately; fullscreen is the Selesai default",
409
+ currentValue: config.tuiMode,
410
+ values: ["fullscreen", "regular"],
411
+ },
412
+ {
413
+ id: "fullscreen-scrollbar",
414
+ label: "Fullscreen scrollbar",
415
+ description: "Scrollbar behavior in fullscreen mode; has no effect in regular mode",
416
+ currentValue: config.fullscreenScrollbar,
417
+ values: ["auto", "always", "hidden"],
418
+ },
405
419
  {
406
420
  id: "theme",
407
421
  label: "Theme",
@@ -586,6 +600,12 @@ export class SettingsSelectorComponent extends Container {
586
600
  case "terminal-progress":
587
601
  callbacks.onShowTerminalProgressChange(newValue === "true");
588
602
  break;
603
+ case "tui-mode":
604
+ callbacks.onTuiModeChange(newValue);
605
+ break;
606
+ case "fullscreen-scrollbar":
607
+ callbacks.onFullscreenScrollbarChange(newValue);
608
+ break;
589
609
  case "theme":
590
610
  callbacks.onThemeChange(newValue);
591
611
  break;
@@ -3,8 +3,11 @@
3
3
  * Handles TUI rendering and user interaction, delegating business logic to AgentSession.
4
4
  */
5
5
  import type { ImageContent } from "@earendil-works/pi-ai/compat";
6
+ import type { Terminal } from "@earendil-works/pi-tui";
7
+ import { type TUI, TuiAltScreen, TuiMainScreen } from "@earendil-works/pi-tui";
6
8
  import { type AgentSessionRuntime } from "../../core/agent-session-runtime.ts";
7
9
  import { SessionManager } from "../../core/session-manager.ts";
10
+ import type { TuiMode } from "../../core/settings-manager.ts";
8
11
  import { type LatestPiRelease } from "../../utils/version-check.ts";
9
12
  type PendingUserInput = {
10
13
  text: string;
@@ -29,14 +32,32 @@ export interface InteractiveModeOptions {
29
32
  initialMessages?: string[];
30
33
  /** Force verbose startup (overrides quietStartup setting) */
31
34
  verbose?: boolean;
35
+ /** TUI layout mode. */
36
+ tuiMode?: TuiMode;
32
37
  }
38
+ interface InteractiveTuiOptions {
39
+ tuiMode: TuiMode;
40
+ showHardwareCursor: boolean;
41
+ logDirectory: string;
42
+ terminal?: Terminal;
43
+ onRightClickPaste?: () => void;
44
+ }
45
+ /** Composition root for selecting the interactive terminal renderer. */
46
+ export declare function createInteractiveTui(options: InteractiveTuiOptions): TuiMainScreen | TuiAltScreen;
47
+ /** Stable reference for components while InteractiveMode replaces the active renderer. */
48
+ export declare function createInteractiveTuiReference(getTui: () => TUI): TUI;
33
49
  export declare class InteractiveMode {
34
50
  private runtimeHost;
51
+ private renderer;
35
52
  private ui;
53
+ private mainScreenRenderState;
54
+ private documentContainer;
36
55
  private loadedResourcesContainer;
37
56
  private chatContainer;
38
57
  private pendingMessagesContainer;
39
58
  private statusContainer;
59
+ private transcriptScrollView;
60
+ private fullscreenLayoutRoot;
40
61
  private defaultEditor;
41
62
  private editor;
42
63
  private editorComponentFactory;
@@ -45,6 +66,7 @@ export declare class InteractiveMode {
45
66
  private fdPath;
46
67
  private editorContainer;
47
68
  private footer;
69
+ private footerContainer;
48
70
  private footerDataProvider;
49
71
  private keybindings;
50
72
  private version;
@@ -95,6 +117,7 @@ export declare class InteractiveMode {
95
117
  private builtInHeader;
96
118
  private customHeader;
97
119
  private options;
120
+ private readonly onRightClickPaste;
98
121
  private autoTrustOnReloadCwd;
99
122
  private themeController;
100
123
  private get session();
@@ -108,6 +131,9 @@ export declare class InteractiveMode {
108
131
  private createBaseAutocompleteProvider;
109
132
  private setupAutocompleteProvider;
110
133
  private showStartupNoticesIfNeeded;
134
+ private mountInteractiveTui;
135
+ private stopInteractiveTui;
136
+ private switchTuiMode;
111
137
  init(): Promise<void>;
112
138
  /**
113
139
  * Update terminal title with session name and cwd.
@@ -193,6 +219,7 @@ export declare class InteractiveMode {
193
219
  */
194
220
  private setExtensionHeader;
195
221
  private addExtensionTerminalInputListener;
222
+ private rebindExtensionTerminalInputListeners;
196
223
  private clearExtensionTerminalInputListeners;
197
224
  /**
198
225
  * Create the ExtensionUIContext for extensions.
@@ -240,6 +267,7 @@ export declare class InteractiveMode {
240
267
  */
241
268
  private showExtensionError;
242
269
  private setupKeyHandlers;
270
+ private handleRightClickPaste;
243
271
  private handleClipboardPaste;
244
272
  private consumePendingInputImages;
245
273
  private handleGitCommand;