@wukonglabs/wukong 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -8
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +4 -1
- package/dist/cli/help.js.map +1 -1
- package/dist/index.js +158 -50
- package/dist/index.js.map +1 -1
- package/dist/tui/app.d.ts +10 -0
- package/dist/tui/app.d.ts.map +1 -1
- package/dist/tui/app.js +41 -8
- package/dist/tui/app.js.map +1 -1
- package/dist/tui/history-entry.d.ts.map +1 -1
- package/dist/tui/history-entry.js +6 -4
- package/dist/tui/history-entry.js.map +1 -1
- package/dist/tui/live-block.js +1 -1
- package/dist/tui/live-block.js.map +1 -1
- package/dist/tui/permission-card.d.ts.map +1 -1
- package/dist/tui/permission-card.js +3 -1
- package/dist/tui/permission-card.js.map +1 -1
- package/dist/tui/plan-panel.d.ts.map +1 -1
- package/dist/tui/plan-panel.js +3 -1
- package/dist/tui/plan-panel.js.map +1 -1
- package/dist/tui/response-panel.d.ts.map +1 -1
- package/dist/tui/response-panel.js +3 -1
- package/dist/tui/response-panel.js.map +1 -1
- package/dist/tui/slash/menu.d.ts.map +1 -1
- package/dist/tui/slash/menu.js +4 -2
- package/dist/tui/slash/menu.js.map +1 -1
- package/dist/tui/slash/registry.js +1 -1
- package/dist/tui/slash/registry.js.map +1 -1
- package/dist/tui/text-input.d.ts.map +1 -1
- package/dist/tui/text-input.js +5 -3
- package/dist/tui/text-input.js.map +1 -1
- package/dist/tui/theme/theme.d.ts +4 -0
- package/dist/tui/theme/theme.d.ts.map +1 -1
- package/dist/tui/theme/theme.js +14 -10
- package/dist/tui/theme/theme.js.map +1 -1
- package/dist/tui/thinking-panel.d.ts.map +1 -1
- package/dist/tui/thinking-panel.js +7 -5
- package/dist/tui/thinking-panel.js.map +1 -1
- package/dist/tui/welcome.js +1 -1
- package/dist/tui/welcome.js.map +1 -1
- package/package.json +2 -2
- package/wukong.json +12 -3
package/README.md
CHANGED
|
@@ -80,26 +80,35 @@ Place at `~/.wukong/wukong.json` (global) or `./wukong.json` (project):
|
|
|
80
80
|
```json
|
|
81
81
|
{
|
|
82
82
|
"agent": {
|
|
83
|
-
"defaultModel": "deepseek-chat"
|
|
83
|
+
"defaultModel": "deepseek-chat"
|
|
84
|
+
},
|
|
85
|
+
"llm": {
|
|
84
86
|
"providers": {
|
|
85
|
-
"
|
|
86
|
-
"
|
|
87
|
+
"deepseek": {
|
|
88
|
+
"type": "openai-compatible",
|
|
89
|
+
"apiKey": "${DEEPSEEK_API_KEY}",
|
|
87
90
|
"baseUrl": "https://api.deepseek.com/v1"
|
|
88
91
|
}
|
|
92
|
+
},
|
|
93
|
+
"models": {
|
|
94
|
+
"deepseek-chat": {
|
|
95
|
+
"provider": "deepseek",
|
|
96
|
+
"id": "deepseek-chat"
|
|
97
|
+
}
|
|
89
98
|
}
|
|
90
99
|
}
|
|
91
100
|
}
|
|
92
101
|
```
|
|
93
102
|
|
|
94
|
-
|
|
103
|
+
Use `--model <alias>` at startup. In TUI, `/model` lists models and `/model <alias>` switches between turns.
|
|
95
104
|
|
|
96
105
|
### Environment Variables (Recommended)
|
|
97
106
|
|
|
98
107
|
```bash
|
|
99
|
-
OPENAI_API_KEY=sk-xxx # → providers.openai.apiKey
|
|
100
|
-
LLM_BASE_URL=https://api.xxx/v1 # → providers.openai.baseUrl
|
|
101
|
-
LLM_MODEL=
|
|
102
|
-
ANTHROPIC_API_KEY=sk-ant-xxx # → providers.anthropic.apiKey
|
|
108
|
+
OPENAI_API_KEY=sk-xxx # → llm.providers.openai.apiKey (if declared)
|
|
109
|
+
LLM_BASE_URL=https://api.xxx/v1 # → llm.providers.openai.baseUrl
|
|
110
|
+
LLM_MODEL=openai-default # → agent.defaultModel (declared alias)
|
|
111
|
+
ANTHROPIC_API_KEY=sk-ant-xxx # → llm.providers.anthropic.apiKey (if declared)
|
|
103
112
|
```
|
|
104
113
|
|
|
105
114
|
Config file string values support `${VAR_NAME}` templates referencing env vars.
|
package/dist/cli/help.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"help.d.ts","sourceRoot":"","sources":["../../src/cli/help.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,eAAO,MAAM,OAAO,UAAU,CAAC;AAE/B,eAAO,MAAM,SAAS,
|
|
1
|
+
{"version":3,"file":"help.d.ts","sourceRoot":"","sources":["../../src/cli/help.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,eAAO,MAAM,OAAO,UAAU,CAAC;AAE/B,eAAO,MAAM,SAAS,QAkFd,CAAC;AAET;;GAEG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAEtC"}
|
package/dist/cli/help.js
CHANGED
|
@@ -22,6 +22,7 @@ Wukong (悟空) — CI-First AI Agent CLI
|
|
|
22
22
|
核心选项:
|
|
23
23
|
-m, --message <text> 任务描述(agent 模式必填)
|
|
24
24
|
-sp, --system-prompt <text> 自定义 system prompt
|
|
25
|
+
--model <alias> 选择 llm.models 中声明的模型别名
|
|
25
26
|
-c, --continue 续接当前 workspace 最近一次会话(交互模式默认)
|
|
26
27
|
-n, --new 强制新开一条会话(CI 模式默认)
|
|
27
28
|
-s, --session-id <uuid> 精确指定要恢复的 session uuid
|
|
@@ -43,7 +44,9 @@ Wukong (悟空) — CI-First AI Agent CLI
|
|
|
43
44
|
|
|
44
45
|
思考模式:
|
|
45
46
|
--think 启用扩展思考模式
|
|
46
|
-
--think
|
|
47
|
+
--no-think 显式关闭思考模式
|
|
48
|
+
--think-budget <tokens> Anthropic 思考 token 预算(最小 1024)
|
|
49
|
+
--reasoning-effort <level> DeepSeek 思考强度: low | high | xhigh | max
|
|
47
50
|
|
|
48
51
|
编辑确认:
|
|
49
52
|
--confirm <mode> 确认模式: always | smart(默认) | never
|
package/dist/cli/help.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"help.js","sourceRoot":"","sources":["../../src/cli/help.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,0CAA0C;AAC1C,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC;AAE/B,MAAM,CAAC,MAAM,SAAS,GAAG
|
|
1
|
+
{"version":3,"file":"help.js","sourceRoot":"","sources":["../../src/cli/help.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,0CAA0C;AAC1C,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC;AAE/B,MAAM,CAAC,MAAM,SAAS,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkFxB,CAAC,IAAI,EAAE,CAAC;AAET;;GAEG;AACH,MAAM,UAAU,aAAa;IAC3B,OAAO,WAAW,OAAO,EAAE,CAAC;AAC9B,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import * as path from "node:path";
|
|
|
5
5
|
import * as os from "node:os";
|
|
6
6
|
// ── 核心能力全部来自独立发布的 SDK 包 ──
|
|
7
7
|
// CLI 只做参数解析、会话编排与输出渲染,组装逻辑(createAgent)在 SDK 内。
|
|
8
|
-
import { createAgent, SessionManager, collectSmartContext, attachSmartContext, AutoVerifier, DEFAULT_VERIFY_CONFIG, compactIfNeeded, loadConfig, Logger, setGlobalLogger, resolveWorkspace, getDefaultEventBus, ToolAuthorizer, TOOL_NAMES, isToolName, } from "@wukonglabs/wukong-agent-sdk";
|
|
8
|
+
import { createAgent, SessionManager, collectSmartContext, attachSmartContext, AutoVerifier, DEFAULT_VERIFY_CONFIG, compactIfNeeded, loadConfig, resolveModelConfig, Logger, setGlobalLogger, resolveWorkspace, getDefaultEventBus, ToolAuthorizer, TOOL_NAMES, isToolName, } from "@wukonglabs/wukong-agent-sdk";
|
|
9
9
|
// ── CLI 本地模块(不属于 SDK 对外能力) ──
|
|
10
10
|
import { EXIT_CODES, resolveExitCode } from "./cli/exit-codes.js";
|
|
11
11
|
import { HELP_TEXT, formatVersion, VERSION } from "./cli/help.js";
|
|
@@ -22,6 +22,7 @@ function parseArgs(argv) {
|
|
|
22
22
|
positional: [],
|
|
23
23
|
message: null,
|
|
24
24
|
systemPrompt: null,
|
|
25
|
+
model: null,
|
|
25
26
|
sessionId: null,
|
|
26
27
|
continueSession: false,
|
|
27
28
|
newSession: false,
|
|
@@ -31,7 +32,9 @@ function parseArgs(argv) {
|
|
|
31
32
|
yes: false,
|
|
32
33
|
confirmMode: null,
|
|
33
34
|
think: false,
|
|
35
|
+
noThink: false,
|
|
34
36
|
thinkBudget: null,
|
|
37
|
+
reasoningEffort: null,
|
|
35
38
|
planMode: 'auto',
|
|
36
39
|
verify: false,
|
|
37
40
|
verifyCommand: null,
|
|
@@ -78,6 +81,9 @@ function parseArgs(argv) {
|
|
|
78
81
|
case "-sp":
|
|
79
82
|
result.systemPrompt = args[++i] ?? null;
|
|
80
83
|
break;
|
|
84
|
+
case "--model":
|
|
85
|
+
result.model = args[++i] ?? null;
|
|
86
|
+
break;
|
|
81
87
|
case "--session-id":
|
|
82
88
|
case "-s":
|
|
83
89
|
result.sessionId = args[++i] ?? null;
|
|
@@ -115,6 +121,11 @@ function parseArgs(argv) {
|
|
|
115
121
|
break;
|
|
116
122
|
case "--think":
|
|
117
123
|
result.think = true;
|
|
124
|
+
result.noThink = false;
|
|
125
|
+
break;
|
|
126
|
+
case "--no-think":
|
|
127
|
+
result.noThink = true;
|
|
128
|
+
result.think = false;
|
|
118
129
|
break;
|
|
119
130
|
case "--think-budget":
|
|
120
131
|
{
|
|
@@ -122,6 +133,17 @@ function parseArgs(argv) {
|
|
|
122
133
|
if (!isNaN(budget) && budget >= 1024) {
|
|
123
134
|
result.thinkBudget = budget;
|
|
124
135
|
result.think = true; // --think-budget 隐含 --think
|
|
136
|
+
result.noThink = false;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
break;
|
|
140
|
+
case "--reasoning-effort":
|
|
141
|
+
{
|
|
142
|
+
const effort = args[++i];
|
|
143
|
+
if (effort === 'low' || effort === 'high' || effort === 'xhigh' || effort === 'max') {
|
|
144
|
+
result.reasoningEffort = effort;
|
|
145
|
+
result.think = true; // reasoning_effort 隐含开启思考
|
|
146
|
+
result.noThink = false;
|
|
125
147
|
}
|
|
126
148
|
}
|
|
127
149
|
break;
|
|
@@ -236,6 +258,7 @@ async function initAgent(opts) {
|
|
|
236
258
|
return createAgent({
|
|
237
259
|
config: opts.cfg,
|
|
238
260
|
logger: opts.logger,
|
|
261
|
+
model: opts.model,
|
|
239
262
|
confirmMode: opts.confirmMode,
|
|
240
263
|
thinking: opts.thinking,
|
|
241
264
|
ciMode: opts.ciMode ?? false,
|
|
@@ -252,8 +275,10 @@ async function runAgentCommand(cliArgs) {
|
|
|
252
275
|
setGlobalLogger(logger);
|
|
253
276
|
const cfg = await loadConfig();
|
|
254
277
|
applyPermissionOverrides(cfg, cliArgs, logger);
|
|
278
|
+
const selectedModel = cliArgs.model ?? cfg.agent.defaultModel;
|
|
279
|
+
resolveModelConfig(cfg, selectedModel);
|
|
255
280
|
const resolvedConfirmMode = cliArgs.yes ? 'never' : (cliArgs.confirmMode ?? cfg.tools.confirmMode ?? 'smart');
|
|
256
|
-
const resolvedThinking = resolveThinking(cliArgs, cfg);
|
|
281
|
+
const resolvedThinking = resolveThinking(cliArgs, cfg, selectedModel);
|
|
257
282
|
// CI 模式:--yes 或 --json 时自动启用
|
|
258
283
|
const isCiMode = cliArgs.yes || cliArgs.json;
|
|
259
284
|
// ── Session 策略解析 ──
|
|
@@ -299,7 +324,7 @@ async function runAgentCommand(cliArgs) {
|
|
|
299
324
|
}
|
|
300
325
|
};
|
|
301
326
|
const { llm, contextBuilder, agent, effectiveMaxSteps, resolvedModel } = await initAgent({
|
|
302
|
-
cfg, logger, confirmMode: resolvedConfirmMode, thinking: resolvedThinking, ciMode: isCiMode, planMode: cliArgs.planMode,
|
|
327
|
+
cfg, logger, model: selectedModel, confirmMode: resolvedConfirmMode, thinking: resolvedThinking, ciMode: isCiMode, planMode: cliArgs.planMode,
|
|
303
328
|
overrides: { maxSteps: cliArgs.maxSteps, contextWindow: cliArgs.contextWindow },
|
|
304
329
|
onStream, tracerOpts: { enabled: !cliArgs.noTrace },
|
|
305
330
|
});
|
|
@@ -579,10 +604,12 @@ function extractModifiedFiles(trace) {
|
|
|
579
604
|
}
|
|
580
605
|
return [...files];
|
|
581
606
|
}
|
|
582
|
-
/**
|
|
607
|
+
/** 检测是否存在至少一个可用模型及其带 apiKey 的 Provider。 */
|
|
583
608
|
function hasConfiguredProvider(cfg) {
|
|
584
|
-
|
|
585
|
-
|
|
609
|
+
return Object.values(cfg.llm.models).some((model) => {
|
|
610
|
+
const provider = cfg.llm.providers[model.provider];
|
|
611
|
+
return !!provider?.apiKey?.trim();
|
|
612
|
+
});
|
|
586
613
|
}
|
|
587
614
|
/**
|
|
588
615
|
* 交互式配置向导:在用户未配置任何 Provider 时收集最小可用配置。
|
|
@@ -602,23 +629,27 @@ async function runSetupWizard(logger) {
|
|
|
602
629
|
logger.info(" 3) OpenAI 兼容(自定义 baseUrl,如 DeepSeek / 通义 / 本地 vLLM 等)");
|
|
603
630
|
const typeChoice = (await question("输入序号 [1]: ")).trim() || "1";
|
|
604
631
|
let providerKey;
|
|
632
|
+
let modelAlias;
|
|
605
633
|
let type;
|
|
606
634
|
let defaultModel;
|
|
607
635
|
let baseUrlRequired = false;
|
|
608
636
|
switch (typeChoice) {
|
|
609
637
|
case "2":
|
|
610
638
|
providerKey = "anthropic";
|
|
639
|
+
modelAlias = "anthropic-default";
|
|
611
640
|
type = "anthropic";
|
|
612
641
|
defaultModel = "claude-sonnet-4-20250514";
|
|
613
642
|
break;
|
|
614
643
|
case "3":
|
|
615
|
-
providerKey = "
|
|
644
|
+
providerKey = "custom";
|
|
645
|
+
modelAlias = "custom-default";
|
|
616
646
|
type = "openai-compatible";
|
|
617
647
|
defaultModel = "";
|
|
618
648
|
baseUrlRequired = true;
|
|
619
649
|
break;
|
|
620
650
|
default:
|
|
621
651
|
providerKey = "openai";
|
|
652
|
+
modelAlias = "openai-default";
|
|
622
653
|
type = "openai";
|
|
623
654
|
defaultModel = "gpt-4o";
|
|
624
655
|
break;
|
|
@@ -650,8 +681,8 @@ async function runSetupWizard(logger) {
|
|
|
650
681
|
logger.error("❌ 未填写模型名,配置未完成");
|
|
651
682
|
return null;
|
|
652
683
|
}
|
|
653
|
-
const providerConf = { apiKey, baseUrl, type
|
|
654
|
-
return { providerKey, model, providerConf };
|
|
684
|
+
const providerConf = { apiKey, baseUrl, type };
|
|
685
|
+
return { providerKey, modelAlias, modelId: model, providerConf };
|
|
655
686
|
}
|
|
656
687
|
finally {
|
|
657
688
|
rl.close();
|
|
@@ -671,10 +702,18 @@ async function persistWukongConfig(setup, logger) {
|
|
|
671
702
|
// 文件不存在或无法解析 → 从空对象开始
|
|
672
703
|
}
|
|
673
704
|
existing.agent = existing.agent ?? {};
|
|
674
|
-
existing.agent.provider
|
|
675
|
-
existing.agent.
|
|
676
|
-
|
|
677
|
-
existing.agent.
|
|
705
|
+
delete existing.agent.provider;
|
|
706
|
+
delete existing.agent.providers;
|
|
707
|
+
delete existing.agent.thinking;
|
|
708
|
+
existing.agent.defaultModel = setup.modelAlias;
|
|
709
|
+
existing.llm = existing.llm ?? {};
|
|
710
|
+
existing.llm.providers = existing.llm.providers ?? {};
|
|
711
|
+
existing.llm.models = existing.llm.models ?? {};
|
|
712
|
+
existing.llm.providers[setup.providerKey] = setup.providerConf;
|
|
713
|
+
existing.llm.models[setup.modelAlias] = {
|
|
714
|
+
provider: setup.providerKey,
|
|
715
|
+
id: setup.modelId,
|
|
716
|
+
};
|
|
678
717
|
try {
|
|
679
718
|
await fs.mkdir(dir, { recursive: true });
|
|
680
719
|
await fs.writeFile(file, JSON.stringify(existing, null, 2) + "\n", { mode: 0o600 });
|
|
@@ -801,16 +840,21 @@ async function runInteractive(cliArgs) {
|
|
|
801
840
|
}
|
|
802
841
|
await persistWukongConfig(setup, logger);
|
|
803
842
|
// 直接注入内存配置,无需重新加载(避免本地 wukong.json 遮蔽全局配置的边界情况)
|
|
804
|
-
cfg.agent.
|
|
805
|
-
cfg.
|
|
806
|
-
cfg.
|
|
843
|
+
cfg.agent.defaultModel = setup.modelAlias;
|
|
844
|
+
cfg.llm.providers[setup.providerKey] = setup.providerConf;
|
|
845
|
+
cfg.llm.models[setup.modelAlias] = {
|
|
846
|
+
provider: setup.providerKey,
|
|
847
|
+
id: setup.modelId,
|
|
848
|
+
};
|
|
807
849
|
logger.info("");
|
|
808
850
|
if (suppressTuiBootInfo) {
|
|
809
851
|
logger.setLevel('warn');
|
|
810
852
|
}
|
|
811
853
|
}
|
|
854
|
+
const selectedModel = cliArgs.model ?? cfg.agent.defaultModel;
|
|
855
|
+
resolveModelConfig(cfg, selectedModel);
|
|
812
856
|
const resolvedConfirmMode = cliArgs.yes ? 'never' : (cliArgs.confirmMode ?? cfg.tools.confirmMode ?? 'smart');
|
|
813
|
-
const resolvedThinking = resolveThinking(cliArgs, cfg);
|
|
857
|
+
const resolvedThinking = resolveThinking(cliArgs, cfg, selectedModel);
|
|
814
858
|
const sessionManager = new SessionManager(cfg.sessions.dir);
|
|
815
859
|
const workspace = resolveWorkspace();
|
|
816
860
|
// 交互模式不视为 CI(即使被 pipe,交互模式一定不会被 --yes/--json 触发)
|
|
@@ -923,7 +967,7 @@ async function runInteractiveReadline(cliArgs, cfg, logger, resolvedConfirmMode,
|
|
|
923
967
|
}
|
|
924
968
|
};
|
|
925
969
|
const { llm, tools, contextBuilder, agent, resolvedModel } = await initAgent({
|
|
926
|
-
cfg, logger, confirmMode: resolvedConfirmMode, thinking: resolvedThinking, planMode: cliArgs.planMode,
|
|
970
|
+
cfg, logger, model: cliArgs.model ?? cfg.agent.defaultModel, confirmMode: resolvedConfirmMode, thinking: resolvedThinking, planMode: cliArgs.planMode,
|
|
927
971
|
overrides: { maxSteps: cliArgs.maxSteps, contextWindow: cliArgs.contextWindow },
|
|
928
972
|
onStream, tracerOpts: { enabled: !cliArgs.noTrace },
|
|
929
973
|
shouldAbort, authorizeTool,
|
|
@@ -1048,11 +1092,11 @@ async function runInteractiveTui(cliArgs, cfg, logger, resolvedConfirmMode, reso
|
|
|
1048
1092
|
};
|
|
1049
1093
|
// 读取持久化输入历史(可选,失败不阻塞)
|
|
1050
1094
|
const initialHistory = await loadInputHistory().catch(() => []);
|
|
1051
|
-
//
|
|
1052
|
-
const agentRef = {
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1095
|
+
// 当前运行时只在完整候选 Agent 构造成功后原子替换。
|
|
1096
|
+
const agentRef = { current: null };
|
|
1097
|
+
const streamSinkRef = { current: null };
|
|
1098
|
+
const onStreamProxy = (event) => {
|
|
1099
|
+
streamSinkRef.current?.(event);
|
|
1056
1100
|
};
|
|
1057
1101
|
const handlers = {
|
|
1058
1102
|
eventBus: getDefaultEventBus(),
|
|
@@ -1068,12 +1112,46 @@ async function runInteractiveTui(cliArgs, cfg, logger, resolvedConfirmMode, reso
|
|
|
1068
1112
|
authorizer,
|
|
1069
1113
|
// M1:/compact 手动压缩(复用 index.ts 作用域内的 compactIfNeeded)
|
|
1070
1114
|
compactIfNeeded: async () => {
|
|
1071
|
-
|
|
1115
|
+
const runtime = agentRef.current;
|
|
1116
|
+
if (!runtime)
|
|
1072
1117
|
return;
|
|
1073
|
-
await compactIfNeeded(session,
|
|
1118
|
+
await compactIfNeeded(session, runtime.contextBuilder.build(session, ''), runtime.llm, cfg.sessions.compaction);
|
|
1119
|
+
},
|
|
1120
|
+
listModels: () => Object.entries(cfg.llm.models)
|
|
1121
|
+
.filter(([, profile]) => {
|
|
1122
|
+
const apiKey = cfg.llm.providers[profile.provider]?.apiKey;
|
|
1123
|
+
return !!apiKey && !/^\$\{[^}]+\}$/.test(apiKey);
|
|
1124
|
+
})
|
|
1125
|
+
.map(([alias, profile]) => ({
|
|
1126
|
+
alias,
|
|
1127
|
+
id: profile.id,
|
|
1128
|
+
provider: profile.provider,
|
|
1129
|
+
})),
|
|
1130
|
+
switchModel: async (alias) => {
|
|
1131
|
+
resolveModelConfig(cfg, alias);
|
|
1132
|
+
const candidate = await initAgent({
|
|
1133
|
+
cfg,
|
|
1134
|
+
logger,
|
|
1135
|
+
model: alias,
|
|
1136
|
+
confirmMode: resolvedConfirmMode,
|
|
1137
|
+
thinking: resolveThinking(cliArgs, cfg, alias),
|
|
1138
|
+
planMode: cliArgs.planMode,
|
|
1139
|
+
overrides: { maxSteps: cliArgs.maxSteps, contextWindow: cliArgs.contextWindow },
|
|
1140
|
+
onStream: onStreamProxy,
|
|
1141
|
+
tracerOpts: { enabled: !cliArgs.noTrace },
|
|
1142
|
+
shouldAbort,
|
|
1143
|
+
authorizeTool,
|
|
1144
|
+
});
|
|
1145
|
+
agentRef.current = candidate;
|
|
1146
|
+
return {
|
|
1147
|
+
alias: candidate.resolvedModelProfile.alias,
|
|
1148
|
+
id: candidate.resolvedModelProfile.id,
|
|
1149
|
+
provider: candidate.resolvedModelProfile.providerName,
|
|
1150
|
+
};
|
|
1074
1151
|
},
|
|
1075
1152
|
sendMessage: async (input) => {
|
|
1076
|
-
|
|
1153
|
+
const runtime = agentRef.current;
|
|
1154
|
+
if (!runtime) {
|
|
1077
1155
|
throw new Error('Agent 未初始化');
|
|
1078
1156
|
}
|
|
1079
1157
|
// 持久化输入历史(异步,不阻塞)
|
|
@@ -1090,14 +1168,14 @@ async function runInteractiveTui(cliArgs, cfg, logger, resolvedConfirmMode, reso
|
|
|
1090
1168
|
// Smart Context 失败不影响主流程
|
|
1091
1169
|
}
|
|
1092
1170
|
}
|
|
1093
|
-
const messages =
|
|
1094
|
-
const result = await
|
|
1171
|
+
const messages = runtime.contextBuilder.build(session, attachSmartContext(input, smartContextText));
|
|
1172
|
+
const result = await runtime.agent.runWithMessages(messages);
|
|
1095
1173
|
await session.appendMessages([
|
|
1096
1174
|
{ role: "user", content: input },
|
|
1097
1175
|
...result.newMessages,
|
|
1098
1176
|
]);
|
|
1099
|
-
const allMessages =
|
|
1100
|
-
await compactIfNeeded(session, allMessages,
|
|
1177
|
+
const allMessages = runtime.contextBuilder.build(session, "");
|
|
1178
|
+
await compactIfNeeded(session, allMessages, runtime.llm, cfg.sessions.compaction);
|
|
1101
1179
|
const durationMs = result.metrics?.durationMs ?? 0;
|
|
1102
1180
|
return {
|
|
1103
1181
|
response: result.response,
|
|
@@ -1131,20 +1209,15 @@ async function runInteractiveTui(cliArgs, cfg, logger, resolvedConfirmMode, reso
|
|
|
1131
1209
|
// - 首帧被日志盖在可视区上方(用户看不到 Header/输入框)
|
|
1132
1210
|
// - Static 追加历史项时打在错的位置(Header 被复制多次)
|
|
1133
1211
|
// - 大量空线框(Ink 尝试重绘时高度计算失效)
|
|
1134
|
-
// onStream
|
|
1135
|
-
const
|
|
1136
|
-
|
|
1137
|
-
streamSinkRef.current?.(event);
|
|
1138
|
-
};
|
|
1139
|
-
const { llm, contextBuilder, agent, tools, resolvedModel, skillsLoaded } = await initAgent({
|
|
1140
|
-
cfg, logger, confirmMode: resolvedConfirmMode, thinking: resolvedThinking, planMode: cliArgs.planMode,
|
|
1212
|
+
// onStream 通过前面创建的稳定 sinkRef 桥接到 Ink。
|
|
1213
|
+
const initialRuntime = await initAgent({
|
|
1214
|
+
cfg, logger, model: cliArgs.model ?? cfg.agent.defaultModel, confirmMode: resolvedConfirmMode, thinking: resolvedThinking, planMode: cliArgs.planMode,
|
|
1141
1215
|
overrides: { maxSteps: cliArgs.maxSteps, contextWindow: cliArgs.contextWindow },
|
|
1142
1216
|
onStream: onStreamProxy, tracerOpts: { enabled: !cliArgs.noTrace },
|
|
1143
1217
|
shouldAbort, authorizeTool,
|
|
1144
1218
|
});
|
|
1145
|
-
agentRef.
|
|
1146
|
-
|
|
1147
|
-
agentRef.agent = agent;
|
|
1219
|
+
agentRef.current = initialRuntime;
|
|
1220
|
+
const { tools, resolvedModel, skillsLoaded } = initialRuntime;
|
|
1148
1221
|
// ── init 完成后再启动 Ink ──
|
|
1149
1222
|
// 此时终端上可能已经有几十行 skills 加载日志,Ink 启动后会:
|
|
1150
1223
|
// - 立即切换到 alt-screen(取决于终端;不切换时也会把 UI 渲染在当前光标处)
|
|
@@ -1245,18 +1318,53 @@ function applyPermissionOverrides(cfg, cliArgs, logger) {
|
|
|
1245
1318
|
logger.info(`🔒 强制拒绝工具: ${cliArgs.denyTools.join(', ')}`);
|
|
1246
1319
|
}
|
|
1247
1320
|
}
|
|
1248
|
-
/**
|
|
1249
|
-
function resolveThinking(cliArgs, cfg) {
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1321
|
+
/** 从所选模型与 CLI 参数解析本次运行的思考配置。 */
|
|
1322
|
+
function resolveThinking(cliArgs, cfg, modelAlias) {
|
|
1323
|
+
const configured = resolveModelConfig(cfg, modelAlias).thinking;
|
|
1324
|
+
const hasCliOverride = cliArgs.noThink || cliArgs.think || cliArgs.thinkBudget !== null || cliArgs.reasoningEffort !== null;
|
|
1325
|
+
if (!configured) {
|
|
1326
|
+
if (hasCliOverride) {
|
|
1327
|
+
throw new Error(`模型 "${modelAlias}" 未声明 thinking 协议,不能使用思考参数`);
|
|
1328
|
+
}
|
|
1329
|
+
return undefined;
|
|
1330
|
+
}
|
|
1331
|
+
const configuredType = configured.enabled ? 'enabled' : 'disabled';
|
|
1332
|
+
if (!hasCliOverride) {
|
|
1333
|
+
return configured.protocol === 'deepseek'
|
|
1334
|
+
? {
|
|
1335
|
+
protocol: 'deepseek',
|
|
1336
|
+
type: configuredType,
|
|
1337
|
+
...(configured.reasoningEffort ? { reasoningEffort: configured.reasoningEffort } : {}),
|
|
1338
|
+
}
|
|
1339
|
+
: {
|
|
1340
|
+
protocol: 'anthropic',
|
|
1341
|
+
type: configuredType,
|
|
1342
|
+
...(configured.budgetTokens !== undefined ? { budgetTokens: configured.budgetTokens } : {}),
|
|
1343
|
+
};
|
|
1254
1344
|
}
|
|
1255
|
-
|
|
1256
|
-
if (
|
|
1257
|
-
|
|
1345
|
+
const type = cliArgs.noThink ? 'disabled' : 'enabled';
|
|
1346
|
+
if (configured.protocol === 'deepseek') {
|
|
1347
|
+
if (cliArgs.thinkBudget !== null) {
|
|
1348
|
+
throw new Error(`模型 "${modelAlias}" 使用 deepseek 思考协议,不支持 --think-budget`);
|
|
1349
|
+
}
|
|
1350
|
+
return {
|
|
1351
|
+
protocol: 'deepseek',
|
|
1352
|
+
type,
|
|
1353
|
+
...(type === 'enabled' && (cliArgs.reasoningEffort ?? configured.reasoningEffort)
|
|
1354
|
+
? { reasoningEffort: cliArgs.reasoningEffort ?? configured.reasoningEffort }
|
|
1355
|
+
: {}),
|
|
1356
|
+
};
|
|
1258
1357
|
}
|
|
1259
|
-
|
|
1358
|
+
if (cliArgs.reasoningEffort !== null) {
|
|
1359
|
+
throw new Error(`模型 "${modelAlias}" 使用 anthropic 思考协议,不支持 --reasoning-effort`);
|
|
1360
|
+
}
|
|
1361
|
+
return {
|
|
1362
|
+
protocol: 'anthropic',
|
|
1363
|
+
type,
|
|
1364
|
+
...(type === 'enabled'
|
|
1365
|
+
? { budgetTokens: Math.max(cliArgs.thinkBudget ?? configured.budgetTokens ?? 10000, 1024) }
|
|
1366
|
+
: {}),
|
|
1367
|
+
};
|
|
1260
1368
|
}
|
|
1261
1369
|
// 🛡️ 全局兜底:任何遗漏监听的异步 'error' 事件(如子进程 spawn 失败)
|
|
1262
1370
|
// 都不应直接秒杀进程。记录日志并优雅退出,保留错误现场便于排查。
|