dsh-ssh-tui 0.3.6 → 0.3.7
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 +5 -1
- package/README.md +6 -0
- package/cordis.patch.yml +17 -3
- package/lib/tui.js +66 -34
- package/lib/tui.js.map +1 -1
- package/lib/types/tui.d.ts +14 -7
- package/package.json +29 -29
package/README.en.md
CHANGED
|
@@ -9,6 +9,10 @@ If you mostly work over SSH — a jump host, a test box, a keyboard-only
|
|
|
9
9
|
session — start here. A local desktop terminal with themes and layout
|
|
10
10
|
you already like can stay as it is.
|
|
11
11
|
|
|
12
|
+
Listed on the [dshfind plugin directory](https://dshfind.com/en/plugins/cyjyyd/dsh-ssh-tui):
|
|
13
|
+
|
|
14
|
+
[](https://dshfind.com/en/plugins/cyjyyd/dsh-ssh-tui?ref=badge)
|
|
15
|
+
|
|
12
16
|
Install with the official CLI (no clone):
|
|
13
17
|
|
|
14
18
|
```sh
|
|
@@ -356,7 +360,7 @@ collapsed card instead of being prefixed onto parent transcript lines;
|
|
|
356
360
|
`/subagents` lists active runs.
|
|
357
361
|
|
|
358
362
|
`/mode` opens the agent-mode picker backed by dsh's official preset roster:
|
|
359
|
-
标准模式 (standard), PTC 模式 (code), 极简模式 (minimal), 创造模式 (cordis),
|
|
363
|
+
标准模式 (standard), PTC 模式 (ptc; `code` on dsh 0.1.1), 极简模式 (minimal), 创造模式 (cordis),
|
|
360
364
|
智能路由模式 (routing-suite, from `dsh-routing-suite`),
|
|
361
365
|
plus any locally authored presets (e.g. `whoami-standard`). On a session that
|
|
362
366
|
has not produced work the switch applies immediately; otherwise it is remembered
|
package/README.md
CHANGED
|
@@ -10,6 +10,10 @@ English: [README.en.md](README.en.md)
|
|
|
10
10
|
|
|
11
11
|
SuperGrok / X Premium 订阅走配套插件 [dsh-llm-xai-oauth](https://github.com/cyjyyd/dsh-llm-xai-oauth),复用本机 grok-bridge token,不需要 xAI API Key。
|
|
12
12
|
|
|
13
|
+
本插件已被 [dshfind 插件目录](https://dshfind.com/zh/plugins/cyjyyd/dsh-ssh-tui) 收录:
|
|
14
|
+
|
|
15
|
+
[](https://dshfind.com/zh/plugins/cyjyyd/dsh-ssh-tui?ref=badge)
|
|
16
|
+
|
|
13
17
|
安装(官方 CLI,无需 clone):
|
|
14
18
|
|
|
15
19
|
```bash
|
|
@@ -194,6 +198,8 @@ dsh --profile tui --no-color
|
|
|
194
198
|
| `Ctrl+D` | 退出 |
|
|
195
199
|
| `Ctrl+L` | 重绘整个画面 |
|
|
196
200
|
|
|
201
|
+
`/mode` 切换官方 preset:标准 (`standard`)、PTC (`ptc`;dsh 0.1.1 上仍是 `code`)、极简 (`minimal`)、创造 (`cordis`),以及本地安装的其它模式。
|
|
202
|
+
|
|
197
203
|
斜杠命令:`/help`、`/find`、`/model`、`/provider`、`/submodel`、`/subeffort`、`/mode`、`/resume`、
|
|
198
204
|
`/status`、`/subagents`、`/usage`(`/balance` 同义)、`/setup`、`/clear`,
|
|
199
205
|
以及 harness 自带命令(`/goal`、`/plan`、`/compact` 等)。`/compact` 进行中会显示
|
package/cordis.patch.yml
CHANGED
|
@@ -13,14 +13,19 @@
|
|
|
13
13
|
directory is {{cwd}}. Verify your work by running the code or tests.
|
|
14
14
|
Keep answers brief and factual.
|
|
15
15
|
|
|
16
|
-
#
|
|
17
|
-
#
|
|
16
|
+
# A patch replaces the targeted row's whole `config`, so restating `root`
|
|
17
|
+
# keeps the official HMR row complete while the TUI keeps module reload off.
|
|
18
|
+
# The launcher's watch-only fallback still keeps user patch layers live.
|
|
18
19
|
- id: hmr
|
|
19
20
|
disabled: true
|
|
21
|
+
config:
|
|
22
|
+
root: ['.']
|
|
20
23
|
|
|
21
24
|
- id: tools
|
|
22
25
|
config:
|
|
23
|
-
mode
|
|
26
|
+
# 0.1.2 renamed the tools-mode value `code` → `ptc` with no alias.
|
|
27
|
+
# Map either env value onto the schema this dsh install actually accepts.
|
|
28
|
+
mode: !!js (function () { var mode = process.env.DSH_TOOLS_MODE; if (mode !== 'code' && mode !== 'ptc') return mode; try { var req = process.getBuiltinModule('module').createRequire(ctx.baseUrl || process.cwd() + '/'); var ver = req('@deepseek-ai/dsh-tools/package.json').version || ''; if (!/^0\.(0|1)\.(0|1)/.test(ver)) return 'ptc'; return 'code' } catch (err) { if (mode === 'code') return 'ptc'; return mode } })()
|
|
24
29
|
|
|
25
30
|
# The `main` agent is created by the TUI plugin itself after the loader
|
|
26
31
|
# settles, so it can read the saved default provider/model from
|
|
@@ -63,6 +68,15 @@
|
|
|
63
68
|
config:
|
|
64
69
|
default: standard
|
|
65
70
|
|
|
71
|
+
# Host-plane PTC runtime used by the shipped `ptc` preset. Absent on
|
|
72
|
+
# dsh-base; web/headless insert it themselves. Keep it optional so an
|
|
73
|
+
# older dsh install without the package still boots standard mode.
|
|
74
|
+
# Disabled when the package is not installed (older dsh 0.1.1 runtimes
|
|
75
|
+
# still boot; 0.1.2's ptc preset can mount once the worker is on disk).
|
|
76
|
+
- id: code-runtime
|
|
77
|
+
name: '@deepseek-ai/dsh-code-runtime-worker-thread'
|
|
78
|
+
disabled: !!js (function () { try { process.getBuiltinModule('module').createRequire(ctx.baseUrl || process.cwd() + '/').resolve('@deepseek-ai/dsh-code-runtime-worker-thread'); return false } catch (err) { return true } })()
|
|
79
|
+
|
|
66
80
|
- id: ssh-tui-startup
|
|
67
81
|
name: 'dsh-ssh-tui/startup'
|
|
68
82
|
|
package/lib/tui.js
CHANGED
|
@@ -26,8 +26,27 @@ import { defaultReasoningEffort } from './reasoning.js';
|
|
|
26
26
|
import { checkForPluginUpdate } from './update-check.js';
|
|
27
27
|
import { ROUTE_MEMORY_NAMESPACE, parseRouteMemory, rememberedRouteFor, upsertRememberedRoute, } from './route-memory.js';
|
|
28
28
|
import { DEFAULT_SUBAGENT_MODEL, SUBAGENT_SETTINGS_NAMESPACE, defaultSubagentModelForProvider, subagentModelMatchesProvider, subagentSettingsValue, } from './subagent-model.js';
|
|
29
|
-
const ROUTE_MEMORY_NS = ROUTE_MEMORY_NAMESPACE;
|
|
30
29
|
import { UserQuestionError, } from '@deepseek-ai/dsh-user-questions';
|
|
30
|
+
function discoverProviderModels(llm, request, signal) {
|
|
31
|
+
return llm.discoverModels(settingsNamespace('llm-pi-ai'), { ...request, signal }, signal);
|
|
32
|
+
}
|
|
33
|
+
function installUserQuestionAnswerer(ctx, questions, ask) {
|
|
34
|
+
const provider = questions;
|
|
35
|
+
if (typeof provider.registerProvider === 'function') {
|
|
36
|
+
return provider.registerProvider({ ask });
|
|
37
|
+
}
|
|
38
|
+
return ctx.on('user-questions/request', async (request, next) => {
|
|
39
|
+
try {
|
|
40
|
+
return await ask(request);
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
if (error instanceof UserQuestionError && error.code === 'ASK_ABORTED')
|
|
44
|
+
throw error;
|
|
45
|
+
return await next();
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
const ROUTE_MEMORY_NS = ROUTE_MEMORY_NAMESPACE;
|
|
31
50
|
const PROVIDER_TEMPLATES = {
|
|
32
51
|
official: {
|
|
33
52
|
label: 'DeepSeek 官方',
|
|
@@ -492,7 +511,7 @@ const LOCAL_COMMANDS = [
|
|
|
492
511
|
{ name: 'provider', description: 'switch provider, then model and reasoning effort' },
|
|
493
512
|
{ name: 'submodel', description: `select subagent model (default ${DEFAULT_SUBAGENT_MODEL}, same provider as parent)` },
|
|
494
513
|
{ name: 'subeffort', description: 'select subagent reasoning effort (default follows provider)' },
|
|
495
|
-
{ name: 'mode', description: 'switch agent mode / preset (standard, minimal,
|
|
514
|
+
{ name: 'mode', description: 'switch agent mode / preset (standard, minimal, ptc, cordis, routing-suite, ...)' },
|
|
496
515
|
{ name: 'quit', description: 'exit the TUI' },
|
|
497
516
|
{ name: 'exit', description: 'exit the TUI' },
|
|
498
517
|
{ name: 'clear', description: 'clear the transcript view' },
|
|
@@ -1197,9 +1216,14 @@ export function remainingPercentFromUsed(usedPercent) {
|
|
|
1197
1216
|
return 100;
|
|
1198
1217
|
return Math.max(0, Math.min(100, Math.round((100 - usedPercent) * 10) / 10));
|
|
1199
1218
|
}
|
|
1200
|
-
/** Cross a remaining-percent threshold from above (50 / 25 / 10 / 5).
|
|
1219
|
+
/** Cross a remaining-percent threshold from above (50 / 25 / 10 / 5).
|
|
1220
|
+
* Only the tightest (lowest) crossed threshold is returned, so one drop
|
|
1221
|
+
* never paints 50/25/10 as three identical warnings. */
|
|
1201
1222
|
export function crossedQuotaThresholds(previousRemaining, remaining) {
|
|
1202
|
-
|
|
1223
|
+
const crossed = QUOTA_ALERT_THRESHOLDS.filter(threshold => remaining <= threshold && (previousRemaining === undefined || previousRemaining > threshold));
|
|
1224
|
+
if (crossed.length === 0)
|
|
1225
|
+
return [];
|
|
1226
|
+
return [crossed[crossed.length - 1]];
|
|
1203
1227
|
}
|
|
1204
1228
|
export function quotaAlertText(snapshot, window) {
|
|
1205
1229
|
const reset = window.resetsAt === undefined ? '' : `(${formatQuotaReset(window.resetsAt)})`;
|
|
@@ -1207,11 +1231,13 @@ export function quotaAlertText(snapshot, window) {
|
|
|
1207
1231
|
}
|
|
1208
1232
|
/**
|
|
1209
1233
|
* How often to re-fetch quota, based on the tightest window.
|
|
1210
|
-
*
|
|
1211
|
-
*
|
|
1212
|
-
*
|
|
1234
|
+
* Counted in model steps (not conversation turns): a turn with several
|
|
1235
|
+
* tool/LLM steps should refresh more often because it spends more quota.
|
|
1236
|
+
* Hourly/5h: every 10 steps, every 4 when near a threshold.
|
|
1237
|
+
* Weekly: every 50 steps, every 10 when near.
|
|
1238
|
+
* Monthly: every 80 steps, every 20 when near.
|
|
1213
1239
|
*/
|
|
1214
|
-
export function
|
|
1240
|
+
export function quotaRefreshEverySteps(window) {
|
|
1215
1241
|
if (window === undefined)
|
|
1216
1242
|
return 10;
|
|
1217
1243
|
const near = window.remainingPercent <= QUOTA_NEAR_THRESHOLD_PERCENT;
|
|
@@ -1223,6 +1249,8 @@ export function quotaRefreshEveryTurns(window) {
|
|
|
1223
1249
|
return near ? 20 : 80;
|
|
1224
1250
|
return near ? 10 : 50;
|
|
1225
1251
|
}
|
|
1252
|
+
/** @deprecated Same cadence as {@link quotaRefreshEverySteps}; the name predates step accounting. */
|
|
1253
|
+
export const quotaRefreshEveryTurns = quotaRefreshEverySteps;
|
|
1226
1254
|
function quotaPeriodLabel(period) {
|
|
1227
1255
|
if (period === 'hourly')
|
|
1228
1256
|
return '5 小时';
|
|
@@ -2353,6 +2381,7 @@ export class SshTui {
|
|
|
2353
2381
|
lastPaintWidth = 0;
|
|
2354
2382
|
lastPaintHeight = 0;
|
|
2355
2383
|
lastChromeStart = 0;
|
|
2384
|
+
lastTranscriptStart = -1;
|
|
2356
2385
|
paintIntervalMs;
|
|
2357
2386
|
paintLink = 'local';
|
|
2358
2387
|
paintProbed = false;
|
|
@@ -2361,7 +2390,7 @@ export class SshTui {
|
|
|
2361
2390
|
llmRetry;
|
|
2362
2391
|
quotaSnapshot;
|
|
2363
2392
|
quotaAlerted = new Set();
|
|
2364
|
-
|
|
2393
|
+
quotaStepsSinceRefresh = 0;
|
|
2365
2394
|
quotaRefreshInFlight = false;
|
|
2366
2395
|
searchHits = [];
|
|
2367
2396
|
searchIndex = -1;
|
|
@@ -2405,7 +2434,7 @@ export class SshTui {
|
|
|
2405
2434
|
this.disposers.push(this.ctx.on('session/event', this.handleSessionEvent), this.ctx.on('agent/status', this.handleStatus), this.ctx.on('agent/error', this.handleError), this.ctx.on('agent/disposed', this.handleDisposed), this.ctx.on('agent/inbox/claimed', this.handleInboxClaimed), this.ctx.on('agent/inbox/discarded', this.handleInboxDiscarded), this.ctx.on('agent/request', this.handleAgentRequest), this.ctx.on('subagent/start', this.handleSubagentStart), this.ctx.on('subagent/end', this.handleSubagentEnd), this.ctx.on('approval/request', this.handleApproval));
|
|
2406
2435
|
const questions = this.ctx.get('userQuestions');
|
|
2407
2436
|
if (questions !== undefined) {
|
|
2408
|
-
this.userQuestionDisposer =
|
|
2437
|
+
this.userQuestionDisposer = installUserQuestionAnswerer(this.ctx, questions, this.handleUserQuestions);
|
|
2409
2438
|
}
|
|
2410
2439
|
this.write(`${this.useAlternateScreen ? '\x1b[?1049h' : ''}\x1b[?1000h\x1b[?1006h\x1b[?2004h\x1b[?25l`);
|
|
2411
2440
|
this.render();
|
|
@@ -3598,7 +3627,8 @@ export class SshTui {
|
|
|
3598
3627
|
String(chromeStart),
|
|
3599
3628
|
].join('\x1f');
|
|
3600
3629
|
const chromeChanged = chromeKey !== this.lastChromeKey || chromeStart !== this.lastChromeStart;
|
|
3601
|
-
const
|
|
3630
|
+
const transcriptScrolled = start !== this.lastTranscriptStart;
|
|
3631
|
+
const sizeChanged = width !== this.lastPaintWidth || height !== this.lastPaintHeight || transcriptScrolled;
|
|
3602
3632
|
// One stdout write per frame: dirty rows only, so jump-host SSH sees a
|
|
3603
3633
|
// single packet instead of one write per line. Clip/pad so leftover
|
|
3604
3634
|
// wide glyphs cannot wrap into the input box.
|
|
@@ -3621,6 +3651,7 @@ export class SshTui {
|
|
|
3621
3651
|
this.lastPaintWidth = width;
|
|
3622
3652
|
this.lastPaintHeight = height;
|
|
3623
3653
|
this.lastChromeStart = chromeStart;
|
|
3654
|
+
this.lastTranscriptStart = start;
|
|
3624
3655
|
};
|
|
3625
3656
|
buildSuggestions() {
|
|
3626
3657
|
const input = this.input;
|
|
@@ -3769,8 +3800,9 @@ export class SshTui {
|
|
|
3769
3800
|
kind === 'reasoning' ? '2;3' :
|
|
3770
3801
|
kind === 'brand' ? '1;38;2;77;107;253' :
|
|
3771
3802
|
kind === 'tool' || kind === 'tool-result' ? '33' :
|
|
3772
|
-
|
|
3773
|
-
|
|
3803
|
+
// Codex-like: muted add/del that blend into the terminal background.
|
|
3804
|
+
kind === 'diff-add' ? '38;2;122;168;116;48;2;18;42;24' :
|
|
3805
|
+
kind === 'diff-del' ? '38;2;196;122;122;48;2;48;20;20' :
|
|
3774
3806
|
kind === 'diff-path' ? '1;36' :
|
|
3775
3807
|
kind === 'todo-done' ? '2;32' :
|
|
3776
3808
|
kind === 'todo-active' ? '1;36' :
|
|
@@ -4006,6 +4038,16 @@ export class SshTui {
|
|
|
4006
4038
|
// Usage accounting is complete for this step; the map only exists to
|
|
4007
4039
|
// deduplicate repeated usage reports during the step.
|
|
4008
4040
|
this.usageByStep.delete(`${event.data.turn}:${event.data.step}`);
|
|
4041
|
+
if (!this.replaying) {
|
|
4042
|
+
this.quotaStepsSinceRefresh += 1;
|
|
4043
|
+
const every = quotaRefreshEverySteps(this.quotaSnapshot === undefined
|
|
4044
|
+
? undefined
|
|
4045
|
+
: tightestQuotaWindow(this.quotaSnapshot));
|
|
4046
|
+
if (this.quotaStepsSinceRefresh >= every) {
|
|
4047
|
+
this.quotaStepsSinceRefresh = 0;
|
|
4048
|
+
void this.refreshQuota({ reason: 'step', announce: false }).catch(() => { });
|
|
4049
|
+
}
|
|
4050
|
+
}
|
|
4009
4051
|
this.markDirty();
|
|
4010
4052
|
break;
|
|
4011
4053
|
}
|
|
@@ -4016,14 +4058,6 @@ export class SshTui {
|
|
|
4016
4058
|
this.markDirty();
|
|
4017
4059
|
break;
|
|
4018
4060
|
case 'turn/end': {
|
|
4019
|
-
this.quotaTurnsSinceRefresh += 1;
|
|
4020
|
-
const every = quotaRefreshEveryTurns(this.quotaSnapshot === undefined
|
|
4021
|
-
? undefined
|
|
4022
|
-
: tightestQuotaWindow(this.quotaSnapshot));
|
|
4023
|
-
if (this.quotaTurnsSinceRefresh >= every) {
|
|
4024
|
-
this.quotaTurnsSinceRefresh = 0;
|
|
4025
|
-
void this.refreshQuota({ reason: 'turn', announce: false }).catch(() => { });
|
|
4026
|
-
}
|
|
4027
4061
|
const reason = event.data.reason;
|
|
4028
4062
|
this.openToolCalls.clear();
|
|
4029
4063
|
this.pendingToolTimes.clear();
|
|
@@ -4062,11 +4096,6 @@ export class SshTui {
|
|
|
4062
4096
|
this.markDirty();
|
|
4063
4097
|
break;
|
|
4064
4098
|
}
|
|
4065
|
-
case 'todo/write': {
|
|
4066
|
-
this.upsertPlanRow({ todos: parsePlanTodos(event.data.todos) });
|
|
4067
|
-
this.markDirty();
|
|
4068
|
-
break;
|
|
4069
|
-
}
|
|
4070
4099
|
default:
|
|
4071
4100
|
this.handleExtensionEvent(event);
|
|
4072
4101
|
break;
|
|
@@ -4132,6 +4161,11 @@ export class SshTui {
|
|
|
4132
4161
|
this.markDirty();
|
|
4133
4162
|
return;
|
|
4134
4163
|
}
|
|
4164
|
+
if (type === 'todo/write') {
|
|
4165
|
+
this.upsertPlanRow({ todos: parsePlanTodos(data?.todos) });
|
|
4166
|
+
this.markDirty();
|
|
4167
|
+
return;
|
|
4168
|
+
}
|
|
4135
4169
|
if (type === 'command/run') {
|
|
4136
4170
|
this.handleCommandRun(data);
|
|
4137
4171
|
return;
|
|
@@ -4707,12 +4741,11 @@ export class SshTui {
|
|
|
4707
4741
|
const llm = this.ctx.get('llm');
|
|
4708
4742
|
if (llm === undefined)
|
|
4709
4743
|
return [];
|
|
4710
|
-
const discovered = await
|
|
4744
|
+
const discovered = await discoverProviderModels(llm, {
|
|
4711
4745
|
baseURL,
|
|
4712
4746
|
...(api === undefined ? {} : { api }),
|
|
4713
4747
|
...(apiKey === undefined ? {} : { apiKey }),
|
|
4714
|
-
|
|
4715
|
-
});
|
|
4748
|
+
}, AbortSignal.timeout(15_000));
|
|
4716
4749
|
return discovered.map(model => ({ id: model.id, label: model.name || model.id }));
|
|
4717
4750
|
}
|
|
4718
4751
|
/** Add one endpoint-listed model to the stored provider profile when needed. */
|
|
@@ -5112,7 +5145,7 @@ export class SshTui {
|
|
|
5112
5145
|
return;
|
|
5113
5146
|
this.quotaSnapshot = undefined;
|
|
5114
5147
|
this.quotaAlerted.clear();
|
|
5115
|
-
this.
|
|
5148
|
+
this.quotaStepsSinceRefresh = 0;
|
|
5116
5149
|
this.markDirty();
|
|
5117
5150
|
}
|
|
5118
5151
|
/** Persist one subagent selection and publish it to the live request waterfall. */
|
|
@@ -5254,7 +5287,7 @@ export class SshTui {
|
|
|
5254
5287
|
});
|
|
5255
5288
|
this.markDirty();
|
|
5256
5289
|
}
|
|
5257
|
-
/** /mode: pick an agent preset (standard / minimal /
|
|
5290
|
+
/** /mode: pick an agent preset (standard / minimal / ptc / cordis / routing-suite / ...). */
|
|
5258
5291
|
async runModeCommand() {
|
|
5259
5292
|
const agentPresets = this.ctx.get('agentPresets');
|
|
5260
5293
|
if (agentPresets === undefined) {
|
|
@@ -6149,12 +6182,11 @@ export class SshTui {
|
|
|
6149
6182
|
const llm = this.ctx.get('llm');
|
|
6150
6183
|
if (llm === undefined)
|
|
6151
6184
|
throw new Error('llm 服务不可用');
|
|
6152
|
-
const discovered = await
|
|
6185
|
+
const discovered = await discoverProviderModels(llm, {
|
|
6153
6186
|
baseURL,
|
|
6154
6187
|
...(template.api === undefined ? {} : { api: template.api }),
|
|
6155
6188
|
...(key === '' ? {} : { apiKey: key }),
|
|
6156
|
-
|
|
6157
|
-
});
|
|
6189
|
+
}, AbortSignal.timeout(15_000));
|
|
6158
6190
|
// Apply only if the wizard is still on the same draft the fetch started
|
|
6159
6191
|
// from, so a stale reply cannot overwrite a newer edit or a reset.
|
|
6160
6192
|
const stillCurrent = this.onboarding === state
|