@oh-my-pi/pi-coding-agent 17.0.0 → 17.0.2
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 +128 -0
- package/dist/cli.js +4006 -3966
- package/dist/types/advisor/advise-tool.d.ts +5 -2
- package/dist/types/advisor/config.d.ts +14 -6
- package/dist/types/advisor/runtime.d.ts +20 -11
- package/dist/types/autolearn/controller.d.ts +1 -0
- package/dist/types/cli-commands.d.ts +14 -1
- package/dist/types/config/model-discovery.d.ts +17 -0
- package/dist/types/config/model-registry.d.ts +4 -0
- package/dist/types/config/model-resolver.d.ts +6 -2
- package/dist/types/config/models-config-schema.d.ts +10 -0
- package/dist/types/config/models-config.d.ts +6 -0
- package/dist/types/config/settings-schema.d.ts +32 -7
- package/dist/types/config/settings.d.ts +50 -0
- package/dist/types/cursor.d.ts +11 -0
- package/dist/types/discovery/helpers.d.ts +5 -2
- package/dist/types/discovery/substitute-plugin-root.d.ts +20 -7
- package/dist/types/exec/bash-executor.d.ts +2 -0
- package/dist/types/extensibility/extensions/managed-timers.d.ts +15 -0
- package/dist/types/extensibility/extensions/runner.d.ts +7 -0
- package/dist/types/extensibility/extensions/types.d.ts +18 -0
- package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +40 -0
- package/dist/types/extensibility/shared-events.d.ts +6 -0
- package/dist/types/extensibility/utils.d.ts +2 -2
- package/dist/types/internal-urls/agent-protocol.d.ts +1 -0
- package/dist/types/lib/xai-http.d.ts +11 -0
- package/dist/types/mcp/transports/stdio.d.ts +13 -1
- package/dist/types/modes/acp/acp-agent.d.ts +3 -0
- package/dist/types/modes/acp/acp-mode.d.ts +6 -2
- package/dist/types/modes/components/advisor-config.d.ts +8 -1
- package/dist/types/modes/components/assistant-message.d.ts +2 -0
- package/dist/types/modes/components/custom-editor-plugin-ctor.test.d.ts +1 -0
- package/dist/types/modes/components/custom-editor.d.ts +23 -1
- package/dist/types/modes/components/hook-editor.d.ts +16 -2
- package/dist/types/modes/components/login-dialog.test.d.ts +1 -0
- package/dist/types/modes/components/model-hub.d.ts +5 -2
- package/dist/types/modes/components/session-selector.d.ts +4 -0
- package/dist/types/modes/components/tool-execution.d.ts +8 -1
- package/dist/types/modes/components/tool-execution.test.d.ts +1 -0
- package/dist/types/modes/controllers/command-controller.d.ts +8 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +3 -1
- package/dist/types/modes/interactive-mode.d.ts +6 -0
- package/dist/types/modes/noninteractive-dispose.test.d.ts +1 -0
- package/dist/types/modes/print-mode.d.ts +1 -1
- package/dist/types/modes/types.d.ts +10 -0
- package/dist/types/modes/utils/transcript-render-helpers.d.ts +2 -2
- package/dist/types/modes/warp-events.d.ts +24 -0
- package/dist/types/modes/warp-events.test.d.ts +1 -0
- package/dist/types/plan-mode/model-transition.d.ts +47 -0
- package/dist/types/plan-mode/model-transition.test.d.ts +1 -0
- package/dist/types/registry/agent-lifecycle.d.ts +26 -1
- package/dist/types/sdk.d.ts +13 -2
- package/dist/types/session/agent-session.d.ts +43 -11
- package/dist/types/session/session-history-format.d.ts +10 -0
- package/dist/types/session/session-manager.d.ts +14 -0
- package/dist/types/session/streaming-output.d.ts +2 -0
- package/dist/types/slash-commands/helpers/active-oauth-account.d.ts +9 -0
- package/dist/types/slash-commands/types.d.ts +4 -4
- package/dist/types/system-prompt.d.ts +1 -1
- package/dist/types/task/label.d.ts +1 -1
- package/dist/types/telemetry-export.d.ts +34 -9
- package/dist/types/tools/approval.d.ts +8 -0
- package/dist/types/tools/bash.d.ts +2 -0
- package/dist/types/tools/browser/registry.d.ts +7 -3
- package/dist/types/tools/browser/tab-supervisor.d.ts +2 -0
- package/dist/types/tools/essential-tools.d.ts +29 -0
- package/dist/types/tools/image-gen.d.ts +2 -1
- package/dist/types/tools/index.d.ts +4 -1
- package/dist/types/tools/manage-skill.d.ts +2 -0
- package/dist/types/tools/xdev.d.ts +11 -2
- package/dist/types/utils/title-generator.d.ts +2 -1
- package/dist/types/web/search/index.d.ts +2 -0
- package/dist/types/web/search/providers/base.d.ts +3 -0
- package/dist/types/web/search/providers/kimi.d.ts +4 -1
- package/dist/types/web/search/types.d.ts +1 -1
- package/package.json +21 -16
- package/src/advisor/__tests__/advisor.test.ts +1358 -86
- package/src/advisor/__tests__/config.test.ts +58 -2
- package/src/advisor/advise-tool.ts +7 -3
- package/src/advisor/config.ts +76 -24
- package/src/advisor/runtime.ts +445 -92
- package/src/autolearn/controller.ts +23 -28
- package/src/cli/bench-cli.ts +4 -1
- package/src/cli/grep-cli.ts +2 -1
- package/src/cli-commands.ts +57 -23
- package/src/cli.ts +5 -1
- package/src/config/model-discovery.ts +81 -21
- package/src/config/model-registry.ts +34 -6
- package/src/config/model-resolver.ts +57 -12
- package/src/config/models-config-schema.ts +1 -0
- package/src/config/settings-schema.ts +43 -6
- package/src/config/settings.ts +405 -25
- package/src/cursor.ts +20 -3
- package/src/debug/report-bundle.ts +40 -4
- package/src/discovery/codex.ts +14 -4
- package/src/discovery/helpers.ts +28 -5
- package/src/discovery/substitute-plugin-root.ts +23 -7
- package/src/exec/bash-executor.ts +14 -5
- package/src/export/html/template.js +2 -0
- package/src/extensibility/custom-tools/loader.ts +3 -3
- package/src/extensibility/custom-tools/wrapper.ts +2 -1
- package/src/extensibility/extensions/loader.ts +3 -3
- package/src/extensibility/extensions/managed-timers.ts +83 -0
- package/src/extensibility/extensions/runner.ts +26 -0
- package/src/extensibility/extensions/types.ts +18 -0
- package/src/extensibility/extensions/wrapper.ts +2 -1
- package/src/extensibility/hooks/loader.ts +3 -3
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +96 -1
- package/src/extensibility/plugins/legacy-pi-compat.ts +225 -22
- package/src/extensibility/plugins/manager.ts +2 -2
- package/src/extensibility/shared-events.ts +6 -0
- package/src/extensibility/utils.ts +91 -25
- package/src/internal-urls/__tests__/agent-protocol-nested.test.ts +73 -0
- package/src/internal-urls/agent-protocol.ts +73 -39
- package/src/irc/bus.ts +22 -3
- package/src/launch/broker.ts +3 -2
- package/src/launch/client.ts +2 -2
- package/src/launch/presence.ts +2 -2
- package/src/lib/xai-http.ts +28 -2
- package/src/lsp/client.ts +1 -1
- package/src/main.ts +11 -8
- package/src/mcp/manager.ts +9 -3
- package/src/mcp/transports/stdio.ts +103 -23
- package/src/modes/acp/acp-agent.ts +28 -9
- package/src/modes/acp/acp-event-mapper.ts +8 -0
- package/src/modes/acp/acp-mode.ts +18 -4
- package/src/modes/components/advisor-config.ts +65 -3
- package/src/modes/components/ask-dialog.ts +1 -1
- package/src/modes/components/assistant-message.ts +35 -14
- package/src/modes/components/chat-transcript-builder.ts +2 -0
- package/src/modes/components/custom-editor-plugin-ctor.test.ts +36 -0
- package/src/modes/components/custom-editor.ts +62 -1
- package/src/modes/components/hook-editor.ts +35 -5
- package/src/modes/components/login-dialog.test.ts +56 -0
- package/src/modes/components/login-dialog.ts +7 -3
- package/src/modes/components/model-hub.ts +138 -42
- package/src/modes/components/plan-review-overlay.ts +51 -10
- package/src/modes/components/session-selector.ts +10 -0
- package/src/modes/components/status-line/component.test.ts +1 -0
- package/src/modes/components/status-line/component.ts +2 -4
- package/src/modes/components/status-line/segments.ts +21 -6
- package/src/modes/components/tool-execution.test.ts +101 -0
- package/src/modes/components/tool-execution.ts +117 -9
- package/src/modes/components/transcript-container.ts +30 -2
- package/src/modes/controllers/command-controller-shared.ts +1 -1
- package/src/modes/controllers/command-controller.ts +168 -48
- package/src/modes/controllers/event-controller.ts +6 -0
- package/src/modes/controllers/extension-ui-controller.ts +4 -22
- package/src/modes/controllers/input-controller.ts +12 -12
- package/src/modes/controllers/selector-controller.ts +208 -46
- package/src/modes/interactive-mode.ts +205 -65
- package/src/modes/noninteractive-dispose.test.ts +60 -0
- package/src/modes/print-mode.ts +10 -5
- package/src/modes/rpc/host-tools.ts +2 -1
- package/src/modes/rpc/rpc-mode.ts +31 -7
- package/src/modes/theme/theme.ts +2 -2
- package/src/modes/types.ts +10 -0
- package/src/modes/utils/interactive-context-helpers.ts +3 -1
- package/src/modes/utils/transcript-render-helpers.ts +3 -2
- package/src/modes/warp-events.test.ts +794 -0
- package/src/modes/warp-events.ts +232 -0
- package/src/plan-mode/model-transition.test.ts +60 -0
- package/src/plan-mode/model-transition.ts +51 -0
- package/src/prompts/system/system-prompt.md +1 -1
- package/src/registry/agent-lifecycle.ts +133 -18
- package/src/sdk.ts +227 -44
- package/src/session/agent-session.ts +1364 -382
- package/src/session/session-history-format.ts +20 -5
- package/src/session/session-listing.ts +6 -5
- package/src/session/session-loader.ts +9 -0
- package/src/session/session-manager.ts +48 -0
- package/src/session/session-persistence.ts +11 -0
- package/src/session/streaming-output.ts +3 -1
- package/src/slash-commands/builtin-registry.ts +9 -0
- package/src/slash-commands/helpers/active-oauth-account.ts +16 -0
- package/src/slash-commands/types.ts +4 -4
- package/src/system-prompt.ts +2 -2
- package/src/task/executor.ts +1 -1
- package/src/task/label.ts +2 -0
- package/src/telemetry-export.ts +453 -97
- package/src/tools/approval.ts +11 -0
- package/src/tools/bash.ts +71 -38
- package/src/tools/browser/registry.ts +9 -2
- package/src/tools/browser/tab-supervisor.ts +65 -10
- package/src/tools/browser.ts +4 -3
- package/src/tools/essential-tools.ts +45 -0
- package/src/tools/eval.ts +3 -0
- package/src/tools/gh.ts +169 -2
- package/src/tools/image-gen.ts +624 -513
- package/src/tools/index.ts +10 -6
- package/src/tools/manage-skill.ts +5 -3
- package/src/tools/output-meta.ts +7 -0
- package/src/tools/path-utils.ts +8 -5
- package/src/tools/read.ts +48 -3
- package/src/tools/write.ts +22 -4
- package/src/tools/xdev.ts +14 -3
- package/src/utils/title-generator.ts +15 -4
- package/src/web/search/index.ts +21 -6
- package/src/web/search/providers/base.ts +3 -0
- package/src/web/search/providers/kimi.ts +18 -12
- package/src/web/search/providers/xai.ts +40 -9
- package/src/web/search/types.ts +6 -1
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* (Ctrl+Q / Ctrl+Enter) submits, bordered popup
|
|
8
8
|
* - Prompt-style (ask): Enter submits, Shift+Enter inserts newline, legacy ask chrome
|
|
9
9
|
*/
|
|
10
|
-
import { Container, Editor, matchesKey, Spacer, Text, type TUI } from "@oh-my-pi/pi-tui";
|
|
10
|
+
import { Container, Editor, type Focusable, matchesKey, Spacer, Text, type TUI } from "@oh-my-pi/pi-tui";
|
|
11
11
|
import { getEditorTheme, theme } from "../../modes/theme/theme";
|
|
12
12
|
import {
|
|
13
13
|
matchesAppExternalEditor,
|
|
@@ -20,14 +20,24 @@ import { DynamicBorder } from "./dynamic-border";
|
|
|
20
20
|
export interface HookEditorOptions {
|
|
21
21
|
/** When true, use prompt-style keybindings with the legacy ask prompt chrome. */
|
|
22
22
|
promptStyle?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Max rows the inner Editor may occupy. When omitted, the editor is
|
|
25
|
+
* bounded to the current terminal height minus the component's chrome
|
|
26
|
+
* (≈10 rows) so long content scrolls instead of pushing the submit
|
|
27
|
+
* hint out of view.
|
|
28
|
+
*/
|
|
29
|
+
maxHeight?: number;
|
|
23
30
|
}
|
|
24
31
|
|
|
25
|
-
|
|
32
|
+
/** Interactive multiline dialog used by hooks and the ask tool's Other response. */
|
|
33
|
+
export class HookEditorComponent extends Container implements Focusable {
|
|
26
34
|
#editor: Editor;
|
|
27
35
|
#onSubmitCallback: (value: string) => void;
|
|
28
36
|
#onCancelCallback: () => void;
|
|
29
37
|
#tui: TUI;
|
|
30
38
|
#promptStyle: boolean;
|
|
39
|
+
/** Focus state mirrored to the nested editor during rendering. */
|
|
40
|
+
focused = false;
|
|
31
41
|
|
|
32
42
|
constructor(
|
|
33
43
|
tui: TUI,
|
|
@@ -47,8 +57,11 @@ export class HookEditorComponent extends Container {
|
|
|
47
57
|
this.addChild(new DynamicBorder());
|
|
48
58
|
this.addChild(new Spacer(1));
|
|
49
59
|
|
|
50
|
-
// Title
|
|
51
|
-
|
|
60
|
+
// Title. Prompt-style renders the borderless editor's `> ` gutter at
|
|
61
|
+
// column 0, so pad the title to match; hook-style keeps the 1-col indent
|
|
62
|
+
// that lines up with its bordered editor body (#5313).
|
|
63
|
+
const chromePadX = this.#promptStyle ? 0 : 1;
|
|
64
|
+
this.addChild(new Text(theme.fg("accent", title), chromePadX, 0));
|
|
52
65
|
this.addChild(new Spacer(1));
|
|
53
66
|
|
|
54
67
|
// Editor
|
|
@@ -58,6 +71,11 @@ export class HookEditorComponent extends Container {
|
|
|
58
71
|
this.#editor.setPromptGutter("> ");
|
|
59
72
|
this.#editor.disableSubmit = true;
|
|
60
73
|
}
|
|
74
|
+
// Bound the editor so long content scrolls instead of pushing the
|
|
75
|
+
// submit hint off-screen. Caller may override via options.maxHeight.
|
|
76
|
+
const termRows = this.#tui.terminal?.rows ?? process.stdout.rows ?? 40;
|
|
77
|
+
this.#editor.setMaxHeight(options?.maxHeight ?? Math.max(3, termRows - 12));
|
|
78
|
+
this.#editor.setScrollbarVisible(true);
|
|
61
79
|
if (prefill) {
|
|
62
80
|
this.#editor.setText(prefill);
|
|
63
81
|
}
|
|
@@ -69,12 +87,24 @@ export class HookEditorComponent extends Container {
|
|
|
69
87
|
const hint = this.#promptStyle
|
|
70
88
|
? "enter or ctrl+q submit esc cancel ctrl+g external editor"
|
|
71
89
|
: "ctrl+q/ctrl+enter submit esc cancel ctrl+g external editor";
|
|
72
|
-
this.addChild(new Text(theme.fg("dim", hint),
|
|
90
|
+
this.addChild(new Text(theme.fg("dim", hint), chromePadX, 0));
|
|
73
91
|
|
|
74
92
|
this.addChild(new Spacer(1));
|
|
75
93
|
this.addChild(new DynamicBorder());
|
|
76
94
|
}
|
|
77
95
|
|
|
96
|
+
/** Keep the nested editor's software/hardware cursor mode aligned with the dialog focus target. */
|
|
97
|
+
setUseTerminalCursor(useTerminalCursor: boolean): void {
|
|
98
|
+
if (this.#editor.getUseTerminalCursor() === useTerminalCursor) return;
|
|
99
|
+
this.#editor.setUseTerminalCursor(useTerminalCursor);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Render the dialog after forwarding its focus state to the nested editor. */
|
|
103
|
+
override render(width: number): readonly string[] {
|
|
104
|
+
this.#editor.focused = this.focused;
|
|
105
|
+
return super.render(width);
|
|
106
|
+
}
|
|
107
|
+
|
|
78
108
|
handleInput(keyData: string): void {
|
|
79
109
|
if (this.#promptStyle) {
|
|
80
110
|
this.#handlePromptStyleInput(keyData);
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { beforeAll, describe, expect, it } from "bun:test";
|
|
2
|
+
import type { TUI } from "@oh-my-pi/pi-tui";
|
|
3
|
+
import { initTheme } from "../theme/theme";
|
|
4
|
+
import { LoginDialogComponent } from "./login-dialog";
|
|
5
|
+
|
|
6
|
+
/** Minimal TUI stub — the dialog only calls requestRender/setFocus. */
|
|
7
|
+
function makeDialog(): LoginDialogComponent {
|
|
8
|
+
const tui = { requestRender() {}, setFocus() {} } as unknown as TUI;
|
|
9
|
+
return new LoginDialogComponent(tui, "openai-codex", () => {});
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
describe("LoginDialogComponent manual code input", () => {
|
|
13
|
+
beforeAll(async () => {
|
|
14
|
+
await initTheme();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("captures a pasted fallback redirect URL and resolves on submit", async () => {
|
|
18
|
+
// Regression for #5339: paste-code providers (Codex) route the fallback
|
|
19
|
+
// URL through the focused dialog. Without a mounted input, the paste is
|
|
20
|
+
// dropped and login never completes.
|
|
21
|
+
const dialog = makeDialog();
|
|
22
|
+
dialog.showProgress("Waiting for callback");
|
|
23
|
+
|
|
24
|
+
const pending = dialog.showManualInput("Paste the authorization code:");
|
|
25
|
+
expect(dialog.render(80).join("\n")).toContain("Paste the authorization code");
|
|
26
|
+
|
|
27
|
+
const url = "http://localhost:1455/auth/callback?code=THECODE&state=abc";
|
|
28
|
+
dialog.pasteText(url);
|
|
29
|
+
dialog.handleInput("\r");
|
|
30
|
+
|
|
31
|
+
expect(await pending).toBe(url);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("reuses the mounted input across re-prompts instead of stacking duplicates", async () => {
|
|
35
|
+
// The OAuth callback loop re-invokes onManualCodeInput after an invalid
|
|
36
|
+
// paste; the second prompt must not append a duplicate input/hint block.
|
|
37
|
+
const dialog = makeDialog();
|
|
38
|
+
dialog.showProgress("Waiting for callback");
|
|
39
|
+
|
|
40
|
+
const first = dialog.showManualInput("Paste the code:");
|
|
41
|
+
dialog.handleInput("garbage");
|
|
42
|
+
dialog.handleInput("\r");
|
|
43
|
+
expect(await first).toBe("garbage");
|
|
44
|
+
|
|
45
|
+
const second = dialog.showManualInput("Paste the code:");
|
|
46
|
+
const rendered = dialog.render(80).join("\n");
|
|
47
|
+
expect(rendered.split("Paste the code:").length - 1).toBe(1);
|
|
48
|
+
// A stale value from the first attempt must not leak into the retry.
|
|
49
|
+
expect(rendered).not.toContain("garbage");
|
|
50
|
+
|
|
51
|
+
const url = "http://localhost:1455/auth/callback?code=OK&state=abc";
|
|
52
|
+
dialog.pasteText(url);
|
|
53
|
+
dialog.handleInput("\r");
|
|
54
|
+
expect(await second).toBe(url);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -108,12 +108,16 @@ export class LoginDialogComponent extends Container {
|
|
|
108
108
|
* Show input for manual code/URL entry (for callback server providers)
|
|
109
109
|
*/
|
|
110
110
|
showManualInput(prompt: string): Promise<string> {
|
|
111
|
-
|
|
112
|
-
|
|
111
|
+
// Invalid pastes re-prompt (the OAuth callback loop calls this again), so
|
|
112
|
+
// reuse the already-mounted input instead of stacking duplicate prompt and
|
|
113
|
+
// hint lines beneath the dialog. Reset the value so each retry starts clean.
|
|
113
114
|
if (!this.#contentContainer.children.includes(this.#input)) {
|
|
115
|
+
this.#contentContainer.addChild(new Spacer(1));
|
|
116
|
+
this.#contentContainer.addChild(new Text(theme.fg("dim", prompt), 1, 0));
|
|
114
117
|
this.#contentContainer.addChild(this.#input);
|
|
118
|
+
this.#contentContainer.addChild(new Text(theme.fg("dim", "(Escape to cancel)"), 1, 0));
|
|
115
119
|
}
|
|
116
|
-
this.#
|
|
120
|
+
this.#input.setValue("");
|
|
117
121
|
this.#tui.requestRender();
|
|
118
122
|
|
|
119
123
|
const { promise, resolve, reject } = Promise.withResolvers<string>();
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
visibleWidth,
|
|
29
29
|
} from "@oh-my-pi/pi-tui";
|
|
30
30
|
import type { ModelRegistry } from "../../config/model-registry";
|
|
31
|
+
import { type ModelRoleLookup, type ResolvedModelRoleValue, resolveModelRoleValue } from "../../config/model-resolver";
|
|
31
32
|
import { getKnownRoleIds, getRoleInfo } from "../../config/model-roles";
|
|
32
33
|
import type { Settings } from "../../config/settings";
|
|
33
34
|
import { AUTO_THINKING, type ConfiguredThinkingLevel, getConfiguredThinkingLevelMetadata } from "../../thinking";
|
|
@@ -75,11 +76,19 @@ export interface ScopedModelItem {
|
|
|
75
76
|
thinkingLevel?: string;
|
|
76
77
|
}
|
|
77
78
|
|
|
79
|
+
export type ModelRoleSelectionScope = "global" | "project";
|
|
80
|
+
|
|
78
81
|
export interface ModelHubCallbacks {
|
|
79
82
|
/** Persist a role assignment. */
|
|
80
|
-
onAssign: (
|
|
83
|
+
onAssign: (
|
|
84
|
+
model: Model,
|
|
85
|
+
role: string,
|
|
86
|
+
thinkingLevel: ConfiguredThinkingLevel | undefined,
|
|
87
|
+
selector: string,
|
|
88
|
+
scope?: ModelRoleSelectionScope,
|
|
89
|
+
) => void;
|
|
81
90
|
/** Clear a configured role back to auto-selection. */
|
|
82
|
-
onUnassign: (role: string) => void;
|
|
91
|
+
onUnassign: (role: string, scope?: ModelRoleSelectionScope) => void;
|
|
83
92
|
/** Persist a `retry.fallbackChains` entry — keyed by a role, `provider/model-id`, or `provider/*`; an empty chain clears the key. */
|
|
84
93
|
onFallbackChainChange?: (role: string, chain: string[]) => void;
|
|
85
94
|
/** Locked provider activation: forward to the /login flow. */
|
|
@@ -111,18 +120,20 @@ interface StripChip {
|
|
|
111
120
|
/** Pre-styled label body (without selection decoration). */
|
|
112
121
|
styled: string;
|
|
113
122
|
role?: string;
|
|
114
|
-
action: "assign" | "unassign" | "fallback" | "fallbackModel" | "fallbackProvider" | "thinking";
|
|
123
|
+
action: "assign" | "unassign" | "fallback" | "fallbackModel" | "fallbackProvider" | "scope" | "thinking";
|
|
115
124
|
thinkingLevel?: ConfiguredThinkingLevel;
|
|
125
|
+
scope?: ModelRoleSelectionScope;
|
|
116
126
|
}
|
|
117
127
|
|
|
118
128
|
type StripState =
|
|
119
129
|
| {
|
|
120
|
-
kind: "role" | "thinking";
|
|
130
|
+
kind: "role" | "scope" | "thinking";
|
|
121
131
|
item: ModelBrowserItem;
|
|
122
132
|
role?: string;
|
|
133
|
+
scope?: ModelRoleSelectionScope;
|
|
123
134
|
chips: StripChip[];
|
|
124
135
|
index: number;
|
|
125
|
-
/** Where to land when a thinking strip closes. */
|
|
136
|
+
/** Where to land when a scope or thinking strip closes. */
|
|
126
137
|
returnToRoles: boolean;
|
|
127
138
|
}
|
|
128
139
|
| {
|
|
@@ -556,6 +567,11 @@ export class ModelHubComponent implements Component {
|
|
|
556
567
|
this.#tui.requestRender();
|
|
557
568
|
}
|
|
558
569
|
|
|
570
|
+
/** Re-sync after an asynchronous callback finishes mutating settings. */
|
|
571
|
+
refreshAfterExternalMutation(): void {
|
|
572
|
+
this.#refreshAfterMutation();
|
|
573
|
+
}
|
|
574
|
+
|
|
559
575
|
/**
|
|
560
576
|
* Recompute per-provider match counts for the active query. Providers
|
|
561
577
|
* without matches gray out and the scope hop skips them; a provider scope
|
|
@@ -745,23 +761,55 @@ export class ModelHubComponent implements Component {
|
|
|
745
761
|
this.#openRoleStrip(item);
|
|
746
762
|
}
|
|
747
763
|
|
|
764
|
+
#roleForScope(role: string, scope: ModelRoleSelectionScope): ResolvedModelRoleValue {
|
|
765
|
+
const roleValue =
|
|
766
|
+
scope === "project" ? this.#settings.getProjectModelRole(role) : this.#settings.getGlobalModelRole(role);
|
|
767
|
+
const allModels =
|
|
768
|
+
this.#scopedModels.length > 0 ? this.#scopedModels.map(scoped => scoped.model) : this.#registry.getAll();
|
|
769
|
+
const roleLookup: ModelRoleLookup = {
|
|
770
|
+
getModelRole: scopedRole =>
|
|
771
|
+
scope === "project"
|
|
772
|
+
? (this.#settings.getProjectModelRole(scopedRole) ?? this.#settings.getGlobalModelRole(scopedRole))
|
|
773
|
+
: this.#settings.getGlobalModelRole(scopedRole),
|
|
774
|
+
};
|
|
775
|
+
return resolveModelRoleValue(roleValue, allModels, { settings: this.#settings, roleLookup });
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
#thinkingLevelForScope(role: string, scope: ModelRoleSelectionScope): ConfiguredThinkingLevel {
|
|
779
|
+
const resolved = this.#roleForScope(role, scope);
|
|
780
|
+
return resolved.explicitThinkingLevel ? (resolved.thinkingLevel ?? ThinkingLevel.Inherit) : ThinkingLevel.Inherit;
|
|
781
|
+
}
|
|
782
|
+
|
|
748
783
|
/** Persist `role → item`, preserving a still-supported thinking level, then open the thinking strip. */
|
|
749
|
-
#assignRole(item: ModelBrowserItem, role: string, returnToRoles: boolean): void {
|
|
784
|
+
#assignRole(item: ModelBrowserItem, role: string, returnToRoles: boolean, scope?: ModelRoleSelectionScope): void {
|
|
785
|
+
if (this.#settings.get("modelRoleStorage") === "project" && scope === undefined) {
|
|
786
|
+
this.#openScopeStrip(item, role, returnToRoles);
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
|
|
750
790
|
const current = this.#roles[role];
|
|
751
791
|
let level: ConfiguredThinkingLevel = ThinkingLevel.Inherit;
|
|
752
|
-
if (
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
792
|
+
if (this.#settings.get("modelRoleStorage") === "project" && scope !== undefined) {
|
|
793
|
+
level = this.#thinkingLevelForScope(role, scope);
|
|
794
|
+
} else if (current && !current.autoSelected) {
|
|
795
|
+
level = current.thinkingLevel;
|
|
796
|
+
}
|
|
797
|
+
const supported = this.#thinkingOptionsFor(item.model);
|
|
798
|
+
if (!supported.includes(level)) level = ThinkingLevel.Inherit;
|
|
799
|
+
this.#callbacks.onAssign(item.model, role, level, item.selector, scope);
|
|
757
800
|
this.#refreshAfterMutation();
|
|
758
|
-
this.#openThinkingStrip(item, role, returnToRoles);
|
|
801
|
+
this.#openThinkingStrip(item, role, returnToRoles, scope);
|
|
759
802
|
}
|
|
760
803
|
|
|
761
804
|
#unassignRole(role: string): void {
|
|
762
805
|
const assignment = this.#roles[role];
|
|
763
806
|
if (!assignment || assignment.autoSelected) return;
|
|
764
|
-
this.#
|
|
807
|
+
if (this.#settings.get("modelRoleStorage") === "project") {
|
|
808
|
+
const source = this.#settings.getModelRoleSource(role);
|
|
809
|
+
this.#callbacks.onUnassign(role, source === "default" ? undefined : source);
|
|
810
|
+
} else {
|
|
811
|
+
this.#callbacks.onUnassign(role);
|
|
812
|
+
}
|
|
765
813
|
this.#refreshAfterMutation();
|
|
766
814
|
}
|
|
767
815
|
|
|
@@ -771,24 +819,32 @@ export class ModelHubComponent implements Component {
|
|
|
771
819
|
|
|
772
820
|
#openRoleStrip(item: ModelBrowserItem): void {
|
|
773
821
|
const chips: StripChip[] = [];
|
|
822
|
+
const scopedStorage = this.#settings.get("modelRoleStorage") === "project";
|
|
823
|
+
const scopes: readonly ModelRoleSelectionScope[] = scopedStorage ? ["project", "global"] : ["global"];
|
|
774
824
|
for (const role of this.#visibleRoleIds()) {
|
|
775
825
|
const info = getRoleInfo(role, this.#settings);
|
|
776
826
|
const assignment = this.#roles[role];
|
|
777
|
-
const
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
827
|
+
for (const scope of scopes) {
|
|
828
|
+
const scopedModel = scopedStorage
|
|
829
|
+
? this.#roleForScope(role, scope).model
|
|
830
|
+
: assignment && !assignment.autoSelected
|
|
831
|
+
? assignment.model
|
|
832
|
+
: undefined;
|
|
833
|
+
const assignedHere =
|
|
834
|
+
!!scopedModel && scopedModel.provider === item.model.provider && scopedModel.id === item.model.id;
|
|
835
|
+
const roleLabel = (info.tag ?? info.name ?? role).toLowerCase();
|
|
836
|
+
const label = scopedStorage ? `${scope} ${roleLabel}` : roleLabel;
|
|
837
|
+
chips.push({
|
|
838
|
+
label,
|
|
839
|
+
styled: assignedHere
|
|
840
|
+
? theme.fg(info.color ?? "muted", `${theme.status.enabled}${label}`) +
|
|
841
|
+
theme.fg("dim", ` ${theme.status.success}`)
|
|
842
|
+
: theme.fg(info.color ?? "muted", label),
|
|
843
|
+
role,
|
|
844
|
+
scope,
|
|
845
|
+
action: assignedHere ? "unassign" : "assign",
|
|
846
|
+
});
|
|
847
|
+
}
|
|
792
848
|
}
|
|
793
849
|
chips.push({
|
|
794
850
|
label: `fallbacks:${item.model.id}`,
|
|
@@ -804,9 +860,25 @@ export class ModelHubComponent implements Component {
|
|
|
804
860
|
this.#strip = { kind: "role", item, chips, index: 0, returnToRoles: false };
|
|
805
861
|
}
|
|
806
862
|
|
|
807
|
-
#
|
|
863
|
+
#openScopeStrip(item: ModelBrowserItem, role: string, returnToRoles: boolean): void {
|
|
864
|
+
const chips: StripChip[] = [
|
|
865
|
+
{ label: "project", styled: theme.fg("accent", "project"), action: "scope", scope: "project" },
|
|
866
|
+
{ label: "global", styled: theme.fg("muted", "global"), action: "scope", scope: "global" },
|
|
867
|
+
];
|
|
868
|
+
this.#strip = { kind: "scope", item, role, chips, index: 0, returnToRoles };
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
#openThinkingStrip(
|
|
872
|
+
item: ModelBrowserItem,
|
|
873
|
+
role: string,
|
|
874
|
+
returnToRoles: boolean,
|
|
875
|
+
scope?: ModelRoleSelectionScope,
|
|
876
|
+
): void {
|
|
808
877
|
const options = this.#thinkingOptionsFor(item.model);
|
|
809
|
-
const current =
|
|
878
|
+
const current =
|
|
879
|
+
this.#settings.get("modelRoleStorage") === "project" && scope !== undefined
|
|
880
|
+
? this.#thinkingLevelForScope(role, scope)
|
|
881
|
+
: (this.#roles[role]?.thinkingLevel ?? ThinkingLevel.Inherit);
|
|
810
882
|
const chips: StripChip[] = options.map(level => {
|
|
811
883
|
const label = getConfiguredThinkingLevelMetadata(level).label;
|
|
812
884
|
const glyph = thinkingLevelGlyph(level);
|
|
@@ -822,6 +894,7 @@ export class ModelHubComponent implements Component {
|
|
|
822
894
|
kind: "thinking",
|
|
823
895
|
item,
|
|
824
896
|
role,
|
|
897
|
+
scope,
|
|
825
898
|
chips,
|
|
826
899
|
index: preselect >= 0 ? preselect : 0,
|
|
827
900
|
returnToRoles,
|
|
@@ -832,7 +905,7 @@ export class ModelHubComponent implements Component {
|
|
|
832
905
|
const strip = this.#strip;
|
|
833
906
|
this.#strip = null;
|
|
834
907
|
this.#chipRanges = [];
|
|
835
|
-
if (strip?.kind === "thinking" && strip.returnToRoles) {
|
|
908
|
+
if ((strip?.kind === "scope" || strip?.kind === "thinking") && strip.returnToRoles) {
|
|
836
909
|
this.#setActiveEntry("roles");
|
|
837
910
|
this.#focus = "list";
|
|
838
911
|
}
|
|
@@ -847,12 +920,16 @@ export class ModelHubComponent implements Component {
|
|
|
847
920
|
case "assign":
|
|
848
921
|
if (chip.role) {
|
|
849
922
|
this.#strip = null;
|
|
850
|
-
this.#assignRole(strip.item, chip.role, false);
|
|
923
|
+
this.#assignRole(strip.item, chip.role, false, chip.scope);
|
|
851
924
|
}
|
|
852
925
|
return;
|
|
853
926
|
case "unassign":
|
|
854
927
|
if (chip.role) {
|
|
855
|
-
this.#
|
|
928
|
+
if (this.#settings.get("modelRoleStorage") === "project") {
|
|
929
|
+
this.#callbacks.onUnassign(chip.role, chip.scope);
|
|
930
|
+
} else {
|
|
931
|
+
this.#callbacks.onUnassign(chip.role);
|
|
932
|
+
}
|
|
856
933
|
this.#refreshAfterMutation();
|
|
857
934
|
}
|
|
858
935
|
this.#closeStrip();
|
|
@@ -869,9 +946,21 @@ export class ModelHubComponent implements Component {
|
|
|
869
946
|
this.#closeStrip();
|
|
870
947
|
this.#startAssignFallback(`${strip.item.model.provider}/*`, null);
|
|
871
948
|
return;
|
|
949
|
+
case "scope":
|
|
950
|
+
if (strip.role && chip.scope) {
|
|
951
|
+
this.#strip = null;
|
|
952
|
+
this.#assignRole(strip.item, strip.role, strip.returnToRoles, chip.scope);
|
|
953
|
+
}
|
|
954
|
+
return;
|
|
872
955
|
case "thinking":
|
|
873
956
|
if (strip.role && chip.thinkingLevel !== undefined) {
|
|
874
|
-
this.#callbacks.onAssign(
|
|
957
|
+
this.#callbacks.onAssign(
|
|
958
|
+
strip.item.model,
|
|
959
|
+
strip.role,
|
|
960
|
+
chip.thinkingLevel,
|
|
961
|
+
strip.item.selector,
|
|
962
|
+
strip.scope,
|
|
963
|
+
);
|
|
875
964
|
this.#refreshAfterMutation();
|
|
876
965
|
}
|
|
877
966
|
this.#closeStrip();
|
|
@@ -1305,13 +1394,20 @@ export class ModelHubComponent implements Component {
|
|
|
1305
1394
|
if (printable === "t") {
|
|
1306
1395
|
const assignment = role ? this.#roles[role] : undefined;
|
|
1307
1396
|
if (role && assignment) {
|
|
1397
|
+
const source =
|
|
1398
|
+
this.#settings.get("modelRoleStorage") === "project"
|
|
1399
|
+
? this.#settings.getModelRoleSource(role)
|
|
1400
|
+
: "default";
|
|
1401
|
+
const scope = source === "project" || source === "global" ? source : undefined;
|
|
1402
|
+
const scopedModel = scope ? this.#roleForScope(role, scope).model : assignment.model;
|
|
1403
|
+
if (!scopedModel) return;
|
|
1308
1404
|
const item: ModelBrowserItem = {
|
|
1309
|
-
provider:
|
|
1310
|
-
id:
|
|
1311
|
-
model:
|
|
1312
|
-
selector: `${
|
|
1405
|
+
provider: scopedModel.provider,
|
|
1406
|
+
id: scopedModel.id,
|
|
1407
|
+
model: scopedModel,
|
|
1408
|
+
selector: `${scopedModel.provider}/${scopedModel.id}`,
|
|
1313
1409
|
};
|
|
1314
|
-
this.#openThinkingStrip(item, role, true);
|
|
1410
|
+
this.#openThinkingStrip(item, role, true, scope);
|
|
1315
1411
|
}
|
|
1316
1412
|
return;
|
|
1317
1413
|
}
|
|
@@ -1770,9 +1866,9 @@ export class ModelHubComponent implements Component {
|
|
|
1770
1866
|
if (strip.kind === "roleName") {
|
|
1771
1867
|
return "Enter create + pick model · Esc cancel";
|
|
1772
1868
|
}
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1869
|
+
if (strip.kind === "role") return "←/→ choose · Enter assign/clear · Esc cancel";
|
|
1870
|
+
if (strip.kind === "scope") return "←/→ save scope · Enter choose · Esc cancel";
|
|
1871
|
+
return "←/→ thinking level · Enter apply · Esc keep";
|
|
1776
1872
|
}
|
|
1777
1873
|
if (this.#assigning !== null) {
|
|
1778
1874
|
switch (this.#assigning.kind) {
|
|
@@ -138,6 +138,8 @@ export class PlanReviewOverlay implements Component {
|
|
|
138
138
|
#tocCursor = 0;
|
|
139
139
|
#sidebarShown = false;
|
|
140
140
|
#pendingScrollToToc = false;
|
|
141
|
+
/** Last meaningful relative body position, retained while a frame cannot scroll. */
|
|
142
|
+
#scrollProgress = 0;
|
|
141
143
|
|
|
142
144
|
// Click hit-testing, rebuilt every render. Keys are 0-based rendered-line
|
|
143
145
|
// indices (== screen rows, since the fullscreen overlay paints from row 0).
|
|
@@ -196,6 +198,7 @@ export class PlanReviewOverlay implements Component {
|
|
|
196
198
|
setPlanContent(planContent: string): void {
|
|
197
199
|
this.#setSections(planContent);
|
|
198
200
|
this.#scrollView.scrollToTop();
|
|
201
|
+
this.#scrollProgress = 0;
|
|
199
202
|
this.#tocCursor = 0;
|
|
200
203
|
// A wholesale external-editor swap supersedes prior in-overlay deletions.
|
|
201
204
|
this.#deleted = [];
|
|
@@ -343,6 +346,7 @@ export class PlanReviewOverlay implements Component {
|
|
|
343
346
|
if (event.wheel !== null) {
|
|
344
347
|
// Scroll wheel: three rows per notch.
|
|
345
348
|
this.#scrollView.scroll(event.wheel * 3);
|
|
349
|
+
this.#captureScrollProgress();
|
|
346
350
|
return true;
|
|
347
351
|
}
|
|
348
352
|
if (event.release) return true;
|
|
@@ -445,13 +449,21 @@ export class PlanReviewOverlay implements Component {
|
|
|
445
449
|
// drops into the actions ("next step"); scrolling off the top steps back up
|
|
446
450
|
// to the ToC.
|
|
447
451
|
if (matchesSelectUp(data) || matchesKey(data, "k")) {
|
|
448
|
-
if (this.#scrollView.getScrollOffset() <= 0 && this.#sidebarShown)
|
|
449
|
-
|
|
452
|
+
if (this.#scrollView.getScrollOffset() <= 0 && this.#sidebarShown) {
|
|
453
|
+
this.#setFocus("toc");
|
|
454
|
+
} else {
|
|
455
|
+
this.#scrollView.scroll(-1);
|
|
456
|
+
this.#captureScrollProgress();
|
|
457
|
+
}
|
|
450
458
|
return;
|
|
451
459
|
}
|
|
452
460
|
if (matchesSelectDown(data) || matchesKey(data, "j")) {
|
|
453
|
-
if (this.#scrollView.getScrollOffset() >= this.#scrollView.getMaxScrollOffset())
|
|
454
|
-
|
|
461
|
+
if (this.#scrollView.getScrollOffset() >= this.#scrollView.getMaxScrollOffset()) {
|
|
462
|
+
this.#setFocus("actions");
|
|
463
|
+
} else {
|
|
464
|
+
this.#scrollView.scroll(1);
|
|
465
|
+
this.#captureScrollProgress();
|
|
466
|
+
}
|
|
455
467
|
return;
|
|
456
468
|
}
|
|
457
469
|
this.#handleBodyScroll(data);
|
|
@@ -464,9 +476,19 @@ export class PlanReviewOverlay implements Component {
|
|
|
464
476
|
* before this runs, so here it only ever sees the paging/fast keys.
|
|
465
477
|
*/
|
|
466
478
|
#handleBodyScroll(data: string): void {
|
|
467
|
-
if (this.#scrollView.handleScrollKey(data))
|
|
468
|
-
|
|
469
|
-
|
|
479
|
+
if (this.#scrollView.handleScrollKey(data)) {
|
|
480
|
+
if (matchesKey(data, "home")) this.#scrollProgress = 0;
|
|
481
|
+
else if (matchesKey(data, "end")) this.#scrollProgress = 1;
|
|
482
|
+
else this.#captureScrollProgress();
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
if (data === "g") {
|
|
486
|
+
this.#scrollView.scrollToTop();
|
|
487
|
+
this.#scrollProgress = 0;
|
|
488
|
+
} else if (data === "G") {
|
|
489
|
+
this.#scrollView.scrollToBottom();
|
|
490
|
+
this.#scrollProgress = 1;
|
|
491
|
+
}
|
|
470
492
|
}
|
|
471
493
|
|
|
472
494
|
#handleToc(data: string): void {
|
|
@@ -517,7 +539,10 @@ export class PlanReviewOverlay implements Component {
|
|
|
517
539
|
const sectionIndex = this.#toc[this.#tocCursor];
|
|
518
540
|
if (sectionIndex === undefined) return;
|
|
519
541
|
const offset = this.#sectionOffsets[sectionIndex];
|
|
520
|
-
if (offset !== undefined)
|
|
542
|
+
if (offset !== undefined) {
|
|
543
|
+
this.#scrollView.setScrollOffset(offset);
|
|
544
|
+
this.#captureScrollProgress();
|
|
545
|
+
}
|
|
521
546
|
}
|
|
522
547
|
|
|
523
548
|
/** Greatest ToC position whose section starts at or above the scroll offset. */
|
|
@@ -690,6 +715,23 @@ export class PlanReviewOverlay implements Component {
|
|
|
690
715
|
return parts.join(sep);
|
|
691
716
|
}
|
|
692
717
|
|
|
718
|
+
/**
|
|
719
|
+
* Retain relative progress across reflow frames. A non-scrollable intermediate
|
|
720
|
+
* frame has no meaningful offset, so it must not erase the last scroll position.
|
|
721
|
+
*/
|
|
722
|
+
#captureScrollProgress(): void {
|
|
723
|
+
const maxOffset = this.#scrollView.getMaxScrollOffset();
|
|
724
|
+
if (maxOffset > 0) this.#scrollProgress = this.#scrollView.getScrollOffset() / maxOffset;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
#layoutBody(lines: readonly string[], height: number): void {
|
|
728
|
+
this.#captureScrollProgress();
|
|
729
|
+
this.#scrollView.setLines(lines);
|
|
730
|
+
this.#scrollView.setHeight(height);
|
|
731
|
+
const maxOffset = this.#scrollView.getMaxScrollOffset();
|
|
732
|
+
if (maxOffset > 0) this.#scrollView.setScrollOffset(Math.round(this.#scrollProgress * maxOffset));
|
|
733
|
+
}
|
|
734
|
+
|
|
693
735
|
/** Build the concatenated body lines and record each section's start row. */
|
|
694
736
|
#buildBody(bodyContentWidth: number): string[] {
|
|
695
737
|
const lines: string[] = [];
|
|
@@ -807,8 +849,7 @@ export class PlanReviewOverlay implements Component {
|
|
|
807
849
|
const regionRows = Math.max(MIN_BODY_ROWS, termHeight - chrome);
|
|
808
850
|
|
|
809
851
|
const bodyLines = this.#buildBody(bodyContentWidth);
|
|
810
|
-
this.#
|
|
811
|
-
this.#scrollView.setHeight(regionRows);
|
|
852
|
+
this.#layoutBody(bodyLines, regionRows);
|
|
812
853
|
if (this.#pendingScrollToToc) {
|
|
813
854
|
this.#pendingScrollToToc = false;
|
|
814
855
|
this.#scrubBodyToToc();
|
|
@@ -760,6 +760,7 @@ export class SessionSelectorComponent extends Container {
|
|
|
760
760
|
#globalSessions: SessionInfo[] | null = null;
|
|
761
761
|
#scope: "folder" | "all" = "folder";
|
|
762
762
|
#toggling = false;
|
|
763
|
+
#inputLocked = false;
|
|
763
764
|
// 0-based line where the session list begins within this component's own
|
|
764
765
|
// render, captured each frame. The fullscreen picker overlay paints from
|
|
765
766
|
// screen row 0, so a mouse row maps to `row - #listLineOffset` inside the
|
|
@@ -874,6 +875,14 @@ export class SessionSelectorComponent extends Container {
|
|
|
874
875
|
setOnRequestRender(callback: () => void): void {
|
|
875
876
|
this.#onRequestRender = callback;
|
|
876
877
|
}
|
|
878
|
+
/** Ignore input after selection while the host resumes the session. */
|
|
879
|
+
lockInput(): void {
|
|
880
|
+
this.#inputLocked = true;
|
|
881
|
+
}
|
|
882
|
+
/** Re-enable input after a failed resume so the user can pick again. */
|
|
883
|
+
unlockInput(): void {
|
|
884
|
+
this.#inputLocked = false;
|
|
885
|
+
}
|
|
877
886
|
|
|
878
887
|
/**
|
|
879
888
|
* Dispose the session list explicitly: while the delete-confirmation dialog
|
|
@@ -972,6 +981,7 @@ export class SessionSelectorComponent extends Container {
|
|
|
972
981
|
}
|
|
973
982
|
|
|
974
983
|
handleInput(keyData: string): void {
|
|
984
|
+
if (this.#inputLocked) return;
|
|
975
985
|
if (keyData.startsWith("\x1b[<")) {
|
|
976
986
|
this.#handleMouse(keyData);
|
|
977
987
|
return;
|
|
@@ -36,6 +36,7 @@ function makeSessionWithLastMessage(lastMessage: unknown, prewalkArmed: boolean
|
|
|
36
36
|
getPrewalkState: () => (prewalkArmed ? { target: { id: "cheap-model", provider: "openai" } } : undefined),
|
|
37
37
|
getAsyncJobSnapshot: () => undefined,
|
|
38
38
|
isAdvisorActive: () => false,
|
|
39
|
+
getAdvisorStatusOverview: () => ({ configured: false, advisors: [] }),
|
|
39
40
|
isFastModeActive: () => false,
|
|
40
41
|
configuredThinkingLevel: () => undefined,
|
|
41
42
|
modelRegistry: {
|
|
@@ -1334,10 +1334,8 @@ export class StatusLineComponent implements Component {
|
|
|
1334
1334
|
return [];
|
|
1335
1335
|
}
|
|
1336
1336
|
|
|
1337
|
-
|
|
1337
|
+
return Array.from(this.#hookStatuses.entries())
|
|
1338
1338
|
.sort(([a], [b]) => a.localeCompare(b))
|
|
1339
|
-
.map(([, text]) => sanitizeStatusText(text));
|
|
1340
|
-
const hookLine = sortedStatuses.join(" ");
|
|
1341
|
-
return [truncateToWidth(hookLine, width)];
|
|
1339
|
+
.map(([, text]) => truncateToWidth(sanitizeStatusText(text), width));
|
|
1342
1340
|
}
|
|
1343
1341
|
}
|