dsh-ssh-tui 0.1.6 → 0.1.8
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 +6 -0
- package/README.md +14 -4
- package/cordis.patch.yml +19 -0
- package/lib/index.js +3 -0
- package/lib/index.js.map +1 -1
- package/lib/subagent-model.js +64 -0
- package/lib/subagent-model.js.map +1 -0
- package/lib/tui.js +228 -15
- package/lib/tui.js.map +1 -1
- package/lib/types/subagent-model.d.ts +56 -0
- package/lib/types/tui.d.ts +21 -0
- package/package.json +2 -1
package/README.en.md
CHANGED
|
@@ -155,6 +155,12 @@ configured catalog when the endpoint cannot be reached. Picking a model that
|
|
|
155
155
|
is not stored in the provider profile automatically adds it to
|
|
156
156
|
`llm-pi-ai.providers.<id>.models` so the harness can serve it.
|
|
157
157
|
|
|
158
|
+
Subagents follow the parent session's provider by default and run on the
|
|
159
|
+
lightweight `deepseek-v4-flash` model. `/submodel [model-id]` picks (or
|
|
160
|
+
directly sets) the subagent model, and `/subeffort` picks the subagent
|
|
161
|
+
reasoning effort or restores the provider default. Both commands are
|
|
162
|
+
remembered under `ssh-tui-subagent` in `$DSH_HOME/settings.yaml`.
|
|
163
|
+
|
|
158
164
|
`/usage` (alias `/quota`) works when the current provider is an OpenCode
|
|
159
165
|
source and keeps the two billing models distinct:
|
|
160
166
|
|
package/README.md
CHANGED
|
@@ -120,9 +120,14 @@ dsh --profile tui --no-color
|
|
|
120
120
|
| `Ctrl+D` | 退出 |
|
|
121
121
|
| `Ctrl+L` | 重绘 |
|
|
122
122
|
|
|
123
|
-
斜杠命令:`/help`、`/model`、`/mode`、`/resume
|
|
124
|
-
`/usage`(`/quota` 同义)、`/setup`、`/clear
|
|
125
|
-
|
|
123
|
+
斜杠命令:`/help`、`/model`、`/submodel`、`/subeffort`、`/mode`、`/resume`、
|
|
124
|
+
`/status`、`/subagents`、`/usage`(`/quota` 同义)、`/setup`、`/clear`,
|
|
125
|
+
以及 harness 自带命令(`/goal`、`/plan`、`/compact` 等)。
|
|
126
|
+
|
|
127
|
+
子代理默认跟随父会话的提供方,模型默认为轻量的 `deepseek-v4-flash`:
|
|
128
|
+
|
|
129
|
+
- `/submodel [model-id]`:打开子代理模型选择器;带参数时直接指定模型;
|
|
130
|
+
- `/subeffort`:选择子代理思考强度,或恢复为“跟随提供商默认”。
|
|
126
131
|
|
|
127
132
|
`/usage` 在当前提供商为 OpenCode 源时可用,并区分两种计费方式:
|
|
128
133
|
|
|
@@ -142,9 +147,14 @@ agent-default-model:
|
|
|
142
147
|
reasoningEffort: max
|
|
143
148
|
agent-presets:
|
|
144
149
|
default: standard
|
|
150
|
+
ssh-tui-subagent:
|
|
151
|
+
model: deepseek-v4-flash
|
|
152
|
+
# provider 可省略:省略时子代理跟随父会话提供方
|
|
153
|
+
# reasoningEffort 可省略:省略时跟随提供商/模型默认
|
|
145
154
|
```
|
|
146
155
|
|
|
147
|
-
`/model` 与 `/
|
|
156
|
+
`/model`、`/mode`、`/submodel` 与 `/subeffort` 的修改会写回这里,
|
|
157
|
+
web 端与 TUI 共用同一份设置。
|
|
148
158
|
|
|
149
159
|
对 OpenCode 和其他第三方提供商,`/model` 会先调用提供商的端点
|
|
150
160
|
(`GET {baseURL}/models`)获取实时模型列表;端点不可达时回退到已配置的
|
package/cordis.patch.yml
CHANGED
|
@@ -35,6 +35,25 @@
|
|
|
35
35
|
thinking: enabled
|
|
36
36
|
reasoningEffort: max
|
|
37
37
|
|
|
38
|
+
# Subagents follow the parent session's provider and default to the
|
|
39
|
+
# lightweight model. The TUI's /submodel and /subeffort commands can override
|
|
40
|
+
# both model and reasoning effort at runtime through the request waterfall.
|
|
41
|
+
- id: tool-subagent
|
|
42
|
+
config:
|
|
43
|
+
provider: spawn
|
|
44
|
+
toolName: subagent
|
|
45
|
+
backgroundMode: continuable
|
|
46
|
+
agentOptions:
|
|
47
|
+
model: deepseek-v4-flash
|
|
48
|
+
|
|
49
|
+
- id: tool-subagent-fork
|
|
50
|
+
config:
|
|
51
|
+
provider: fork
|
|
52
|
+
toolName: subagent_fork
|
|
53
|
+
backgroundMode: one-shot
|
|
54
|
+
agentOptions:
|
|
55
|
+
model: deepseek-v4-flash
|
|
56
|
+
|
|
38
57
|
- insert:
|
|
39
58
|
# The agent-mode roster (standard / PTC / minimal / cordis / routing-suite).
|
|
40
59
|
# The launcher injects the shipped preset root into this row when it is
|
package/lib/index.js
CHANGED
|
@@ -11,6 +11,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
11
11
|
import { showSessionPicker } from './picker.js';
|
|
12
12
|
import { mountTui } from './tui.js';
|
|
13
13
|
import { defaultReasoningEffort } from './reasoning.js';
|
|
14
|
+
import { createSubagentSelection } from './subagent-model.js';
|
|
14
15
|
export const name = 'ssh-tui';
|
|
15
16
|
/** Core services required before the terminal channel can drive an agent. */
|
|
16
17
|
export const inject = ['agents', 'agentDefaultModel'];
|
|
@@ -24,6 +25,7 @@ export function apply(ctx, config) {
|
|
|
24
25
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
25
26
|
throw new Error('dsh-ssh-tui: both stdin and stdout must be TTYs; use a terminal/SSH session');
|
|
26
27
|
}
|
|
28
|
+
const subagentSelection = createSubagentSelection(ctx);
|
|
27
29
|
ctx.effect(() => {
|
|
28
30
|
let disposed = false;
|
|
29
31
|
let switching = false;
|
|
@@ -131,6 +133,7 @@ export function apply(ctx, config) {
|
|
|
131
133
|
provider,
|
|
132
134
|
model,
|
|
133
135
|
selectionRef,
|
|
136
|
+
subagentSelection,
|
|
134
137
|
presetId,
|
|
135
138
|
presetName,
|
|
136
139
|
goodbye: goodbyeFor(String(sessionId)),
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,qBAAqB,EAAiE,MAAM,wBAAwB,CAAA;AAG7H,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AAEjD,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAA;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAC/C,OAAO,EAAE,QAAQ,EAAsB,MAAM,UAAU,CAAA;AACvD,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subagent model defaults and the settings section the TUI edits with
|
|
3
|
+
* `/submodel` and `/subeffort`.
|
|
4
|
+
*
|
|
5
|
+
* The provider is deliberately inherited from the running parent session:
|
|
6
|
+
* the subagent selection only overrides the provider when the user stored an
|
|
7
|
+
* explicit route. The model defaults to the lightweight `deepseek-v4-flash`.
|
|
8
|
+
*/
|
|
9
|
+
import z from '@deepseek-ai/schemastery';
|
|
10
|
+
import { ReasoningEffortId } from '@deepseek-ai/dsh-llm';
|
|
11
|
+
import { installSettingsSection, settingsNamespace } from '@deepseek-ai/dsh-settings';
|
|
12
|
+
/** Settings namespace carrying the TUI's subagent model selection. */
|
|
13
|
+
export const SUBAGENT_SETTINGS_NAMESPACE = settingsNamespace('ssh-tui-subagent');
|
|
14
|
+
/** Lightweight default model used for subagent children. */
|
|
15
|
+
export const DEFAULT_SUBAGENT_MODEL = 'deepseek-v4-flash';
|
|
16
|
+
/** Settings schema for `$DSH_HOME/settings.yaml`. */
|
|
17
|
+
export const SUBAGENT_SETTINGS_SCHEMA = z.object({
|
|
18
|
+
provider: z.string(),
|
|
19
|
+
model: z.string().default(DEFAULT_SUBAGENT_MODEL),
|
|
20
|
+
reasoningEffort: z.string(),
|
|
21
|
+
});
|
|
22
|
+
/** Normalize a raw settings section into a live typed selection. */
|
|
23
|
+
export function normalizeSubagentSelection(value) {
|
|
24
|
+
const raw = (typeof value === 'object' && value !== null ? value : {});
|
|
25
|
+
return {
|
|
26
|
+
...(typeof raw.provider === 'string' && raw.provider.trim() !== ''
|
|
27
|
+
? { provider: raw.provider.trim() }
|
|
28
|
+
: {}),
|
|
29
|
+
model: typeof raw.model === 'string' && raw.model.trim() !== ''
|
|
30
|
+
? raw.model.trim()
|
|
31
|
+
: DEFAULT_SUBAGENT_MODEL,
|
|
32
|
+
...(typeof raw.reasoningEffort === 'string' && raw.reasoningEffort.trim() !== ''
|
|
33
|
+
? { reasoningEffort: ReasoningEffortId(raw.reasoningEffort.trim()) }
|
|
34
|
+
: {}),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Install the settings-backed subagent selection for one TUI plugin fiber.
|
|
39
|
+
* Returns the mutable ref the request waterfall and the slash commands share.
|
|
40
|
+
*/
|
|
41
|
+
export function createSubagentSelection(ctx) {
|
|
42
|
+
let source = () => ({ model: DEFAULT_SUBAGENT_MODEL });
|
|
43
|
+
const ref = { current: normalizeSubagentSelection(source()) };
|
|
44
|
+
installSettingsSection(ctx, SUBAGENT_SETTINGS_NAMESPACE, SUBAGENT_SETTINGS_SCHEMA, {
|
|
45
|
+
model: DEFAULT_SUBAGENT_MODEL,
|
|
46
|
+
}, {
|
|
47
|
+
setSource: (current) => {
|
|
48
|
+
source = () => current();
|
|
49
|
+
},
|
|
50
|
+
onChange: () => {
|
|
51
|
+
ref.current = normalizeSubagentSelection(source());
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
return ref;
|
|
55
|
+
}
|
|
56
|
+
/** Serialize a live selection back into the settings document shape. */
|
|
57
|
+
export function subagentSettingsValue(selection) {
|
|
58
|
+
return {
|
|
59
|
+
...(selection.provider === undefined ? {} : { provider: selection.provider }),
|
|
60
|
+
model: selection.model,
|
|
61
|
+
...(selection.reasoningEffort === undefined ? {} : { reasoningEffort: String(selection.reasoningEffort) }),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=subagent-model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subagent-model.js","sourceRoot":"","sources":["../src/subagent-model.ts"],"names":[],"mappings":"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"}
|
package/lib/tui.js
CHANGED
|
@@ -21,6 +21,7 @@ import { SessionId } from '@deepseek-ai/dsh-session';
|
|
|
21
21
|
import { settingsNamespace } from '@deepseek-ai/dsh-settings';
|
|
22
22
|
import { formatSessionTime, listResumableSessions } from './session-list.js';
|
|
23
23
|
import { defaultReasoningEffort } from './reasoning.js';
|
|
24
|
+
import { DEFAULT_SUBAGENT_MODEL, SUBAGENT_SETTINGS_NAMESPACE, subagentSettingsValue, } from './subagent-model.js';
|
|
24
25
|
import { UserQuestionError, } from '@deepseek-ai/dsh-user-questions';
|
|
25
26
|
const PROVIDER_TEMPLATES = {
|
|
26
27
|
official: {
|
|
@@ -63,6 +64,7 @@ const WAIT_INDICATOR_MS = 8000;
|
|
|
63
64
|
const STALL_WARNING_MS = 60000;
|
|
64
65
|
const SPINNER = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
65
66
|
const QUESTION_OPTION_KEYS = '123456789abcdefghijklmnopqrstuvwxyz';
|
|
67
|
+
const SUBAGENT_DEFAULT_EFFORT_LABEL = '跟随提供商默认';
|
|
66
68
|
const RESERVED_BOTTOM_LINES = 3; // input line + stats line + status line
|
|
67
69
|
const MAX_TRANSCRIPT_ROWS = 5000;
|
|
68
70
|
const IS_WINDOWS = process.platform === 'win32';
|
|
@@ -195,6 +197,8 @@ const DEEPSEEK_LOGO_VARIANTS = [
|
|
|
195
197
|
const LOCAL_COMMANDS = [
|
|
196
198
|
{ name: 'help', description: 'show all available commands' },
|
|
197
199
|
{ name: 'model', description: 'select model and reasoning effort (same provider)' },
|
|
200
|
+
{ name: 'submodel', description: `select subagent model (default ${DEFAULT_SUBAGENT_MODEL}, same provider as parent)` },
|
|
201
|
+
{ name: 'subeffort', description: 'select subagent reasoning effort (default follows provider)' },
|
|
198
202
|
{ name: 'mode', description: 'switch agent mode / preset (standard, minimal, code, cordis, routing-suite, ...)' },
|
|
199
203
|
{ name: 'quit', description: 'exit the TUI' },
|
|
200
204
|
{ name: 'exit', description: 'exit the TUI' },
|
|
@@ -800,6 +804,12 @@ function lastCodePoints(text, max) {
|
|
|
800
804
|
return '';
|
|
801
805
|
return Array.from(text).slice(-max).join('');
|
|
802
806
|
}
|
|
807
|
+
/** Format a model list compactly: show the first few entries and an ellipsis. */
|
|
808
|
+
function formatModelList(models, max = 5) {
|
|
809
|
+
const shown = models.slice(0, max);
|
|
810
|
+
const text = shown.join(', ');
|
|
811
|
+
return models.length > max ? `${text}…(共 ${models.length} 个)` : text;
|
|
812
|
+
}
|
|
803
813
|
/** Prefer the fields a human scans for; fall back to the first scalar pairs. */
|
|
804
814
|
function friendlyArgsSummary(name, args) {
|
|
805
815
|
const parsed = parseJsonArgs(args);
|
|
@@ -1159,6 +1169,7 @@ export class SshTui {
|
|
|
1159
1169
|
resume;
|
|
1160
1170
|
providerName;
|
|
1161
1171
|
selectionRef;
|
|
1172
|
+
subagentSelection;
|
|
1162
1173
|
onSwitchSession;
|
|
1163
1174
|
onSelectionChanged;
|
|
1164
1175
|
resumePicker;
|
|
@@ -1220,6 +1231,7 @@ export class SshTui {
|
|
|
1220
1231
|
this.resume = config.resume === true;
|
|
1221
1232
|
this.providerName = config.provider ?? 'deepseek-official';
|
|
1222
1233
|
this.selectionRef = config.selectionRef;
|
|
1234
|
+
this.subagentSelection = config.subagentSelection ?? { current: { model: DEFAULT_SUBAGENT_MODEL } };
|
|
1223
1235
|
this.onSwitchSession = config.onSwitchSession;
|
|
1224
1236
|
this.onSelectionChanged = config.onSelectionChanged;
|
|
1225
1237
|
this.resumePicker = config.resumePicker === true;
|
|
@@ -1237,7 +1249,7 @@ export class SshTui {
|
|
|
1237
1249
|
process.stdin.on('data', this.handleData);
|
|
1238
1250
|
process.stdout.on('resize', this.markDirty);
|
|
1239
1251
|
process.on('SIGWINCH', this.markDirty);
|
|
1240
|
-
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('subagent/start', this.handleSubagentStart), this.ctx.on('subagent/end', this.handleSubagentEnd), this.ctx.on('approval/request', this.handleApproval));
|
|
1252
|
+
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));
|
|
1241
1253
|
const questions = this.ctx.get('userQuestions');
|
|
1242
1254
|
if (questions !== undefined) {
|
|
1243
1255
|
this.userQuestionDisposer = questions.registerProvider({ ask: this.handleUserQuestions });
|
|
@@ -1699,7 +1711,9 @@ export class SshTui {
|
|
|
1699
1711
|
case 'models':
|
|
1700
1712
|
addDialog(`提供商:${providerLabel}`);
|
|
1701
1713
|
addDialog('模型 ID(多个用逗号或空格分隔):');
|
|
1702
|
-
addDialog(
|
|
1714
|
+
addDialog(ob.models.length > 0
|
|
1715
|
+
? ` 已获取(${ob.models.length}):${formatModelList(ob.models, 6)}`
|
|
1716
|
+
: ` 默认:${template.defaultModels.join(', ')}`);
|
|
1703
1717
|
if (template.api !== undefined)
|
|
1704
1718
|
addDialog(' Ctrl+F = 从端点获取模型列表');
|
|
1705
1719
|
addDialog(' Enter 确认,Esc 取消');
|
|
@@ -1710,7 +1724,7 @@ export class SshTui {
|
|
|
1710
1724
|
addDialog(` Provider ID: ${ob.providerId}`);
|
|
1711
1725
|
addDialog(` Base URL: ${ob.baseUrl === '' ? (template.defaultBaseUrl || '(默认)') : ob.baseUrl}`);
|
|
1712
1726
|
addDialog(` API 协议: ${template.api ?? 'deepseek-official'}`);
|
|
1713
|
-
addDialog(` 模型: ${ob.models
|
|
1727
|
+
addDialog(` 模型: ${formatModelList(ob.models, 8)}`);
|
|
1714
1728
|
addDialog(` API Key: ${sliceCodePoints(ob.key, 6)}…${lastCodePoints(ob.key, 4)}(长度 ${ob.key.length})`);
|
|
1715
1729
|
addDialog(' y = 保存, n = 重填, Esc = 取消');
|
|
1716
1730
|
break;
|
|
@@ -1829,6 +1843,12 @@ export class SshTui {
|
|
|
1829
1843
|
const statsText = this.statsText();
|
|
1830
1844
|
const statsLine = this.styleLine('system', fitLine(statsText === '' ? '— 尚无会话统计' : statsText));
|
|
1831
1845
|
let statusText = `${this.status} [${this.presetName}] ${this.currentSelectionLabel()}`;
|
|
1846
|
+
const sub = this.subagentSelection.current;
|
|
1847
|
+
const subProvider = sub.provider ?? this.selectionRef?.current?.provider ?? this.agent.options.provider ?? this.providerName;
|
|
1848
|
+
const subEffort = sub.reasoningEffort === undefined ? '' : `(${sub.reasoningEffort})`;
|
|
1849
|
+
statusText += sub.provider === undefined
|
|
1850
|
+
? ` · sub:${sub.model}${subEffort}`
|
|
1851
|
+
: ` · sub:${subProvider}/${sub.model}${subEffort}`;
|
|
1832
1852
|
if (inputView.folded)
|
|
1833
1853
|
statusText += ' · 输入已折叠 · Ctrl+T 展开';
|
|
1834
1854
|
else if (inputRows > 1)
|
|
@@ -2041,6 +2061,24 @@ export class SshTui {
|
|
|
2041
2061
|
return `\x1b[${code}m${safe}`;
|
|
2042
2062
|
}
|
|
2043
2063
|
// ── event handling ──────────────────────────────────────────────────────
|
|
2064
|
+
/**
|
|
2065
|
+
* Apply the TUI's subagent model selection to every child-agent request.
|
|
2066
|
+
* The parent request is left untouched (its own `/model` waterfall already
|
|
2067
|
+
* owns the route); direct children created by tool-subagent inherit the
|
|
2068
|
+
* parent provider unless `/submodel` stored an explicit subagent provider.
|
|
2069
|
+
*/
|
|
2070
|
+
handleAgentRequest = async ({ agent }, next) => {
|
|
2071
|
+
const resolved = await next();
|
|
2072
|
+
if (agent === this.agent)
|
|
2073
|
+
return resolved;
|
|
2074
|
+
const selection = this.subagentSelection.current;
|
|
2075
|
+
return {
|
|
2076
|
+
...resolved,
|
|
2077
|
+
...(selection.provider === undefined ? {} : { provider: selection.provider }),
|
|
2078
|
+
model: selection.model,
|
|
2079
|
+
...(selection.reasoningEffort === undefined ? {} : { reasoningEffort: selection.reasoningEffort }),
|
|
2080
|
+
};
|
|
2081
|
+
};
|
|
2044
2082
|
handleSessionEvent = (session, event) => {
|
|
2045
2083
|
if (session.id !== this.agent.id) {
|
|
2046
2084
|
if (this.subagentSessions.has(session.id))
|
|
@@ -2728,17 +2766,21 @@ export class SshTui {
|
|
|
2728
2766
|
effortOptions = [];
|
|
2729
2767
|
}
|
|
2730
2768
|
if (effortOptions.length === 0) {
|
|
2731
|
-
|
|
2769
|
+
// No selectable reasoning effort for this model: do not invent
|
|
2770
|
+
// `off/high/max`, which the adapter may reject for the exact model.
|
|
2771
|
+
}
|
|
2772
|
+
let effort;
|
|
2773
|
+
if (effortOptions.length > 0) {
|
|
2774
|
+
const effortAnswer = await this.askQuestion({
|
|
2775
|
+
id: 'effort-pick',
|
|
2776
|
+
question: `选择思考强度(${selected.id})`,
|
|
2777
|
+
options: effortOptions.map(option => ({
|
|
2778
|
+
label: option.label,
|
|
2779
|
+
description: option.id === String(current?.reasoningEffort) ? '当前' : undefined,
|
|
2780
|
+
})),
|
|
2781
|
+
});
|
|
2782
|
+
effort = effortOptions.find(option => option.label === effortAnswer.selected[0])?.id;
|
|
2732
2783
|
}
|
|
2733
|
-
const effortAnswer = await this.askQuestion({
|
|
2734
|
-
id: 'effort-pick',
|
|
2735
|
-
question: `选择思考强度(${selected.id})`,
|
|
2736
|
-
options: effortOptions.map(option => ({
|
|
2737
|
-
label: option.label,
|
|
2738
|
-
description: option.id === String(current?.reasoningEffort) ? '当前' : undefined,
|
|
2739
|
-
})),
|
|
2740
|
-
});
|
|
2741
|
-
const effort = effortOptions.find(option => option.label === effortAnswer.selected[0])?.id;
|
|
2742
2784
|
const next = {
|
|
2743
2785
|
provider,
|
|
2744
2786
|
model: selected.id,
|
|
@@ -2748,7 +2790,156 @@ export class SshTui {
|
|
|
2748
2790
|
this.selectionRef.current = next;
|
|
2749
2791
|
this.onSelectionChanged?.(next);
|
|
2750
2792
|
await this.ctx.get('agentDefaultModel')?.saveSelection(next);
|
|
2751
|
-
this.pushRow({
|
|
2793
|
+
this.pushRow({
|
|
2794
|
+
kind: 'system',
|
|
2795
|
+
text: `模型已切换:${selected.id}(思考强度 ${effort ?? '默认'}${effortOptions.length === 0 ? ',该模型未声明可选强度' : ''});下一步请求生效。`,
|
|
2796
|
+
});
|
|
2797
|
+
this.markDirty();
|
|
2798
|
+
}
|
|
2799
|
+
/** Provider route the next subagent request should use. */
|
|
2800
|
+
effectiveSubagentProvider() {
|
|
2801
|
+
return this.subagentSelection.current.provider
|
|
2802
|
+
?? this.selectionRef?.current?.provider
|
|
2803
|
+
?? this.agent.options.provider
|
|
2804
|
+
?? this.providerName;
|
|
2805
|
+
}
|
|
2806
|
+
/** Persist one subagent selection and publish it to the live request waterfall. */
|
|
2807
|
+
async saveSubagentSelection(next) {
|
|
2808
|
+
this.subagentSelection.current = next;
|
|
2809
|
+
const settings = this.ctx.get('settings');
|
|
2810
|
+
if (settings === undefined) {
|
|
2811
|
+
this.pushRow({ kind: 'error', text: '设置服务不可用,子代理选择仅当前会话生效。' });
|
|
2812
|
+
this.markDirty();
|
|
2813
|
+
return false;
|
|
2814
|
+
}
|
|
2815
|
+
await settings.replace(SUBAGENT_SETTINGS_NAMESPACE, subagentSettingsValue(next));
|
|
2816
|
+
return true;
|
|
2817
|
+
}
|
|
2818
|
+
/** Resolve the picker model list for one provider (endpoint first, then catalog). */
|
|
2819
|
+
async subagentModelOptions(provider) {
|
|
2820
|
+
const llm = this.ctx.get('llm');
|
|
2821
|
+
let options = [];
|
|
2822
|
+
let source = '已配置列表';
|
|
2823
|
+
if (this.piAiProviderProfile(provider) !== undefined || provider === 'opencode' || provider === 'opencode-go') {
|
|
2824
|
+
const previousStatus = this.status;
|
|
2825
|
+
try {
|
|
2826
|
+
this.status = `正在从端点获取子代理模型列表(${provider})…`;
|
|
2827
|
+
this.markDirty();
|
|
2828
|
+
options = await this.discoverEndpointModels(provider);
|
|
2829
|
+
if (options.length > 0) {
|
|
2830
|
+
source = '端点实时列表';
|
|
2831
|
+
try {
|
|
2832
|
+
const listed = (await llm?.listModels(provider)) ?? [];
|
|
2833
|
+
const endpointIds = new Set(options.map(model => model.id));
|
|
2834
|
+
for (const model of listed) {
|
|
2835
|
+
if (!endpointIds.has(model.id))
|
|
2836
|
+
options.push({ id: model.id, label: model.name || model.id });
|
|
2837
|
+
}
|
|
2838
|
+
}
|
|
2839
|
+
catch {
|
|
2840
|
+
// The endpoint list stands alone when the catalog cannot be read.
|
|
2841
|
+
}
|
|
2842
|
+
}
|
|
2843
|
+
}
|
|
2844
|
+
catch {
|
|
2845
|
+
options = [];
|
|
2846
|
+
}
|
|
2847
|
+
finally {
|
|
2848
|
+
this.status = previousStatus;
|
|
2849
|
+
this.markDirty();
|
|
2850
|
+
}
|
|
2851
|
+
}
|
|
2852
|
+
if (options.length === 0) {
|
|
2853
|
+
try {
|
|
2854
|
+
const listed = (await llm?.listModels(provider)) ?? [];
|
|
2855
|
+
options = listed.map(model => ({ id: model.id, label: model.name || model.id }));
|
|
2856
|
+
}
|
|
2857
|
+
catch {
|
|
2858
|
+
options = [];
|
|
2859
|
+
}
|
|
2860
|
+
}
|
|
2861
|
+
return { options, source };
|
|
2862
|
+
}
|
|
2863
|
+
/** /submodel: pick (or set) the model subagent children use. */
|
|
2864
|
+
async runSubmodelCommand(arg) {
|
|
2865
|
+
const provider = this.effectiveSubagentProvider();
|
|
2866
|
+
const current = this.subagentSelection.current;
|
|
2867
|
+
const direct = arg.trim();
|
|
2868
|
+
let selectedId = direct;
|
|
2869
|
+
if (selectedId === '') {
|
|
2870
|
+
const { options, source } = await this.subagentModelOptions(provider);
|
|
2871
|
+
if (options.length === 0) {
|
|
2872
|
+
options.push({ id: current.model, label: current.model });
|
|
2873
|
+
}
|
|
2874
|
+
const selected = await this.pickModelOption(options, provider, source, current.model);
|
|
2875
|
+
if (selected === undefined)
|
|
2876
|
+
return;
|
|
2877
|
+
selectedId = selected.id;
|
|
2878
|
+
}
|
|
2879
|
+
if (!(await this.ensureProviderModelConfigured(provider, selectedId)))
|
|
2880
|
+
return;
|
|
2881
|
+
const persisted = await this.saveSubagentSelection({ ...current, model: selectedId });
|
|
2882
|
+
this.pushRow({
|
|
2883
|
+
kind: 'system',
|
|
2884
|
+
text: `${current.provider === undefined
|
|
2885
|
+
? `子代理模型已切换:${selectedId}(提供方跟随父会话 ${provider})。`
|
|
2886
|
+
: `子代理模型已切换:${selectedId}(提供方 ${provider})。`}${persisted ? '' : '(仅当前会话)'}`,
|
|
2887
|
+
});
|
|
2888
|
+
this.markDirty();
|
|
2889
|
+
}
|
|
2890
|
+
/** /subeffort: pick the reasoning effort subagent children use. */
|
|
2891
|
+
async runSubeffortCommand() {
|
|
2892
|
+
const provider = this.effectiveSubagentProvider();
|
|
2893
|
+
const current = this.subagentSelection.current;
|
|
2894
|
+
const llm = this.ctx.get('llm');
|
|
2895
|
+
let effortOptions = [];
|
|
2896
|
+
try {
|
|
2897
|
+
const info = await llm?.resolveModelInfo(provider, current.model);
|
|
2898
|
+
effortOptions = (info?.reasoning?.efforts ?? []).map(effort => ({ id: String(effort.id), label: effort.name }));
|
|
2899
|
+
}
|
|
2900
|
+
catch {
|
|
2901
|
+
effortOptions = [];
|
|
2902
|
+
}
|
|
2903
|
+
if (effortOptions.length === 0 && current.reasoningEffort === undefined) {
|
|
2904
|
+
this.pushRow({
|
|
2905
|
+
kind: 'system',
|
|
2906
|
+
text: `模型 ${provider}/${current.model} 未声明可选 reasoning effort,已保持提供商默认;请勿手动设置 high/max。`,
|
|
2907
|
+
});
|
|
2908
|
+
this.markDirty();
|
|
2909
|
+
return;
|
|
2910
|
+
}
|
|
2911
|
+
const choices = [
|
|
2912
|
+
{ id: undefined, label: SUBAGENT_DEFAULT_EFFORT_LABEL },
|
|
2913
|
+
...effortOptions.map(option => ({ id: option.id, label: option.label })),
|
|
2914
|
+
];
|
|
2915
|
+
const answer = await this.askQuestion({
|
|
2916
|
+
id: 'subagent-effort-pick',
|
|
2917
|
+
question: `选择子代理思考强度(${provider}/${current.model})`,
|
|
2918
|
+
options: choices.map(option => ({
|
|
2919
|
+
label: option.label,
|
|
2920
|
+
description: option.id === undefined
|
|
2921
|
+
? '清空自定义强度,跟随提供商/模型默认'
|
|
2922
|
+
: option.id === String(current.reasoningEffort)
|
|
2923
|
+
? '当前'
|
|
2924
|
+
: undefined,
|
|
2925
|
+
})),
|
|
2926
|
+
});
|
|
2927
|
+
const picked = choices.find(option => option.label === answer.selected[0]);
|
|
2928
|
+
if (picked === undefined)
|
|
2929
|
+
return;
|
|
2930
|
+
const next = {
|
|
2931
|
+
...current,
|
|
2932
|
+
...(picked.id === undefined
|
|
2933
|
+
? { reasoningEffort: undefined }
|
|
2934
|
+
: { reasoningEffort: ReasoningEffortId(picked.id) }),
|
|
2935
|
+
};
|
|
2936
|
+
const persisted = await this.saveSubagentSelection(next);
|
|
2937
|
+
this.pushRow({
|
|
2938
|
+
kind: 'system',
|
|
2939
|
+
text: `${picked.id === undefined
|
|
2940
|
+
? '子代理思考强度已恢复为提供商默认。'
|
|
2941
|
+
: `子代理思考强度已切换:${picked.id}。`}${persisted ? '' : '(仅当前会话)'}`,
|
|
2942
|
+
});
|
|
2752
2943
|
this.markDirty();
|
|
2753
2944
|
}
|
|
2754
2945
|
/** /mode: pick an agent preset (standard / minimal / code / cordis / routing-suite / ...). */
|
|
@@ -3486,7 +3677,7 @@ export class SshTui {
|
|
|
3486
3677
|
state.models = ids;
|
|
3487
3678
|
this.input = '';
|
|
3488
3679
|
this.cursor = 0;
|
|
3489
|
-
this.pushRow({ kind: 'system', text: `已从端点获取 ${ids.length}
|
|
3680
|
+
this.pushRow({ kind: 'system', text: `已从端点获取 ${ids.length} 个模型:${formatModelList(ids, 6)}(Enter 确认,也可继续修改)。` });
|
|
3490
3681
|
}
|
|
3491
3682
|
}
|
|
3492
3683
|
catch (error) {
|
|
@@ -3844,6 +4035,28 @@ export class SshTui {
|
|
|
3844
4035
|
this.markDirty();
|
|
3845
4036
|
});
|
|
3846
4037
|
break;
|
|
4038
|
+
case 'submodel':
|
|
4039
|
+
void this.runSubmodelCommand(arg).catch((error) => {
|
|
4040
|
+
if (error instanceof UserQuestionError) {
|
|
4041
|
+
this.pushRow({ kind: 'system', text: '子代理模型选择已取消。' });
|
|
4042
|
+
}
|
|
4043
|
+
else {
|
|
4044
|
+
this.pushRow({ kind: 'error', text: `/submodel failed: ${errorChain(error)}` });
|
|
4045
|
+
}
|
|
4046
|
+
this.markDirty();
|
|
4047
|
+
});
|
|
4048
|
+
break;
|
|
4049
|
+
case 'subeffort':
|
|
4050
|
+
void this.runSubeffortCommand().catch((error) => {
|
|
4051
|
+
if (error instanceof UserQuestionError) {
|
|
4052
|
+
this.pushRow({ kind: 'system', text: '子代理思考强度选择已取消。' });
|
|
4053
|
+
}
|
|
4054
|
+
else {
|
|
4055
|
+
this.pushRow({ kind: 'error', text: `/subeffort failed: ${errorChain(error)}` });
|
|
4056
|
+
}
|
|
4057
|
+
this.markDirty();
|
|
4058
|
+
});
|
|
4059
|
+
break;
|
|
3847
4060
|
case 'mode':
|
|
3848
4061
|
void this.runModeCommand().catch((error) => {
|
|
3849
4062
|
if (error instanceof UserQuestionError) {
|