@selesai/code 0.8.1 → 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.
- package/CHANGELOG.md +15 -0
- package/README.md +4 -2
- package/dist/cli/args.d.ts +2 -0
- package/dist/cli/args.js +18 -0
- package/dist/cli/config-selector.js +2 -2
- package/dist/cli/startup-ui.d.ts +1 -1
- package/dist/cli/startup-ui.js +2 -2
- package/dist/core/keybindings.d.ts +44 -4
- package/dist/core/llama/provider.js +22 -7
- package/dist/core/model-resolver.js +2 -0
- package/dist/core/model-runtime.d.ts +4 -2
- package/dist/core/model-runtime.js +23 -2
- package/dist/core/remote-catalog-provider.js +72 -67
- package/dist/core/settings-manager.d.ts +8 -0
- package/dist/core/settings-manager.js +18 -0
- package/dist/extensions/pi-powerline-footer/CHANGELOG.md +3 -1
- package/dist/extensions/pi-powerline-footer/guide.ts +11 -3
- package/dist/extensions/pi-powerline-footer/index.ts +3 -272
- package/dist/extensions/pi-powerline-footer/package.json +4 -5
- package/dist/extensions/pi-powerline-footer/powerline-config.ts +0 -6
- package/dist/extensions/pi-powerline-footer/tests/fixed-editor-border.test.ts +2 -2
- package/dist/extensions/pi-powerline-footer/tests/guide.test.ts +24 -1
- package/dist/extensions/pi-powerline-footer/tsconfig.json +1 -1
- package/dist/extensions/pi-subagents/package-lock.json +2 -2
- package/dist/extensions/pi-subagents/package.json +1 -1
- package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/main.js +1 -0
- package/dist/modes/interactive/components/settings-selector.d.ts +6 -2
- package/dist/modes/interactive/components/settings-selector.js +20 -0
- package/dist/modes/interactive/interactive-mode.d.ts +28 -0
- package/dist/modes/interactive/interactive-mode.js +214 -28
- package/dist/modes/interactive/theme/dark.json +1 -0
- package/dist/modes/interactive/theme/light.json +1 -0
- package/dist/modes/interactive/theme/theme-controller.d.ts +3 -0
- package/dist/modes/interactive/theme/theme-controller.js +10 -1
- package/dist/modes/interactive/theme/theme.d.ts +2 -2
- package/dist/modes/interactive/theme/theme.js +24 -6
- package/docs/keybindings.md +35 -7
- package/docs/settings.md +16 -14
- package/docs/usage.md +5 -0
- package/package.json +5 -5
- package/dist/extensions/pi-powerline-footer/fixed-editor/cluster.ts +0 -113
- package/dist/extensions/pi-powerline-footer/fixed-editor/terminal-split.ts +0 -1080
- package/dist/extensions/pi-powerline-footer/tests/fixed-editor.test.ts +0 -1452
- package/dist/extensions/pi-powerline-footer/tests/jump-shortcuts.test.ts +0 -49
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@selesai/code` will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
## [0.8.3] - 2026-08-12
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- **Pi v0.84.1 migration with fullscreen TUI by default.** Selesai now runs Pi's native fullscreen TUI mode (`tuiMode: "fullscreen"`) by default, with an alternate-screen viewport, a fixed dock for the input editor/status/widgets/footer, a scrollable transcript, and fullscreen-specific keybindings. `--tui-mode regular`, the `tuiMode` setting, or the `TUI mode` entry in `/settings` opt back into regular mode. The powerline extension's unsupported terminal-split compositor and mouse-scroll/chat-jump machinery were removed; its status, editor, and widgets now live in Pi's native dock through the standard extension APIs.
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- **Runtime dependencies updated to Pi v0.84.1.** `@earendil-works/pi-ai`, `@earendil-works/pi-tui`, and `@earendil-works/pi-agent-core` moved to 0.84.1 and `undici` to 8.9.0, matching upstream `@earendil-works/pi-coding-agent` 0.84.1. The model-runtime/refresh APIs were ported to the new pi-ai publication model, and the interactive renderer now selects between Pi's `TuiMainScreen` (regular) and `TuiAltScreen` (fullscreen).
|
|
14
|
+
|
|
15
|
+
## [0.8.2] - 2026-08-11
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
- **Guide version-marking no longer locks in the implicit mode.** When the recurring guide marks a new version as seen, Selesai no longer writes the fallback `compact` mode into `settings.json` as if it were a user choice. An explicit `full`, `compact`, or `off` mode is still preserved across version-only writes, so the guide keeps following the current default until you choose otherwise.
|
|
19
|
+
|
|
5
20
|
## [0.8.1] - 2026-08-11
|
|
6
21
|
|
|
7
22
|
### Fixed
|
package/README.md
CHANGED
|
@@ -86,14 +86,16 @@ This is the interaction layer used by the workflow modes and available to normal
|
|
|
86
86
|
|
|
87
87
|
### A richer terminal workspace
|
|
88
88
|
|
|
89
|
+
The interactive terminal runs Pi's native **fullscreen TUI mode** by default: the chat scrolls inside an alternate-screen viewport while the input editor, status line, and widgets stay fixed in a dock at the bottom. Choose `regular` (inline output) at any time with `--tui-mode regular`, the `tuiMode` setting, or the `TUI mode` entry in `/settings`.
|
|
90
|
+
|
|
89
91
|
The bundled `pi-powerline-footer` improves the interactive display with:
|
|
90
92
|
|
|
91
93
|
- a powerline status bar for model, thinking level, path, git state, context, tokens, cost, cache, and session data
|
|
92
|
-
-
|
|
94
|
+
- status, editor, and widgets that live in Pi's native fixed dock (the old terminal-split compositor was decommissioned)
|
|
93
95
|
- a file-backed queue/inbox for deferred ideas and compaction-safe prompts (`# <idea>`, `/idea`, `/ideas`, and `/queue`), plus session-aware `/cd`
|
|
94
96
|
- a recurring `/guide` tour with compact and remembered-dismissal modes, covering workflows, delegation/research prompts, skills, handoff, and recovery
|
|
95
97
|
- persistent bash mode with shell state, transcript output, history, and ghost suggestions
|
|
96
|
-
- editor stash/restore, prompt history, clipboard shortcuts, and
|
|
98
|
+
- editor stash/restore, prompt history, clipboard shortcuts, and editor boundary navigation
|
|
97
99
|
- themed working-vibe messages, Nerd Font detection, configurable presets, layout, placement, and cost display
|
|
98
100
|
|
|
99
101
|
### Coordination between sessions
|
package/dist/cli/args.d.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import type { ThinkingLevel } from "@earendil-works/pi-agent-core";
|
|
5
5
|
import type { ExtensionFlag } from "../core/extensions/types.ts";
|
|
6
|
+
import type { TuiMode } from "../core/settings-manager.ts";
|
|
6
7
|
export type Mode = "text" | "json" | "rpc";
|
|
7
8
|
export interface Args {
|
|
8
9
|
provider?: string;
|
|
@@ -40,6 +41,7 @@ export interface Args {
|
|
|
40
41
|
noContextFiles?: boolean;
|
|
41
42
|
listModels?: string | true;
|
|
42
43
|
offline?: boolean;
|
|
44
|
+
tuiMode?: TuiMode;
|
|
43
45
|
verbose?: boolean;
|
|
44
46
|
projectTrustOverride?: boolean;
|
|
45
47
|
messages: string[];
|
package/dist/cli/args.js
CHANGED
|
@@ -157,6 +157,23 @@ export function parseArgs(args) {
|
|
|
157
157
|
result.listModels = true;
|
|
158
158
|
}
|
|
159
159
|
}
|
|
160
|
+
else if (arg === "--tui-mode") {
|
|
161
|
+
const mode = args[i + 1];
|
|
162
|
+
if (mode === "regular" || mode === "fullscreen") {
|
|
163
|
+
result.tuiMode = mode;
|
|
164
|
+
i++;
|
|
165
|
+
}
|
|
166
|
+
else if (mode === undefined || mode.startsWith("-")) {
|
|
167
|
+
result.diagnostics.push({ type: "error", message: "--tui-mode requires regular or fullscreen" });
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
i++;
|
|
171
|
+
result.diagnostics.push({
|
|
172
|
+
type: "error",
|
|
173
|
+
message: `Invalid TUI mode "${mode}". Valid values: regular, fullscreen`,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
160
177
|
else if (arg === "--verbose") {
|
|
161
178
|
result.verbose = true;
|
|
162
179
|
}
|
|
@@ -259,6 +276,7 @@ ${chalk.bold("Options:")}
|
|
|
259
276
|
--no-context-files, -nc Disable AGENTS.md and CLAUDE.md discovery and loading
|
|
260
277
|
--export <file> Export session file to HTML and exit
|
|
261
278
|
--list-models [search] List available models (with optional fuzzy search)
|
|
279
|
+
--tui-mode <mode> TUI mode: fullscreen (default) or regular
|
|
262
280
|
--verbose Force verbose startup (overrides quietStartup setting)
|
|
263
281
|
--approve, -a Trust project-local files for this run
|
|
264
282
|
--no-approve, -na Ignore project-local files for this run
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* TUI config selector for `pi config` command
|
|
3
3
|
*/
|
|
4
|
-
import { ProcessTerminal,
|
|
4
|
+
import { ProcessTerminal, TuiMainScreen } from "@earendil-works/pi-tui";
|
|
5
5
|
import { ConfigSelectorComponent } from "../modes/interactive/components/config-selector.js";
|
|
6
6
|
import { initTheme, stopThemeWatcher } from "../modes/interactive/theme/theme.js";
|
|
7
7
|
/** Show TUI config selector and return when closed */
|
|
@@ -9,7 +9,7 @@ export async function selectConfig(options) {
|
|
|
9
9
|
// Initialize theme before showing TUI
|
|
10
10
|
initTheme(options.settingsManager.getTheme(), true);
|
|
11
11
|
return new Promise((resolve) => {
|
|
12
|
-
const ui = new
|
|
12
|
+
const ui = new TuiMainScreen(new ProcessTerminal(), undefined, options.agentDir);
|
|
13
13
|
let resolved = false;
|
|
14
14
|
const selector = new ConfigSelectorComponent(options.resolvedPaths, options.settingsManager, options.cwd, options.agentDir, () => {
|
|
15
15
|
if (!resolved) {
|
package/dist/cli/startup-ui.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TUI } from "@earendil-works/pi-tui";
|
|
1
|
+
import { type TUI } from "@earendil-works/pi-tui";
|
|
2
2
|
import { SettingsManager } from "../core/settings-manager.ts";
|
|
3
3
|
export declare function createStartupTui(settingsManager: SettingsManager): Promise<TUI>;
|
|
4
4
|
export declare function startStartupTui(ui: TUI, settingsManager: SettingsManager): void;
|
package/dist/cli/startup-ui.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ProcessTerminal, setKeybindings,
|
|
1
|
+
import { ProcessTerminal, setKeybindings, TuiMainScreen } from "@earendil-works/pi-tui";
|
|
2
2
|
import { existsSync } from "fs";
|
|
3
3
|
import { ENV_AGENT_DIR, getAgentDir, getFirstRunMarkerPath, markFirstRunComplete } from "../config.js";
|
|
4
4
|
import { KeybindingsManager } from "../core/keybindings.js";
|
|
@@ -47,7 +47,7 @@ export async function createStartupTui(settingsManager) {
|
|
|
47
47
|
const terminalTheme = detectTerminalBackgroundFromEnv().theme;
|
|
48
48
|
initTheme(resolveThemeSetting(settingsManager.getThemeSetting(), terminalTheme) ?? terminalTheme);
|
|
49
49
|
setKeybindings(KeybindingsManager.create());
|
|
50
|
-
const ui = new
|
|
50
|
+
const ui = new TuiMainScreen(new ProcessTerminal(), settingsManager.getShowHardwareCursor(), getAgentDir());
|
|
51
51
|
ui.setClearOnShrink(settingsManager.getClearOnShrink());
|
|
52
52
|
return ui;
|
|
53
53
|
}
|
|
@@ -57,6 +57,14 @@ export declare const KEYBINDINGS: {
|
|
|
57
57
|
readonly defaultKeys: "down";
|
|
58
58
|
readonly description: "Move cursor down";
|
|
59
59
|
};
|
|
60
|
+
readonly "tui.editor.historyPrevious": {
|
|
61
|
+
readonly defaultKeys: [];
|
|
62
|
+
readonly description: "Select previous prompt history entry";
|
|
63
|
+
};
|
|
64
|
+
readonly "tui.editor.historyNext": {
|
|
65
|
+
readonly defaultKeys: [];
|
|
66
|
+
readonly description: "Select next prompt history entry";
|
|
67
|
+
};
|
|
60
68
|
readonly "tui.editor.cursorLeft": {
|
|
61
69
|
readonly defaultKeys: ["left", "ctrl+b"];
|
|
62
70
|
readonly description: "Move cursor left";
|
|
@@ -74,11 +82,11 @@ export declare const KEYBINDINGS: {
|
|
|
74
82
|
readonly description: "Move cursor word right";
|
|
75
83
|
};
|
|
76
84
|
readonly "tui.editor.cursorLineStart": {
|
|
77
|
-
readonly defaultKeys: ["home", "ctrl+a"];
|
|
85
|
+
readonly defaultKeys: ["home", "ctrl+home", "ctrl+a"];
|
|
78
86
|
readonly description: "Move to line start";
|
|
79
87
|
};
|
|
80
88
|
readonly "tui.editor.cursorLineEnd": {
|
|
81
|
-
readonly defaultKeys: ["end", "ctrl+e"];
|
|
89
|
+
readonly defaultKeys: ["end", "ctrl+end", "ctrl+e"];
|
|
82
90
|
readonly description: "Move to line end";
|
|
83
91
|
};
|
|
84
92
|
readonly "tui.editor.jumpForward": {
|
|
@@ -90,11 +98,11 @@ export declare const KEYBINDINGS: {
|
|
|
90
98
|
readonly description: "Jump backward to character";
|
|
91
99
|
};
|
|
92
100
|
readonly "tui.editor.pageUp": {
|
|
93
|
-
readonly defaultKeys: "pageUp";
|
|
101
|
+
readonly defaultKeys: ["pageUp", "ctrl+pageUp"];
|
|
94
102
|
readonly description: "Page up";
|
|
95
103
|
};
|
|
96
104
|
readonly "tui.editor.pageDown": {
|
|
97
|
-
readonly defaultKeys: "pageDown";
|
|
105
|
+
readonly defaultKeys: ["pageDown", "ctrl+pageDown"];
|
|
98
106
|
readonly description: "Page down";
|
|
99
107
|
};
|
|
100
108
|
readonly "tui.editor.deleteCharBackward": {
|
|
@@ -173,6 +181,38 @@ export declare const KEYBINDINGS: {
|
|
|
173
181
|
readonly defaultKeys: ["escape", "ctrl+c"];
|
|
174
182
|
readonly description: "Cancel selection";
|
|
175
183
|
};
|
|
184
|
+
readonly "tui.altScreen.pageUp": {
|
|
185
|
+
readonly defaultKeys: "pageUp";
|
|
186
|
+
readonly description: "Scroll viewport up one page";
|
|
187
|
+
};
|
|
188
|
+
readonly "tui.altScreen.pageDown": {
|
|
189
|
+
readonly defaultKeys: "pageDown";
|
|
190
|
+
readonly description: "Scroll viewport down one page";
|
|
191
|
+
};
|
|
192
|
+
readonly "tui.altScreen.halfPageUp": {
|
|
193
|
+
readonly defaultKeys: [];
|
|
194
|
+
readonly description: "Scroll viewport up half a page";
|
|
195
|
+
};
|
|
196
|
+
readonly "tui.altScreen.halfPageDown": {
|
|
197
|
+
readonly defaultKeys: [];
|
|
198
|
+
readonly description: "Scroll viewport down half a page";
|
|
199
|
+
};
|
|
200
|
+
readonly "tui.altScreen.previousPrompt": {
|
|
201
|
+
readonly defaultKeys: "ctrl+shift+up";
|
|
202
|
+
readonly description: "Jump to previous semantic prompt";
|
|
203
|
+
};
|
|
204
|
+
readonly "tui.altScreen.nextPrompt": {
|
|
205
|
+
readonly defaultKeys: "ctrl+shift+down";
|
|
206
|
+
readonly description: "Jump to next semantic prompt";
|
|
207
|
+
};
|
|
208
|
+
readonly "tui.altScreen.top": {
|
|
209
|
+
readonly defaultKeys: "home";
|
|
210
|
+
readonly description: "Scroll viewport to top";
|
|
211
|
+
};
|
|
212
|
+
readonly "tui.altScreen.bottom": {
|
|
213
|
+
readonly defaultKeys: "end";
|
|
214
|
+
readonly description: "Scroll viewport to bottom";
|
|
215
|
+
};
|
|
176
216
|
readonly "app.interrupt": {
|
|
177
217
|
readonly defaultKeys: "escape";
|
|
178
218
|
readonly description: "Cancel or abort";
|
|
@@ -85,19 +85,34 @@ export function createLlamaProvider() {
|
|
|
85
85
|
},
|
|
86
86
|
getModels: () => models,
|
|
87
87
|
refreshModels: async (context) => {
|
|
88
|
-
const stored =
|
|
89
|
-
|
|
90
|
-
|
|
88
|
+
const stored = context.stored;
|
|
89
|
+
const restored = stored
|
|
90
|
+
? stored.models.filter((model) => model.provider === LLAMA_PROVIDER_ID && model.api === "openai-completions")
|
|
91
|
+
: [];
|
|
92
|
+
if (!(await context.publish({
|
|
93
|
+
update: () => {
|
|
94
|
+
models = restored;
|
|
95
|
+
},
|
|
96
|
+
}))) {
|
|
97
|
+
return;
|
|
91
98
|
}
|
|
92
|
-
if (!context.allowNetwork || context.signal
|
|
99
|
+
if (!context.allowNetwork || context.signal.aborted || context.credential?.type !== "api_key")
|
|
93
100
|
return;
|
|
94
101
|
const serverUrl = credentialServerUrl(context.credential);
|
|
95
102
|
if (!serverUrl)
|
|
96
103
|
return;
|
|
97
104
|
const catalog = await new LlamaClient(serverUrl, context.credential.key).list({ signal: context.signal });
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
105
|
+
const refreshed = catalog
|
|
106
|
+
.filter((model) => model.status.value === "loaded")
|
|
107
|
+
.map((model) => toPiModel(model, serverUrl));
|
|
108
|
+
if (context.signal.aborted)
|
|
109
|
+
return;
|
|
110
|
+
await context.publish({
|
|
111
|
+
persist: { models: refreshed, checkedAt: Date.now() },
|
|
112
|
+
update: () => {
|
|
113
|
+
models = refreshed;
|
|
114
|
+
},
|
|
115
|
+
});
|
|
101
116
|
},
|
|
102
117
|
stream: (model, context, options) => stream(model, context, options),
|
|
103
118
|
streamSimple: (model, context, options) => streamSimple(model, context, options),
|
|
@@ -35,6 +35,7 @@ export const defaultModelPerProvider = {
|
|
|
35
35
|
huggingface: "moonshotai/Kimi-K2.6",
|
|
36
36
|
fireworks: "accounts/fireworks/models/kimi-k2p6",
|
|
37
37
|
together: "moonshotai/Kimi-K2.6",
|
|
38
|
+
baseten: "zai-org/GLM-5.2",
|
|
38
39
|
opencode: "kimi-k2.6",
|
|
39
40
|
"opencode-go": "kimi-k2.6",
|
|
40
41
|
"kimi-coding": "kimi-for-coding",
|
|
@@ -42,6 +43,7 @@ export const defaultModelPerProvider = {
|
|
|
42
43
|
"cloudflare-ai-gateway": "workers-ai/@cf/moonshotai/kimi-k2.6",
|
|
43
44
|
"qwen-token-plan": "qwen3.7-max",
|
|
44
45
|
"qwen-token-plan-cn": "qwen3.7-max",
|
|
46
|
+
"qwen-token-plan-individual": "qwen3.8-max",
|
|
45
47
|
xiaomi: "mimo-v2.5-pro",
|
|
46
48
|
"xiaomi-token-plan-cn": "mimo-v2.5-pro",
|
|
47
49
|
"xiaomi-token-plan-ams": "mimo-v2.5-pro",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Api, type AssistantMessage, type AssistantMessageEventStream, type AuthCheck, type AuthInteraction, type AuthResult, type AuthType, type Context, type Credential, type CredentialInfo, type CredentialStore, type Model, type Models, type ModelsApiStreamOptions, type ModelsRefreshOptions, type ModelsRefreshResult, type ModelsSimpleStreamOptions, type ModelsStore, type Provider } from "@earendil-works/pi-ai";
|
|
1
|
+
import { type Api, type AssistantMessage, type AssistantMessageEventStream, type AuthCheck, type AuthInteraction, type AuthOperationOptions, type AuthResult, type AuthType, type Context, type Credential, type CredentialInfo, type CredentialStore, type DeferredHandle, type Model, type Models, type ModelsApiStreamOptions, type ModelsDeferredCancelOptions, type ModelsDeferredFetchOptions, type ModelsRefreshOptions, type ModelsRefreshResult, type ModelsSimpleStreamOptions, type ModelsStore, type Provider } from "@earendil-works/pi-ai";
|
|
2
2
|
import { type AuthStatus, type CompatibilityRequestConfig, type ProviderConfigInput } from "./provider-composer.ts";
|
|
3
3
|
export interface CreateModelRuntimeOptions {
|
|
4
4
|
/** Credential storage. Defaults to the file at authPath. */
|
|
@@ -13,7 +13,7 @@ export interface CreateModelRuntimeOptions {
|
|
|
13
13
|
modelRefreshTimeoutMs?: number;
|
|
14
14
|
catalogBaseUrl?: string;
|
|
15
15
|
}
|
|
16
|
-
export interface ModelRuntimeAuthOverrides {
|
|
16
|
+
export interface ModelRuntimeAuthOverrides extends AuthOperationOptions {
|
|
17
17
|
apiKey?: string;
|
|
18
18
|
env?: Record<string, string>;
|
|
19
19
|
/** Require this much remaining OAuth-token validity; defaults to five minutes. */
|
|
@@ -73,6 +73,8 @@ export declare class ModelRuntime implements Models {
|
|
|
73
73
|
complete<TApi extends Api>(model: Model<TApi>, context: Context, options?: ModelsApiStreamOptions<TApi>): Promise<AssistantMessage>;
|
|
74
74
|
streamSimple(model: Model<Api>, context: Context, options?: ModelsSimpleStreamOptions): AssistantMessageEventStream;
|
|
75
75
|
completeSimple(model: Model<Api>, context: Context, options?: ModelsSimpleStreamOptions): Promise<AssistantMessage>;
|
|
76
|
+
fetchDeferred(model: Model<Api>, handle: DeferredHandle, options?: ModelsDeferredFetchOptions): Promise<AssistantMessage>;
|
|
77
|
+
cancelDeferred(model: Model<Api>, handle: DeferredHandle, options?: ModelsDeferredCancelOptions): Promise<void>;
|
|
76
78
|
login(providerId: string, type: AuthType, interaction: AuthInteraction): Promise<Credential>;
|
|
77
79
|
logout(providerId: string): Promise<void>;
|
|
78
80
|
getCredentialStore(): CredentialStore;
|
|
@@ -313,10 +313,15 @@ export class ModelRuntime {
|
|
|
313
313
|
const provider = this.models.getProvider(model.provider);
|
|
314
314
|
if (!provider)
|
|
315
315
|
throw new ModelsError("provider", `Unknown provider: ${model.provider}`);
|
|
316
|
-
const resolution = await this.getAuth(model, {
|
|
316
|
+
const resolution = await this.getAuth(model, {
|
|
317
|
+
apiKey: options?.apiKey,
|
|
318
|
+
env: options?.env,
|
|
319
|
+
signal: options?.signal,
|
|
320
|
+
});
|
|
317
321
|
if (!resolution)
|
|
318
322
|
throw new ModelsError("auth", `Provider is not configured: ${model.provider}`);
|
|
319
|
-
const { transformHeaders, ...
|
|
323
|
+
const { transformHeaders, ...rawProviderOptions } = options ?? {};
|
|
324
|
+
const providerOptions = rawProviderOptions;
|
|
320
325
|
let headers = mergeHeaders(resolution.auth.headers, providerOptions.headers);
|
|
321
326
|
if (transformHeaders)
|
|
322
327
|
headers = await transformHeaders(headers ?? {});
|
|
@@ -352,6 +357,22 @@ export class ModelRuntime {
|
|
|
352
357
|
completeSimple(model, context, options) {
|
|
353
358
|
return this.streamSimple(model, context, options).result();
|
|
354
359
|
}
|
|
360
|
+
async fetchDeferred(model, handle, options) {
|
|
361
|
+
return lazyStream(model, async () => {
|
|
362
|
+
const prepared = await this.prepareRequest(model, options);
|
|
363
|
+
if (!prepared.provider.fetchDeferred) {
|
|
364
|
+
throw new ModelsError("provider", `Provider ${model.provider} does not support deferred responses`);
|
|
365
|
+
}
|
|
366
|
+
return prepared.provider.fetchDeferred(prepared.model, handle, prepared.options);
|
|
367
|
+
}).result();
|
|
368
|
+
}
|
|
369
|
+
async cancelDeferred(model, handle, options) {
|
|
370
|
+
const prepared = await this.prepareRequest(model, options);
|
|
371
|
+
if (!prepared.provider.cancelDeferred) {
|
|
372
|
+
throw new ModelsError("provider", `Provider ${model.provider} does not support deferred responses`);
|
|
373
|
+
}
|
|
374
|
+
await prepared.provider.cancelDeferred(prepared.model, handle, prepared.options);
|
|
375
|
+
}
|
|
355
376
|
async login(providerId, type, interaction) {
|
|
356
377
|
const credential = await this.models.login(providerId, type, interaction);
|
|
357
378
|
await this.refresh({ allowNetwork: this.modelNetworkEnabled });
|
|
@@ -38,77 +38,82 @@ function remoteModels(entry, localGeneratedAt) {
|
|
|
38
38
|
/** Add a persisted pi.dev catalog overlay to a static built-in provider. */
|
|
39
39
|
export function withRemoteCatalog(provider, catalogBaseUrl = DEFAULT_CATALOG_BASE_URL, localGeneratedAt) {
|
|
40
40
|
let dynamicModels = [];
|
|
41
|
-
let inflightRefresh;
|
|
42
41
|
return {
|
|
43
42
|
...provider,
|
|
44
43
|
getModels: () => mergeModels(provider.getModels(), dynamicModels),
|
|
45
|
-
refreshModels: (context) => {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
if (!response.ok) {
|
|
89
|
-
// Transient failure: the cached body and its validator stay valid, so keep the
|
|
90
|
-
// etag and let the next refresh revalidate instead of downloading the catalog.
|
|
91
|
-
await context.store.write({ ...(stored ?? { models: [] }), checkedAt });
|
|
92
|
-
throw new Error(`Model catalog request failed for ${provider.id}: ${response.status}`);
|
|
93
|
-
}
|
|
94
|
-
const refreshed = parseCatalog(provider.id, await response.json());
|
|
95
|
-
const lastModified = Date.parse(response.headers.get("last-modified") ?? "");
|
|
96
|
-
if (context.signal?.aborted)
|
|
97
|
-
return;
|
|
98
|
-
const entry = {
|
|
99
|
-
models: refreshed,
|
|
44
|
+
refreshModels: async (context) => {
|
|
45
|
+
const stored = context.stored;
|
|
46
|
+
const restored = remoteModels(stored, localGeneratedAt).filter((model) => model.provider === provider.id);
|
|
47
|
+
if (!(await context.publish({
|
|
48
|
+
update: () => {
|
|
49
|
+
dynamicModels = restored;
|
|
50
|
+
},
|
|
51
|
+
}))) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (!context.allowNetwork || context.signal.aborted)
|
|
55
|
+
return;
|
|
56
|
+
if (!context.force &&
|
|
57
|
+
stored?.checkedAt !== undefined &&
|
|
58
|
+
stored.lastModified !== undefined &&
|
|
59
|
+
Date.now() - stored.checkedAt < REMOTE_CATALOG_REFRESH_INTERVAL_MS) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
// Only revalidate when a cached body backs the validator, so a 304 can never
|
|
63
|
+
// leave the overlay empty.
|
|
64
|
+
const validator = stored?.models.length ? stored.etag : undefined;
|
|
65
|
+
const url = new URL(`/api/models/providers/${encodeURIComponent(provider.id)}`, catalogBaseUrl);
|
|
66
|
+
const response = await fetch(url, {
|
|
67
|
+
headers: {
|
|
68
|
+
accept: "application/json",
|
|
69
|
+
"User-Agent": getPiUserAgent(VERSION),
|
|
70
|
+
...(validator ? { "if-none-match": validator } : {}),
|
|
71
|
+
},
|
|
72
|
+
signal: context.signal,
|
|
73
|
+
});
|
|
74
|
+
if (context.signal.aborted)
|
|
75
|
+
return;
|
|
76
|
+
const checkedAt = Date.now();
|
|
77
|
+
// Unchanged: dynamicModels already holds the stored overlay, so only the
|
|
78
|
+
// freshness window moves.
|
|
79
|
+
if (response.status === 304 && stored) {
|
|
80
|
+
await context.publish({ persist: { ...stored, checkedAt } });
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (response.status === 404 || response.status === 501) {
|
|
84
|
+
await context.publish({
|
|
85
|
+
persist: {
|
|
86
|
+
...(stored ?? { models: [] }),
|
|
100
87
|
checkedAt,
|
|
101
|
-
lastModified:
|
|
102
|
-
etag:
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
88
|
+
lastModified: 0,
|
|
89
|
+
etag: undefined,
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (!response.ok) {
|
|
95
|
+
// Transient failure: the cached body and its validator stay valid, so keep the
|
|
96
|
+
// etag and let the next refresh revalidate instead of downloading the catalog.
|
|
97
|
+
await context.publish({ persist: { ...(stored ?? { models: [] }), checkedAt } });
|
|
98
|
+
throw new Error(`Model catalog request failed for ${provider.id}: ${response.status}`);
|
|
99
|
+
}
|
|
100
|
+
const refreshed = parseCatalog(provider.id, await response.json());
|
|
101
|
+
const lastModified = Date.parse(response.headers.get("last-modified") ?? "");
|
|
102
|
+
if (context.signal.aborted)
|
|
103
|
+
return;
|
|
104
|
+
const entry = {
|
|
105
|
+
models: refreshed,
|
|
106
|
+
checkedAt,
|
|
107
|
+
lastModified: Number.isNaN(lastModified) ? 0 : lastModified,
|
|
108
|
+
etag: response.headers.get("etag") ?? undefined,
|
|
109
|
+
};
|
|
110
|
+
const published = remoteModels(entry, localGeneratedAt);
|
|
111
|
+
await context.publish({
|
|
112
|
+
persist: entry,
|
|
113
|
+
update: () => {
|
|
114
|
+
dynamicModels = published;
|
|
115
|
+
},
|
|
116
|
+
});
|
|
112
117
|
},
|
|
113
118
|
};
|
|
114
119
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ThinkingLevel } from "@earendil-works/pi-agent-core";
|
|
2
2
|
import type { Transport } from "@earendil-works/pi-ai";
|
|
3
|
+
import type { ScrollViewScrollbar, TuiMode as RendererTuiMode } from "@earendil-works/pi-tui";
|
|
3
4
|
export interface CompactionSettings {
|
|
4
5
|
enabled?: boolean;
|
|
5
6
|
reserveTokens?: number;
|
|
@@ -20,6 +21,7 @@ export interface RetrySettings {
|
|
|
20
21
|
baseDelayMs?: number;
|
|
21
22
|
provider?: ProviderRetrySettings;
|
|
22
23
|
}
|
|
24
|
+
export type TuiMode = RendererTuiMode;
|
|
23
25
|
export interface TerminalSettings {
|
|
24
26
|
showImages?: boolean;
|
|
25
27
|
imageWidthCells?: number;
|
|
@@ -93,6 +95,8 @@ export interface Settings {
|
|
|
93
95
|
themes?: string[];
|
|
94
96
|
agents?: string[];
|
|
95
97
|
terminal?: TerminalSettings;
|
|
98
|
+
tuiMode?: TuiMode;
|
|
99
|
+
fullscreenScrollbar?: ScrollViewScrollbar;
|
|
96
100
|
images?: ImageSettings;
|
|
97
101
|
enabledModels?: string[];
|
|
98
102
|
doubleEscapeAction?: "fork" | "tree" | "none";
|
|
@@ -278,6 +282,10 @@ export declare class SettingsManager {
|
|
|
278
282
|
setClearOnShrink(enabled: boolean): void;
|
|
279
283
|
getShowTerminalProgress(): boolean;
|
|
280
284
|
setShowTerminalProgress(enabled: boolean): void;
|
|
285
|
+
getTuiMode(): TuiMode;
|
|
286
|
+
setTuiMode(mode: TuiMode): void;
|
|
287
|
+
getFullscreenScrollbar(): ScrollViewScrollbar;
|
|
288
|
+
setFullscreenScrollbar(mode: ScrollViewScrollbar): void;
|
|
281
289
|
getImageAutoResize(): boolean;
|
|
282
290
|
setImageAutoResize(enabled: boolean): void;
|
|
283
291
|
getBlockImages(): boolean;
|
|
@@ -816,6 +816,24 @@ export class SettingsManager {
|
|
|
816
816
|
this.markModified("terminal", "showTerminalProgress");
|
|
817
817
|
this.save();
|
|
818
818
|
}
|
|
819
|
+
getTuiMode() {
|
|
820
|
+
// Selesai defaults to Pi's native fullscreen TUI; users may explicitly choose regular.
|
|
821
|
+
return this.settings.tuiMode === "regular" ? "regular" : "fullscreen";
|
|
822
|
+
}
|
|
823
|
+
setTuiMode(mode) {
|
|
824
|
+
this.globalSettings.tuiMode = mode;
|
|
825
|
+
this.markModified("tuiMode");
|
|
826
|
+
this.save();
|
|
827
|
+
}
|
|
828
|
+
getFullscreenScrollbar() {
|
|
829
|
+
const mode = this.settings.fullscreenScrollbar;
|
|
830
|
+
return mode === "always" || mode === "hidden" ? mode : "auto";
|
|
831
|
+
}
|
|
832
|
+
setFullscreenScrollbar(mode) {
|
|
833
|
+
this.globalSettings.fullscreenScrollbar = mode;
|
|
834
|
+
this.markModified("fullscreenScrollbar");
|
|
835
|
+
this.save();
|
|
836
|
+
}
|
|
819
837
|
getImageAutoResize() {
|
|
820
838
|
return this.settings.images?.autoResize ?? true;
|
|
821
839
|
}
|
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
5
|
### Fixed
|
|
6
|
+
- **Guide version-marking never persists an implicit mode** — `markGuideVersionSeen` (version-only writes) preserves an explicit user mode but never writes the fallback default as a user choice, so the guide keeps following the current default instead of locking in `compact`.
|
|
6
7
|
- **Guide defaults to compact mode** — When `selesaiGuide.mode` is unset, the recurring guide opens in compact mode instead of the full tour. Explicit `full`, `compact`, or `off` settings are still honored.
|
|
7
8
|
|
|
8
9
|
### Changed
|
|
9
|
-
- **Upstream 0.12.3 merge (Selesai fork)** — Updated from upstream v0.5.4 to v0.12.3. Adopts the Powerline queue/inbox (`/idea`, `/ideas`, `/queue`, `/ideas next`, `/ideas issue`), compaction-aware queue delivery, `/cd` session directory switching, cost currency and subagent cost accounting, `powerline.separator`/`layout`/`placement`/`disabledSegments`/`welcome` options, git host icons, cached session token stats, strict TypeScript gate, and all upstream fixes through 0.12.3. Selesai-specific features are preserved: the
|
|
10
|
+
- **Upstream 0.12.3 merge (Selesai fork)** — Updated from upstream v0.5.4 to v0.12.3. Adopts the Powerline queue/inbox (`/idea`, `/ideas`, `/queue`, `/ideas next`, `/ideas issue`), compaction-aware queue delivery, `/cd` session directory switching, cost currency and subagent cost accounting, `powerline.separator`/`layout`/`placement`/`disabledSegments`/`welcome` options, git host icons, cached session token stats, strict TypeScript gate, and all upstream fixes through 0.12.3. Selesai-specific features are preserved: the guide overlay, the TPS tracker, session-usage accounting, and mode-aware autocomplete trigger forwarding.
|
|
11
|
+
- **Native fullscreen dock** — Removed the unsupported terminal-split compositor and its fixed-editor/mouse-scroll/chat-jump controls. Powerline status, editor, and widgets now use Pi 0.84.1's native fullscreen dock; Selesai defaults to fullscreen while `tuiMode: "regular"` opts out.
|
|
10
12
|
|
|
11
13
|
|
|
12
14
|
## [0.12.3] - 2026-08-09
|
|
@@ -200,9 +200,17 @@ export function saveGuidePreferences(
|
|
|
200
200
|
if (!settings) return false;
|
|
201
201
|
|
|
202
202
|
const current = getGuidePreferences(settings);
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
203
|
+
const raw = isRecord(settings) && isRecord(settings.selesaiGuide)
|
|
204
|
+
? settings.selesaiGuide
|
|
205
|
+
: {};
|
|
206
|
+
const next: Record<string, unknown> = {};
|
|
207
|
+
if (update.mode !== undefined) {
|
|
208
|
+
next.mode = update.mode;
|
|
209
|
+
} else if (typeof raw.mode === "string") {
|
|
210
|
+
// Version-only writes (markGuideVersionSeen) must preserve an explicit user
|
|
211
|
+
// mode but never persist the fallback default as a user choice.
|
|
212
|
+
next.mode = raw.mode;
|
|
213
|
+
}
|
|
206
214
|
const lastSeenVersion = update.lastSeenVersion === null
|
|
207
215
|
? undefined
|
|
208
216
|
: update.lastSeenVersion ?? current.lastSeenVersion;
|