@wukonglabs/wukong 0.0.9 → 0.0.11
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/dist/cli/exit-codes.js +12 -16
- package/dist/cli/exit-codes.js.map +1 -1
- package/dist/cli/help.js +4 -8
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/session-commands.js +1 -4
- package/dist/cli/session-commands.js.map +1 -1
- package/dist/cli/summary.js +2 -6
- package/dist/cli/summary.js.map +1 -1
- package/dist/cli/workspace-commands.js +1 -4
- package/dist/cli/workspace-commands.js.map +1 -1
- package/dist/index.js +373 -121
- package/dist/index.js.map +1 -1
- package/dist/sigint-handler.d.ts +62 -0
- package/dist/sigint-handler.d.ts.map +1 -0
- package/dist/sigint-handler.js +62 -0
- package/dist/sigint-handler.js.map +1 -0
- package/dist/tui/app.d.ts +57 -0
- package/dist/tui/app.d.ts.map +1 -0
- package/dist/tui/app.js +268 -0
- package/dist/tui/app.js.map +1 -0
- package/dist/tui/header.d.ts +24 -0
- package/dist/tui/header.d.ts.map +1 -0
- package/dist/tui/header.js +26 -0
- package/dist/tui/header.js.map +1 -0
- package/dist/tui/permission-card.d.ts +27 -0
- package/dist/tui/permission-card.d.ts.map +1 -0
- package/dist/tui/permission-card.js +75 -0
- package/dist/tui/permission-card.js.map +1 -0
- package/dist/tui/plan-panel.d.ts +31 -0
- package/dist/tui/plan-panel.d.ts.map +1 -0
- package/dist/tui/plan-panel.js +30 -0
- package/dist/tui/plan-panel.js.map +1 -0
- package/dist/tui/response-panel.d.ts +49 -0
- package/dist/tui/response-panel.d.ts.map +1 -0
- package/dist/tui/response-panel.js +14 -0
- package/dist/tui/response-panel.js.map +1 -0
- package/dist/tui/run-ink.d.ts +22 -0
- package/dist/tui/run-ink.d.ts.map +1 -0
- package/dist/tui/run-ink.js +21 -0
- package/dist/tui/run-ink.js.map +1 -0
- package/dist/tui/should-use-tui.d.ts +25 -0
- package/dist/tui/should-use-tui.d.ts.map +1 -0
- package/dist/tui/should-use-tui.js +29 -0
- package/dist/tui/should-use-tui.js.map +1 -0
- package/dist/tui/text-input.d.ts +61 -0
- package/dist/tui/text-input.d.ts.map +1 -0
- package/dist/tui/text-input.js +318 -0
- package/dist/tui/text-input.js.map +1 -0
- package/dist/tui/thinking-panel.d.ts +32 -0
- package/dist/tui/thinking-panel.d.ts.map +1 -0
- package/dist/tui/thinking-panel.js +14 -0
- package/dist/tui/thinking-panel.js.map +1 -0
- package/dist/tui/tool-cards.d.ts +30 -0
- package/dist/tui/tool-cards.d.ts.map +1 -0
- package/dist/tui/tool-cards.js +37 -0
- package/dist/tui/tool-cards.js.map +1 -0
- package/dist/tui/use-input-history.d.ts +40 -0
- package/dist/tui/use-input-history.d.ts.map +1 -0
- package/dist/tui/use-input-history.js +123 -0
- package/dist/tui/use-input-history.js.map +1 -0
- package/dist/tui/use-plan-state.d.ts +4 -0
- package/dist/tui/use-plan-state.d.ts.map +1 -0
- package/dist/tui/use-plan-state.js +77 -0
- package/dist/tui/use-plan-state.js.map +1 -0
- package/package.json +9 -4
package/dist/index.js
CHANGED
|
@@ -1,52 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
-
}
|
|
9
|
-
Object.defineProperty(o, k2, desc);
|
|
10
|
-
}) : (function(o, m, k, k2) {
|
|
11
|
-
if (k2 === undefined) k2 = k;
|
|
12
|
-
o[k2] = m[k];
|
|
13
|
-
}));
|
|
14
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
-
}) : function(o, v) {
|
|
17
|
-
o["default"] = v;
|
|
18
|
-
});
|
|
19
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
-
var ownKeys = function(o) {
|
|
21
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
-
var ar = [];
|
|
23
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
-
return ar;
|
|
25
|
-
};
|
|
26
|
-
return ownKeys(o);
|
|
27
|
-
};
|
|
28
|
-
return function (mod) {
|
|
29
|
-
if (mod && mod.__esModule) return mod;
|
|
30
|
-
var result = {};
|
|
31
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
-
__setModuleDefault(result, mod);
|
|
33
|
-
return result;
|
|
34
|
-
};
|
|
35
|
-
})();
|
|
36
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
-
const readline = __importStar(require("node:readline"));
|
|
38
|
-
const fs = __importStar(require("node:fs/promises"));
|
|
39
|
-
const path = __importStar(require("node:path"));
|
|
40
|
-
const os = __importStar(require("node:os"));
|
|
2
|
+
import * as readline from "node:readline";
|
|
3
|
+
import * as fs from "node:fs/promises";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import * as os from "node:os";
|
|
41
6
|
// ── 核心能力全部来自独立发布的 SDK 包 ──
|
|
42
7
|
// CLI 只做参数解析、会话编排与输出渲染,组装逻辑(createAgent)在 SDK 内。
|
|
43
|
-
|
|
8
|
+
import { createAgent, SessionManager, collectSmartContext, AutoVerifier, DEFAULT_VERIFY_CONFIG, compactIfNeeded, loadConfig, Logger, setGlobalLogger, resolveWorkspace, getDefaultEventBus, ToolAuthorizer, } from "@wukonglabs/wukong-agent-sdk";
|
|
44
9
|
// ── CLI 本地模块(不属于 SDK 对外能力) ──
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
10
|
+
import { EXIT_CODES, resolveExitCode } from "./cli/exit-codes.js";
|
|
11
|
+
import { HELP_TEXT, formatVersion } from "./cli/help.js";
|
|
12
|
+
import { buildJsonSummary } from "./cli/summary.js";
|
|
13
|
+
import { runSessionCommand } from "./cli/session-commands.js";
|
|
14
|
+
import { runWorkspaceCommand } from "./cli/workspace-commands.js";
|
|
15
|
+
import { shouldUseTuiFromProcess } from "./tui/should-use-tui.js";
|
|
16
|
+
import { runInk } from "./tui/run-ink.js";
|
|
17
|
+
import { createAbortController } from "./sigint-handler.js";
|
|
50
18
|
function parseArgs(argv) {
|
|
51
19
|
const args = argv.slice(2);
|
|
52
20
|
const result = {
|
|
@@ -264,23 +232,25 @@ function parseArgs(argv) {
|
|
|
264
232
|
}
|
|
265
233
|
return result;
|
|
266
234
|
}
|
|
267
|
-
async function initAgent(
|
|
268
|
-
return
|
|
269
|
-
config: cfg,
|
|
270
|
-
logger,
|
|
271
|
-
confirmMode,
|
|
272
|
-
thinking,
|
|
273
|
-
ciMode,
|
|
274
|
-
planMode,
|
|
275
|
-
overrides,
|
|
276
|
-
onStream,
|
|
277
|
-
tracer: tracerOpts,
|
|
235
|
+
async function initAgent(opts) {
|
|
236
|
+
return createAgent({
|
|
237
|
+
config: opts.cfg,
|
|
238
|
+
logger: opts.logger,
|
|
239
|
+
confirmMode: opts.confirmMode,
|
|
240
|
+
thinking: opts.thinking,
|
|
241
|
+
ciMode: opts.ciMode ?? false,
|
|
242
|
+
planMode: opts.planMode ?? 'auto',
|
|
243
|
+
overrides: opts.overrides,
|
|
244
|
+
onStream: opts.onStream,
|
|
245
|
+
tracer: opts.tracerOpts,
|
|
246
|
+
shouldAbort: opts.shouldAbort,
|
|
247
|
+
authorizeTool: opts.authorizeTool,
|
|
278
248
|
});
|
|
279
249
|
}
|
|
280
250
|
async function runAgentCommand(cliArgs) {
|
|
281
|
-
const logger = new
|
|
282
|
-
|
|
283
|
-
const cfg = await
|
|
251
|
+
const logger = new Logger({ jsonMode: cliArgs.json, level: cliArgs.verbose ? 'debug' : 'info' });
|
|
252
|
+
setGlobalLogger(logger);
|
|
253
|
+
const cfg = await loadConfig();
|
|
284
254
|
applyPermissionOverrides(cfg, cliArgs, logger);
|
|
285
255
|
const resolvedConfirmMode = cliArgs.yes ? 'never' : (cliArgs.confirmMode ?? cfg.tools.confirmMode ?? 'smart');
|
|
286
256
|
const resolvedThinking = resolveThinking(cliArgs, cfg);
|
|
@@ -291,7 +261,7 @@ async function runAgentCommand(cliArgs) {
|
|
|
291
261
|
const sessionStrategy = resolveCliSessionStrategy(cliArgs, isCiMode, logger);
|
|
292
262
|
if (sessionStrategy === null) {
|
|
293
263
|
// 参数冲突,已经打过日志,直接退出
|
|
294
|
-
process.exit(
|
|
264
|
+
process.exit(EXIT_CODES.CONFIG_ERROR);
|
|
295
265
|
}
|
|
296
266
|
const isEphemeral = cliArgs.ephemeral || isCiMode;
|
|
297
267
|
// ── 流式输出回调 ──
|
|
@@ -328,12 +298,13 @@ async function runAgentCommand(cliArgs) {
|
|
|
328
298
|
break;
|
|
329
299
|
}
|
|
330
300
|
};
|
|
331
|
-
const { llm, contextBuilder, agent, effectiveMaxSteps, resolvedModel } = await initAgent(
|
|
332
|
-
|
|
333
|
-
contextWindow: cliArgs.contextWindow,
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
const
|
|
301
|
+
const { llm, contextBuilder, agent, effectiveMaxSteps, resolvedModel } = await initAgent({
|
|
302
|
+
cfg, logger, confirmMode: resolvedConfirmMode, thinking: resolvedThinking, ciMode: isCiMode, planMode: cliArgs.planMode,
|
|
303
|
+
overrides: { maxSteps: cliArgs.maxSteps, contextWindow: cliArgs.contextWindow },
|
|
304
|
+
onStream, tracerOpts: { enabled: !cliArgs.noTrace },
|
|
305
|
+
});
|
|
306
|
+
const sessionManager = new SessionManager(cfg.sessions.dir);
|
|
307
|
+
const workspace = resolveWorkspace();
|
|
337
308
|
const resolved = await sessionManager.resolveSession({ workspace, strategy: sessionStrategy });
|
|
338
309
|
const session = resolved.session;
|
|
339
310
|
const sessionUuidShort = resolved.session.ref?.sessionUuid.slice(0, 8) ?? "";
|
|
@@ -344,7 +315,7 @@ async function runAgentCommand(cliArgs) {
|
|
|
344
315
|
version: 2,
|
|
345
316
|
runId: "",
|
|
346
317
|
status: "error",
|
|
347
|
-
exitCode:
|
|
318
|
+
exitCode: EXIT_CODES.CONFIG_ERROR,
|
|
348
319
|
summary: "缺少 --message 参数",
|
|
349
320
|
result: { payloads: [] },
|
|
350
321
|
};
|
|
@@ -353,7 +324,7 @@ async function runAgentCommand(cliArgs) {
|
|
|
353
324
|
else {
|
|
354
325
|
logger.error("❌ 缺少 --message 参数。用法: wukong agent --message \"你的消息\"");
|
|
355
326
|
}
|
|
356
|
-
process.exit(
|
|
327
|
+
process.exit(EXIT_CODES.CONFIG_ERROR);
|
|
357
328
|
}
|
|
358
329
|
logger.info(`📡 LLM: ${resolvedModel}`);
|
|
359
330
|
logger.info(`📂 会话: ${sessionUuidShort} @ ${workspace.root}` +
|
|
@@ -379,7 +350,7 @@ async function runAgentCommand(cliArgs) {
|
|
|
379
350
|
let smartContextText;
|
|
380
351
|
if (!cliArgs.noSmartContext && userMessage) {
|
|
381
352
|
try {
|
|
382
|
-
const sc = await
|
|
353
|
+
const sc = await collectSmartContext(userMessage, process.cwd());
|
|
383
354
|
if (sc.contextText && sc.files.length > 0) {
|
|
384
355
|
smartContextText = sc.contextText;
|
|
385
356
|
logger.info(`📂 Smart Context: 注入 ${sc.files.length} 个相关文件摘要 (~${sc.estimatedTokens} token)`);
|
|
@@ -396,14 +367,14 @@ async function runAgentCommand(cliArgs) {
|
|
|
396
367
|
...result.newMessages,
|
|
397
368
|
]);
|
|
398
369
|
const allMessages = contextBuilder.build(session, "");
|
|
399
|
-
await
|
|
370
|
+
await compactIfNeeded(session, allMessages, llm, cfg.sessions.compaction);
|
|
400
371
|
// ── 自动验证 ──
|
|
401
372
|
let verification;
|
|
402
373
|
const agentVerifyCfg = cfg.agent.verify;
|
|
403
374
|
const shouldVerify = !cliArgs.noVerify && (cliArgs.verify || agentVerifyCfg?.enabled);
|
|
404
375
|
if (shouldVerify && !result.aborted) {
|
|
405
376
|
const verifyConfig = {
|
|
406
|
-
...
|
|
377
|
+
...DEFAULT_VERIFY_CONFIG,
|
|
407
378
|
...(agentVerifyCfg ?? {}),
|
|
408
379
|
enabled: true,
|
|
409
380
|
// CLI 参数覆盖:验证超时
|
|
@@ -411,7 +382,7 @@ async function runAgentCommand(cliArgs) {
|
|
|
411
382
|
// CI 模式下默认包含 build 命令
|
|
412
383
|
includeBuild: isCiMode,
|
|
413
384
|
};
|
|
414
|
-
const verifier = new
|
|
385
|
+
const verifier = new AutoVerifier(verifyConfig, logger);
|
|
415
386
|
verification = await verifier.verifyAndFix(process.cwd(), async (fixPrompt) => {
|
|
416
387
|
// 让 Agent 执行修复
|
|
417
388
|
const fixMessages = contextBuilder.build(session, fixPrompt);
|
|
@@ -424,7 +395,7 @@ async function runAgentCommand(cliArgs) {
|
|
|
424
395
|
}, cliArgs.verifyCommand ?? undefined);
|
|
425
396
|
}
|
|
426
397
|
// ── 计算退出码 ──
|
|
427
|
-
const exitCode =
|
|
398
|
+
const exitCode = resolveExitCode({
|
|
428
399
|
aborted: result.aborted,
|
|
429
400
|
steps: result.steps,
|
|
430
401
|
maxSteps: effectiveMaxSteps,
|
|
@@ -447,8 +418,8 @@ async function runAgentCommand(cliArgs) {
|
|
|
447
418
|
const filesModified = extractModifiedFiles(result.trace);
|
|
448
419
|
// 推断 status
|
|
449
420
|
const status = result.aborted ? 'error'
|
|
450
|
-
: exitCode ===
|
|
451
|
-
: exitCode ===
|
|
421
|
+
: exitCode === EXIT_CODES.TASK_INCOMPLETE ? 'incomplete'
|
|
422
|
+
: exitCode === EXIT_CODES.VERIFY_FAILED ? 'verify_failed'
|
|
452
423
|
: 'success';
|
|
453
424
|
const metrics = result.metrics;
|
|
454
425
|
const durationMs = metrics?.durationMs ?? 0;
|
|
@@ -457,7 +428,7 @@ async function runAgentCommand(cliArgs) {
|
|
|
457
428
|
runId: `run_${Date.now()}`,
|
|
458
429
|
status,
|
|
459
430
|
exitCode,
|
|
460
|
-
summary:
|
|
431
|
+
summary: buildJsonSummary(result.response, {
|
|
461
432
|
steps: result.steps,
|
|
462
433
|
totalTokens: result.totalTokens.prompt + result.totalTokens.completion,
|
|
463
434
|
}),
|
|
@@ -565,7 +536,7 @@ async function runAgentCommand(cliArgs) {
|
|
|
565
536
|
version: 2,
|
|
566
537
|
runId: `run_${Date.now()}`,
|
|
567
538
|
status: "error",
|
|
568
|
-
exitCode:
|
|
539
|
+
exitCode: EXIT_CODES.LLM_ERROR,
|
|
569
540
|
summary: error instanceof Error ? error.message : String(error),
|
|
570
541
|
result: { payloads: [] },
|
|
571
542
|
};
|
|
@@ -574,7 +545,7 @@ async function runAgentCommand(cliArgs) {
|
|
|
574
545
|
else {
|
|
575
546
|
logger.error("❌ Agent 执行出错:", error);
|
|
576
547
|
}
|
|
577
|
-
process.exit(
|
|
548
|
+
process.exit(EXIT_CODES.LLM_ERROR);
|
|
578
549
|
}
|
|
579
550
|
}
|
|
580
551
|
/** 从 trace 中提取被修改的文件路径 */
|
|
@@ -702,13 +673,69 @@ async function persistWukongConfig(setup, logger) {
|
|
|
702
673
|
logger.warn(`⚠️ 配置写入失败(本次运行仍可继续): ${String(err)}`);
|
|
703
674
|
}
|
|
704
675
|
}
|
|
676
|
+
/** 输入历史持久化文件路径(~/.wukong/history.jsonl,每行一条) */
|
|
677
|
+
function inputHistoryPath() {
|
|
678
|
+
return path.join(os.homedir(), '.wukong', 'history.jsonl');
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* 读取持久化的输入历史(跨 session 共享)。
|
|
682
|
+
*
|
|
683
|
+
* 文件格式:JSONL,每行一个 JSON 字符串(带引号),便于 append。
|
|
684
|
+
* 失败(文件不存在/解析错误)返回空数组,不阻塞启动。
|
|
685
|
+
*/
|
|
686
|
+
async function loadInputHistory() {
|
|
687
|
+
const file = inputHistoryPath();
|
|
688
|
+
let content;
|
|
689
|
+
try {
|
|
690
|
+
content = await fs.readFile(file, 'utf8');
|
|
691
|
+
}
|
|
692
|
+
catch {
|
|
693
|
+
return [];
|
|
694
|
+
}
|
|
695
|
+
const entries = [];
|
|
696
|
+
for (const line of content.split('\n')) {
|
|
697
|
+
const trimmed = line.trim();
|
|
698
|
+
if (!trimmed)
|
|
699
|
+
continue;
|
|
700
|
+
try {
|
|
701
|
+
const parsed = JSON.parse(trimmed);
|
|
702
|
+
if (typeof parsed === 'string' && parsed.length > 0) {
|
|
703
|
+
entries.push(parsed);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
catch {
|
|
707
|
+
// 损坏行跳过
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
// 最多返回最近 1000 条
|
|
711
|
+
return entries.slice(-1000);
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* 追加一条输入历史到持久化文件。
|
|
715
|
+
* 失败静默(历史持久化不是关键路径)。
|
|
716
|
+
*/
|
|
717
|
+
async function appendInputHistory(entry) {
|
|
718
|
+
const trimmed = entry.trim();
|
|
719
|
+
if (!trimmed)
|
|
720
|
+
return;
|
|
721
|
+
const file = inputHistoryPath();
|
|
722
|
+
try {
|
|
723
|
+
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
724
|
+
await fs.appendFile(file, JSON.stringify(trimmed) + '\n', { mode: 0o600 });
|
|
725
|
+
}
|
|
726
|
+
catch {
|
|
727
|
+
// 静默失败
|
|
728
|
+
}
|
|
729
|
+
}
|
|
705
730
|
async function runInteractive(cliArgs) {
|
|
706
|
-
|
|
707
|
-
|
|
731
|
+
// Logger 全程走 stdout(TUI 路径下,Ink 启动前已把所有日志打完;Ink 启动后
|
|
732
|
+
// logger 不应该再有输出。见 runInteractiveTui 的启动顺序)。
|
|
733
|
+
const logger = new Logger({ jsonMode: false });
|
|
734
|
+
setGlobalLogger(logger);
|
|
708
735
|
logger.info("🐒 Wukong (悟空) v0.1.0");
|
|
709
736
|
logger.info("========================");
|
|
710
737
|
logger.info("");
|
|
711
|
-
const cfg = await
|
|
738
|
+
const cfg = await loadConfig();
|
|
712
739
|
applyPermissionOverrides(cfg, cliArgs, logger);
|
|
713
740
|
// ── 未配置模型时:TTY 下进入交互式向导,非 TTY 下打印手动配置指引 ──
|
|
714
741
|
if (!hasConfiguredProvider(cfg)) {
|
|
@@ -719,12 +746,12 @@ async function runInteractive(cliArgs) {
|
|
|
719
746
|
logger.error(" export LLM_BASE_URL=https://api.deepseek.com/v1 # OpenAI 兼容端点");
|
|
720
747
|
logger.error(" export LLM_MODEL=deepseek-chat");
|
|
721
748
|
logger.error(" 或编辑 ~/.wukong/wukong.json(详见 README 的「配置」章节)。");
|
|
722
|
-
process.exit(
|
|
749
|
+
process.exit(EXIT_CODES.CONFIG_ERROR);
|
|
723
750
|
}
|
|
724
751
|
const setup = await runSetupWizard(logger);
|
|
725
752
|
if (!setup) {
|
|
726
753
|
logger.error("❌ 配置未完成,无法启动。可参考 README 手动配置后重试。");
|
|
727
|
-
process.exit(
|
|
754
|
+
process.exit(EXIT_CODES.CONFIG_ERROR);
|
|
728
755
|
}
|
|
729
756
|
await persistWukongConfig(setup, logger);
|
|
730
757
|
// 直接注入内存配置,无需重新加载(避免本地 wukong.json 遮蔽全局配置的边界情况)
|
|
@@ -735,7 +762,90 @@ async function runInteractive(cliArgs) {
|
|
|
735
762
|
}
|
|
736
763
|
const resolvedConfirmMode = cliArgs.yes ? 'never' : (cliArgs.confirmMode ?? cfg.tools.confirmMode ?? 'smart');
|
|
737
764
|
const resolvedThinking = resolveThinking(cliArgs, cfg);
|
|
738
|
-
|
|
765
|
+
const sessionManager = new SessionManager(cfg.sessions.dir);
|
|
766
|
+
const workspace = resolveWorkspace();
|
|
767
|
+
// 交互模式不视为 CI(即使被 pipe,交互模式一定不会被 --yes/--json 触发)
|
|
768
|
+
const interactiveStrategy = resolveCliSessionStrategy(cliArgs, /* isCiMode */ false, logger);
|
|
769
|
+
if (interactiveStrategy === null) {
|
|
770
|
+
process.exit(EXIT_CODES.CONFIG_ERROR);
|
|
771
|
+
}
|
|
772
|
+
const resolvedInteractive = await sessionManager.resolveSession({
|
|
773
|
+
workspace,
|
|
774
|
+
strategy: interactiveStrategy,
|
|
775
|
+
});
|
|
776
|
+
const session = resolvedInteractive.session;
|
|
777
|
+
const sessionUuidShort = resolvedInteractive.session.ref?.sessionUuid.slice(0, 8) ?? "";
|
|
778
|
+
// ── 判定是否启用 TUI(§3.1 M1 降级矩阵) ──
|
|
779
|
+
const useTui = shouldUseTuiFromProcess();
|
|
780
|
+
if (!useTui) {
|
|
781
|
+
// 降级到 readline(readline 路径装 process SIGINT handler)
|
|
782
|
+
return runInteractiveReadline(cliArgs, cfg, logger, resolvedConfirmMode, resolvedThinking, session, workspace, sessionUuidShort, resolvedInteractive.created);
|
|
783
|
+
}
|
|
784
|
+
// ── TUI 路径(Ink raw mode 拦截 Ctrl+C,不装 process handler,由 TextInput onAbort 触发) ──
|
|
785
|
+
return runInteractiveTui(cliArgs, cfg, logger, resolvedConfirmMode, resolvedThinking, session, workspace, sessionUuidShort);
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* readline 降级路径(原 runInteractive 的实现,保持完全不变)。
|
|
789
|
+
*
|
|
790
|
+
* 触发条件(见 shouldUseTui):
|
|
791
|
+
* - WUKONG_TUI=off
|
|
792
|
+
* - CI=true
|
|
793
|
+
* - stdin/stdout 非 TTY
|
|
794
|
+
* - TERM=dumb
|
|
795
|
+
*/
|
|
796
|
+
async function runInteractiveReadline(cliArgs, cfg, logger, resolvedConfirmMode, resolvedThinking, session, workspace, sessionUuidShort, sessionCreated) {
|
|
797
|
+
// §3.3 M3:graceful shutdown(readline 路径装 process SIGINT handler)
|
|
798
|
+
const abortController = createAbortController({
|
|
799
|
+
onFirstAbort: () => {
|
|
800
|
+
process.stderr.write('\n\n⚠️ 收到中断信号,正在优雅退出(等当前步骤完成)...\n');
|
|
801
|
+
process.stderr.write(' 再按一次 Ctrl+C 立即退出(session 可能不完整)\n');
|
|
802
|
+
},
|
|
803
|
+
onSecondAbort: () => {
|
|
804
|
+
process.stderr.write('\n\n⏏️ 立即退出\n');
|
|
805
|
+
},
|
|
806
|
+
installProcessHandler: true,
|
|
807
|
+
});
|
|
808
|
+
const shouldAbort = abortController.shouldAbort;
|
|
809
|
+
// §3.6 M1:工具授权器(readline 路径用简单 question UI)
|
|
810
|
+
const authorizer = new ToolAuthorizer({
|
|
811
|
+
workspaceRoot: workspace.root,
|
|
812
|
+
ciMode: false,
|
|
813
|
+
uiCallback: async (req) => {
|
|
814
|
+
return new Promise((resolve) => {
|
|
815
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
|
|
816
|
+
process.stderr.write(`\n🔒 授权请求: ${req.toolName} (step ${req.step})\n`);
|
|
817
|
+
process.stderr.write(` 参数: ${req.argumentsJson.slice(0, 200)}\n`);
|
|
818
|
+
rl.question('[y]仅本次 [s]session [l]本项目 [g]全局 [n]拒绝 [x]永久拒绝: ', (ans) => {
|
|
819
|
+
rl.close();
|
|
820
|
+
const c = ans.trim().toLowerCase();
|
|
821
|
+
switch (c) {
|
|
822
|
+
case 's':
|
|
823
|
+
resolve({ approved: true, decision: 'session' });
|
|
824
|
+
break;
|
|
825
|
+
case 'l':
|
|
826
|
+
resolve({ approved: true, decision: 'always-local' });
|
|
827
|
+
break;
|
|
828
|
+
case 'g':
|
|
829
|
+
resolve({ approved: true, decision: 'always-global' });
|
|
830
|
+
break;
|
|
831
|
+
case 'n':
|
|
832
|
+
resolve({ approved: false, decision: 'no' });
|
|
833
|
+
break;
|
|
834
|
+
case 'x':
|
|
835
|
+
resolve({ approved: false, decision: 'no-always' });
|
|
836
|
+
break;
|
|
837
|
+
default:
|
|
838
|
+
resolve({ approved: true, decision: 'once' });
|
|
839
|
+
break;
|
|
840
|
+
}
|
|
841
|
+
});
|
|
842
|
+
});
|
|
843
|
+
},
|
|
844
|
+
});
|
|
845
|
+
const authorizeTool = async (req) => {
|
|
846
|
+
return authorizer.requestAuthorization(req);
|
|
847
|
+
};
|
|
848
|
+
// ── 流式输出回调(交互模式 readline 降级) ──
|
|
739
849
|
let interactiveStreamStarted = false;
|
|
740
850
|
const onStream = (event) => {
|
|
741
851
|
switch (event.type) {
|
|
@@ -760,36 +870,24 @@ async function runInteractive(cliArgs) {
|
|
|
760
870
|
break;
|
|
761
871
|
}
|
|
762
872
|
};
|
|
763
|
-
const { llm, tools, contextBuilder, agent, resolvedModel } = await initAgent(
|
|
764
|
-
|
|
765
|
-
contextWindow: cliArgs.contextWindow,
|
|
766
|
-
|
|
873
|
+
const { llm, tools, contextBuilder, agent, resolvedModel } = await initAgent({
|
|
874
|
+
cfg, logger, confirmMode: resolvedConfirmMode, thinking: resolvedThinking, planMode: cliArgs.planMode,
|
|
875
|
+
overrides: { maxSteps: cliArgs.maxSteps, contextWindow: cliArgs.contextWindow },
|
|
876
|
+
onStream, tracerOpts: { enabled: !cliArgs.noTrace },
|
|
877
|
+
shouldAbort, authorizeTool,
|
|
878
|
+
});
|
|
767
879
|
logger.info(`📡 LLM: ${resolvedModel}`);
|
|
768
880
|
logger.info(`🔧 已注册 ${tools.size} 个工具: ${tools.listNames().join(", ")}`);
|
|
769
|
-
const sessionManager = new wukong_agent_sdk_1.SessionManager(cfg.sessions.dir);
|
|
770
|
-
const workspace = (0, wukong_agent_sdk_1.resolveWorkspace)();
|
|
771
|
-
// 交互模式不视为 CI(即使被 pipe,交互模式一定不会被 --yes/--json 触发)
|
|
772
|
-
const interactiveStrategy = resolveCliSessionStrategy(cliArgs, /* isCiMode */ false, logger);
|
|
773
|
-
if (interactiveStrategy === null) {
|
|
774
|
-
process.exit(exit_codes_js_1.EXIT_CODES.CONFIG_ERROR);
|
|
775
|
-
}
|
|
776
|
-
const resolvedInteractive = await sessionManager.resolveSession({
|
|
777
|
-
workspace,
|
|
778
|
-
strategy: interactiveStrategy,
|
|
779
|
-
});
|
|
780
|
-
const session = resolvedInteractive.session;
|
|
781
|
-
const sessionUuidShort = resolvedInteractive.session.ref?.sessionUuid.slice(0, 8) ?? "";
|
|
782
881
|
logger.info(`📂 会话存储: ${cfg.sessions.dir}`);
|
|
783
882
|
logger.info(`📂 会话: ${sessionUuidShort} @ ${workspace.root}` +
|
|
784
|
-
` (${session.recordCount} 条记录${
|
|
883
|
+
` (${session.recordCount} 条记录${sessionCreated ? ", 新建" : ""})`);
|
|
785
884
|
logger.info(`📜 历史记录: ${session.recordCount} 条`);
|
|
786
885
|
logger.info("");
|
|
787
886
|
async function sendMessage(input) {
|
|
788
|
-
// ── Smart Context 收集(交互模式首条消息) ──
|
|
789
887
|
let smartContextText;
|
|
790
888
|
if (!cliArgs.noSmartContext && session.recordCount === 0) {
|
|
791
889
|
try {
|
|
792
|
-
const sc = await
|
|
890
|
+
const sc = await collectSmartContext(input, process.cwd());
|
|
793
891
|
if (sc.contextText && sc.files.length > 0) {
|
|
794
892
|
smartContextText = sc.contextText;
|
|
795
893
|
logger.info(`📂 Smart Context: 注入 ${sc.files.length} 个相关文件摘要 (~${sc.estimatedTokens} token)`);
|
|
@@ -800,9 +898,8 @@ async function runInteractive(cliArgs) {
|
|
|
800
898
|
}
|
|
801
899
|
}
|
|
802
900
|
const messages = contextBuilder.build(session, input, undefined, smartContextText);
|
|
803
|
-
interactiveStreamStarted = false;
|
|
901
|
+
interactiveStreamStarted = false;
|
|
804
902
|
const result = await agent.runWithMessages(messages);
|
|
805
|
-
// 如果已经流式输出了,不需要再重复打印
|
|
806
903
|
if (!interactiveStreamStarted) {
|
|
807
904
|
logger.info(`\nWukong: ${result.response}`);
|
|
808
905
|
}
|
|
@@ -812,8 +909,7 @@ async function runInteractive(cliArgs) {
|
|
|
812
909
|
...result.newMessages,
|
|
813
910
|
]);
|
|
814
911
|
const allMessages = contextBuilder.build(session, "");
|
|
815
|
-
await
|
|
816
|
-
return result;
|
|
912
|
+
await compactIfNeeded(session, allMessages, llm, cfg.sessions.compaction);
|
|
817
913
|
}
|
|
818
914
|
if (cliArgs.test) {
|
|
819
915
|
logger.info("🧪 测试模式:发送一条测试消息...");
|
|
@@ -844,6 +940,7 @@ async function runInteractive(cliArgs) {
|
|
|
844
940
|
if (trimmed.toLowerCase() === "exit") {
|
|
845
941
|
logger.info("👋 再见!");
|
|
846
942
|
rl.close();
|
|
943
|
+
abortController.uninstall();
|
|
847
944
|
return;
|
|
848
945
|
}
|
|
849
946
|
if (trimmed.toLowerCase() === "clear") {
|
|
@@ -863,6 +960,155 @@ async function runInteractive(cliArgs) {
|
|
|
863
960
|
};
|
|
864
961
|
ask();
|
|
865
962
|
}
|
|
963
|
+
/**
|
|
964
|
+
* TUI 路径(Ink 渲染)。
|
|
965
|
+
*
|
|
966
|
+
* 与 readline 路径的差异:
|
|
967
|
+
* - onStream 通过 streamSinkRef 桥接给 App,由 App 渲染流式文本(不走 stdout)
|
|
968
|
+
* - sendMessage 返回 CompletedRun,供 App 展示统计信息
|
|
969
|
+
* - 输入用 Ink TextInput(多行 + 历史 + Ctrl+R)
|
|
970
|
+
*
|
|
971
|
+
* M1 简化:model 用 cfg.agent.defaultModel 作为初始显示值(initAgent 内部的
|
|
972
|
+
* resolvedModel 通常与之一致;即便不一致也不影响功能,只影响 Header 显示)。
|
|
973
|
+
* M2 引入 step counter 时会顺便修这个 —— 届时通过 EventBus 的 run_start 事件
|
|
974
|
+
* 携带真实 model,App 订阅后更新即可。
|
|
975
|
+
*/
|
|
976
|
+
async function runInteractiveTui(cliArgs, cfg, logger, resolvedConfirmMode, resolvedThinking, session, workspace, sessionUuidShort) {
|
|
977
|
+
// §3.3 M3:graceful shutdown(TUI 路径不装 process handler,Ink raw mode 拦截 Ctrl+C)
|
|
978
|
+
// TextInput 的 onAbort → requestAbort,实现两次 Ctrl+C 语义
|
|
979
|
+
const abortController = createAbortController({
|
|
980
|
+
onFirstAbort: () => {
|
|
981
|
+
// 提示通过 App 状态显示(不直接写 stderr,避免打乱 Ink 渲染)
|
|
982
|
+
},
|
|
983
|
+
onSecondAbort: () => {
|
|
984
|
+
// 二次直接退出,Ink unmount 由 process.exit 触发
|
|
985
|
+
},
|
|
986
|
+
installProcessHandler: false,
|
|
987
|
+
});
|
|
988
|
+
const shouldAbort = abortController.shouldAbort;
|
|
989
|
+
// §3.6 M1:工具授权器(交互模式;CI 模式 runAgentCommand 不创建)
|
|
990
|
+
const authorizer = new ToolAuthorizer({
|
|
991
|
+
workspaceRoot: workspace.root,
|
|
992
|
+
ciMode: false,
|
|
993
|
+
});
|
|
994
|
+
const authorizeTool = async (req) => {
|
|
995
|
+
return authorizer.requestAuthorization(req);
|
|
996
|
+
};
|
|
997
|
+
// 读取持久化输入历史(可选,失败不阻塞)
|
|
998
|
+
const initialHistory = await loadInputHistory().catch(() => []);
|
|
999
|
+
// agent 引用:sendMessage 通过它跑 agent;init 完成前为 null
|
|
1000
|
+
const agentRef = {
|
|
1001
|
+
llm: null,
|
|
1002
|
+
contextBuilder: null,
|
|
1003
|
+
agent: null,
|
|
1004
|
+
};
|
|
1005
|
+
const handlers = {
|
|
1006
|
+
eventBus: getDefaultEventBus(),
|
|
1007
|
+
clearSession: async () => {
|
|
1008
|
+
await session.clear();
|
|
1009
|
+
},
|
|
1010
|
+
// 与 initAgent 内部逻辑一致(effectiveMaxSteps = overrides?.maxSteps ?? cfg.agent.maxSteps)
|
|
1011
|
+
maxSteps: cliArgs.maxSteps ?? cfg.agent.maxSteps,
|
|
1012
|
+
// §3.3 M3:Ctrl+C 两次语义(TextInput onAbort → requestAbort)
|
|
1013
|
+
requestAbort: abortController.requestAbort,
|
|
1014
|
+
isAbortRequested: abortController.isFirstAbortReceived,
|
|
1015
|
+
// §3.6 M1:工具授权器(App 把 uiCallback 桥接到 PermissionCard)
|
|
1016
|
+
authorizer,
|
|
1017
|
+
sendMessage: async (input) => {
|
|
1018
|
+
if (!agentRef.agent || !agentRef.contextBuilder || !agentRef.llm) {
|
|
1019
|
+
throw new Error('Agent 未初始化');
|
|
1020
|
+
}
|
|
1021
|
+
// 持久化输入历史(异步,不阻塞)
|
|
1022
|
+
void appendInputHistory(input);
|
|
1023
|
+
let smartContextText;
|
|
1024
|
+
if (!cliArgs.noSmartContext && session.recordCount === 0) {
|
|
1025
|
+
try {
|
|
1026
|
+
const sc = await collectSmartContext(input, process.cwd());
|
|
1027
|
+
if (sc.contextText && sc.files.length > 0) {
|
|
1028
|
+
smartContextText = sc.contextText;
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
catch {
|
|
1032
|
+
// Smart Context 失败不影响主流程
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
const messages = agentRef.contextBuilder.build(session, input, undefined, smartContextText);
|
|
1036
|
+
const result = await agentRef.agent.runWithMessages(messages);
|
|
1037
|
+
await session.appendMessages([
|
|
1038
|
+
{ role: "user", content: input },
|
|
1039
|
+
...result.newMessages,
|
|
1040
|
+
]);
|
|
1041
|
+
const allMessages = agentRef.contextBuilder.build(session, "");
|
|
1042
|
+
await compactIfNeeded(session, allMessages, agentRef.llm, cfg.sessions.compaction);
|
|
1043
|
+
const durationMs = result.metrics?.durationMs ?? 0;
|
|
1044
|
+
return {
|
|
1045
|
+
response: result.response,
|
|
1046
|
+
steps: result.steps,
|
|
1047
|
+
totalTokens: result.totalTokens,
|
|
1048
|
+
durationMs,
|
|
1049
|
+
};
|
|
1050
|
+
},
|
|
1051
|
+
};
|
|
1052
|
+
// ── 先 init agent(此时 Ink 还没启动,skills 加载日志正常打 stdout) ──
|
|
1053
|
+
// 关键顺序:必须在 runInk 之前完成 initAgent,否则 skills 加载的几十行 info
|
|
1054
|
+
// 日志会在 Ink 启动后打到同一终端上,把 Ink 记住的光标位置推乱,导致:
|
|
1055
|
+
// - 首帧被日志盖在可视区上方(用户看不到 Header/输入框)
|
|
1056
|
+
// - Static 追加历史项时打在错的位置(Header 被复制多次)
|
|
1057
|
+
// - 大量空线框(Ink 尝试重绘时高度计算失效)
|
|
1058
|
+
// onStream 通过 sinkRef 桥接:先创建占位,Ink 启动时挂上真正的 sink。
|
|
1059
|
+
const streamSinkRef = { current: null };
|
|
1060
|
+
const onStreamProxy = (event) => {
|
|
1061
|
+
streamSinkRef.current?.(event);
|
|
1062
|
+
};
|
|
1063
|
+
const { llm, contextBuilder, agent } = await initAgent({
|
|
1064
|
+
cfg, logger, confirmMode: resolvedConfirmMode, thinking: resolvedThinking, planMode: cliArgs.planMode,
|
|
1065
|
+
overrides: { maxSteps: cliArgs.maxSteps, contextWindow: cliArgs.contextWindow },
|
|
1066
|
+
onStream: onStreamProxy, tracerOpts: { enabled: !cliArgs.noTrace },
|
|
1067
|
+
shouldAbort, authorizeTool,
|
|
1068
|
+
});
|
|
1069
|
+
agentRef.llm = llm;
|
|
1070
|
+
agentRef.contextBuilder = contextBuilder;
|
|
1071
|
+
agentRef.agent = agent;
|
|
1072
|
+
// ── init 完成后再启动 Ink ──
|
|
1073
|
+
// 此时终端上可能已经有几十行 skills 加载日志,Ink 启动后会:
|
|
1074
|
+
// - 立即切换到 alt-screen(取决于终端;不切换时也会把 UI 渲染在当前光标处)
|
|
1075
|
+
// - 记住当前光标为 UI 起点,后续重绘不会跨越这个起点向上
|
|
1076
|
+
// 这样 Ink 记住的光标位置就是正确的,不会被日志推乱。
|
|
1077
|
+
const inkHandle = runInk({
|
|
1078
|
+
model: cfg.agent.defaultModel ?? 'unknown',
|
|
1079
|
+
sessionUuidShort,
|
|
1080
|
+
workspaceRoot: workspace.root,
|
|
1081
|
+
handlers,
|
|
1082
|
+
initialHistory,
|
|
1083
|
+
streamSinkRef,
|
|
1084
|
+
});
|
|
1085
|
+
// ── TUI 启动后把 logger 静音 ──
|
|
1086
|
+
// agent 在运行时会通过 `this.log.info` 打大量进度日志(Token/Step/工具调用等),
|
|
1087
|
+
// 这些信息在 TUI 里已经被 Header + HistoryEntry + ToolCards 完整覆盖,继续
|
|
1088
|
+
// 走 stdout 只会把 Ink 记住的渲染区推乱,画出一堆空线框。
|
|
1089
|
+
// 提级到 error:只有异常才允许打到终端(走 stderr),info/warn 全部静默。
|
|
1090
|
+
// 如果用户需要观测进度,可以看:
|
|
1091
|
+
// - Header 的 step counter / cache 命中率
|
|
1092
|
+
// - HistoryEntry stats(steps / token / 耗时)
|
|
1093
|
+
// - ToolCards 的工具调用摘要
|
|
1094
|
+
// - trace 文件(通过 `wukong session show` 查看)
|
|
1095
|
+
logger.setTuiMode(true);
|
|
1096
|
+
logger.setLevel('error');
|
|
1097
|
+
// test 模式:发一条消息验证后退出
|
|
1098
|
+
if (cliArgs.test) {
|
|
1099
|
+
try {
|
|
1100
|
+
await handlers.sendMessage('请列出当前目录下有哪些文件和文件夹。');
|
|
1101
|
+
process.stderr.write("\n✅ 测试通过,AI 对话正常!\n");
|
|
1102
|
+
inkHandle.unmount();
|
|
1103
|
+
process.exit(0);
|
|
1104
|
+
}
|
|
1105
|
+
catch (error) {
|
|
1106
|
+
process.stderr.write(`\n❌ 测试失败: ${String(error)}\n`);
|
|
1107
|
+
inkHandle.unmount();
|
|
1108
|
+
process.exit(1);
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
866
1112
|
/**
|
|
867
1113
|
* 把 CLI 参数(--session-id / --continue / --new)与运行模式(CI/交互)综合起来,
|
|
868
1114
|
* 得到 SessionManager.resolveSession() 需要的 strategy 对象。
|
|
@@ -934,13 +1180,19 @@ function resolveThinking(cliArgs, cfg) {
|
|
|
934
1180
|
}
|
|
935
1181
|
async function main() {
|
|
936
1182
|
const cliArgs = parseArgs(process.argv);
|
|
1183
|
+
// 把 --yes / --json 反射到 env,让 SDK 层的 config loader 也能识别
|
|
1184
|
+
// CI 模式(§1.3)。参见 core 里 isCiMode()。
|
|
1185
|
+
if (cliArgs.yes)
|
|
1186
|
+
process.env.WUKONG_YES = "1";
|
|
1187
|
+
if (cliArgs.json)
|
|
1188
|
+
process.env.WUKONG_JSON = "1";
|
|
937
1189
|
// 优先处理 --help 和 --version
|
|
938
1190
|
if (cliArgs.help) {
|
|
939
|
-
process.stdout.write(
|
|
1191
|
+
process.stdout.write(HELP_TEXT + '\n');
|
|
940
1192
|
process.exit(0);
|
|
941
1193
|
}
|
|
942
1194
|
if (cliArgs.version) {
|
|
943
|
-
process.stdout.write(
|
|
1195
|
+
process.stdout.write(formatVersion() + '\n');
|
|
944
1196
|
process.exit(0);
|
|
945
1197
|
}
|
|
946
1198
|
switch (cliArgs.command) {
|
|
@@ -959,20 +1211,20 @@ async function main() {
|
|
|
959
1211
|
}
|
|
960
1212
|
}
|
|
961
1213
|
async function runSessionSubcommand(cliArgs) {
|
|
962
|
-
const logger = new
|
|
963
|
-
|
|
964
|
-
const cfg = await
|
|
965
|
-
const manager = new
|
|
966
|
-
const workspace =
|
|
967
|
-
const code = await
|
|
1214
|
+
const logger = new Logger({ jsonMode: false });
|
|
1215
|
+
setGlobalLogger(logger);
|
|
1216
|
+
const cfg = await loadConfig();
|
|
1217
|
+
const manager = new SessionManager(cfg.sessions.dir);
|
|
1218
|
+
const workspace = resolveWorkspace();
|
|
1219
|
+
const code = await runSessionCommand({ manager, workspace, args: cliArgs.positional });
|
|
968
1220
|
process.exit(code);
|
|
969
1221
|
}
|
|
970
1222
|
async function runWorkspaceSubcommand(cliArgs) {
|
|
971
|
-
const logger = new
|
|
972
|
-
|
|
973
|
-
const cfg = await
|
|
974
|
-
const manager = new
|
|
975
|
-
const code = await
|
|
1223
|
+
const logger = new Logger({ jsonMode: false });
|
|
1224
|
+
setGlobalLogger(logger);
|
|
1225
|
+
const cfg = await loadConfig();
|
|
1226
|
+
const manager = new SessionManager(cfg.sessions.dir);
|
|
1227
|
+
const code = await runWorkspaceCommand({ manager, args: cliArgs.positional });
|
|
976
1228
|
process.exit(code);
|
|
977
1229
|
}
|
|
978
1230
|
main().catch((err) => {
|