@zergai/ztc 0.2.6-dev.36101352252 → 0.2.7-dev.36236183737
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -17
- package/bin/zmux-darwin-arm64 +0 -0
- package/bin/ztc-tui-darwin-arm64 +0 -0
- package/bin/zterm-darwin-arm64 +0 -0
- package/dist/App.js +19 -5
- package/dist/agent/agent.d.ts +5 -1
- package/dist/agent/agent.js +51 -8
- package/dist/agent/backends/account_pool.d.ts +5 -6
- package/dist/agent/backends/account_pool.js +39 -14
- package/dist/agent/backends/account_rotation.d.ts +3 -0
- package/dist/agent/backends/account_rotation.js +14 -3
- package/dist/agent/backends/codex_account_status.d.ts +42 -0
- package/dist/agent/backends/codex_account_status.js +196 -0
- package/dist/agent/backends/codex_app_server.d.ts +12 -2
- package/dist/agent/backends/codex_app_server.js +66 -6
- package/dist/agent/backends/index.d.ts +2 -0
- package/dist/agent/backends/index.js +13 -5
- package/dist/agent/backends/openai.d.ts +2 -2
- package/dist/agent/backends/openai.js +4 -3
- package/dist/agent/backends/openai_compatible.js +10 -10
- package/dist/agent/backends/openai_responses.d.ts +17 -5
- package/dist/agent/backends/openai_responses.js +2 -4
- package/dist/agent/backends/token_usage.js +1 -1
- package/dist/agent/backends/types.d.ts +3 -0
- package/dist/agent/commands/accounts.d.ts +7 -1
- package/dist/agent/commands/accounts.js +299 -101
- package/dist/agent/commands/config.js +11 -3
- package/dist/agent/commands/config_profile_switch.d.ts +2 -0
- package/dist/agent/commands/config_profile_switch.js +22 -0
- package/dist/agent/commands/context.js +4 -3
- package/dist/agent/commands/index.js +1 -2
- package/dist/agent/commands/input_mode.js +1 -1
- package/dist/agent/commands/model.js +7 -2
- package/dist/agent/commands/plan.d.ts +12 -0
- package/dist/agent/commands/plan.js +39 -28
- package/dist/agent/commands/profile.d.ts +1 -1
- package/dist/agent/commands/profile.js +34 -257
- package/dist/agent/commands/provider_choices.d.ts +12 -1
- package/dist/agent/commands/provider_choices.js +29 -2
- package/dist/agent/commands/provider_selection.d.ts +3 -0
- package/dist/agent/commands/provider_selection.js +63 -0
- package/dist/agent/commands/settings.d.ts +2 -0
- package/dist/agent/commands/settings.js +655 -50
- package/dist/agent/commands/types.d.ts +19 -2
- package/dist/agent/tools/ask_user.js +2 -2
- package/dist/app/agent_runner.d.ts +1 -1
- package/dist/app/agent_runner.js +2 -2
- package/dist/app/ask_user_response.d.ts +3 -0
- package/dist/app/ask_user_response.js +16 -0
- package/dist/app/command_context.js +29 -13
- package/dist/app/controller.d.ts +17 -2
- package/dist/app/controller.js +24 -1
- package/dist/app/selection_completion.d.ts +9 -3
- package/dist/app/selection_completion.js +54 -22
- package/dist/cli_args.js +10 -1
- package/dist/cli_main.js +85 -2
- package/dist/cli_ratatui.d.ts +22 -4
- package/dist/cli_ratatui.js +421 -146
- package/dist/config/defaults.d.ts +6 -0
- package/dist/config/defaults.js +20 -0
- package/dist/config/profiles.d.ts +9 -0
- package/dist/config/profiles.js +87 -0
- package/dist/config/types.d.ts +2 -0
- package/dist/config.d.ts +7 -0
- package/dist/config.js +283 -40
- package/dist/headless.js +14 -0
- package/dist/panes/model_catalog.d.ts +1 -1
- package/dist/panes/model_catalog.js +24 -2
- package/dist/panes/ztc_launch.js +6 -0
- package/dist/router_auth.js +3 -2
- package/dist/session.d.ts +2 -0
- package/dist/session.js +3 -0
- package/dist/types.d.ts +23 -2
- package/dist/ui/views/app.d.ts +3 -1
- package/dist/ui/views/app.js +3 -2
- package/dist/ui/views/completion_menu.js +6 -6
- package/dist/ui/views/input_area.d.ts +2 -1
- package/dist/ui/views/input_area.js +9 -8
- package/dist/ui/views/status_bar.d.ts +2 -0
- package/dist/ui/views/status_bar.js +15 -4
- package/dist/ui/widgets/color_panel.d.ts +9 -0
- package/dist/ui/widgets/color_panel.js +41 -26
- package/dist/ui/widgets/navigator_panel.d.ts +9 -0
- package/dist/ui/widgets/navigator_panel.js +17 -16
- package/dist/ui/widgets/panel_frame.d.ts +83 -3
- package/dist/ui/widgets/panel_frame.js +107 -9
- package/dist/ui/widgets/questionnaire.d.ts +9 -0
- package/dist/ui/widgets/questionnaire.js +214 -16
- package/dist/ui/widgets/resume_panel.d.ts +9 -0
- package/dist/ui/widgets/resume_panel.js +18 -17
- package/dist/ui/widgets/rewind_panel.d.ts +9 -0
- package/dist/ui/widgets/rewind_panel.js +15 -16
- package/dist/ui/widgets/select.d.ts +6 -0
- package/dist/ui/widgets/select.js +47 -12
- package/dist/ui/widgets/settings_panel.d.ts +75 -0
- package/dist/ui/widgets/settings_panel.js +197 -39
- package/dist/ui/widgets/status_line_panel.d.ts +9 -0
- package/dist/ui/widgets/status_line_panel.js +18 -19
- package/dist/ui/widgets/transcript_preview.d.ts +8 -0
- package/dist/ui/widgets/transcript_preview.js +10 -0
- package/dist/utils/context.js +2 -0
- package/dist/utils/fireworks.d.ts +20 -0
- package/dist/utils/fireworks.js +23 -0
- package/dist/utils/models.d.ts +2 -1
- package/dist/utils/models.js +19 -0
- package/dist/web/accounts_mirror.d.ts +7 -0
- package/dist/web/accounts_mirror.js +59 -7
- package/dist/web/mirror_stream.js +19 -5
- package/dist/zlm-embed.mjs +5734 -5362
- package/package.json +2 -2
- package/dist/agent/commands/provider.d.ts +0 -2
- package/dist/agent/commands/provider.js +0 -75
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { getKimiModelInfo, getSupportedCodexReasoningEfforts, KIMI_CODE_MODEL_IDS, KIMI_MODEL_IDS, KIMI_MODEL_INFO, OFFICIAL_CODEX_MODEL_IDS, OFFICIAL_CODEX_MODEL_INFO, } from '../utils/models.js';
|
|
1
|
+
import { GPT_6_API_EFFORTS, getKimiModelInfo, getSupportedCodexReasoningEfforts, KIMI_CODE_MODEL_IDS, KIMI_MODEL_IDS, KIMI_MODEL_INFO, OFFICIAL_CODEX_MODEL_IDS, OFFICIAL_CODEX_MODEL_INFO, } from '../utils/models.js';
|
|
2
2
|
import { DEEPSEEK_FLASH_EFFORTS, DEEPSEEK_FLASH_MODEL, MODAL_DEEPSEEK_FLASH_EFFORTS, MODAL_DEEPSEEK_FLASH_MODEL } from '../utils/deepseek.js';
|
|
3
|
-
|
|
3
|
+
import { FIREWORKS_CHAT_MODELS, FIREWORKS_FUNDED_MODEL } from '../utils/fireworks.js';
|
|
4
|
+
export const PANE_PROVIDERS = ['anthropic', 'claude_code', 'openai', 'codex_app_server', 'codex_legacy', 'gemini', 'deepseek', 'fireworks', 'modal', 'kimi', 'kimi_code'];
|
|
4
5
|
export const MODEL_CATALOG = [
|
|
5
6
|
{
|
|
6
7
|
provider: 'anthropic',
|
|
@@ -25,6 +26,10 @@ export const MODEL_CATALOG = [
|
|
|
25
26
|
provider: 'openai',
|
|
26
27
|
label: 'OpenAI',
|
|
27
28
|
models: [
|
|
29
|
+
{ id: 'gpt-6-sol', name: 'GPT-6 Sol', aliases: ['gpt6sol', 'gpt-6 sol'],
|
|
30
|
+
defaultReasoningEffort: 'medium', supportedReasoningEfforts: GPT_6_API_EFFORTS },
|
|
31
|
+
{ id: 'gpt-6-luna', name: 'GPT-6 Luna', aliases: ['gpt6luna', 'gpt-6 luna'],
|
|
32
|
+
defaultReasoningEffort: 'medium', supportedReasoningEfforts: GPT_6_API_EFFORTS },
|
|
28
33
|
{ id: 'gpt-5.4', name: 'GPT-5.4', aliases: ['gpt54', 'gpt5.4'] },
|
|
29
34
|
{ id: 'gpt-5.4-mini', name: 'GPT-5.4 Mini', aliases: ['gpt54mini', 'gpt-5.4 mini', 'gpt5.4mini'] },
|
|
30
35
|
{ id: 'gpt-5.4-nano', name: 'GPT-5.4 Nano', aliases: ['gpt54nano', 'gpt-5.4 nano', 'gpt5.4nano'] },
|
|
@@ -87,6 +92,19 @@ export const MODEL_CATALOG = [
|
|
|
87
92
|
label: 'Kimi Code (subscription)',
|
|
88
93
|
models: KIMI_CODE_MODEL_IDS.map(makeKimiCatalogModel),
|
|
89
94
|
},
|
|
95
|
+
// Last on purpose: Fireworks reuses upstream slugs such as `kimi-k3`, and a
|
|
96
|
+
// bare id must resolve to the provider's own model before a Router mirror.
|
|
97
|
+
{
|
|
98
|
+
provider: 'fireworks',
|
|
99
|
+
label: 'Fireworks via Zerg Router',
|
|
100
|
+
models: FIREWORKS_CHAT_MODELS.map(model => ({
|
|
101
|
+
id: model.slug,
|
|
102
|
+
name: model.name,
|
|
103
|
+
description: model.slug === FIREWORKS_FUNDED_MODEL
|
|
104
|
+
? 'Serverless through Zerg Router. Funded on platform credits unless your workspace holds a Fireworks key.'
|
|
105
|
+
: 'Serverless through Zerg Router. Needs a workspace Fireworks key.',
|
|
106
|
+
})),
|
|
107
|
+
},
|
|
90
108
|
];
|
|
91
109
|
const PROVIDER_SET = new Set(PANE_PROVIDERS);
|
|
92
110
|
const MODEL_BY_ID = new Map();
|
|
@@ -141,6 +159,10 @@ function makeKimiCatalogModel(id) {
|
|
|
141
159
|
}
|
|
142
160
|
function getCodexModelAliases(id) {
|
|
143
161
|
switch (id) {
|
|
162
|
+
case 'gpt-6-sol':
|
|
163
|
+
return ['gpt6sol', 'gpt-6 sol'];
|
|
164
|
+
case 'gpt-6-luna':
|
|
165
|
+
return ['gpt6luna', 'gpt-6 luna'];
|
|
144
166
|
case 'gpt-5.6-sol':
|
|
145
167
|
return ['gpt-5.6', 'gpt56', 'gpt5.6', 'gpt56sol', 'sol'];
|
|
146
168
|
case 'gpt-5.6-terra':
|
package/dist/panes/ztc_launch.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync } from 'fs';
|
|
2
2
|
import { dirname, join } from 'path';
|
|
3
3
|
import { generateSessionId } from '../session.js';
|
|
4
|
+
import { activeProfile } from '../config/profiles.js';
|
|
4
5
|
const CHILD_ZTC_SCRIPT_RE = /(?:^|[\\/])cli_(?:main|ratatui)\.(?:js|ts)$/;
|
|
5
6
|
function filterChildZtcArgs(args) {
|
|
6
7
|
const skipFlags = new Set([
|
|
@@ -28,6 +29,7 @@ function filterChildZtcArgs(args) {
|
|
|
28
29
|
'--session-id',
|
|
29
30
|
'--provider',
|
|
30
31
|
'--model',
|
|
32
|
+
'--profile',
|
|
31
33
|
'--cwd',
|
|
32
34
|
'--mode',
|
|
33
35
|
'--prompt',
|
|
@@ -37,6 +39,8 @@ function filterChildZtcArgs(args) {
|
|
|
37
39
|
const filtered = [];
|
|
38
40
|
for (let i = 0; i < args.length; i++) {
|
|
39
41
|
const arg = args[i];
|
|
42
|
+
if (arg === '--')
|
|
43
|
+
break;
|
|
40
44
|
if (i === 0 && arg === 'resume') {
|
|
41
45
|
if (args[i + 1] && !args[i + 1].startsWith('-')) {
|
|
42
46
|
i++;
|
|
@@ -64,6 +68,8 @@ export function buildZtcPaneLaunchCommand(spec, argv = process.argv) {
|
|
|
64
68
|
}
|
|
65
69
|
const parentArgs = filterChildZtcArgs(argv.slice(2));
|
|
66
70
|
const childArgs = [...parentArgs, ...(spec.extraArgs || [])];
|
|
71
|
+
if (!childArgs.includes('--profile'))
|
|
72
|
+
childArgs.push('--profile', activeProfile());
|
|
67
73
|
if (spec.session.mode === 'resume') {
|
|
68
74
|
childArgs.push('--resume', spec.session.id);
|
|
69
75
|
}
|
package/dist/router_auth.js
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import { createHash, randomBytes, randomUUID } from 'node:crypto';
|
|
2
2
|
import { chmod, mkdir, readFile, rename, unlink, writeFile } from 'node:fs/promises';
|
|
3
|
-
import {
|
|
3
|
+
import { hostname } from 'node:os';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { spawn } from 'node:child_process';
|
|
6
6
|
import { setTimeout as delay } from 'node:timers/promises';
|
|
7
7
|
import { getVersion } from './utils/version.js';
|
|
8
|
+
import { profileConfigDir } from './config/profiles.js';
|
|
8
9
|
export const DEFAULT_ROUTER_ORIGIN = 'https://zergrouter.com';
|
|
9
10
|
/** Local, actionable routing failures whose messages contain no remote payload. */
|
|
10
11
|
export class RouterConfigurationError extends Error {
|
|
11
12
|
}
|
|
12
13
|
const terminalOutput = (message, stream) => { process[stream].write(message); };
|
|
13
14
|
function credentialDirectory() {
|
|
14
|
-
return
|
|
15
|
+
return profileConfigDir();
|
|
15
16
|
}
|
|
16
17
|
function credentialPath() {
|
|
17
18
|
return join(credentialDirectory(), 'router-auth.json');
|
package/dist/session.d.ts
CHANGED
|
@@ -24,6 +24,8 @@ export interface SessionMetadata {
|
|
|
24
24
|
cwd: string;
|
|
25
25
|
model: string;
|
|
26
26
|
provider: string;
|
|
27
|
+
/** Config profile selected for this session; absent in sessions saved before profiles. */
|
|
28
|
+
configProfile?: string;
|
|
27
29
|
createdAt: string;
|
|
28
30
|
updatedAt: string;
|
|
29
31
|
messageCount: number;
|
package/dist/session.js
CHANGED
|
@@ -19,6 +19,7 @@ import { homedir } from 'os';
|
|
|
19
19
|
import { createHash } from 'crypto';
|
|
20
20
|
import { writeFileAtomic } from './utils/atomic_write.js';
|
|
21
21
|
import { stripEphemeralMessages } from './utils/ephemeral.js';
|
|
22
|
+
import { activeProfile } from './config/profiles.js';
|
|
22
23
|
/**
|
|
23
24
|
* Schema version for on-disk Session payloads.
|
|
24
25
|
*
|
|
@@ -681,6 +682,7 @@ export async function saveSession(sessionId, cwd, messages, model, provider, mux
|
|
|
681
682
|
cwd,
|
|
682
683
|
model,
|
|
683
684
|
provider,
|
|
685
|
+
configProfile: activeProfile(),
|
|
684
686
|
createdAt: existingSession?.metadata.createdAt || new Date().toISOString(),
|
|
685
687
|
updatedAt: new Date().toISOString(),
|
|
686
688
|
messageCount: messages.length,
|
|
@@ -775,6 +777,7 @@ export async function saveSessionToFile(path, sessionId, cwd, messages, model, p
|
|
|
775
777
|
cwd,
|
|
776
778
|
model,
|
|
777
779
|
provider,
|
|
780
|
+
configProfile: activeProfile(),
|
|
778
781
|
createdAt,
|
|
779
782
|
updatedAt: new Date().toISOString(),
|
|
780
783
|
messageCount: messages.length,
|
package/dist/types.d.ts
CHANGED
|
@@ -12,12 +12,29 @@ export type TodoStatus = typeof TODO_STATUSES[number];
|
|
|
12
12
|
*/
|
|
13
13
|
export declare const AGENT_STATUS_LABELS: Record<AgentStatus, string>;
|
|
14
14
|
export type InputMode = 'queue' | 'interrupt';
|
|
15
|
+
/**
|
|
16
|
+
* When a busy-turn submission should reach the model.
|
|
17
|
+
*
|
|
18
|
+
* - `subturn`: insert after the current tool call finishes, inside the running
|
|
19
|
+
* turn (Enter). Falls back to a fresh turn when no tool boundary remains.
|
|
20
|
+
* - `end-of-turn`: hold until the agent finishes the whole turn (Tab).
|
|
21
|
+
*
|
|
22
|
+
* This is the message's own intent, independent of `InputMode`. `InputMode`
|
|
23
|
+
* only selects what *Enter* does while busy; it must never strand a message the
|
|
24
|
+
* user queued with Tab.
|
|
25
|
+
*/
|
|
26
|
+
export type DeliveryIntent = 'subturn' | 'end-of-turn';
|
|
15
27
|
/** A single entry in the outbound message queue. Plain data — JSON-safe. */
|
|
16
28
|
export interface QueueEntry {
|
|
17
29
|
id: string;
|
|
18
30
|
text: string;
|
|
19
31
|
attachments?: MessageAttachment[];
|
|
20
32
|
queuedAt: Date;
|
|
33
|
+
/**
|
|
34
|
+
* Delivery timing for this entry. Absent on legacy persisted entries means
|
|
35
|
+
* `end-of-turn` (the conservative "wait for the whole turn" behavior).
|
|
36
|
+
*/
|
|
37
|
+
delivery?: DeliveryIntent;
|
|
21
38
|
}
|
|
22
39
|
export interface ToolCall {
|
|
23
40
|
id: string;
|
|
@@ -86,9 +103,11 @@ export interface AgentState {
|
|
|
86
103
|
* undefined falls back to MODEL_CONTEXT_WINDOWS.
|
|
87
104
|
*/
|
|
88
105
|
contextWindow?: number;
|
|
106
|
+
/** Raw input tokens for the last model call, before durable-context adjustments. */
|
|
107
|
+
cacheInputTokens?: number;
|
|
89
108
|
/**
|
|
90
|
-
* Provider-reported cached input for the last
|
|
91
|
-
* `
|
|
109
|
+
* Provider-reported cached input for the last model call, as a subset of
|
|
110
|
+
* `cacheInputTokens`. Absent means the provider reported no cache accounting —
|
|
92
111
|
* NOT a 0% hit rate. See `cacheHitRate` in utils/context.ts.
|
|
93
112
|
*/
|
|
94
113
|
cachedInputTokens?: number;
|
|
@@ -167,6 +186,8 @@ export type AgentEvent = {
|
|
|
167
186
|
usage: TokenUsage;
|
|
168
187
|
contextTokens?: number;
|
|
169
188
|
contextWindow?: number;
|
|
189
|
+
cacheInputTokens?: number;
|
|
190
|
+
contextCachedInputTokens?: number;
|
|
170
191
|
} | {
|
|
171
192
|
type: 'api_retry';
|
|
172
193
|
attempt: number;
|
package/dist/ui/views/app.d.ts
CHANGED
|
@@ -65,6 +65,8 @@ interface AppViewProps {
|
|
|
65
65
|
statusLine?: StatusLineSettings;
|
|
66
66
|
/** Effort in effect for the active model; undefined follows the provider. */
|
|
67
67
|
effort?: CodexReasoningEffort;
|
|
68
|
+
/** Saved effort the active model rejects; the status line flags it. */
|
|
69
|
+
unsupportedEffort?: CodexReasoningEffort;
|
|
68
70
|
/**
|
|
69
71
|
* When true, the message-list region is painted by the mux pane via
|
|
70
72
|
* scrollback (Phase 1+ of always-scrolling rollout) and the Ink
|
|
@@ -104,5 +106,5 @@ export declare function getAppLayoutMetrics({ rows, cols, inputState, commands,
|
|
|
104
106
|
widgetHeight: number;
|
|
105
107
|
transcriptHeight: number;
|
|
106
108
|
};
|
|
107
|
-
export declare function buildAppView({ messages, agentState, inputState, sessionId, sessionName, cols, rows, commands, completion, hasApiKey, version, contextLength, contextEstimated, contextStatus, provider, model, emulationId, inputMode, toast, spinnerLabel, spinnerFrame, lastEventAt, stallHintMs, debug, expandToolOutputs, showThinking, renderer, showHeader, showMessages, isRecording, recordingDevice, audioLevel, partialTranscript, todos, showTodos, activeTask, taskStartedAt, mode, authEmail, authWorkspace, backgroundRunning, widgetContent, widgetView: providedWidgetView, queuePreview, queueTotal, queueSelectedIndex, externalTranscript, compactWidget, agentActive, statusLine, effort, }: AppViewProps): LayoutNode;
|
|
109
|
+
export declare function buildAppView({ messages, agentState, inputState, sessionId, sessionName, cols, rows, commands, completion, hasApiKey, version, contextLength, contextEstimated, contextStatus, provider, model, emulationId, inputMode, toast, spinnerLabel, spinnerFrame, lastEventAt, stallHintMs, debug, expandToolOutputs, showThinking, renderer, showHeader, showMessages, isRecording, recordingDevice, audioLevel, partialTranscript, todos, showTodos, activeTask, taskStartedAt, mode, authEmail, authWorkspace, backgroundRunning, widgetContent, widgetView: providedWidgetView, queuePreview, queueTotal, queueSelectedIndex, externalTranscript, compactWidget, agentActive, statusLine, effort, unsupportedEffort, }: AppViewProps): LayoutNode;
|
|
108
110
|
export {};
|
package/dist/ui/views/app.js
CHANGED
|
@@ -93,7 +93,7 @@ export function getAppLayoutMetrics({ rows, cols, inputState, commands = [], com
|
|
|
93
93
|
// the menu instead of reserving a partial row or displacing the status line.
|
|
94
94
|
const completionHeight = activeCompletion?.kind === 'usage'
|
|
95
95
|
? Math.min(1, completionBudget)
|
|
96
|
-
: completionBudget >= 2 ?
|
|
96
|
+
: completionBudget >= 2 ? selectionCompletionRows(activeCompletion, completionBudget) : 0;
|
|
97
97
|
const inputHeight = Math.min(inputLineCount + previewLines, maxInputLines) + completionHeight + inputOverhead;
|
|
98
98
|
const auxiliaryBudget = Math.max(0, rows - headerHeight - statusHeight - activityHeight - inputHeight - transcriptReserve);
|
|
99
99
|
const { queueHeight, todoHeight } = splitAuxiliaryBudget(auxiliaryBudget, rawQueueHeight, rawTodoHeight);
|
|
@@ -148,7 +148,7 @@ export function getAppLayoutMetrics({ rows, cols, inputState, commands = [], com
|
|
|
148
148
|
}
|
|
149
149
|
export function buildAppView({ messages, agentState, inputState, sessionId, sessionName, cols, rows, commands, completion, hasApiKey, version = '0.1.0', contextLength, contextEstimated, contextStatus, provider, model, emulationId, inputMode, toast, spinnerLabel, spinnerFrame, lastEventAt, stallHintMs, debug = false, expandToolOutputs = false, showThinking = true, renderer, showHeader = true, showMessages = true, isRecording = false, recordingDevice, audioLevel = -60, partialTranscript = '', todos = [], showTodos = true, activeTask = null, taskStartedAt = null, mode = 'normal', authEmail, authWorkspace, backgroundRunning = 0, widgetContent = null, widgetView: providedWidgetView = null, queuePreview = [], queueTotal = 0, queueSelectedIndex = null, externalTranscript = false, compactWidget = false, agentActive = agentState.status === 'thinking'
|
|
150
150
|
|| agentState.status === 'streaming'
|
|
151
|
-
|| agentState.status === 'tool_use', statusLine, effort, }) {
|
|
151
|
+
|| agentState.status === 'tool_use', statusLine, effort, unsupportedEffort, }) {
|
|
152
152
|
// A compact widget (the question form) sizes to its own content. Deriving the
|
|
153
153
|
// row count here keeps the widget region and the transcript reserve aligned
|
|
154
154
|
// with what the frame actually paints.
|
|
@@ -269,6 +269,7 @@ export function buildAppView({ messages, agentState, inputState, sessionId, sess
|
|
|
269
269
|
model,
|
|
270
270
|
emulationId,
|
|
271
271
|
effort,
|
|
272
|
+
unsupportedEffort,
|
|
272
273
|
inputMode,
|
|
273
274
|
toast,
|
|
274
275
|
debug,
|
|
@@ -42,10 +42,10 @@ export function buildCompletionMenu(view, cols) {
|
|
|
42
42
|
if (view.kind === 'usage') {
|
|
43
43
|
return { menu: box([text(clip(`Usage: ${view.usage ?? ''}`, width), muted)], { paddingX: 2, height: 1 }) };
|
|
44
44
|
}
|
|
45
|
-
const rowCount = Math.max(1,
|
|
46
|
-
const first = Math.max(0, view.selected - rowCount + 1, Math.min(view.firstVisible ?? 0, view.selected));
|
|
45
|
+
const rowCount = Math.max(1, view.visibleRows ?? SELECTION_VISIBLE_ROWS);
|
|
46
|
+
const first = Math.min(Math.max(0, view.candidates.length - rowCount), Math.max(0, view.selected - rowCount + 1, Math.min(view.firstVisible ?? 0, view.selected)));
|
|
47
47
|
const kind = view.kind ?? (view.provider ? 'model' : 'provider');
|
|
48
|
-
const title = kind === 'model' ? `Models · ${view.provider ?? ''}` : kind === 'command' ? 'Commands' : 'Providers';
|
|
48
|
+
const title = kind === 'model' ? `Models · ${view.provider ?? ''}` : kind === 'command' ? 'Commands' : view.mixed ? 'Providers & models' : 'Providers';
|
|
49
49
|
const count = view.candidates.length ? `${view.selected + 1}/${view.candidates.length}` : '0 results';
|
|
50
50
|
const state = view.loading ? ' · loading' : view.unavailable ? ' · cached' : '';
|
|
51
51
|
const heading = clip(title + state, Math.max(1, width - displayWidth(count) - 2));
|
|
@@ -84,10 +84,10 @@ export function buildCompletionMenu(view, cols) {
|
|
|
84
84
|
rows.push(text(clip(empty, width), muted));
|
|
85
85
|
}
|
|
86
86
|
const selected = view.candidates[view.selected];
|
|
87
|
-
const
|
|
87
|
+
const enterAction = selected && view.query === selected.value && !selected.needsArgument ? 'run' : 'fill';
|
|
88
88
|
const hints = view.candidates.length ? [
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
enterAction === 'run' ? '↑↓ move · Tab fill · Enter run · Esc close' : '↑↓ move · Tab/Enter fill · Esc close',
|
|
90
|
+
enterAction === 'run' ? '↑↓ · Tab fill · ↵ run · Esc' : '↑↓ · Tab/↵ fill · Esc',
|
|
91
91
|
'↑↓ Tab ↵ Esc',
|
|
92
92
|
] : [kind === 'command' ? 'Keep typing · Esc close' : 'Enter exact ID · Esc close', '↵ exact ID · Esc', '↵ Esc'];
|
|
93
93
|
const hint = hints.find(value => displayWidth(value) <= width) ?? hints.at(-1);
|
|
@@ -18,7 +18,7 @@ interface InputAreaViewProps {
|
|
|
18
18
|
spinnerFrame?: string | null;
|
|
19
19
|
inputMode?: string;
|
|
20
20
|
}
|
|
21
|
-
interface RenderToken {
|
|
21
|
+
export interface RenderToken {
|
|
22
22
|
text: string;
|
|
23
23
|
style?: {
|
|
24
24
|
color?: string;
|
|
@@ -42,6 +42,7 @@ export declare function wrapInputSegments(segments: InputSegment[], cursor: {
|
|
|
42
42
|
cursorLine: number;
|
|
43
43
|
cursorCol: number;
|
|
44
44
|
};
|
|
45
|
+
export declare function renderLineWithCursorTokens(line: RenderToken[], cursorCol: number, isCursorLine: boolean): RenderToken[];
|
|
45
46
|
export declare function estimateInputLines(segments: InputSegment[], cols: number): number;
|
|
46
47
|
export declare function buildInputAreaView({ state, placeholder, disabled, commands, completion, cols, maxInputLines, badgePreview, showBadgePreview, debug, renderContent, spinnerLabel, spinnerFrame }: InputAreaViewProps): LayoutNode;
|
|
47
48
|
export {};
|
|
@@ -145,9 +145,9 @@ export function wrapInputSegments(segments, cursor, cols, prefixWidth = promptWi
|
|
|
145
145
|
}
|
|
146
146
|
return { lines, cursorLine, cursorCol };
|
|
147
147
|
}
|
|
148
|
-
function
|
|
148
|
+
export function renderLineWithCursorTokens(line, cursorCol, isCursorLine) {
|
|
149
149
|
if (!isCursorLine)
|
|
150
|
-
return line
|
|
150
|
+
return line;
|
|
151
151
|
const nodes = [];
|
|
152
152
|
let col = 0;
|
|
153
153
|
let cursorRendered = false;
|
|
@@ -165,17 +165,17 @@ function renderLineWithCursor(line, cursorCol, isCursorLine) {
|
|
|
165
165
|
}
|
|
166
166
|
if (cursorOffset >= 0) {
|
|
167
167
|
if (cursorOffset)
|
|
168
|
-
nodes.push(text
|
|
169
|
-
nodes.push(text
|
|
168
|
+
nodes.push({ text: token.text.slice(0, cursorOffset), style: token.style });
|
|
169
|
+
nodes.push({ text: token.text.slice(cursorOffset, cursorEnd), style: { ...token.style, inverse: true } });
|
|
170
170
|
if (cursorEnd < token.text.length)
|
|
171
|
-
nodes.push(text
|
|
171
|
+
nodes.push({ text: token.text.slice(cursorEnd), style: token.style });
|
|
172
172
|
}
|
|
173
173
|
else {
|
|
174
|
-
nodes.push(
|
|
174
|
+
nodes.push(token);
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
177
|
if (!cursorRendered && cursorCol === col)
|
|
178
|
-
nodes.push(text
|
|
178
|
+
nodes.push({ text: ' ', style: { inverse: true } });
|
|
179
179
|
return nodes;
|
|
180
180
|
}
|
|
181
181
|
export function estimateInputLines(segments, cols) {
|
|
@@ -222,7 +222,8 @@ export function buildInputAreaView({ state, placeholder, disabled = false, comma
|
|
|
222
222
|
: sourceLine === lastLine - 1 && lastLine < wrapped.lines.length ? '↧ ' : ' ';
|
|
223
223
|
const segments = [
|
|
224
224
|
text(prefix, { color: promptColor, bold: index === 0 }),
|
|
225
|
-
...
|
|
225
|
+
...renderLineWithCursorTokens(line, wrapped.cursorCol, sourceLine === wrapped.cursorLine)
|
|
226
|
+
.map(token => text(token.text, token.style)),
|
|
226
227
|
];
|
|
227
228
|
return box(segments, { flexDirection: 'row' });
|
|
228
229
|
});
|
|
@@ -18,6 +18,8 @@ interface StatusBarProps {
|
|
|
18
18
|
emulationId?: string;
|
|
19
19
|
/** Effort in effect for the active model; undefined follows the provider. */
|
|
20
20
|
effort?: CodexReasoningEffort;
|
|
21
|
+
/** Saved effort the active model rejects, shown so it is not silently ignored. */
|
|
22
|
+
unsupportedEffort?: CodexReasoningEffort;
|
|
21
23
|
inputMode?: 'queue' | 'interrupt';
|
|
22
24
|
toast?: string | null;
|
|
23
25
|
spinnerLabel?: string | null;
|
|
@@ -104,7 +104,7 @@ function statusItemForms(item, props) {
|
|
|
104
104
|
// Undefined when the provider reports no cache accounting. The field is then
|
|
105
105
|
// omitted rather than painted as 0%, so an unknown rate is never read as a
|
|
106
106
|
// measured miss.
|
|
107
|
-
const cacheShare = cacheHitRate(props.state.cachedInputTokens, usedTokens);
|
|
107
|
+
const cacheShare = cacheHitRate(props.state.cachedInputTokens, props.state.cacheInputTokens ?? usedTokens);
|
|
108
108
|
const cacheLabel = cacheShare === undefined ? undefined : `${Math.round(cacheShare)}%`;
|
|
109
109
|
const percentage = props.contextStatus ? `${Math.round(props.contextStatus.percentage)}%` : undefined;
|
|
110
110
|
const remaining = props.contextStatus ? `${Math.max(0, Math.round(props.contextStatus.remaining))}%` : undefined;
|
|
@@ -172,8 +172,19 @@ function statusItemForms(item, props) {
|
|
|
172
172
|
short: emulation && `emu:${emulation}`,
|
|
173
173
|
long: emulation && `Emulating ${emulation}`,
|
|
174
174
|
};
|
|
175
|
-
case 'effort':
|
|
176
|
-
|
|
175
|
+
case 'effort': {
|
|
176
|
+
const unsupported = props.unsupportedEffort;
|
|
177
|
+
if (!unsupported) {
|
|
178
|
+
return { raw: props.effort, short: props.effort && `eff ${props.effort}`, long: props.effort && `Effort ${props.effort}` };
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
raw: unsupported,
|
|
182
|
+
short: props.effort ? `eff ${props.effort} (${unsupported} n/a)` : `eff ${unsupported} n/a`,
|
|
183
|
+
long: props.effort
|
|
184
|
+
? `Effort ${props.effort} (${unsupported} unsupported by this model)`
|
|
185
|
+
: `Effort ${unsupported} unsupported by this model`,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
177
188
|
case 'reasoning':
|
|
178
189
|
// The field is the thinking switch, so the short form says the thing the
|
|
179
190
|
// user is actually turning on rather than restating the flag.
|
|
@@ -212,7 +223,7 @@ function statusItemColor(item, props) {
|
|
|
212
223
|
case 'model':
|
|
213
224
|
case 'provider-model': return TERMINAL_THEME.muted;
|
|
214
225
|
case 'emulation': return TERMINAL_THEME.info;
|
|
215
|
-
case 'effort': return TERMINAL_THEME.muted;
|
|
226
|
+
case 'effort': return props.unsupportedEffort ? TERMINAL_THEME.attention : TERMINAL_THEME.muted;
|
|
216
227
|
case 'reasoning': return reasoningEnabled(props.effort) ? TERMINAL_THEME.info : TERMINAL_THEME.muted;
|
|
217
228
|
case 'input-mode': return TERMINAL_THEME.attention;
|
|
218
229
|
case 'version': return TERMINAL_THEME.subtle;
|
|
@@ -46,6 +46,15 @@ export interface ColorPanelSurface {
|
|
|
46
46
|
export declare class ColorPanelWidget {
|
|
47
47
|
private readonly host;
|
|
48
48
|
private active;
|
|
49
|
+
/**
|
|
50
|
+
* Column the painted frame starts on, in surface coordinates.
|
|
51
|
+
*
|
|
52
|
+
* Zero unless the terminal is wide enough to trigger the shared cap, in which
|
|
53
|
+
* case it is that centring margin. The wheel bands add it back: a notch lands
|
|
54
|
+
* in terminal columns, so without it a capped page would scroll from a pane
|
|
55
|
+
* that is no longer under the pointer.
|
|
56
|
+
*/
|
|
57
|
+
private frameInset;
|
|
49
58
|
private draft;
|
|
50
59
|
private focus;
|
|
51
60
|
private presetIndex;
|
|
@@ -25,7 +25,7 @@ import { displayWidth, padEndDisplay } from '../core/text_width.js';
|
|
|
25
25
|
import { TERMINAL_THEME } from '../views/theme.js';
|
|
26
26
|
import { COLOR_PRESETS, blendOver, formatColor, parseColor } from '../../config/colors.js';
|
|
27
27
|
import { fuzzyMatch } from '../../utils/fuzzy_match.js';
|
|
28
|
-
import { bodyBand, bottomBorder, clipText, dropLastGrapheme, framedLine, isInsideBand, lineText, planPanelSplit, sideBySideBands, stackedBands, topBorder, WHEEL_LINES, } from './panel_frame.js';
|
|
28
|
+
import { widgetFrameGeometry, bodyBand, bottomBorder, clipText, dropLastGrapheme, framedLine, isInsideBand, lineText, planPanelSplit, sideBySideBands, stackedBands, topBorder, WHEEL_LINES, } from './panel_frame.js';
|
|
29
29
|
const CHANNEL_ORDER = ['hex', 'r', 'g', 'b', 'a'];
|
|
30
30
|
const CHANNEL_LABELS = {
|
|
31
31
|
hex: 'Hex',
|
|
@@ -76,6 +76,15 @@ function presetsFor(data) {
|
|
|
76
76
|
export class ColorPanelWidget {
|
|
77
77
|
host;
|
|
78
78
|
active = null;
|
|
79
|
+
/**
|
|
80
|
+
* Column the painted frame starts on, in surface coordinates.
|
|
81
|
+
*
|
|
82
|
+
* Zero unless the terminal is wide enough to trigger the shared cap, in which
|
|
83
|
+
* case it is that centring margin. The wheel bands add it back: a notch lands
|
|
84
|
+
* in terminal columns, so without it a capped page would scroll from a pane
|
|
85
|
+
* that is no longer under the pointer.
|
|
86
|
+
*/
|
|
87
|
+
frameInset = 0;
|
|
79
88
|
draft = { r: 255, g: 255, b: 255, a: 1 };
|
|
80
89
|
focus = 'presets';
|
|
81
90
|
presetIndex = 0;
|
|
@@ -324,28 +333,33 @@ export class ColorPanelWidget {
|
|
|
324
333
|
// ── Rendering ─────────────────────────────────────────────────────────────
|
|
325
334
|
buildSurface(cols, maxRows) {
|
|
326
335
|
const lines = this.buildSurfaceLines(cols, maxRows);
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
flexDirection: 'row',
|
|
334
|
-
height: 1,
|
|
335
|
-
flexShrink: 0,
|
|
336
|
-
backgroundColor: segment.backgroundColor,
|
|
337
|
-
})
|
|
338
|
-
: text(segment.text, segment.style))), {
|
|
336
|
+
// This panel keeps its own view builder rather than the shared one: a swatch
|
|
337
|
+
// needs a filled cell, which is a background on the run and not a colour on
|
|
338
|
+
// the glyph. The centring margin still goes on the frame, so it lands on the
|
|
339
|
+
// same column as every other page.
|
|
340
|
+
const frame = box(lines.map(line => box(line.segments.map(segment => (segment.backgroundColor
|
|
341
|
+
? box([text(segment.text, segment.style)], {
|
|
339
342
|
flexDirection: 'row',
|
|
340
343
|
height: 1,
|
|
341
344
|
flexShrink: 0,
|
|
342
|
-
backgroundColor:
|
|
343
|
-
})
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
345
|
+
backgroundColor: segment.backgroundColor,
|
|
346
|
+
})
|
|
347
|
+
: text(segment.text, segment.style))), {
|
|
348
|
+
flexDirection: 'row',
|
|
349
|
+
height: 1,
|
|
350
|
+
flexShrink: 0,
|
|
351
|
+
backgroundColor: line.backgroundColor,
|
|
352
|
+
})), {
|
|
353
|
+
flexDirection: 'column',
|
|
354
|
+
height: lines.length,
|
|
355
|
+
flexShrink: 0,
|
|
356
|
+
backgroundColor: TERMINAL_THEME.bg,
|
|
357
|
+
});
|
|
358
|
+
return {
|
|
359
|
+
content: lines.map(lineText).join('\n'),
|
|
360
|
+
view: this.frameInset > 0
|
|
361
|
+
? box([frame], { flexDirection: 'column', marginLeft: this.frameInset, flexShrink: 0 })
|
|
362
|
+
: frame,
|
|
349
363
|
lineCount: lines.length,
|
|
350
364
|
};
|
|
351
365
|
}
|
|
@@ -358,9 +372,10 @@ export class ColorPanelWidget {
|
|
|
358
372
|
if (maxRows !== undefined && maxRows <= 0)
|
|
359
373
|
return [];
|
|
360
374
|
const termCols = Math.max(1, Math.floor(cols || process.stdout.columns || 80));
|
|
361
|
-
// The host embeds the surface in a `paddingX: 1` column, so
|
|
362
|
-
// the
|
|
363
|
-
const width =
|
|
375
|
+
// The host embeds the surface in a `paddingX: 1` column, so the frame asks
|
|
376
|
+
// for `termCols - 2`; the shared cap bounds the frame and it sits flush left.
|
|
377
|
+
const { width, inset } = widgetFrameGeometry(termCols);
|
|
378
|
+
this.frameInset = inset;
|
|
364
379
|
const height = Math.max(1, Math.floor(maxRows ?? 20));
|
|
365
380
|
const innerWidth = Math.max(0, width - 4);
|
|
366
381
|
const lines = [topBorder(width, this.active.label, this.currentValue())];
|
|
@@ -415,7 +430,7 @@ export class ColorPanelWidget {
|
|
|
415
430
|
...padRow(channels.rows[index], split.previewWidth),
|
|
416
431
|
], width));
|
|
417
432
|
}
|
|
418
|
-
this.presetsBand = sideBySideBands(headingRow, lines.length - 1, split.listWidth, split.previewWidth).list;
|
|
433
|
+
this.presetsBand = sideBySideBands(headingRow, lines.length - 1, split.listWidth, split.previewWidth, this.frameInset).list;
|
|
419
434
|
}
|
|
420
435
|
else if (placement === 'bottom') {
|
|
421
436
|
const channelRows = this.channelRows(innerWidth);
|
|
@@ -436,7 +451,7 @@ export class ColorPanelWidget {
|
|
|
436
451
|
for (let index = 0; index < channelCapacity; index += 1) {
|
|
437
452
|
lines.push(framedLine(channels.rows[index]?.segments ?? [], width));
|
|
438
453
|
}
|
|
439
|
-
this.presetsBand = stackedBands({ top: presetsTop - presetCapacity + 1, bottom: presetsTop }, { top: channelsTop, bottom: lines.length - 1 }, innerWidth).list;
|
|
454
|
+
this.presetsBand = stackedBands({ top: presetsTop - presetCapacity + 1, bottom: presetsTop }, { top: channelsTop, bottom: lines.length - 1 }, innerWidth, this.frameInset).list;
|
|
440
455
|
}
|
|
441
456
|
else {
|
|
442
457
|
// Too short for two panes: the one being worked in takes the whole body.
|
|
@@ -446,7 +461,7 @@ export class ColorPanelWidget {
|
|
|
446
461
|
for (let index = 0; index < bodyRows; index += 1) {
|
|
447
462
|
lines.push(framedLine(windowed.rows[index]?.segments ?? [], width));
|
|
448
463
|
}
|
|
449
|
-
this.presetsBand = bodyBand(top, lines.length - 1, innerWidth);
|
|
464
|
+
this.presetsBand = bodyBand(top, lines.length - 1, innerWidth, this.frameInset);
|
|
450
465
|
}
|
|
451
466
|
// Each preview row is framed like every other line, so the bar is painted
|
|
452
467
|
// inside the page rather than across it.
|
|
@@ -88,6 +88,15 @@ export declare class NavigatorPanelWidget {
|
|
|
88
88
|
private refreshInFlight;
|
|
89
89
|
private lastBodyRows;
|
|
90
90
|
private lastInnerWidth;
|
|
91
|
+
/**
|
|
92
|
+
* Column the painted frame starts on, in surface coordinates.
|
|
93
|
+
*
|
|
94
|
+
* Zero unless the terminal is wide enough to trigger the shared cap, in which
|
|
95
|
+
* case it is that centring margin. The wheel bands add it back: a notch lands
|
|
96
|
+
* in terminal columns, so without it a capped page would scroll from a pane
|
|
97
|
+
* that is no longer under the pointer.
|
|
98
|
+
*/
|
|
99
|
+
private frameInset;
|
|
91
100
|
private lastSplit;
|
|
92
101
|
private visibleCache;
|
|
93
102
|
private focus;
|
|
@@ -28,11 +28,10 @@
|
|
|
28
28
|
* Secondary keys and the filter share one keyboard, as in the other pickers:
|
|
29
29
|
* letters are actions until `/` (or any unused letter) opens the query.
|
|
30
30
|
*/
|
|
31
|
-
import { box, text } from '../core/index.js';
|
|
32
31
|
import { displayWidth, padEndDisplay } from '../core/text_width.js';
|
|
33
32
|
import { TERMINAL_THEME } from '../views/theme.js';
|
|
34
33
|
import { fuzzyMatch } from '../../utils/fuzzy_match.js';
|
|
35
|
-
import { bottomBorder, clipText, dropLastGrapheme, fitSegments, framedLine, isInsideBand, joinColumns, lineText, planPanelSplit, sideBySideBands, stackedBands, topBorder, WHEEL_LINES, } from './panel_frame.js';
|
|
34
|
+
import { bottomBorder, clipText, dropLastGrapheme, fitSegments, framedLine, isInsideBand, joinColumns, lineText, planPanelSplit, sideBySideBands, stackedBands, topBorder, WHEEL_LINES, buildWidgetFrameView, widgetFrameGeometry, } from './panel_frame.js';
|
|
36
35
|
import { buildTranscriptLines, transcriptHeaderText, windowedLines, TRANSCRIPT_PANE_MIN_BODY_ROWS, TRANSCRIPT_PANE_MIN_WIDTH, } from './transcript_preview.js';
|
|
37
36
|
/** Chrome rows: top border, filter line, footer, bottom border, one slack. */
|
|
38
37
|
const CHROME_ROWS = 5;
|
|
@@ -51,6 +50,15 @@ export class NavigatorPanelWidget {
|
|
|
51
50
|
refreshInFlight = false;
|
|
52
51
|
lastBodyRows = 1;
|
|
53
52
|
lastInnerWidth = 78;
|
|
53
|
+
/**
|
|
54
|
+
* Column the painted frame starts on, in surface coordinates.
|
|
55
|
+
*
|
|
56
|
+
* Zero unless the terminal is wide enough to trigger the shared cap, in which
|
|
57
|
+
* case it is that centring margin. The wheel bands add it back: a notch lands
|
|
58
|
+
* in terminal columns, so without it a capped page would scroll from a pane
|
|
59
|
+
* that is no longer under the pointer.
|
|
60
|
+
*/
|
|
61
|
+
frameInset = 0;
|
|
54
62
|
lastSplit = null;
|
|
55
63
|
visibleCache = null;
|
|
56
64
|
focus = 'list';
|
|
@@ -559,17 +567,7 @@ export class NavigatorPanelWidget {
|
|
|
559
567
|
const lines = this.buildSurfaceLines(cols, maxRows);
|
|
560
568
|
return {
|
|
561
569
|
content: lines.map(lineText).join('\n'),
|
|
562
|
-
view:
|
|
563
|
-
flexDirection: 'row',
|
|
564
|
-
height: 1,
|
|
565
|
-
flexShrink: 0,
|
|
566
|
-
backgroundColor: line.backgroundColor,
|
|
567
|
-
})), {
|
|
568
|
-
flexDirection: 'column',
|
|
569
|
-
height: Math.max(1, lines.length),
|
|
570
|
-
flexShrink: 0,
|
|
571
|
-
backgroundColor: TERMINAL_THEME.bg,
|
|
572
|
-
}),
|
|
570
|
+
view: buildWidgetFrameView(lines, this.frameInset),
|
|
573
571
|
lineCount: lines.length,
|
|
574
572
|
};
|
|
575
573
|
}
|
|
@@ -602,7 +600,8 @@ export class NavigatorPanelWidget {
|
|
|
602
600
|
if (maxRows !== undefined && maxRows <= 0)
|
|
603
601
|
return [];
|
|
604
602
|
const termCols = Math.max(1, Math.floor(cols || process.stdout.columns || 80));
|
|
605
|
-
const width =
|
|
603
|
+
const { width, inset } = widgetFrameGeometry(termCols);
|
|
604
|
+
this.frameInset = inset;
|
|
606
605
|
const height = Math.max(1, Math.floor(maxRows ?? 20));
|
|
607
606
|
const innerWidth = Math.max(0, width - 4);
|
|
608
607
|
this.lastInnerWidth = innerWidth;
|
|
@@ -691,7 +690,7 @@ export class NavigatorPanelWidget {
|
|
|
691
690
|
...(previewRow ? previewRow.segments : []),
|
|
692
691
|
], width));
|
|
693
692
|
}
|
|
694
|
-
this.paneBands = sideBySideBands(bandTop, lines.length - 1, split.listWidth, paneWidth);
|
|
693
|
+
this.paneBands = sideBySideBands(bandTop, lines.length - 1, split.listWidth, paneWidth, this.frameInset);
|
|
695
694
|
}
|
|
696
695
|
/** Transcript under the list, with its own heading — a narrow terminal. */
|
|
697
696
|
drawStackedPane(lines, split, width, innerWidth) {
|
|
@@ -722,7 +721,7 @@ export class NavigatorPanelWidget {
|
|
|
722
721
|
lines.push(framedLine(previewRow ? previewRow.segments : [], width));
|
|
723
722
|
}
|
|
724
723
|
// Stacked, the panes share the full width and are told apart by rows.
|
|
725
|
-
this.paneBands = stackedBands({ top: listTop, bottom: listBottom }, { top: previewTop, bottom: lines.length - 1 }, innerWidth);
|
|
724
|
+
this.paneBands = stackedBands({ top: listTop, bottom: listBottom }, { top: previewTop, bottom: lines.length - 1 }, innerWidth, this.frameInset);
|
|
726
725
|
}
|
|
727
726
|
previewHeaderText() {
|
|
728
727
|
const entry = this.selectedEntry();
|
|
@@ -733,6 +732,8 @@ export class NavigatorPanelWidget {
|
|
|
733
732
|
scroll: this.previewScroll,
|
|
734
733
|
viewportRows: this.previewViewportRows,
|
|
735
734
|
focused: this.focus === 'preview',
|
|
735
|
+
// One cell of the heading's column is the gutter between the two panes.
|
|
736
|
+
maxWidth: Math.max(8, (this.lastSplit?.previewWidth ?? 0) - 1),
|
|
736
737
|
});
|
|
737
738
|
}
|
|
738
739
|
previewRenderLines() {
|