dsh-ssh-tui 0.3.10 → 0.4.1
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 +293 -36
- 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
|
-
};
|
|
686
|
+
const extra = toolSummary === '' ? '' : ` ${Array.from(toolSummary).slice(0, 40).join('')}`;
|
|
687
|
+
return { header, detail: `${toolTitle}${extra}` };
|
|
658
688
|
}
|
|
659
|
-
|
|
660
|
-
if (prompt !== '') {
|
|
661
|
-
return { header: t('wait.working'), detail: prompt };
|
|
662
|
-
}
|
|
663
|
-
return { header: t('wait.working') };
|
|
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,6 +3330,22 @@ export class SshTui {
|
|
|
3160
3330
|
}
|
|
3161
3331
|
/** The transcript rows that support per-row expand/collapse. */
|
|
3162
3332
|
collapsibleRows() {
|
|
3333
|
+
const compact = this.isCompactView();
|
|
3334
|
+
if (compact) {
|
|
3335
|
+
const groups = compactToolGroups(this.rows.filter((row) => row.kind === 'tool'));
|
|
3336
|
+
const rows = this.rows.filter((row) => row.kind === 'subagent'
|
|
3337
|
+
|| row.kind === 'plan'
|
|
3338
|
+
|| row.kind === 'question'
|
|
3339
|
+
|| row.kind === 'goal'
|
|
3340
|
+
|| row.kind === 'compaction');
|
|
3341
|
+
const callAnchor = groups.calls.at(-1);
|
|
3342
|
+
const editAnchor = groups.edits.at(-1);
|
|
3343
|
+
if (callAnchor !== undefined)
|
|
3344
|
+
rows.push(callAnchor);
|
|
3345
|
+
if (editAnchor !== undefined)
|
|
3346
|
+
rows.push(editAnchor);
|
|
3347
|
+
return rows;
|
|
3348
|
+
}
|
|
3163
3349
|
const rows = this.rows.filter((row) => row.kind === 'reasoning'
|
|
3164
3350
|
|| row.kind === 'tool'
|
|
3165
3351
|
|| row.kind === 'subagent'
|
|
@@ -3178,16 +3364,12 @@ export class SshTui {
|
|
|
3178
3364
|
return SPINNER[Math.floor(Date.now() / periodMs) % SPINNER.length] ?? '⠋';
|
|
3179
3365
|
}
|
|
3180
3366
|
/**
|
|
3181
|
-
* Codex wait card: shown while the turn is running
|
|
3182
|
-
*
|
|
3183
|
-
* name what is happening (Codex `update_details`).
|
|
3367
|
+
* Codex wait card: shown while the turn is running. Thinking/reply streams
|
|
3368
|
+
* feed the shimmer header; a live tool becomes the detail line.
|
|
3184
3369
|
*/
|
|
3185
3370
|
waitCardVisible() {
|
|
3186
3371
|
if (this.agent.status !== 'running')
|
|
3187
3372
|
return false;
|
|
3188
|
-
if (this.streaming !== undefined && (this.streaming.reasoning !== '' || this.streaming.text !== '')) {
|
|
3189
|
-
return false;
|
|
3190
|
-
}
|
|
3191
3373
|
if (this.rows.some(row => row.kind === 'compaction' && row.status === 'running'))
|
|
3192
3374
|
return false;
|
|
3193
3375
|
if (this.dialog?.kind === 'questions' || this.dialog?.kind === 'confirm')
|
|
@@ -3205,14 +3387,21 @@ export class SshTui {
|
|
|
3205
3387
|
}
|
|
3206
3388
|
waitCardSource() {
|
|
3207
3389
|
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
3390
|
const liveSub = this.rows.findLast((row) => row.kind === 'subagent' && row.status === 'running');
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3391
|
+
return {
|
|
3392
|
+
...(liveTool === undefined ? {} : { toolTitle: liveTool.title, toolSummary: liveTool.summary }),
|
|
3393
|
+
...(liveTool !== undefined || liveSub === undefined
|
|
3394
|
+
? {}
|
|
3395
|
+
: { toolTitle: liveSub.label, toolSummary: liveSub.lastActivity }),
|
|
3396
|
+
...(this.streaming?.reasoning ? { reasoning: this.streaming.reasoning } : {}),
|
|
3397
|
+
...(this.streaming?.text ? { reply: this.streaming.text } : {}),
|
|
3398
|
+
...(this.waitPrompt === undefined ? {} : { prompt: this.waitPrompt }),
|
|
3399
|
+
};
|
|
3400
|
+
}
|
|
3401
|
+
planShouldDefaultExpand(plan) {
|
|
3402
|
+
return plan.active === true
|
|
3403
|
+
|| plan.pending === true
|
|
3404
|
+
|| plan.todos.some(item => item.status === 'in_progress');
|
|
3216
3405
|
}
|
|
3217
3406
|
findSubagentRow(sessionId) {
|
|
3218
3407
|
return this.rows.findLast((row) => row.kind === 'subagent' && row.sessionId === sessionId);
|
|
@@ -3249,6 +3438,9 @@ export class SshTui {
|
|
|
3249
3438
|
existing.archived = true;
|
|
3250
3439
|
existing.expanded = false;
|
|
3251
3440
|
}
|
|
3441
|
+
else if (patch.expanded === undefined && this.planShouldDefaultExpand(existing)) {
|
|
3442
|
+
existing.expanded = true;
|
|
3443
|
+
}
|
|
3252
3444
|
this.archiveStalePlans(planIsLive(existing) ? existing : undefined);
|
|
3253
3445
|
return existing;
|
|
3254
3446
|
}
|
|
@@ -3264,7 +3456,13 @@ export class SshTui {
|
|
|
3264
3456
|
pending: patch.pending ?? false,
|
|
3265
3457
|
todos: patch.todos ?? [],
|
|
3266
3458
|
...(patch.planMarkdown === undefined ? {} : { planMarkdown: patch.planMarkdown }),
|
|
3267
|
-
expanded:
|
|
3459
|
+
expanded: this.planShouldDefaultExpand({
|
|
3460
|
+
kind: 'plan',
|
|
3461
|
+
active: patch.active ?? false,
|
|
3462
|
+
pending: patch.pending ?? false,
|
|
3463
|
+
todos: patch.todos ?? [],
|
|
3464
|
+
expanded: false,
|
|
3465
|
+
}),
|
|
3268
3466
|
archived: false,
|
|
3269
3467
|
};
|
|
3270
3468
|
this.pushRow(row);
|
|
@@ -3662,7 +3860,13 @@ export class SshTui {
|
|
|
3662
3860
|
addDisplay(this.styleLine(kind, line), ref);
|
|
3663
3861
|
}
|
|
3664
3862
|
};
|
|
3863
|
+
const compact = this.isCompactView();
|
|
3864
|
+
const compactGroups = compact
|
|
3865
|
+
? compactToolGroups(this.rows.filter((row) => row.kind === 'tool'))
|
|
3866
|
+
: undefined;
|
|
3665
3867
|
for (const row of this.rows) {
|
|
3868
|
+
if (compact && (row.kind === 'reasoning' || row.kind === 'prompt' || row.kind === 'tool'))
|
|
3869
|
+
continue;
|
|
3666
3870
|
if (row.kind === 'brand-logo') {
|
|
3667
3871
|
const variant = DEEPSEEK_LOGO_VARIANTS.find(candidate => candidate.width <= width - 2)
|
|
3668
3872
|
?? DEEPSEEK_LOGO_VARIANTS[DEEPSEEK_LOGO_VARIANTS.length - 1];
|
|
@@ -3872,7 +4076,7 @@ export class SshTui {
|
|
|
3872
4076
|
pushRow(row.kind, row.text, row);
|
|
3873
4077
|
}
|
|
3874
4078
|
if (this.streaming !== undefined) {
|
|
3875
|
-
if (this.showReasoning && this.streaming.reasoning !== '') {
|
|
4079
|
+
if (!compact && this.showReasoning && this.streaming.reasoning !== '') {
|
|
3876
4080
|
const block = this.streamingReasoning ??= { kind: 'streaming-reasoning', expanded: false };
|
|
3877
4081
|
const focused = this.focusedRow === block;
|
|
3878
4082
|
const marker = block.expanded ? '▾' : '▸';
|
|
@@ -3903,7 +4107,17 @@ export class SshTui {
|
|
|
3903
4107
|
}
|
|
3904
4108
|
}
|
|
3905
4109
|
}
|
|
3906
|
-
|
|
4110
|
+
if (compact && compactGroups !== undefined) {
|
|
4111
|
+
const editAnchor = compactGroups.edits.at(-1);
|
|
4112
|
+
const callAnchor = compactGroups.calls.at(-1);
|
|
4113
|
+
if (callAnchor !== undefined) {
|
|
4114
|
+
this.paintCompactSummary(addDisplay, callAnchor, 'calls', compactGroups, width);
|
|
4115
|
+
}
|
|
4116
|
+
if (editAnchor !== undefined) {
|
|
4117
|
+
this.paintCompactSummary(addDisplay, editAnchor, 'edits', compactGroups, width);
|
|
4118
|
+
}
|
|
4119
|
+
}
|
|
4120
|
+
if (this.waitCardVisible()) {
|
|
3907
4121
|
const copy = waitCardCopy(this.waitCardSource());
|
|
3908
4122
|
const started = this.waitStartedAt ?? Date.now();
|
|
3909
4123
|
const elapsed = fmtElapsedCompact((Date.now() - started) / 1000);
|
|
@@ -4183,6 +4397,7 @@ export class SshTui {
|
|
|
4183
4397
|
multiLineInput: inputRows > 1,
|
|
4184
4398
|
queued: this.pendingMessages.size,
|
|
4185
4399
|
cwdLabel: formatFooterCwd(this.workspaceCwd()),
|
|
4400
|
+
compactView: this.isCompactView(),
|
|
4186
4401
|
};
|
|
4187
4402
|
const activity = footerActivity(footer);
|
|
4188
4403
|
const activityText = activity.kind === 'compacting'
|
|
@@ -5969,13 +6184,41 @@ export class SshTui {
|
|
|
5969
6184
|
this.pushRow({ kind: 'error', text: t('lang.settingsMissing') });
|
|
5970
6185
|
}
|
|
5971
6186
|
else {
|
|
5972
|
-
await
|
|
6187
|
+
await this.mergeUiSettings({ language: next });
|
|
5973
6188
|
applySavedLocale({ language: next });
|
|
5974
6189
|
}
|
|
5975
6190
|
this.forceFullPaint = true;
|
|
5976
6191
|
this.pushRow({ kind: 'system', text: t('lang.switched', { name: localeDisplayName(next) }) });
|
|
5977
6192
|
this.markDirty();
|
|
5978
6193
|
}
|
|
6194
|
+
/** /view: detailed (see the work) vs compact (Codex-like summary). */
|
|
6195
|
+
async runViewCommand(arg) {
|
|
6196
|
+
const direct = parseWorkspaceView(arg);
|
|
6197
|
+
let next = direct;
|
|
6198
|
+
if (next === undefined && arg.trim() !== '') {
|
|
6199
|
+
this.pushRow({ kind: 'error', text: t('view.unknown', { id: arg.trim() }) });
|
|
6200
|
+
this.markDirty();
|
|
6201
|
+
return;
|
|
6202
|
+
}
|
|
6203
|
+
if (next === undefined) {
|
|
6204
|
+
const current = this.workspaceView;
|
|
6205
|
+
const answer = await this.askQuestion({
|
|
6206
|
+
id: 'view-pick',
|
|
6207
|
+
question: t('view.pick'),
|
|
6208
|
+
options: [
|
|
6209
|
+
{ label: t('view.detailed'), description: current === 'detailed' ? t('view.current') : t('view.detailedDesc') },
|
|
6210
|
+
{ label: t('view.compact'), description: current === 'compact' ? t('view.current') : t('view.compactDesc') },
|
|
6211
|
+
],
|
|
6212
|
+
}, 0, 1, current === 'compact' ? 1 : 0);
|
|
6213
|
+
const picked = answer.selected[0];
|
|
6214
|
+
next = picked === t('view.compact') ? 'compact' : 'detailed';
|
|
6215
|
+
}
|
|
6216
|
+
this.workspaceView = next;
|
|
6217
|
+
await this.mergeUiSettings({ view: next });
|
|
6218
|
+
this.forceFullPaint = true;
|
|
6219
|
+
this.pushRow({ kind: 'system', text: t('view.switched', { name: next === 'compact' ? t('view.compact') : t('view.detailed') }) });
|
|
6220
|
+
this.markDirty();
|
|
6221
|
+
}
|
|
5979
6222
|
/** /mode: pick an agent preset (standard / minimal / ptc / cordis / routing-suite / ...). */
|
|
5980
6223
|
async runModeCommand() {
|
|
5981
6224
|
const agentPresets = this.ctx.get('agentPresets');
|
|
@@ -6625,7 +6868,10 @@ export class SshTui {
|
|
|
6625
6868
|
this.moveCollapsibleFocus(-1);
|
|
6626
6869
|
return;
|
|
6627
6870
|
case '\x12':
|
|
6628
|
-
this.
|
|
6871
|
+
if (this.focusedRow === null)
|
|
6872
|
+
this.toggleCollapsible();
|
|
6873
|
+
else
|
|
6874
|
+
this.toggleAllCollapsible();
|
|
6629
6875
|
return;
|
|
6630
6876
|
case '\x14':
|
|
6631
6877
|
this.inputFolded = !this.inputFolded;
|
|
@@ -7392,6 +7638,17 @@ export class SshTui {
|
|
|
7392
7638
|
this.markDirty();
|
|
7393
7639
|
});
|
|
7394
7640
|
break;
|
|
7641
|
+
case 'view':
|
|
7642
|
+
void this.runViewCommand(arg).catch((error) => {
|
|
7643
|
+
if (error instanceof UserQuestionError) {
|
|
7644
|
+
this.pushRow({ kind: 'system', text: t('help.modeCancel') });
|
|
7645
|
+
}
|
|
7646
|
+
else {
|
|
7647
|
+
this.pushRow({ kind: 'error', text: `/view failed: ${errorChain(error)}` });
|
|
7648
|
+
}
|
|
7649
|
+
this.markDirty();
|
|
7650
|
+
});
|
|
7651
|
+
break;
|
|
7395
7652
|
case 'find':
|
|
7396
7653
|
this.runFindCommand(arg);
|
|
7397
7654
|
break;
|