@oh-my-pi/pi-coding-agent 15.9.5 → 15.9.67
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 +35 -0
- package/dist/types/config/keybindings.d.ts +4 -1
- package/dist/types/config/settings-schema.d.ts +11 -1
- package/dist/types/edit/file-snapshot-store.d.ts +1 -1
- package/dist/types/eval/__tests__/kernel-spawn.test.d.ts +1 -0
- package/dist/types/eval/backend.d.ts +6 -6
- package/dist/types/eval/bridge-timeout.d.ts +27 -0
- package/dist/types/eval/idle-timeout.d.ts +16 -14
- package/dist/types/eval/js/executor.d.ts +3 -3
- package/dist/types/eval/py/executor.d.ts +2 -2
- package/dist/types/eval/py/spawn-options.d.ts +58 -0
- package/dist/types/modes/components/assistant-message.d.ts +5 -0
- package/dist/types/modes/components/copy-selector.d.ts +22 -0
- package/dist/types/modes/components/model-selector.d.ts +1 -0
- package/dist/types/modes/controllers/command-controller.d.ts +0 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -1
- package/dist/types/modes/types.d.ts +1 -1
- package/dist/types/modes/utils/copy-targets.d.ts +53 -0
- package/dist/types/tools/eval-render.d.ts +8 -0
- package/dist/types/tools/render-utils.d.ts +25 -0
- package/dist/types/tui/code-cell.d.ts +6 -0
- package/dist/types/tui/output-block.d.ts +11 -0
- package/package.json +9 -9
- package/src/autoresearch/dashboard.ts +11 -21
- package/src/cli/claude-trace-cli.ts +13 -1
- package/src/config/keybindings.ts +58 -1
- package/src/config/settings-schema.ts +11 -1
- package/src/debug/raw-sse.ts +18 -4
- package/src/edit/file-snapshot-store.ts +1 -1
- package/src/edit/index.ts +1 -1
- package/src/edit/renderer.ts +7 -7
- package/src/edit/streaming.ts +1 -1
- package/src/eval/__tests__/agent-bridge.test.ts +28 -27
- package/src/eval/__tests__/bridge-timeout.test.ts +64 -0
- package/src/eval/__tests__/idle-timeout.test.ts +26 -12
- package/src/eval/__tests__/kernel-spawn.test.ts +103 -0
- package/src/eval/__tests__/llm-bridge.test.ts +10 -10
- package/src/eval/__tests__/shared-executors.test.ts +2 -2
- package/src/eval/agent-bridge.ts +4 -5
- package/src/eval/backend.ts +6 -6
- package/src/eval/bridge-timeout.ts +44 -0
- package/src/eval/idle-timeout.ts +33 -15
- package/src/eval/js/executor.ts +10 -10
- package/src/eval/llm-bridge.ts +4 -5
- package/src/eval/py/executor.ts +6 -6
- package/src/eval/py/kernel.ts +11 -1
- package/src/eval/py/spawn-options.ts +126 -0
- package/src/export/ttsr.ts +9 -0
- package/src/extensibility/extensions/runner.ts +2 -0
- package/src/internal-urls/docs-index.generated.ts +6 -5
- package/src/lsp/client.ts +80 -2
- package/src/lsp/index.ts +38 -4
- package/src/lsp/render.ts +3 -3
- package/src/main.ts +1 -1
- package/src/modes/components/agent-dashboard.ts +13 -4
- package/src/modes/components/assistant-message.ts +22 -1
- package/src/modes/components/copy-selector.ts +249 -0
- package/src/modes/components/extensions/extension-list.ts +17 -8
- package/src/modes/components/history-search.ts +19 -11
- package/src/modes/components/model-selector.ts +125 -29
- package/src/modes/components/oauth-selector.ts +28 -12
- package/src/modes/components/session-observer-overlay.ts +13 -15
- package/src/modes/components/session-selector.ts +24 -13
- package/src/modes/components/tool-execution.ts +27 -13
- package/src/modes/components/tree-selector.ts +19 -7
- package/src/modes/components/user-message-selector.ts +25 -14
- package/src/modes/controllers/command-controller.ts +0 -116
- package/src/modes/controllers/event-controller.ts +26 -10
- package/src/modes/controllers/selector-controller.ts +38 -1
- package/src/modes/interactive-mode.ts +4 -4
- package/src/modes/theme/theme.ts +46 -10
- package/src/modes/types.ts +1 -1
- package/src/modes/utils/copy-targets.ts +254 -0
- package/src/prompts/tools/ast-edit.md +1 -1
- package/src/prompts/tools/ast-grep.md +1 -1
- package/src/prompts/tools/read.md +1 -1
- package/src/prompts/tools/search.md +1 -1
- package/src/session/agent-session.ts +6 -2
- package/src/slash-commands/builtin-registry.ts +3 -11
- package/src/task/render.ts +38 -11
- package/src/tools/bash.ts +18 -8
- package/src/tools/browser/render.ts +5 -4
- package/src/tools/debug.ts +3 -3
- package/src/tools/eval-render.ts +24 -9
- package/src/tools/eval.ts +14 -19
- package/src/tools/fetch.ts +5 -5
- package/src/tools/read.ts +7 -7
- package/src/tools/render-utils.ts +46 -0
- package/src/tools/ssh.ts +21 -8
- package/src/tools/write.ts +17 -8
- package/src/tui/code-cell.ts +19 -4
- package/src/tui/output-block.ts +14 -0
- package/src/web/search/render.ts +3 -3
- package/dist/types/eval/heartbeat.d.ts +0 -45
- package/src/eval/__tests__/heartbeat.test.ts +0 -84
- package/src/eval/heartbeat.ts +0 -74
- /package/dist/types/eval/__tests__/{heartbeat.test.d.ts → bridge-timeout.test.d.ts} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,41 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [15.9.67] - 2026-06-06
|
|
6
|
+
### Added
|
|
7
|
+
|
|
8
|
+
- Added `timeout-pause` and `timeout-resume` eval bridge status events emitted around `agent()`/`llm()` operations
|
|
9
|
+
- Added a `/copy` picker: `/copy` now opens a fullscreen, outlined tree of recent assistant messages with their code blocks nested beneath (like `/tree`). Navigate with ↑↓, and Enter copies the highlighted node — a whole message, an individual code block, "All N blocks", or a bash/eval command interleaved with the assistant turn that issued it. A live preview pane shows the selected target, wrapping prose and syntax-highlighting code/commands.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- Changed eval timeout accounting so delegated bridge calls now suspend the cell watchdog and start a fresh timeout window when runtime control returns
|
|
14
|
+
- Changed `IdleTimeout` to support reference-counted pauses so overlapping delegated bridge calls keep timeout paused until all calls complete
|
|
15
|
+
- Changed the default `app.message.followUp` binding from `Ctrl+Enter` alone to `[Ctrl+Q, Ctrl+Enter]` so the follow-up shortcut works in Windows Terminal, which does not deliver a distinct `Ctrl+Enter` event to console apps. `Ctrl+Q` mirrors the GitHub Copilot CLI default for the same action; existing remaps in `~/.omp/agent/keybindings.yml` are untouched, and if another user-remapped action already claims `Ctrl+Q`, that user binding wins while follow-up keeps `Ctrl+Enter`. `Ctrl+Q` is also reserved by `ExtensionRunner` so an extension cannot register that chord and be silently overwritten by the built-in follow-up handler ([#1903](https://github.com/can1357/oh-my-pi/issues/1903)).
|
|
16
|
+
- Changed all scrollable TUI pickers and viewports to render through the shared `ScrollView` right-edge scrollbar for a uniform look, replacing their ad-hoc `(N/M)` / `[a-b/total]` text indicators (search hints and the tree filter-mode label are preserved). Covers the session/resume picker, model selector, OAuth provider selector, history search, session tree selector, agent dashboard list, extension list, user-message selector, the raw SSE debug viewer, the autoresearch dashboard overlay, and the session observer overlay.
|
|
17
|
+
- Changed the `/model` and `/switch` selectors to dim and skip models whose context windows are smaller than the current chat context.
|
|
18
|
+
- Changed `/copy` command targets to appear inline with recent assistant messages instead of as a separate "Last bash command" row at the end of the picker.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- Fixed the idle `Working...` loader freezing on ED3-risk terminals with unobservable native scrollback by keeping foreground live-region rendering enabled from `agent_start` until `agent_end`, before the first assistant or tool event arrives.
|
|
23
|
+
- Fixed framed tool output blocks rendering one column inset inside tool boxes; modern bordered blocks now span the same width as legacy background-filled tool boxes.
|
|
24
|
+
- Fixed potential `TimeoutError` aborts for short `timeout` eval cells during long bridged `agent()`/`llm()` work where no progress events are emitted until completion
|
|
25
|
+
- Fixed retry recovery to allow automatic retries without switching models when `retry.modelFallback` is disabled.
|
|
26
|
+
- Fixed `ttsr.enabled: false` being ignored at runtime. TTSR rules were still being registered with `TtsrManager.addRule` and matched against stream deltas even when the global toggle was off, so disabling TTSR did not suppress rule injection or stream abort. The manager now gates `addRule`, `hasRules`, and `#matchBuffer` on the enabled flag, so disabling fully short-circuits the TTSR path. Condition rules fall through to the rulebook bucket instead of being silently swallowed. ([#1767](https://github.com/can1357/oh-my-pi/issues/1767))
|
|
27
|
+
- Fixed the Python eval kernel hanging on Windows during `import pandas` / `import numpy`, with SIGINT unable to recover the cell. `PythonKernel.start()` spawned the runner with `windowsHide: true`, which in Bun maps to the Win32 `CREATE_NO_WINDOW` flag and detaches the long-lived child from any inherited console — so native extensions like `numpy/_core/_multiarray_umath.pyd` (and its bundled OpenBLAS/SLEEF thread-pool init) could deadlock inside `LoadLibraryExW`, and `GenerateConsoleCtrlEvent`-based SIGINT delivery silently became a no-op. The kernel now hides its window only when the host itself has no console to share (service / piped launch); an interactive TUI launch lets the kernel inherit the parent's console, matching the behavior of `python.exe` invoked from `cmd.exe` ([#1960](https://github.com/can1357/oh-my-pi/issues/1960)).
|
|
28
|
+
- Fixed `task` renderer crashing the TUI with `TypeError: completeData?.map is not a function` when a subagent's `extractedToolData.yield` slot held a non-array value. `renderAgentResult` (and the live-progress sibling) cast the slot to `Array<{ data }>` and called `?.map`, but optional chaining short-circuits only on `null`/`undefined`, so a plain object made `.map` `undefined` and threw — taking down every `review` task render. Both sites now go through `normalizeYieldData`, which wraps a single object as a 1-element array and drops primitives ([#1987](https://github.com/can1357/oh-my-pi/issues/1987))
|
|
29
|
+
- Fixed `sdk-async-job-manager-singleton` tests flaking under the full parallel suite. The four `createAgentSession`-based cases ran on the default 5000ms per-test timeout, which two real session startups can exceed when `test:ts` saturates the machine across packages; on timeout the still-running test body and `afterEach` reset raced, surfacing a spurious "Unhandled error between tests" on the `AsyncJobManager.instance()` assertion. They now carry an explicit 60000ms timeout, matching the convention used by the other session-creating tests in this suite.
|
|
30
|
+
- Fixed streaming `eval`, `bash`, `ssh`, and `task` call previews overflowing the live transcript viewport and cutting off their top while pending. A volatile tool block taller than the viewport could strand its scrolled-off head out of native scrollback on ED3-risk terminals (committed nowhere, repainted nowhere) until the result landed. The pending `eval` source preview now follows the streaming edge in a bounded 12-line tail window (newest lines pinned to the bottom, "… N earlier lines" on top) so you can watch the code being written without the box overflowing; `bash`/`ssh` commands and `task` context use a bounded head+tail window. `Ctrl+O` still lifts the cap for a full view.
|
|
31
|
+
- Fixed the streaming `write` call preview ignoring `Ctrl+O` so the expand toggle was a no-op while a file was being written. Unlike the `eval`/`bash`/`ssh`/`task` streaming previews, `formatStreamingContent` never received the `expanded` flag, leaving the preview pinned to a bounded 12-line tail window even after pressing `Ctrl+O` — so on a large write you could not widen past the streaming edge until the tool result landed. The preview now lifts the cap to the full file (head through tail) when expanded, matching the documented streaming-preview behavior of the other tools.
|
|
32
|
+
- Fixed turn-ending provider errors rendering twice — once as the transcript's inline `Error: …` line and again in the pinned banner above the editor (added in 15.9.5). The inline line is now suppressed while the same error is mirrored in the banner and restored to the transcript when the banner clears at the next turn, so the error stays in history without the duplicate render at the error moment.
|
|
33
|
+
|
|
34
|
+
### Removed
|
|
35
|
+
|
|
36
|
+
- Removed the `/copy last|code|all|cmd` subcommands; every copy target is now reachable by picking it in the `/copy` tree.
|
|
37
|
+
|
|
5
38
|
## [15.9.5] - 2026-06-05
|
|
39
|
+
|
|
6
40
|
### Added
|
|
7
41
|
|
|
8
42
|
- Added a persistent error banner pinned above the editor when an assistant turn ends on a provider error (e.g. Anthropic's "Output blocked by content filtering policy"). The transcript `Error: …` line scrolls away as the conversation grows, so terminal turns that ended on a stream error could pass unnoticed; the banner stays in the fixed region above the input and is cleared when the next turn starts.
|
|
@@ -31,6 +65,7 @@
|
|
|
31
65
|
- Blocked OSC 8 hyperlink wrapping for URI targets containing terminal control bytes to avoid rendering malformed control-sequence links
|
|
32
66
|
|
|
33
67
|
## [15.9.4] - 2026-06-05
|
|
68
|
+
|
|
34
69
|
### Fixed
|
|
35
70
|
|
|
36
71
|
- Fixed chat transcript updates after submitting input so frozen scrollback is only thawed when native scrollback replay succeeds, preventing misplaced or duplicated rows when the viewport is not at the tail
|
|
@@ -224,7 +224,7 @@ export declare const KEYBINDINGS: {
|
|
|
224
224
|
readonly description: "Open external editor";
|
|
225
225
|
};
|
|
226
226
|
readonly "app.message.followUp": {
|
|
227
|
-
readonly defaultKeys: "ctrl+enter";
|
|
227
|
+
readonly defaultKeys: ["ctrl+q", "ctrl+enter"];
|
|
228
228
|
readonly description: "Send follow-up message";
|
|
229
229
|
};
|
|
230
230
|
readonly "app.message.dequeue": {
|
|
@@ -329,6 +329,9 @@ export declare class KeybindingsManager extends TuiKeybindingsManager {
|
|
|
329
329
|
* Reload keybindings from the config file.
|
|
330
330
|
*/
|
|
331
331
|
reload(): void;
|
|
332
|
+
setUserBindings(userBindings: KeybindingsConfig): void;
|
|
333
|
+
getKeys(keybinding: Keybinding): KeyId[];
|
|
334
|
+
getResolvedBindings(): KeybindingsConfig;
|
|
332
335
|
/**
|
|
333
336
|
* Get the effective resolved bindings (defaults + user overrides).
|
|
334
337
|
*/
|
|
@@ -1027,6 +1027,15 @@ export declare const SETTINGS_SCHEMA: {
|
|
|
1027
1027
|
readonly description: "Maximum wait between retries, in ms. When the provider asks us to wait longer than this and no credential or model fallback succeeds, the request fails fast instead of sleeping (e.g. 3-hour Anthropic rate-limit windows).";
|
|
1028
1028
|
};
|
|
1029
1029
|
};
|
|
1030
|
+
readonly "retry.modelFallback": {
|
|
1031
|
+
readonly type: "boolean";
|
|
1032
|
+
readonly default: true;
|
|
1033
|
+
readonly ui: {
|
|
1034
|
+
readonly tab: "model";
|
|
1035
|
+
readonly label: "Retry Model Fallback";
|
|
1036
|
+
readonly description: "Allow retry recovery to switch to configured fallback models";
|
|
1037
|
+
};
|
|
1038
|
+
};
|
|
1030
1039
|
readonly "retry.fallbackChains": {
|
|
1031
1040
|
readonly type: "record";
|
|
1032
1041
|
readonly default: Record<string, string[]>;
|
|
@@ -2186,7 +2195,7 @@ export declare const SETTINGS_SCHEMA: {
|
|
|
2186
2195
|
readonly ui: {
|
|
2187
2196
|
readonly tab: "editing";
|
|
2188
2197
|
readonly label: "Hash Lines";
|
|
2189
|
-
readonly description: "Include snapshot-tag headers and line numbers in read output for hashline edit mode (
|
|
2198
|
+
readonly description: "Include snapshot-tag headers and line numbers in read output for hashline edit mode ([PATH#TAG] plus LINE:content)";
|
|
2190
2199
|
};
|
|
2191
2200
|
};
|
|
2192
2201
|
readonly "read.defaultLimit": {
|
|
@@ -3902,6 +3911,7 @@ export interface RetrySettings {
|
|
|
3902
3911
|
maxRetries: number;
|
|
3903
3912
|
baseDelayMs: number;
|
|
3904
3913
|
maxDelayMs: number;
|
|
3914
|
+
modelFallback: boolean;
|
|
3905
3915
|
}
|
|
3906
3916
|
export interface MemoriesSettings {
|
|
3907
3917
|
enabled: boolean;
|
|
@@ -12,7 +12,7 @@ import { InMemorySnapshotStore } from "@oh-my-pi/hashline";
|
|
|
12
12
|
/**
|
|
13
13
|
* Upper bound on the file size we snapshot. A section tag is a content hash of
|
|
14
14
|
* the *whole* file, so minting one means holding the full normalized text in
|
|
15
|
-
* the store. Files above this cap emit no
|
|
15
|
+
* the store. Files above this cap emit no `[path#tag]` header — line-anchored
|
|
16
16
|
* editing of multi-megabyte files is out of scope under the full-content model.
|
|
17
17
|
*/
|
|
18
18
|
export declare const SNAPSHOT_MAX_BYTES: number;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -9,12 +9,12 @@ export interface ExecutorBackendExecOptions {
|
|
|
9
9
|
signal?: AbortSignal;
|
|
10
10
|
session: ToolSession;
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
13
|
-
* driven entirely by `signal`, which the eval tool arms as
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* and as cold-start headroom; they MUST
|
|
17
|
-
* timer from it.
|
|
12
|
+
* Runtime-work budget in milliseconds (the cell's `timeout`). Cancellation is
|
|
13
|
+
* driven entirely by `signal`, which the eval tool arms as a watchdog that
|
|
14
|
+
* pauses on bridge timeout-control status events and fires a `TimeoutError`
|
|
15
|
+
* reason only while the Python/JS runtime owns control. Backends use this
|
|
16
|
+
* value only for timeout-annotation text and as cold-start headroom; they MUST
|
|
17
|
+
* NOT derive a competing wall-clock timer from it.
|
|
18
18
|
*/
|
|
19
19
|
idleTimeoutMs: number;
|
|
20
20
|
reset: boolean;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Timeout suspension for in-flight host-side eval bridge calls.
|
|
3
|
+
*
|
|
4
|
+
* The eval watchdog caps a cell's `timeout` as a budget on the cell runtime's
|
|
5
|
+
* own work. Host-side `agent()` / `parallel()` / `llm()` bridge calls hand
|
|
6
|
+
* control to the outer TypeScript process, where the Python kernel or JS VM is
|
|
7
|
+
* only waiting for a result. While that delegated work is in flight, the cell
|
|
8
|
+
* timeout must be ignored completely; once the bridge returns and the runtime is
|
|
9
|
+
* back in control, the watchdog starts a fresh timeout window.
|
|
10
|
+
*
|
|
11
|
+
* Bridge helpers express that handoff with synthetic pause/resume status events
|
|
12
|
+
* on the existing `emitStatus → onStatus` path. Consumers MUST treat these as
|
|
13
|
+
* timeout-control events only: update the watchdog and drop them from rendered
|
|
14
|
+
* or persisted cell output.
|
|
15
|
+
*/
|
|
16
|
+
import type { JsStatusEvent } from "./js/shared/types";
|
|
17
|
+
/** Synthetic status op emitted when a bridge call leaves the cell runtime. */
|
|
18
|
+
export declare const EVAL_TIMEOUT_PAUSE_OP = "timeout-pause";
|
|
19
|
+
/** Synthetic status op emitted when a bridge call returns control to the runtime. */
|
|
20
|
+
export declare const EVAL_TIMEOUT_RESUME_OP = "timeout-resume";
|
|
21
|
+
/** Whether a status event is pure eval-timeout control and should not render. */
|
|
22
|
+
export declare function isEvalTimeoutControlEvent(event: JsStatusEvent): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Run {@link operation} while suspending the eval watchdog through
|
|
25
|
+
* {@link emitStatus}. A no-op wrapper when no status sink is wired.
|
|
26
|
+
*/
|
|
27
|
+
export declare function withBridgeTimeoutPause<T>(emitStatus: ((event: JsStatusEvent) => void) | undefined, operation: () => Promise<T>): Promise<T>;
|
|
@@ -1,27 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Watchdog for eval cell work.
|
|
3
3
|
*
|
|
4
|
-
* A cell's `timeout`
|
|
5
|
-
*
|
|
6
|
-
* `
|
|
7
|
-
*
|
|
8
|
-
* long-running fanout that keeps reporting progress (e.g. `agent()` status
|
|
9
|
-
* updates, `log()`/`phase()`) never trips the timeout, while a genuinely
|
|
10
|
-
* stalled cell still gets interrupted.
|
|
4
|
+
* A cell's `timeout` bounds time while the Python kernel or JS VM is in control.
|
|
5
|
+
* Host-side bridge calls can {@link pause} the watchdog so delegated
|
|
6
|
+
* `agent()`/`parallel()`/`llm()` work is ignored completely, then {@link resume}
|
|
7
|
+
* starts a fresh timeout window once the runtime gets control back.
|
|
11
8
|
*
|
|
12
|
-
* The timer self-reschedules instead of being torn down
|
|
13
|
-
*
|
|
14
|
-
*
|
|
9
|
+
* The active timer self-reschedules instead of being torn down on every
|
|
10
|
+
* activity event, so frequent activity costs one timestamp write per event.
|
|
11
|
+
* Pause is reference-counted because `parallel()` can have multiple bridge calls
|
|
12
|
+
* in flight at once.
|
|
15
13
|
*/
|
|
16
14
|
export declare class IdleTimeout {
|
|
17
15
|
#private;
|
|
18
16
|
constructor(idleMs: number);
|
|
19
|
-
/** Aborts with a `TimeoutError` reason once the
|
|
17
|
+
/** Aborts with a `TimeoutError` reason once the active timeout window is exhausted. */
|
|
20
18
|
get signal(): AbortSignal;
|
|
21
|
-
/** Configured
|
|
19
|
+
/** Configured active timeout window in milliseconds. */
|
|
22
20
|
get idleMs(): number;
|
|
23
|
-
/** Record activity, pushing the
|
|
21
|
+
/** Record runtime activity, pushing the active deadline forward by `idleMs`. */
|
|
24
22
|
bump(): void;
|
|
23
|
+
/** Suspend timeout accounting while control is delegated to host-side work. */
|
|
24
|
+
pause(): void;
|
|
25
|
+
/** Resume timeout accounting with a fresh timeout window. */
|
|
26
|
+
resume(): void;
|
|
25
27
|
/** Stop the watchdog. Safe to call multiple times. */
|
|
26
28
|
dispose(): void;
|
|
27
29
|
[Symbol.dispose](): void;
|
|
@@ -6,9 +6,9 @@ export interface JsExecutorOptions {
|
|
|
6
6
|
timeoutMs?: number;
|
|
7
7
|
deadlineMs?: number;
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
10
|
-
* timeout-annotation text when the caller drives cancellation via
|
|
11
|
-
*
|
|
9
|
+
* Runtime-work budget (ms). Used for worker cold-start headroom and
|
|
10
|
+
* timeout-annotation text when the caller drives cancellation via the eval
|
|
11
|
+
* watchdog `signal` instead of `deadlineMs`/`timeoutMs`. Never arms a timer.
|
|
12
12
|
*/
|
|
13
13
|
idleTimeoutMs?: number;
|
|
14
14
|
onChunk?: (chunk: string) => Promise<void> | void;
|
|
@@ -10,8 +10,8 @@ export interface PythonExecutorOptions {
|
|
|
10
10
|
/** Absolute wall-clock deadline in milliseconds since epoch */
|
|
11
11
|
deadlineMs?: number;
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
14
|
-
* caller drives cancellation via
|
|
13
|
+
* Runtime-work budget (ms). Used only for timeout-annotation text when the
|
|
14
|
+
* caller drives cancellation via the eval watchdog `signal` instead of a
|
|
15
15
|
* wall-clock `deadlineMs`/`timeoutMs`. Does not arm a timer.
|
|
16
16
|
*/
|
|
17
17
|
idleTimeoutMs?: number;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decide whether the long-lived Python kernel subprocess should be spawned
|
|
3
|
+
* with `windowsHide: true`.
|
|
4
|
+
*
|
|
5
|
+
* On Windows, Bun maps `windowsHide: true` to the `CREATE_NO_WINDOW` flag,
|
|
6
|
+
* which detaches the child from any inherited console. The Python kernel
|
|
7
|
+
* runs user code that imports NumPy/pandas; those native extensions
|
|
8
|
+
* (`numpy/_core/_multiarray_umath.pyd` + bundled OpenBLAS/SLEEF thread-pool
|
|
9
|
+
* init) can deadlock inside `LoadLibraryExW` when no console is attached,
|
|
10
|
+
* and a console-less child cannot receive SIGINT via
|
|
11
|
+
* `GenerateConsoleCtrlEvent` (the recovery path the host relies on). See
|
|
12
|
+
* issue #1960.
|
|
13
|
+
*
|
|
14
|
+
* So on Windows we hide only when the host itself has no console to share.
|
|
15
|
+
* In any launch where a console is attached — even one with every stdio
|
|
16
|
+
* stream redirected — the kernel inherits the parent's console, matching
|
|
17
|
+
* `python.exe` invoked from `cmd.exe`, which keeps native imports and
|
|
18
|
+
* SIGINT recovery working.
|
|
19
|
+
*
|
|
20
|
+
* Short-lived helper subprocesses elsewhere in the codebase (LSP probes,
|
|
21
|
+
* git, plugin installs) keep `windowsHide: true` because they don't load
|
|
22
|
+
* complex native modules and the brief console flash would be user-visible
|
|
23
|
+
* noise.
|
|
24
|
+
*/
|
|
25
|
+
export declare function shouldHideKernelWindow(opts: {
|
|
26
|
+
platform: NodeJS.Platform;
|
|
27
|
+
hostHasInheritableConsole: boolean;
|
|
28
|
+
}): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* TTY-based fallback used when the Win32 console probe is unavailable.
|
|
31
|
+
*
|
|
32
|
+
* Returns `true` if any of stdin/stdout/stderr is currently a TTY. This
|
|
33
|
+
* correctly detects the common interactive launches and the partial-
|
|
34
|
+
* redirection cases (`omp -p > out.txt`, `< in.txt`, `2> err.log`) where at
|
|
35
|
+
* least one stream stays bound to the terminal. The all-stdio-redirected
|
|
36
|
+
* case (`< in > out 2> err` from a console) is the reason we prefer the
|
|
37
|
+
* Win32 probe over this fallback whenever possible.
|
|
38
|
+
*/
|
|
39
|
+
export declare function consoleAttachedViaTTY(opts: {
|
|
40
|
+
stdinIsTTY: boolean;
|
|
41
|
+
stdoutIsTTY: boolean;
|
|
42
|
+
stderrIsTTY: boolean;
|
|
43
|
+
}): boolean;
|
|
44
|
+
/** Reset the cached Win32 probe result. Test-only; not part of the public surface. */
|
|
45
|
+
export declare function __resetWindowsConsoleProbeCache(): void;
|
|
46
|
+
/**
|
|
47
|
+
* Whether the host process owns a console its children can inherit.
|
|
48
|
+
*
|
|
49
|
+
* - On Windows, the authoritative signal is `GetConsoleWindow()`. It returns
|
|
50
|
+
* a non-NULL HWND whenever the process has a console attached, regardless
|
|
51
|
+
* of how the standard streams are redirected — so an `omp -p ... < in.txt
|
|
52
|
+
* > out.txt 2> err.log` launched from a real Windows Terminal session is
|
|
53
|
+
* correctly classified as console-attached and the kernel keeps its
|
|
54
|
+
* inheritable console.
|
|
55
|
+
* - On any other platform, or if the FFI probe fails, fall back to the
|
|
56
|
+
* TTY-OR heuristic. That still catches the common interactive cases.
|
|
57
|
+
*/
|
|
58
|
+
export declare function hostHasInheritableConsole(): boolean;
|
|
@@ -13,6 +13,11 @@ export declare class AssistantMessageComponent extends Container {
|
|
|
13
13
|
constructor(message?: AssistantMessage, hideThinkingBlock?: boolean, onImageUpdate?: (() => void) | undefined, thinkingRenderers?: readonly AssistantThinkingRenderer[], imageBudget?: ImageBudget | undefined);
|
|
14
14
|
invalidate(): void;
|
|
15
15
|
setHideThinkingBlock(hide: boolean): void;
|
|
16
|
+
/**
|
|
17
|
+
* Toggle suppression of the inline `Error: …` line while the same error is
|
|
18
|
+
* pinned in the banner above the editor. Re-renders so the change is visible.
|
|
19
|
+
*/
|
|
20
|
+
setErrorPinned(pinned: boolean): void;
|
|
16
21
|
isTranscriptBlockFinalized(): boolean;
|
|
17
22
|
/**
|
|
18
23
|
* Assistant text/thinking streams in append-only: earlier rendered rows never
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type Component } from "@oh-my-pi/pi-tui";
|
|
2
|
+
import type { CopyTarget } from "../utils/copy-targets";
|
|
3
|
+
export interface CopySelectorCallbacks {
|
|
4
|
+
/** A copy target was chosen — copy its `content`. */
|
|
5
|
+
onPick: (target: CopyTarget) => void;
|
|
6
|
+
/** The picker was dismissed. */
|
|
7
|
+
onCancel: () => void;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Fullscreen `/copy` picker rendered as a `/tree`-style tree inside one
|
|
11
|
+
* outlined box: a title, the tree of copy targets (recent assistant messages
|
|
12
|
+
* with their code blocks nested beneath), a live preview of the highlighted
|
|
13
|
+
* node, and a keybinding footer. Every node copies its `content` on Enter.
|
|
14
|
+
*/
|
|
15
|
+
export declare class CopySelectorComponent implements Component {
|
|
16
|
+
#private;
|
|
17
|
+
private readonly callbacks;
|
|
18
|
+
constructor(roots: CopyTarget[], callbacks: CopySelectorCallbacks);
|
|
19
|
+
invalidate(): void;
|
|
20
|
+
handleInput(keyData: string): void;
|
|
21
|
+
render(width: number): string[];
|
|
22
|
+
}
|
|
@@ -20,6 +20,7 @@ export declare class ModelSelectorComponent extends Container {
|
|
|
20
20
|
constructor(tui: TUI, _currentModel: Model | undefined, settings: Settings, modelRegistry: ModelRegistry, scopedModels: ReadonlyArray<ScopedModelItem>, onSelect: RoleSelectCallback, onCancel: () => void, options?: {
|
|
21
21
|
temporaryOnly?: boolean;
|
|
22
22
|
initialSearchInput?: string;
|
|
23
|
+
currentContextTokens?: number;
|
|
23
24
|
});
|
|
24
25
|
handleInput(keyData: string): void;
|
|
25
26
|
getSearchInput(): Input;
|
|
@@ -13,7 +13,6 @@ export declare class CommandController {
|
|
|
13
13
|
handleDumpCommand(): void;
|
|
14
14
|
handleDebugTranscriptCommand(): Promise<void>;
|
|
15
15
|
handleShareCommand(): Promise<void>;
|
|
16
|
-
handleCopyCommand(sub?: string): void;
|
|
17
16
|
handleSessionCommand(): Promise<void>;
|
|
18
17
|
handleJobsCommand(): Promise<void>;
|
|
19
18
|
handleUsageCommand(reports?: UsageReport[] | null): Promise<void>;
|
|
@@ -35,6 +35,7 @@ export declare class SelectorController {
|
|
|
35
35
|
}): void;
|
|
36
36
|
showPluginSelector(mode?: "install" | "uninstall"): Promise<void>;
|
|
37
37
|
showUserMessageSelector(): void;
|
|
38
|
+
showCopySelector(): void;
|
|
38
39
|
showTreeSelector(): void;
|
|
39
40
|
showSessionSelector(): Promise<void>;
|
|
40
41
|
handleResumeSession(sessionPath: string): Promise<void>;
|
|
@@ -199,7 +199,6 @@ export declare class InteractiveMode implements InteractiveModeContext {
|
|
|
199
199
|
handleDumpCommand(): void;
|
|
200
200
|
handleDebugTranscriptCommand(): Promise<void>;
|
|
201
201
|
handleShareCommand(): Promise<void>;
|
|
202
|
-
handleCopyCommand(sub?: string): void;
|
|
203
202
|
handleTodoCommand(args: string): Promise<void>;
|
|
204
203
|
handleSessionCommand(): Promise<void>;
|
|
205
204
|
handleJobsCommand(): Promise<void>;
|
|
@@ -236,6 +235,7 @@ export declare class InteractiveMode implements InteractiveModeContext {
|
|
|
236
235
|
}): void;
|
|
237
236
|
showPluginSelector(mode?: "install" | "uninstall"): void;
|
|
238
237
|
showUserMessageSelector(): void;
|
|
238
|
+
showCopySelector(): void;
|
|
239
239
|
showTreeSelector(): void;
|
|
240
240
|
showSessionSelector(): void;
|
|
241
241
|
handleResumeSession(sessionPath: string): Promise<void>;
|
|
@@ -197,7 +197,6 @@ export interface InteractiveModeContext {
|
|
|
197
197
|
toggleTodoExpansion(): void;
|
|
198
198
|
handleExportCommand(text: string): Promise<void>;
|
|
199
199
|
handleShareCommand(): Promise<void>;
|
|
200
|
-
handleCopyCommand(sub?: string): void;
|
|
201
200
|
handleTodoCommand(args: string): Promise<void>;
|
|
202
201
|
handleSessionCommand(): Promise<void>;
|
|
203
202
|
handleJobsCommand(): Promise<void>;
|
|
@@ -235,6 +234,7 @@ export interface InteractiveModeContext {
|
|
|
235
234
|
}): void;
|
|
236
235
|
showPluginSelector(mode?: "install" | "uninstall"): void;
|
|
237
236
|
showUserMessageSelector(): void;
|
|
237
|
+
showCopySelector(): void;
|
|
238
238
|
showTreeSelector(): void;
|
|
239
239
|
showSessionSelector(): void;
|
|
240
240
|
handleResumeSession(sessionPath: string): Promise<void>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
2
|
+
/** A fenced code block extracted from assistant markdown. */
|
|
3
|
+
export interface CodeBlock {
|
|
4
|
+
/** Info string after the opening fence (language id), trimmed. */
|
|
5
|
+
lang: string;
|
|
6
|
+
/** Block body with the trailing newline stripped. */
|
|
7
|
+
code: string;
|
|
8
|
+
}
|
|
9
|
+
/** A runnable command found in the transcript. */
|
|
10
|
+
export interface LastCommand {
|
|
11
|
+
kind: "bash" | "eval";
|
|
12
|
+
code: string;
|
|
13
|
+
/** Highlight language: "bash" for bash, "python"/"javascript" for eval. */
|
|
14
|
+
language: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* A node in the `/copy` picker tree. Leaves carry `content` (placed on the
|
|
18
|
+
* clipboard) plus `copyMessage` (the status shown afterwards); groups carry
|
|
19
|
+
* `children` to drill into.
|
|
20
|
+
*/
|
|
21
|
+
export interface CopyTarget {
|
|
22
|
+
/** Stable identifier (e.g. "msg:1", "msg:1:code:0", "msg:1:all", "cmd:1"). */
|
|
23
|
+
id: string;
|
|
24
|
+
label: string;
|
|
25
|
+
/** Dim annotation: line/block counts, language, or tool name. */
|
|
26
|
+
hint?: string;
|
|
27
|
+
/** Full text rendered in the preview pane. */
|
|
28
|
+
preview: string;
|
|
29
|
+
/** Highlight language for code/command previews (undefined = plain/markdown). */
|
|
30
|
+
language?: string;
|
|
31
|
+
/** Leaf: text copied to the clipboard. */
|
|
32
|
+
content?: string;
|
|
33
|
+
/** Leaf: status message shown after copying. */
|
|
34
|
+
copyMessage?: string;
|
|
35
|
+
/** Group: nested targets to drill into. */
|
|
36
|
+
children?: CopyTarget[];
|
|
37
|
+
}
|
|
38
|
+
/** Minimal session surface needed to assemble copy targets (eases testing). */
|
|
39
|
+
export interface CopySource {
|
|
40
|
+
readonly messages: readonly AgentMessage[];
|
|
41
|
+
getLastVisibleHandoffText(): string | undefined;
|
|
42
|
+
}
|
|
43
|
+
/** Extract fenced code blocks from assistant markdown, in document order. */
|
|
44
|
+
export declare function extractCodeBlocks(text: string): CodeBlock[];
|
|
45
|
+
/** Walk the transcript backwards for the most recent bash command or eval code. */
|
|
46
|
+
export declare function extractLastCommand(messages: readonly AgentMessage[]): LastCommand | undefined;
|
|
47
|
+
/**
|
|
48
|
+
* Assemble the unified `/copy` target tree: recent assistant messages
|
|
49
|
+
* (most recent first, each drillable into its code blocks), runnable command
|
|
50
|
+
* targets interleaved after the assistant message that issued them, and a
|
|
51
|
+
* fresh-handoff fallback when no assistant message exists yet.
|
|
52
|
+
*/
|
|
53
|
+
export declare function buildCopyTargets(source: CopySource): CopyTarget[];
|
|
@@ -13,6 +13,14 @@ import type { EvalStatusEvent, EvalToolDetails } from "../eval/types";
|
|
|
13
13
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
14
14
|
import { type Theme } from "../modes/theme/theme";
|
|
15
15
|
export declare const EVAL_DEFAULT_PREVIEW_LINES = 10;
|
|
16
|
+
/**
|
|
17
|
+
* Rows of source kept in the *pending* eval preview. The window follows the
|
|
18
|
+
* streaming edge (newest lines pinned to the bottom) so you can watch the code
|
|
19
|
+
* being written, while staying bounded — a volatile tool block taller than the
|
|
20
|
+
* viewport would otherwise strand its scrolled-off head out of native scrollback
|
|
21
|
+
* on ED3-risk terminals. Matches the streaming windows used by edit/write.
|
|
22
|
+
*/
|
|
23
|
+
export declare const EVAL_STREAMING_PREVIEW_LINES = 12;
|
|
16
24
|
interface EvalRenderCellArg {
|
|
17
25
|
language?: string;
|
|
18
26
|
code?: string;
|
|
@@ -76,6 +76,31 @@ export declare function formatBadge(label: string, color: ToolUIColor, theme: Th
|
|
|
76
76
|
* Uses consistent wording pattern.
|
|
77
77
|
*/
|
|
78
78
|
export declare function formatMoreItems(remaining: number, itemType: string): string;
|
|
79
|
+
/**
|
|
80
|
+
* Maximum rows a tool's streaming/pending *call* preview may render before it is
|
|
81
|
+
* capped. This is intentionally conservative: the preview still sits inside a
|
|
82
|
+
* transcript that already consumed some viewport rows, and tool blocks carry
|
|
83
|
+
* extra chrome (status/header/border/"more lines"), so a "reasonable" raw code
|
|
84
|
+
* or command preview like 10-12 lines can still overflow and strand its top
|
|
85
|
+
* while the block is volatile. Keeping the live call window short avoids that
|
|
86
|
+
* across terminals without turning the transcript into an interactive scroller.
|
|
87
|
+
*/
|
|
88
|
+
export declare const CALL_PREVIEW_MAX_LINES = 6;
|
|
89
|
+
/**
|
|
90
|
+
* Cap a pre-rendered pending/call preview to a bounded window. When truncated,
|
|
91
|
+
* show both the head and the live tail so the user can still see what the tool
|
|
92
|
+
* is currently writing while the volatile block stays short enough not to strand
|
|
93
|
+
* its top above the viewport. `Ctrl+O` widens the bounded window, but does not
|
|
94
|
+
* fully uncap live tool previews for the same reason.
|
|
95
|
+
*
|
|
96
|
+
* `prefix` (raw, e.g. a dim tree gutter) is prepended to the summary line so
|
|
97
|
+
* nested previews stay aligned.
|
|
98
|
+
*/
|
|
99
|
+
export declare function capPreviewLines(lines: string[], theme: Theme, options?: {
|
|
100
|
+
max?: number;
|
|
101
|
+
expanded?: boolean;
|
|
102
|
+
prefix?: string;
|
|
103
|
+
}): string[];
|
|
79
104
|
export declare function formatMeta(meta: string[], theme: Theme): string;
|
|
80
105
|
export declare function formatErrorMessage(message: string | undefined, theme: Theme): string;
|
|
81
106
|
export declare function formatEmptyMessage(message: string, theme: Theme): string;
|
|
@@ -11,6 +11,12 @@ export interface CodeCellOptions {
|
|
|
11
11
|
output?: string;
|
|
12
12
|
outputMaxLines?: number;
|
|
13
13
|
codeMaxLines?: number;
|
|
14
|
+
/**
|
|
15
|
+
* Show the LAST `codeMaxLines` rows (the live streaming edge) instead of the
|
|
16
|
+
* first, with a "… N earlier lines" marker on top. Lets a pending preview
|
|
17
|
+
* follow code as it is written while staying bounded. Ignored when `expanded`.
|
|
18
|
+
*/
|
|
19
|
+
codeTail?: boolean;
|
|
14
20
|
expanded?: boolean;
|
|
15
21
|
/** Animate the cell border with a sweeping segment while pending/running. */
|
|
16
22
|
animate?: boolean;
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bordered output container with optional header and sections.
|
|
3
|
+
*/
|
|
4
|
+
import type { Component } from "@oh-my-pi/pi-tui";
|
|
1
5
|
import type { Theme } from "../modes/theme/theme";
|
|
2
6
|
import type { State } from "./types";
|
|
3
7
|
export interface OutputBlockOptions {
|
|
@@ -13,6 +17,12 @@ export interface OutputBlockOptions {
|
|
|
13
17
|
/** Animate the border with a sweeping dark segment (pending/running state). */
|
|
14
18
|
animate?: boolean;
|
|
15
19
|
}
|
|
20
|
+
declare const FRAMED_BLOCK_COMPONENT: unique symbol;
|
|
21
|
+
export type FramedBlockComponent = Component & {
|
|
22
|
+
[FRAMED_BLOCK_COMPONENT]?: true;
|
|
23
|
+
};
|
|
24
|
+
export declare function markFramedBlockComponent<T extends Component>(component: T): T & FramedBlockComponent;
|
|
25
|
+
export declare function isFramedBlockComponent(component: Component): boolean;
|
|
16
26
|
/**
|
|
17
27
|
* Monotonic frame counter for animated borders, quantized to the TUI's ~16ms
|
|
18
28
|
* render cap so the cache key advances once per ~60fps frame — fine enough for a
|
|
@@ -44,3 +54,4 @@ export declare class CachedOutputBlock {
|
|
|
44
54
|
/** Invalidate the cache, forcing a rebuild on next render. */
|
|
45
55
|
invalidate(): void;
|
|
46
56
|
}
|
|
57
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
4
|
-
"version": "15.9.
|
|
4
|
+
"version": "15.9.67",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -47,14 +47,14 @@
|
|
|
47
47
|
"@agentclientprotocol/sdk": "0.22.1",
|
|
48
48
|
"@babel/parser": "^7.29.7",
|
|
49
49
|
"@mozilla/readability": "^0.6.0",
|
|
50
|
-
"@oh-my-pi/hashline": "15.9.
|
|
51
|
-
"@oh-my-pi/omp-stats": "15.9.
|
|
52
|
-
"@oh-my-pi/pi-agent-core": "15.9.
|
|
53
|
-
"@oh-my-pi/pi-ai": "15.9.
|
|
54
|
-
"@oh-my-pi/pi-mnemopi": "15.9.
|
|
55
|
-
"@oh-my-pi/pi-natives": "15.9.
|
|
56
|
-
"@oh-my-pi/pi-tui": "15.9.
|
|
57
|
-
"@oh-my-pi/pi-utils": "15.9.
|
|
50
|
+
"@oh-my-pi/hashline": "15.9.67",
|
|
51
|
+
"@oh-my-pi/omp-stats": "15.9.67",
|
|
52
|
+
"@oh-my-pi/pi-agent-core": "15.9.67",
|
|
53
|
+
"@oh-my-pi/pi-ai": "15.9.67",
|
|
54
|
+
"@oh-my-pi/pi-mnemopi": "15.9.67",
|
|
55
|
+
"@oh-my-pi/pi-natives": "15.9.67",
|
|
56
|
+
"@oh-my-pi/pi-tui": "15.9.67",
|
|
57
|
+
"@oh-my-pi/pi-utils": "15.9.67",
|
|
58
58
|
"@opentelemetry/api": "^1.9.1",
|
|
59
59
|
"@opentelemetry/context-async-hooks": "^2.7.1",
|
|
60
60
|
"@opentelemetry/exporter-trace-otlp-proto": "^0.218.0",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { matchesKey, replaceTabs, Text, truncateToWidth, visibleWidth } from "@oh-my-pi/pi-tui";
|
|
1
|
+
import { matchesKey, replaceTabs, ScrollView, Text, truncateToWidth, visibleWidth } from "@oh-my-pi/pi-tui";
|
|
2
2
|
import type { Theme } from "../modes/theme/theme";
|
|
3
3
|
import { formatElapsed, formatNum, isBetter } from "./helpers";
|
|
4
4
|
import { currentResults, findBaselineMetric, findBaselineRunNumber, findBaselineSecondary } from "./state";
|
|
@@ -76,14 +76,14 @@ export function createDashboardController(): DashboardController {
|
|
|
76
76
|
const viewportRows = Math.max(4, terminalRows - 4);
|
|
77
77
|
const maxScroll = Math.max(0, body.length - viewportRows);
|
|
78
78
|
if (scrollOffset > maxScroll) scrollOffset = maxScroll;
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
];
|
|
79
|
+
const sv = new ScrollView(body.slice(scrollOffset, scrollOffset + viewportRows), {
|
|
80
|
+
height: viewportRows,
|
|
81
|
+
scrollbar: "auto",
|
|
82
|
+
totalRows: body.length,
|
|
83
|
+
theme: { track: t => theme.fg("dim", t), thumb: t => theme.fg("accent", t) },
|
|
84
|
+
});
|
|
85
|
+
sv.setScrollOffset(scrollOffset);
|
|
86
|
+
return [header, ...sv.render(width), renderOverlayFooter(width, theme)];
|
|
87
87
|
},
|
|
88
88
|
handleInput(data: string): void {
|
|
89
89
|
const totalRows =
|
|
@@ -406,18 +406,8 @@ function renderOverlayRunningLine(
|
|
|
406
406
|
);
|
|
407
407
|
}
|
|
408
408
|
|
|
409
|
-
function renderOverlayFooter(
|
|
410
|
-
|
|
411
|
-
scrollOffset: number,
|
|
412
|
-
viewportRows: number,
|
|
413
|
-
totalRows: number,
|
|
414
|
-
theme: Theme,
|
|
415
|
-
): string {
|
|
416
|
-
const position =
|
|
417
|
-
totalRows > viewportRows
|
|
418
|
-
? ` ${scrollOffset + 1}-${Math.min(totalRows, scrollOffset + viewportRows)}/${totalRows}`
|
|
419
|
-
: "";
|
|
420
|
-
const hint = theme.fg("dim", ` up/down j/k pageup pagedown g G esc${position} `);
|
|
409
|
+
function renderOverlayFooter(width: number, theme: Theme): string {
|
|
410
|
+
const hint = theme.fg("dim", " up/down j/k pageup pagedown g G esc ");
|
|
421
411
|
const fill = Math.max(0, width - visibleWidth(hint));
|
|
422
412
|
return theme.fg("borderMuted", "-".repeat(fill)) + hint;
|
|
423
413
|
}
|