@wukonglabs/wukong 0.1.1 → 0.1.3
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 +171 -54
- package/dist/index.js.map +1 -1
- package/dist/tui/app.d.ts +15 -1
- package/dist/tui/app.d.ts.map +1 -1
- package/dist/tui/app.js +51 -11
- package/dist/tui/app.js.map +1 -1
- package/dist/tui/hint-bar.js +1 -1
- package/dist/tui/hint-bar.js.map +1 -1
- package/dist/tui/history-entry.d.ts +3 -1
- package/dist/tui/history-entry.d.ts.map +1 -1
- package/dist/tui/history-entry.js +23 -4
- package/dist/tui/history-entry.js.map +1 -1
- package/dist/tui/history-item.d.ts +14 -8
- package/dist/tui/history-item.d.ts.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 +6 -1
- package/dist/tui/response-panel.d.ts.map +1 -1
- package/dist/tui/response-panel.js +10 -1
- package/dist/tui/response-panel.js.map +1 -1
- package/dist/tui/run-ink.d.ts +18 -0
- package/dist/tui/run-ink.d.ts.map +1 -1
- package/dist/tui/run-ink.js +74 -2
- package/dist/tui/run-ink.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 +16 -1
- package/dist/tui/text-input.d.ts.map +1 -1
- package/dist/tui/text-input.js +96 -42
- 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
|
});
|
|
@@ -463,16 +488,18 @@ async function runAgentCommand(cliArgs) {
|
|
|
463
488
|
...(metrics ? {
|
|
464
489
|
metrics: {
|
|
465
490
|
steps: metrics.steps,
|
|
491
|
+
llmRequests: metrics.llmRequests,
|
|
466
492
|
totalTokens: metrics.totalTokens,
|
|
467
493
|
toolCalls: metrics.toolCalls,
|
|
468
494
|
toolFailures: metrics.toolFailures,
|
|
469
495
|
llmRetries: metrics.llmRetries,
|
|
470
496
|
durationMs,
|
|
471
497
|
duration: `${(durationMs / 1000).toFixed(1)}s`,
|
|
472
|
-
...(metrics.cachedPromptTokens ? {
|
|
498
|
+
...(metrics.cachedPromptTokens !== undefined && metrics.cacheInputTokens !== undefined ? {
|
|
473
499
|
cachedPromptTokens: metrics.cachedPromptTokens,
|
|
474
|
-
|
|
475
|
-
|
|
500
|
+
cacheInputTokens: metrics.cacheInputTokens,
|
|
501
|
+
cacheHitRate: metrics.cacheInputTokens > 0
|
|
502
|
+
? Number((metrics.cachedPromptTokens / metrics.cacheInputTokens).toFixed(4))
|
|
476
503
|
: 0,
|
|
477
504
|
} : {}),
|
|
478
505
|
...(metrics.cacheCreationTokens ? { cacheCreationTokens: metrics.cacheCreationTokens } : {}),
|
|
@@ -577,10 +604,12 @@ function extractModifiedFiles(trace) {
|
|
|
577
604
|
}
|
|
578
605
|
return [...files];
|
|
579
606
|
}
|
|
580
|
-
/**
|
|
607
|
+
/** 检测是否存在至少一个可用模型及其带 apiKey 的 Provider。 */
|
|
581
608
|
function hasConfiguredProvider(cfg) {
|
|
582
|
-
|
|
583
|
-
|
|
609
|
+
return Object.values(cfg.llm.models).some((model) => {
|
|
610
|
+
const provider = cfg.llm.providers[model.provider];
|
|
611
|
+
return !!provider?.apiKey?.trim();
|
|
612
|
+
});
|
|
584
613
|
}
|
|
585
614
|
/**
|
|
586
615
|
* 交互式配置向导:在用户未配置任何 Provider 时收集最小可用配置。
|
|
@@ -600,23 +629,27 @@ async function runSetupWizard(logger) {
|
|
|
600
629
|
logger.info(" 3) OpenAI 兼容(自定义 baseUrl,如 DeepSeek / 通义 / 本地 vLLM 等)");
|
|
601
630
|
const typeChoice = (await question("输入序号 [1]: ")).trim() || "1";
|
|
602
631
|
let providerKey;
|
|
632
|
+
let modelAlias;
|
|
603
633
|
let type;
|
|
604
634
|
let defaultModel;
|
|
605
635
|
let baseUrlRequired = false;
|
|
606
636
|
switch (typeChoice) {
|
|
607
637
|
case "2":
|
|
608
638
|
providerKey = "anthropic";
|
|
639
|
+
modelAlias = "anthropic-default";
|
|
609
640
|
type = "anthropic";
|
|
610
641
|
defaultModel = "claude-sonnet-4-20250514";
|
|
611
642
|
break;
|
|
612
643
|
case "3":
|
|
613
|
-
providerKey = "
|
|
644
|
+
providerKey = "custom";
|
|
645
|
+
modelAlias = "custom-default";
|
|
614
646
|
type = "openai-compatible";
|
|
615
647
|
defaultModel = "";
|
|
616
648
|
baseUrlRequired = true;
|
|
617
649
|
break;
|
|
618
650
|
default:
|
|
619
651
|
providerKey = "openai";
|
|
652
|
+
modelAlias = "openai-default";
|
|
620
653
|
type = "openai";
|
|
621
654
|
defaultModel = "gpt-4o";
|
|
622
655
|
break;
|
|
@@ -648,8 +681,8 @@ async function runSetupWizard(logger) {
|
|
|
648
681
|
logger.error("❌ 未填写模型名,配置未完成");
|
|
649
682
|
return null;
|
|
650
683
|
}
|
|
651
|
-
const providerConf = { apiKey, baseUrl, type
|
|
652
|
-
return { providerKey, model, providerConf };
|
|
684
|
+
const providerConf = { apiKey, baseUrl, type };
|
|
685
|
+
return { providerKey, modelAlias, modelId: model, providerConf };
|
|
653
686
|
}
|
|
654
687
|
finally {
|
|
655
688
|
rl.close();
|
|
@@ -669,10 +702,18 @@ async function persistWukongConfig(setup, logger) {
|
|
|
669
702
|
// 文件不存在或无法解析 → 从空对象开始
|
|
670
703
|
}
|
|
671
704
|
existing.agent = existing.agent ?? {};
|
|
672
|
-
existing.agent.provider
|
|
673
|
-
existing.agent.
|
|
674
|
-
|
|
675
|
-
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
|
+
};
|
|
676
717
|
try {
|
|
677
718
|
await fs.mkdir(dir, { recursive: true });
|
|
678
719
|
await fs.writeFile(file, JSON.stringify(existing, null, 2) + "\n", { mode: 0o600 });
|
|
@@ -799,16 +840,21 @@ async function runInteractive(cliArgs) {
|
|
|
799
840
|
}
|
|
800
841
|
await persistWukongConfig(setup, logger);
|
|
801
842
|
// 直接注入内存配置,无需重新加载(避免本地 wukong.json 遮蔽全局配置的边界情况)
|
|
802
|
-
cfg.agent.
|
|
803
|
-
cfg.
|
|
804
|
-
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
|
+
};
|
|
805
849
|
logger.info("");
|
|
806
850
|
if (suppressTuiBootInfo) {
|
|
807
851
|
logger.setLevel('warn');
|
|
808
852
|
}
|
|
809
853
|
}
|
|
854
|
+
const selectedModel = cliArgs.model ?? cfg.agent.defaultModel;
|
|
855
|
+
resolveModelConfig(cfg, selectedModel);
|
|
810
856
|
const resolvedConfirmMode = cliArgs.yes ? 'never' : (cliArgs.confirmMode ?? cfg.tools.confirmMode ?? 'smart');
|
|
811
|
-
const resolvedThinking = resolveThinking(cliArgs, cfg);
|
|
857
|
+
const resolvedThinking = resolveThinking(cliArgs, cfg, selectedModel);
|
|
812
858
|
const sessionManager = new SessionManager(cfg.sessions.dir);
|
|
813
859
|
const workspace = resolveWorkspace();
|
|
814
860
|
// 交互模式不视为 CI(即使被 pipe,交互模式一定不会被 --yes/--json 触发)
|
|
@@ -921,7 +967,7 @@ async function runInteractiveReadline(cliArgs, cfg, logger, resolvedConfirmMode,
|
|
|
921
967
|
}
|
|
922
968
|
};
|
|
923
969
|
const { llm, tools, contextBuilder, agent, resolvedModel } = await initAgent({
|
|
924
|
-
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,
|
|
925
971
|
overrides: { maxSteps: cliArgs.maxSteps, contextWindow: cliArgs.contextWindow },
|
|
926
972
|
onStream, tracerOpts: { enabled: !cliArgs.noTrace },
|
|
927
973
|
shouldAbort, authorizeTool,
|
|
@@ -1046,11 +1092,11 @@ async function runInteractiveTui(cliArgs, cfg, logger, resolvedConfirmMode, reso
|
|
|
1046
1092
|
};
|
|
1047
1093
|
// 读取持久化输入历史(可选,失败不阻塞)
|
|
1048
1094
|
const initialHistory = await loadInputHistory().catch(() => []);
|
|
1049
|
-
//
|
|
1050
|
-
const agentRef = {
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1095
|
+
// 当前运行时只在完整候选 Agent 构造成功后原子替换。
|
|
1096
|
+
const agentRef = { current: null };
|
|
1097
|
+
const streamSinkRef = { current: null };
|
|
1098
|
+
const onStreamProxy = (event) => {
|
|
1099
|
+
streamSinkRef.current?.(event);
|
|
1054
1100
|
};
|
|
1055
1101
|
const handlers = {
|
|
1056
1102
|
eventBus: getDefaultEventBus(),
|
|
@@ -1066,12 +1112,46 @@ async function runInteractiveTui(cliArgs, cfg, logger, resolvedConfirmMode, reso
|
|
|
1066
1112
|
authorizer,
|
|
1067
1113
|
// M1:/compact 手动压缩(复用 index.ts 作用域内的 compactIfNeeded)
|
|
1068
1114
|
compactIfNeeded: async () => {
|
|
1069
|
-
|
|
1115
|
+
const runtime = agentRef.current;
|
|
1116
|
+
if (!runtime)
|
|
1070
1117
|
return;
|
|
1071
|
-
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
|
+
};
|
|
1072
1151
|
},
|
|
1073
1152
|
sendMessage: async (input) => {
|
|
1074
|
-
|
|
1153
|
+
const runtime = agentRef.current;
|
|
1154
|
+
if (!runtime) {
|
|
1075
1155
|
throw new Error('Agent 未初始化');
|
|
1076
1156
|
}
|
|
1077
1157
|
// 持久化输入历史(异步,不阻塞)
|
|
@@ -1088,19 +1168,26 @@ async function runInteractiveTui(cliArgs, cfg, logger, resolvedConfirmMode, reso
|
|
|
1088
1168
|
// Smart Context 失败不影响主流程
|
|
1089
1169
|
}
|
|
1090
1170
|
}
|
|
1091
|
-
const messages =
|
|
1092
|
-
const result = await
|
|
1171
|
+
const messages = runtime.contextBuilder.build(session, attachSmartContext(input, smartContextText));
|
|
1172
|
+
const result = await runtime.agent.runWithMessages(messages);
|
|
1093
1173
|
await session.appendMessages([
|
|
1094
1174
|
{ role: "user", content: input },
|
|
1095
1175
|
...result.newMessages,
|
|
1096
1176
|
]);
|
|
1097
|
-
const allMessages =
|
|
1098
|
-
await compactIfNeeded(session, allMessages,
|
|
1177
|
+
const allMessages = runtime.contextBuilder.build(session, "");
|
|
1178
|
+
await compactIfNeeded(session, allMessages, runtime.llm, cfg.sessions.compaction);
|
|
1099
1179
|
const durationMs = result.metrics?.durationMs ?? 0;
|
|
1100
1180
|
return {
|
|
1101
1181
|
response: result.response,
|
|
1102
|
-
|
|
1182
|
+
requestCount: result.metrics?.llmRequests ?? result.steps,
|
|
1103
1183
|
totalTokens: result.totalTokens,
|
|
1184
|
+
...(result.metrics?.cachedPromptTokens !== undefined &&
|
|
1185
|
+
result.metrics.cacheInputTokens !== undefined
|
|
1186
|
+
? {
|
|
1187
|
+
cachedPromptTokens: result.metrics.cachedPromptTokens,
|
|
1188
|
+
cacheInputTokens: result.metrics.cacheInputTokens,
|
|
1189
|
+
}
|
|
1190
|
+
: {}),
|
|
1104
1191
|
durationMs,
|
|
1105
1192
|
};
|
|
1106
1193
|
},
|
|
@@ -1122,20 +1209,15 @@ async function runInteractiveTui(cliArgs, cfg, logger, resolvedConfirmMode, reso
|
|
|
1122
1209
|
// - 首帧被日志盖在可视区上方(用户看不到 Header/输入框)
|
|
1123
1210
|
// - Static 追加历史项时打在错的位置(Header 被复制多次)
|
|
1124
1211
|
// - 大量空线框(Ink 尝试重绘时高度计算失效)
|
|
1125
|
-
// onStream
|
|
1126
|
-
const
|
|
1127
|
-
|
|
1128
|
-
streamSinkRef.current?.(event);
|
|
1129
|
-
};
|
|
1130
|
-
const { llm, contextBuilder, agent, tools, resolvedModel, skillsLoaded } = await initAgent({
|
|
1131
|
-
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,
|
|
1132
1215
|
overrides: { maxSteps: cliArgs.maxSteps, contextWindow: cliArgs.contextWindow },
|
|
1133
1216
|
onStream: onStreamProxy, tracerOpts: { enabled: !cliArgs.noTrace },
|
|
1134
1217
|
shouldAbort, authorizeTool,
|
|
1135
1218
|
});
|
|
1136
|
-
agentRef.
|
|
1137
|
-
|
|
1138
|
-
agentRef.agent = agent;
|
|
1219
|
+
agentRef.current = initialRuntime;
|
|
1220
|
+
const { tools, resolvedModel, skillsLoaded } = initialRuntime;
|
|
1139
1221
|
// ── init 完成后再启动 Ink ──
|
|
1140
1222
|
// 此时终端上可能已经有几十行 skills 加载日志,Ink 启动后会:
|
|
1141
1223
|
// - 立即切换到 alt-screen(取决于终端;不切换时也会把 UI 渲染在当前光标处)
|
|
@@ -1236,18 +1318,53 @@ function applyPermissionOverrides(cfg, cliArgs, logger) {
|
|
|
1236
1318
|
logger.info(`🔒 强制拒绝工具: ${cliArgs.denyTools.join(', ')}`);
|
|
1237
1319
|
}
|
|
1238
1320
|
}
|
|
1239
|
-
/**
|
|
1240
|
-
function resolveThinking(cliArgs, cfg) {
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
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;
|
|
1245
1330
|
}
|
|
1246
|
-
|
|
1247
|
-
if (
|
|
1248
|
-
return
|
|
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
|
+
};
|
|
1249
1344
|
}
|
|
1250
|
-
|
|
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
|
+
};
|
|
1357
|
+
}
|
|
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
|
+
};
|
|
1251
1368
|
}
|
|
1252
1369
|
// 🛡️ 全局兜底:任何遗漏监听的异步 'error' 事件(如子进程 spawn 失败)
|
|
1253
1370
|
// 都不应直接秒杀进程。记录日志并优雅退出,保留错误现场便于排查。
|