dsh-ssh-tui 0.3.10 → 0.4.0
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.en.md +18 -13
- package/README.md +11 -13
- package/lib/i18n/en.js +27 -2
- package/lib/i18n/en.js.map +1 -1
- package/lib/i18n/index.js +2 -0
- package/lib/i18n/index.js.map +1 -1
- package/lib/i18n/zh.js +27 -2
- package/lib/i18n/zh.js.map +1 -1
- package/lib/tui.js +294 -45
- package/lib/tui.js.map +1 -1
- package/lib/types/i18n/index.d.ts +4 -0
- package/lib/types/tui.d.ts +41 -6
- package/lib/types/update-check.d.ts +16 -2
- package/lib/update-check.js +46 -5
- package/lib/update-check.js.map +1 -1
- package/package.json +1 -1
package/lib/tui.js
CHANGED
|
@@ -24,7 +24,7 @@ import { settingsNamespace } from '@deepseek-ai/dsh-settings';
|
|
|
24
24
|
import { formatFooterCwd, formatSessionTime, listResumableSessions } from './session-list.js';
|
|
25
25
|
import { applySavedLocale, getLocale, localeDisplayName, localeFromTag, setLocale, t, UI_LOCALE_NAMESPACE, } from './i18n/index.js';
|
|
26
26
|
import { defaultReasoningEffort } from './reasoning.js';
|
|
27
|
-
import { checkForPluginUpdate } from './update-check.js';
|
|
27
|
+
import { checkForPluginUpdate, installPluginLatest } from './update-check.js';
|
|
28
28
|
import { ROUTE_MEMORY_NAMESPACE, parseRouteMemory, rememberedRouteFor, upsertRememberedRoute, } from './route-memory.js';
|
|
29
29
|
import { AGENT_DEFAULT_MODEL_SETTINGS_NAMESPACE } from '@deepseek-ai/dsh-agent-default-model';
|
|
30
30
|
import { DEFAULT_SUBAGENT_MODEL, SUBAGENT_SETTINGS_NAMESPACE, defaultSubagentModelForProvider, describeSubagentFit, subagentModelMatchesProvider, subagentSettingsValue, } from './subagent-model.js';
|
|
@@ -286,6 +286,8 @@ export function formatQuotaBar(remainingPercent, width = 8) {
|
|
|
286
286
|
}
|
|
287
287
|
export function footerIdentityParts(input) {
|
|
288
288
|
const parts = [];
|
|
289
|
+
if (input.compactView === true)
|
|
290
|
+
parts.push(`[${t('view.footerCompact')}]`);
|
|
289
291
|
if (input.preset !== undefined && input.preset !== '')
|
|
290
292
|
parts.push(`[${input.preset}]`);
|
|
291
293
|
if (input.cwdLabel !== undefined && input.cwdLabel !== '')
|
|
@@ -582,12 +584,18 @@ const LOCAL_COMMANDS = [
|
|
|
582
584
|
{ name: 'find', description: 'search thinking / plan / subagent / reply cards' },
|
|
583
585
|
{ name: 'language', description: 'switch UI language (zh / en); empty opens a picker' },
|
|
584
586
|
{ name: 'lang', description: 'alias of /language' },
|
|
587
|
+
{ name: 'view', description: 'switch workspace view (detailed / compact); empty opens a picker' },
|
|
585
588
|
{ name: 'dialog-test', description: 'verify the question dialog' },
|
|
586
589
|
];
|
|
587
590
|
function localizedCommands() {
|
|
588
|
-
return LOCAL_COMMANDS.map(command =>
|
|
589
|
-
|
|
590
|
-
|
|
591
|
+
return LOCAL_COMMANDS.map(command => {
|
|
592
|
+
if (command.name === 'language' || command.name === 'lang') {
|
|
593
|
+
return { name: command.name, description: t('lang.cmd') };
|
|
594
|
+
}
|
|
595
|
+
if (command.name === 'view')
|
|
596
|
+
return { name: command.name, description: t('view.cmd') };
|
|
597
|
+
return command;
|
|
598
|
+
});
|
|
591
599
|
}
|
|
592
600
|
/**
|
|
593
601
|
* Terminal cell width for one string.
|
|
@@ -646,21 +654,39 @@ export function shimmerText(text, nowMs, color) {
|
|
|
646
654
|
}
|
|
647
655
|
return out;
|
|
648
656
|
}
|
|
649
|
-
|
|
657
|
+
const WAIT_SUMMARY_MAX = 18;
|
|
658
|
+
/**
|
|
659
|
+
* Codex-style short status from reasoning: first `**bold**` / heading, else
|
|
660
|
+
* the first short clause. Keeps the wait card in sync with what the model is
|
|
661
|
+
* doing instead of repeating the user's prompt.
|
|
662
|
+
*/
|
|
663
|
+
export function waitSummaryFromReasoning(text, maxChars = WAIT_SUMMARY_MAX) {
|
|
664
|
+
const raw = text.replace(/\r\n?/gu, '\n').trim();
|
|
665
|
+
if (raw === '')
|
|
666
|
+
return undefined;
|
|
667
|
+
const bold = /\*\*([^*]{2,80})\*\*/u.exec(raw)?.[1]
|
|
668
|
+
?? /^#{1,6}\s+(.+)$/mu.exec(raw)?.[1];
|
|
669
|
+
const source = (bold ?? raw.split('\n').find(line => line.trim() !== '') ?? '').replace(/\s+/gu, ' ').trim();
|
|
670
|
+
if (source === '')
|
|
671
|
+
return undefined;
|
|
672
|
+
const clause = source.split(/[。!?!?\n]/u)[0]?.trim() ?? source;
|
|
673
|
+
const chars = Array.from(clause);
|
|
674
|
+
if (chars.length <= maxChars)
|
|
675
|
+
return clause;
|
|
676
|
+
return `${chars.slice(0, Math.max(2, maxChars - 1)).join('')}…`;
|
|
677
|
+
}
|
|
678
|
+
/** Wait-card header + optional detail. Header tracks model work when known. */
|
|
650
679
|
export function waitCardCopy(input) {
|
|
651
680
|
const toolTitle = input.toolTitle?.trim() ?? '';
|
|
652
681
|
const toolSummary = input.toolSummary?.trim() ?? '';
|
|
682
|
+
const fromReasoning = waitSummaryFromReasoning(input.reasoning ?? '');
|
|
683
|
+
const fromReply = waitSummaryFromReasoning(input.reply ?? '');
|
|
684
|
+
const header = fromReasoning ?? fromReply ?? t('wait.working');
|
|
653
685
|
if (toolTitle !== '') {
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
detail: toolSummary === '' ? toolTitle : `${toolTitle} ${toolSummary}`,
|
|
657
|
-
};
|
|
658
|
-
}
|
|
659
|
-
const prompt = (input.prompt ?? '').replace(/\s+/gu, ' ').trim();
|
|
660
|
-
if (prompt !== '') {
|
|
661
|
-
return { header: t('wait.working'), detail: prompt };
|
|
686
|
+
const extra = toolSummary === '' ? '' : ` ${Array.from(toolSummary).slice(0, 40).join('')}`;
|
|
687
|
+
return { header, detail: `${toolTitle}${extra}` };
|
|
662
688
|
}
|
|
663
|
-
return { header
|
|
689
|
+
return { header };
|
|
664
690
|
}
|
|
665
691
|
export function displayWidth(text) {
|
|
666
692
|
let width = 0;
|
|
@@ -1836,6 +1862,45 @@ function friendlyArgsSummary(name, args) {
|
|
|
1836
1862
|
}
|
|
1837
1863
|
const SHELL_TOOL_NAMES = new Set(['bash', 'pwsh']);
|
|
1838
1864
|
const DIFF_TOOL_NAMES = new Set(['edit', 'write', 'str_replace_editor']);
|
|
1865
|
+
export function parseWorkspaceView(raw) {
|
|
1866
|
+
const id = raw.trim().toLowerCase();
|
|
1867
|
+
if (id === 'detailed' || id === 'detail' || id === 'full' || id === '详细')
|
|
1868
|
+
return 'detailed';
|
|
1869
|
+
if (id === 'compact' || id === 'minimal' || id === 'min' || id === '极简')
|
|
1870
|
+
return 'compact';
|
|
1871
|
+
return undefined;
|
|
1872
|
+
}
|
|
1873
|
+
export function countDiffLines(hunks) {
|
|
1874
|
+
if (hunks === undefined || hunks.length === 0)
|
|
1875
|
+
return 0;
|
|
1876
|
+
let total = 0;
|
|
1877
|
+
for (const hunk of hunks) {
|
|
1878
|
+
const added = hunk.newText === '' ? 0 : hunk.newText.split('\n').length;
|
|
1879
|
+
if (hunk.oldText === null) {
|
|
1880
|
+
total += added;
|
|
1881
|
+
continue;
|
|
1882
|
+
}
|
|
1883
|
+
const removed = hunk.oldText === '' ? 0 : hunk.oldText.split('\n').length;
|
|
1884
|
+
total += added + removed;
|
|
1885
|
+
}
|
|
1886
|
+
return total;
|
|
1887
|
+
}
|
|
1888
|
+
export function compactToolGroups(tools) {
|
|
1889
|
+
const edits = [];
|
|
1890
|
+
const calls = [];
|
|
1891
|
+
for (const tool of tools) {
|
|
1892
|
+
if (DIFF_TOOL_NAMES.has(tool.name) || (tool.diff !== undefined && tool.diff.length > 0))
|
|
1893
|
+
edits.push(tool);
|
|
1894
|
+
else
|
|
1895
|
+
calls.push(tool);
|
|
1896
|
+
}
|
|
1897
|
+
return {
|
|
1898
|
+
edits,
|
|
1899
|
+
calls,
|
|
1900
|
+
editLines: edits.reduce((sum, tool) => sum + Math.max(1, countDiffLines(tool.diff)), 0),
|
|
1901
|
+
failedCalls: calls.filter(tool => tool.status === 'error').length,
|
|
1902
|
+
};
|
|
1903
|
+
}
|
|
1839
1904
|
const SUBAGENT_TOOL_NAMES = new Set(['subagent', 'subagent_fork', 'task']);
|
|
1840
1905
|
/**
|
|
1841
1906
|
* Tool calls that already have a dedicated transcript card (goal/change,
|
|
@@ -2726,6 +2791,7 @@ export class SshTui {
|
|
|
2726
2791
|
color;
|
|
2727
2792
|
maxToolOutputLines;
|
|
2728
2793
|
showReasoning;
|
|
2794
|
+
workspaceView = 'detailed';
|
|
2729
2795
|
goodbye;
|
|
2730
2796
|
resume;
|
|
2731
2797
|
providerName;
|
|
@@ -2812,6 +2878,7 @@ export class SshTui {
|
|
|
2812
2878
|
this.color = config.color !== false && !noColorEnv && process.env.TERM !== 'dumb';
|
|
2813
2879
|
this.maxToolOutputLines = Math.max(1, config.maxToolOutputLines ?? 6);
|
|
2814
2880
|
this.showReasoning = config.showReasoning !== false;
|
|
2881
|
+
this.workspaceView = this.readWorkspaceView();
|
|
2815
2882
|
this.goodbye = config.goodbye
|
|
2816
2883
|
?? this.ctx.get('tuiGoodbyeMessage')
|
|
2817
2884
|
?? `To resume this session: dsh --profile tui --resume=${this.agent.id}`;
|
|
@@ -2879,11 +2946,114 @@ export class SshTui {
|
|
|
2879
2946
|
});
|
|
2880
2947
|
}
|
|
2881
2948
|
async notifyPluginUpdate() {
|
|
2882
|
-
const
|
|
2883
|
-
if (this.disposed ||
|
|
2949
|
+
const info = await checkForPluginUpdate(PLUGIN_VERSION);
|
|
2950
|
+
if (this.disposed || info === undefined)
|
|
2884
2951
|
return;
|
|
2885
|
-
this.
|
|
2886
|
-
|
|
2952
|
+
const skipped = this.readSkippedUpdate();
|
|
2953
|
+
if (skipped !== undefined && skipped === info.latest)
|
|
2954
|
+
return;
|
|
2955
|
+
try {
|
|
2956
|
+
const answer = await this.askQuestion({
|
|
2957
|
+
id: 'plugin-update',
|
|
2958
|
+
question: t('update.pick', { latest: info.latest, current: info.current }),
|
|
2959
|
+
options: [
|
|
2960
|
+
{ label: t('update.now'), description: t('update.nowDesc', { command: info.command }) },
|
|
2961
|
+
{ label: t('update.later'), description: t('update.laterDesc') },
|
|
2962
|
+
{ label: t('update.skip'), description: t('update.skipDesc', { latest: info.latest }) },
|
|
2963
|
+
],
|
|
2964
|
+
}, 0, 1, 0);
|
|
2965
|
+
if (this.disposed)
|
|
2966
|
+
return;
|
|
2967
|
+
const picked = answer.selected[0];
|
|
2968
|
+
if (picked === t('update.skip')) {
|
|
2969
|
+
await this.persistSkippedUpdate(info.latest);
|
|
2970
|
+
this.pushRow({ kind: 'system', text: t('update.skipDesc', { latest: info.latest }) });
|
|
2971
|
+
this.markDirty();
|
|
2972
|
+
return;
|
|
2973
|
+
}
|
|
2974
|
+
if (picked !== t('update.now'))
|
|
2975
|
+
return;
|
|
2976
|
+
this.pushRow({ kind: 'system', text: t('update.installing', { latest: info.latest }) });
|
|
2977
|
+
this.markDirty();
|
|
2978
|
+
const result = await installPluginLatest(info.profile);
|
|
2979
|
+
if (this.disposed)
|
|
2980
|
+
return;
|
|
2981
|
+
if (result.ok) {
|
|
2982
|
+
this.pushRow({ kind: 'system', text: t('update.installed', { latest: info.latest, profile: info.profile }) });
|
|
2983
|
+
}
|
|
2984
|
+
else {
|
|
2985
|
+
this.pushRow({ kind: 'error', text: t('update.failed', { error: result.output === '' ? info.command : result.output }) });
|
|
2986
|
+
this.pushRow({ kind: 'system', text: t('update.manual', { command: info.command }) });
|
|
2987
|
+
}
|
|
2988
|
+
this.markDirty();
|
|
2989
|
+
}
|
|
2990
|
+
catch {
|
|
2991
|
+
if (this.disposed)
|
|
2992
|
+
return;
|
|
2993
|
+
this.pushRow({ kind: 'system', text: info.notice });
|
|
2994
|
+
this.markDirty();
|
|
2995
|
+
}
|
|
2996
|
+
}
|
|
2997
|
+
readSkippedUpdate() {
|
|
2998
|
+
const raw = this.ctx.get('settings')?.get(UI_LOCALE_NAMESPACE);
|
|
2999
|
+
if (raw === null || typeof raw !== 'object' || Array.isArray(raw))
|
|
3000
|
+
return undefined;
|
|
3001
|
+
const skip = raw.skipUpdate;
|
|
3002
|
+
return typeof skip === 'string' && skip.trim() !== '' ? skip.trim() : undefined;
|
|
3003
|
+
}
|
|
3004
|
+
async persistSkippedUpdate(latest) {
|
|
3005
|
+
await this.mergeUiSettings({ skipUpdate: latest });
|
|
3006
|
+
}
|
|
3007
|
+
async mergeUiSettings(patch) {
|
|
3008
|
+
const settings = this.ctx.get('settings');
|
|
3009
|
+
if (settings === undefined)
|
|
3010
|
+
return;
|
|
3011
|
+
const raw = settings.get(UI_LOCALE_NAMESPACE);
|
|
3012
|
+
const previous = raw !== null && typeof raw === 'object' && !Array.isArray(raw)
|
|
3013
|
+
? raw
|
|
3014
|
+
: {};
|
|
3015
|
+
await settings.replace(UI_LOCALE_NAMESPACE, { ...previous, ...patch });
|
|
3016
|
+
}
|
|
3017
|
+
readWorkspaceView() {
|
|
3018
|
+
const raw = this.ctx.get('settings')?.get(UI_LOCALE_NAMESPACE);
|
|
3019
|
+
if (raw === null || typeof raw !== 'object' || Array.isArray(raw))
|
|
3020
|
+
return 'detailed';
|
|
3021
|
+
return parseWorkspaceView(String(raw.view ?? '')) ?? 'detailed';
|
|
3022
|
+
}
|
|
3023
|
+
isCompactView() {
|
|
3024
|
+
return this.workspaceView === 'compact';
|
|
3025
|
+
}
|
|
3026
|
+
/** Test helper: switch the workspace view without going through /view. */
|
|
3027
|
+
setWorkspaceView(view) {
|
|
3028
|
+
this.workspaceView = view;
|
|
3029
|
+
}
|
|
3030
|
+
paintCompactSummary(addDisplay, anchor, kind, groups, width) {
|
|
3031
|
+
const focused = this.focusedRow === anchor;
|
|
3032
|
+
const marker = anchor.expanded ? '▾' : '▸';
|
|
3033
|
+
const running = kind === 'edits'
|
|
3034
|
+
? groups.edits.some(item => item.status === undefined || item.status === 'running')
|
|
3035
|
+
: groups.calls.some(item => item.status === undefined || item.status === 'running');
|
|
3036
|
+
const spinner = running ? ` ${this.spinnerFrame()}` : '';
|
|
3037
|
+
const title = kind === 'edits'
|
|
3038
|
+
? (groups.edits.length > 1
|
|
3039
|
+
? t('compact.editsFiles', { lines: groups.editLines, files: groups.edits.length })
|
|
3040
|
+
: t('compact.edits', { lines: groups.editLines }))
|
|
3041
|
+
: (groups.failedCalls > 0
|
|
3042
|
+
? t('compact.toolsFailed', { count: groups.calls.length, failed: groups.failedCalls })
|
|
3043
|
+
: t('compact.tools', { count: groups.calls.length }));
|
|
3044
|
+
const header = `${focused ? '▶ ' : ' '}${marker} ● ${title}${spinner}${anchor.expanded ? '' : t('card.expand')}`;
|
|
3045
|
+
const styled = this.styleLine(groups.failedCalls > 0 && kind === 'calls' ? 'error' : 'tool', header);
|
|
3046
|
+
addDisplay(focused && this.color ? `\x1b[7m${styled}\x1b[27m` : styled, anchor);
|
|
3047
|
+
if (!anchor.expanded)
|
|
3048
|
+
return;
|
|
3049
|
+
const items = kind === 'edits' ? groups.edits : groups.calls;
|
|
3050
|
+
for (const item of items) {
|
|
3051
|
+
const state = item.status === 'error' ? 'error' : item.status === 'ok' ? 'ok' : 'running…';
|
|
3052
|
+
const extra = kind === 'edits'
|
|
3053
|
+
? `${countDiffLines(item.diff) || 1} ${getLocale() === 'en' ? 'lines' : '行'}`
|
|
3054
|
+
: item.summary;
|
|
3055
|
+
addDisplay(this.styleLine('system', truncateToWidth(` ${item.title} ${extra} [${state}]`, width)), item);
|
|
3056
|
+
}
|
|
2887
3057
|
}
|
|
2888
3058
|
startRenderTimer() {
|
|
2889
3059
|
if (this.renderTimer !== undefined) {
|
|
@@ -3160,15 +3330,20 @@ export class SshTui {
|
|
|
3160
3330
|
}
|
|
3161
3331
|
/** The transcript rows that support per-row expand/collapse. */
|
|
3162
3332
|
collapsibleRows() {
|
|
3163
|
-
const
|
|
3164
|
-
|
|
3165
|
-
|| row.kind === '
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3333
|
+
const compact = this.isCompactView();
|
|
3334
|
+
const rows = this.rows.filter((row) => {
|
|
3335
|
+
if (compact && (row.kind === 'reasoning' || row.kind === 'prompt'))
|
|
3336
|
+
return false;
|
|
3337
|
+
return row.kind === 'reasoning'
|
|
3338
|
+
|| row.kind === 'tool'
|
|
3339
|
+
|| row.kind === 'subagent'
|
|
3340
|
+
|| row.kind === 'plan'
|
|
3341
|
+
|| row.kind === 'question'
|
|
3342
|
+
|| row.kind === 'goal'
|
|
3343
|
+
|| row.kind === 'compaction'
|
|
3344
|
+
|| row.kind === 'prompt';
|
|
3345
|
+
});
|
|
3346
|
+
if (!compact && this.streaming !== undefined && this.streaming.reasoning !== '') {
|
|
3172
3347
|
this.streamingReasoning ??= { kind: 'streaming-reasoning', expanded: false };
|
|
3173
3348
|
rows.push(this.streamingReasoning);
|
|
3174
3349
|
}
|
|
@@ -3178,16 +3353,12 @@ export class SshTui {
|
|
|
3178
3353
|
return SPINNER[Math.floor(Date.now() / periodMs) % SPINNER.length] ?? '⠋';
|
|
3179
3354
|
}
|
|
3180
3355
|
/**
|
|
3181
|
-
* Codex wait card: shown while the turn is running
|
|
3182
|
-
*
|
|
3183
|
-
* name what is happening (Codex `update_details`).
|
|
3356
|
+
* Codex wait card: shown while the turn is running. Thinking/reply streams
|
|
3357
|
+
* feed the shimmer header; a live tool becomes the detail line.
|
|
3184
3358
|
*/
|
|
3185
3359
|
waitCardVisible() {
|
|
3186
3360
|
if (this.agent.status !== 'running')
|
|
3187
3361
|
return false;
|
|
3188
|
-
if (this.streaming !== undefined && (this.streaming.reasoning !== '' || this.streaming.text !== '')) {
|
|
3189
|
-
return false;
|
|
3190
|
-
}
|
|
3191
3362
|
if (this.rows.some(row => row.kind === 'compaction' && row.status === 'running'))
|
|
3192
3363
|
return false;
|
|
3193
3364
|
if (this.dialog?.kind === 'questions' || this.dialog?.kind === 'confirm')
|
|
@@ -3205,14 +3376,21 @@ export class SshTui {
|
|
|
3205
3376
|
}
|
|
3206
3377
|
waitCardSource() {
|
|
3207
3378
|
const liveTool = this.rows.findLast((row) => row.kind === 'tool' && (row.status === undefined || row.status === 'running'));
|
|
3208
|
-
if (liveTool !== undefined) {
|
|
3209
|
-
return { toolTitle: liveTool.title, toolSummary: liveTool.summary, prompt: this.waitPrompt };
|
|
3210
|
-
}
|
|
3211
3379
|
const liveSub = this.rows.findLast((row) => row.kind === 'subagent' && row.status === 'running');
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3380
|
+
return {
|
|
3381
|
+
...(liveTool === undefined ? {} : { toolTitle: liveTool.title, toolSummary: liveTool.summary }),
|
|
3382
|
+
...(liveTool !== undefined || liveSub === undefined
|
|
3383
|
+
? {}
|
|
3384
|
+
: { toolTitle: liveSub.label, toolSummary: liveSub.lastActivity }),
|
|
3385
|
+
...(this.streaming?.reasoning ? { reasoning: this.streaming.reasoning } : {}),
|
|
3386
|
+
...(this.streaming?.text ? { reply: this.streaming.text } : {}),
|
|
3387
|
+
...(this.waitPrompt === undefined ? {} : { prompt: this.waitPrompt }),
|
|
3388
|
+
};
|
|
3389
|
+
}
|
|
3390
|
+
planShouldDefaultExpand(plan) {
|
|
3391
|
+
return plan.active === true
|
|
3392
|
+
|| plan.pending === true
|
|
3393
|
+
|| plan.todos.some(item => item.status === 'in_progress');
|
|
3216
3394
|
}
|
|
3217
3395
|
findSubagentRow(sessionId) {
|
|
3218
3396
|
return this.rows.findLast((row) => row.kind === 'subagent' && row.sessionId === sessionId);
|
|
@@ -3249,6 +3427,9 @@ export class SshTui {
|
|
|
3249
3427
|
existing.archived = true;
|
|
3250
3428
|
existing.expanded = false;
|
|
3251
3429
|
}
|
|
3430
|
+
else if (patch.expanded === undefined && this.planShouldDefaultExpand(existing)) {
|
|
3431
|
+
existing.expanded = true;
|
|
3432
|
+
}
|
|
3252
3433
|
this.archiveStalePlans(planIsLive(existing) ? existing : undefined);
|
|
3253
3434
|
return existing;
|
|
3254
3435
|
}
|
|
@@ -3264,7 +3445,13 @@ export class SshTui {
|
|
|
3264
3445
|
pending: patch.pending ?? false,
|
|
3265
3446
|
todos: patch.todos ?? [],
|
|
3266
3447
|
...(patch.planMarkdown === undefined ? {} : { planMarkdown: patch.planMarkdown }),
|
|
3267
|
-
expanded:
|
|
3448
|
+
expanded: this.planShouldDefaultExpand({
|
|
3449
|
+
kind: 'plan',
|
|
3450
|
+
active: patch.active ?? false,
|
|
3451
|
+
pending: patch.pending ?? false,
|
|
3452
|
+
todos: patch.todos ?? [],
|
|
3453
|
+
expanded: false,
|
|
3454
|
+
}),
|
|
3268
3455
|
archived: false,
|
|
3269
3456
|
};
|
|
3270
3457
|
this.pushRow(row);
|
|
@@ -3662,7 +3849,26 @@ export class SshTui {
|
|
|
3662
3849
|
addDisplay(this.styleLine(kind, line), ref);
|
|
3663
3850
|
}
|
|
3664
3851
|
};
|
|
3852
|
+
const compact = this.isCompactView();
|
|
3853
|
+
const compactGroups = compact
|
|
3854
|
+
? compactToolGroups(this.rows.filter((row) => row.kind === 'tool'))
|
|
3855
|
+
: undefined;
|
|
3856
|
+
const compactEditCard = compactGroups?.edits[0];
|
|
3857
|
+
const compactCallCard = compactGroups?.calls[0];
|
|
3665
3858
|
for (const row of this.rows) {
|
|
3859
|
+
if (compact && (row.kind === 'reasoning' || row.kind === 'prompt'))
|
|
3860
|
+
continue;
|
|
3861
|
+
if (compact && compactGroups !== undefined && row.kind === 'tool') {
|
|
3862
|
+
if (row === compactEditCard) {
|
|
3863
|
+
this.paintCompactSummary(addDisplay, row, 'edits', compactGroups, width);
|
|
3864
|
+
continue;
|
|
3865
|
+
}
|
|
3866
|
+
if (row === compactCallCard) {
|
|
3867
|
+
this.paintCompactSummary(addDisplay, row, 'calls', compactGroups, width);
|
|
3868
|
+
continue;
|
|
3869
|
+
}
|
|
3870
|
+
continue;
|
|
3871
|
+
}
|
|
3666
3872
|
if (row.kind === 'brand-logo') {
|
|
3667
3873
|
const variant = DEEPSEEK_LOGO_VARIANTS.find(candidate => candidate.width <= width - 2)
|
|
3668
3874
|
?? DEEPSEEK_LOGO_VARIANTS[DEEPSEEK_LOGO_VARIANTS.length - 1];
|
|
@@ -3872,7 +4078,7 @@ export class SshTui {
|
|
|
3872
4078
|
pushRow(row.kind, row.text, row);
|
|
3873
4079
|
}
|
|
3874
4080
|
if (this.streaming !== undefined) {
|
|
3875
|
-
if (this.showReasoning && this.streaming.reasoning !== '') {
|
|
4081
|
+
if (!compact && this.showReasoning && this.streaming.reasoning !== '') {
|
|
3876
4082
|
const block = this.streamingReasoning ??= { kind: 'streaming-reasoning', expanded: false };
|
|
3877
4083
|
const focused = this.focusedRow === block;
|
|
3878
4084
|
const marker = block.expanded ? '▾' : '▸';
|
|
@@ -3903,7 +4109,7 @@ export class SshTui {
|
|
|
3903
4109
|
}
|
|
3904
4110
|
}
|
|
3905
4111
|
}
|
|
3906
|
-
|
|
4112
|
+
if (this.waitCardVisible()) {
|
|
3907
4113
|
const copy = waitCardCopy(this.waitCardSource());
|
|
3908
4114
|
const started = this.waitStartedAt ?? Date.now();
|
|
3909
4115
|
const elapsed = fmtElapsedCompact((Date.now() - started) / 1000);
|
|
@@ -4183,6 +4389,7 @@ export class SshTui {
|
|
|
4183
4389
|
multiLineInput: inputRows > 1,
|
|
4184
4390
|
queued: this.pendingMessages.size,
|
|
4185
4391
|
cwdLabel: formatFooterCwd(this.workspaceCwd()),
|
|
4392
|
+
compactView: this.isCompactView(),
|
|
4186
4393
|
};
|
|
4187
4394
|
const activity = footerActivity(footer);
|
|
4188
4395
|
const activityText = activity.kind === 'compacting'
|
|
@@ -5969,13 +6176,41 @@ export class SshTui {
|
|
|
5969
6176
|
this.pushRow({ kind: 'error', text: t('lang.settingsMissing') });
|
|
5970
6177
|
}
|
|
5971
6178
|
else {
|
|
5972
|
-
await
|
|
6179
|
+
await this.mergeUiSettings({ language: next });
|
|
5973
6180
|
applySavedLocale({ language: next });
|
|
5974
6181
|
}
|
|
5975
6182
|
this.forceFullPaint = true;
|
|
5976
6183
|
this.pushRow({ kind: 'system', text: t('lang.switched', { name: localeDisplayName(next) }) });
|
|
5977
6184
|
this.markDirty();
|
|
5978
6185
|
}
|
|
6186
|
+
/** /view: detailed (see the work) vs compact (Codex-like summary). */
|
|
6187
|
+
async runViewCommand(arg) {
|
|
6188
|
+
const direct = parseWorkspaceView(arg);
|
|
6189
|
+
let next = direct;
|
|
6190
|
+
if (next === undefined && arg.trim() !== '') {
|
|
6191
|
+
this.pushRow({ kind: 'error', text: t('view.unknown', { id: arg.trim() }) });
|
|
6192
|
+
this.markDirty();
|
|
6193
|
+
return;
|
|
6194
|
+
}
|
|
6195
|
+
if (next === undefined) {
|
|
6196
|
+
const current = this.workspaceView;
|
|
6197
|
+
const answer = await this.askQuestion({
|
|
6198
|
+
id: 'view-pick',
|
|
6199
|
+
question: t('view.pick'),
|
|
6200
|
+
options: [
|
|
6201
|
+
{ label: t('view.detailed'), description: current === 'detailed' ? t('view.current') : t('view.detailedDesc') },
|
|
6202
|
+
{ label: t('view.compact'), description: current === 'compact' ? t('view.current') : t('view.compactDesc') },
|
|
6203
|
+
],
|
|
6204
|
+
}, 0, 1, current === 'compact' ? 1 : 0);
|
|
6205
|
+
const picked = answer.selected[0];
|
|
6206
|
+
next = picked === t('view.compact') ? 'compact' : 'detailed';
|
|
6207
|
+
}
|
|
6208
|
+
this.workspaceView = next;
|
|
6209
|
+
await this.mergeUiSettings({ view: next });
|
|
6210
|
+
this.forceFullPaint = true;
|
|
6211
|
+
this.pushRow({ kind: 'system', text: t('view.switched', { name: next === 'compact' ? t('view.compact') : t('view.detailed') }) });
|
|
6212
|
+
this.markDirty();
|
|
6213
|
+
}
|
|
5979
6214
|
/** /mode: pick an agent preset (standard / minimal / ptc / cordis / routing-suite / ...). */
|
|
5980
6215
|
async runModeCommand() {
|
|
5981
6216
|
const agentPresets = this.ctx.get('agentPresets');
|
|
@@ -6625,7 +6860,10 @@ export class SshTui {
|
|
|
6625
6860
|
this.moveCollapsibleFocus(-1);
|
|
6626
6861
|
return;
|
|
6627
6862
|
case '\x12':
|
|
6628
|
-
this.
|
|
6863
|
+
if (this.focusedRow === null)
|
|
6864
|
+
this.toggleCollapsible();
|
|
6865
|
+
else
|
|
6866
|
+
this.toggleAllCollapsible();
|
|
6629
6867
|
return;
|
|
6630
6868
|
case '\x14':
|
|
6631
6869
|
this.inputFolded = !this.inputFolded;
|
|
@@ -7392,6 +7630,17 @@ export class SshTui {
|
|
|
7392
7630
|
this.markDirty();
|
|
7393
7631
|
});
|
|
7394
7632
|
break;
|
|
7633
|
+
case 'view':
|
|
7634
|
+
void this.runViewCommand(arg).catch((error) => {
|
|
7635
|
+
if (error instanceof UserQuestionError) {
|
|
7636
|
+
this.pushRow({ kind: 'system', text: t('help.modeCancel') });
|
|
7637
|
+
}
|
|
7638
|
+
else {
|
|
7639
|
+
this.pushRow({ kind: 'error', text: `/view failed: ${errorChain(error)}` });
|
|
7640
|
+
}
|
|
7641
|
+
this.markDirty();
|
|
7642
|
+
});
|
|
7643
|
+
break;
|
|
7395
7644
|
case 'find':
|
|
7396
7645
|
this.runFindCommand(arg);
|
|
7397
7646
|
break;
|