@wukonglabs/wukong 0.0.8 → 0.0.10
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.d.ts.map +1 -1
- package/dist/cli/help.js +10 -9
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/session-commands.d.ts +28 -0
- package/dist/cli/session-commands.d.ts.map +1 -0
- package/dist/cli/session-commands.js +274 -0
- package/dist/cli/session-commands.js.map +1 -0
- package/dist/cli/summary.js +2 -6
- package/dist/cli/summary.js.map +1 -1
- package/dist/cli/workspace-commands.d.ts +15 -0
- package/dist/cli/workspace-commands.d.ts.map +1 -0
- package/dist/cli/workspace-commands.js +98 -0
- package/dist/cli/workspace-commands.js.map +1 -0
- package/dist/index.js +429 -131
- 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 +194 -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 +29 -0
- package/dist/tui/plan-panel.d.ts.map +1 -0
- package/dist/tui/plan-panel.js +26 -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 +13 -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 +31 -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 +52 -0
- package/dist/tui/text-input.d.ts.map +1 -0
- package/dist/tui/text-input.js +321 -0
- package/dist/tui/text-input.js.map +1 -0
- package/dist/tui/thinking-panel.d.ts +40 -0
- package/dist/tui/thinking-panel.d.ts.map +1 -0
- package/dist/tui/thinking-panel.js +54 -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 +34 -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 +73 -0
- package/dist/tui/use-plan-state.js.map +1 -0
- package/package.json +7 -2
package/dist/index.js
CHANGED
|
@@ -1,81 +1,30 @@
|
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
* 组合为 `ci:<branch>:<commit>:<jobId>` 格式。
|
|
54
|
-
*/
|
|
55
|
-
function generateCiSessionId() {
|
|
56
|
-
const branch = process.env.CI_COMMIT_BRANCH
|
|
57
|
-
|| process.env.GITHUB_HEAD_REF
|
|
58
|
-
|| process.env.GITHUB_REF_NAME
|
|
59
|
-
|| process.env.BRANCH_NAME
|
|
60
|
-
|| 'unknown';
|
|
61
|
-
const commit = (process.env.CI_COMMIT_SHA
|
|
62
|
-
|| process.env.GITHUB_SHA
|
|
63
|
-
|| process.env.GIT_COMMIT
|
|
64
|
-
|| '').substring(0, 8) || 'nocommit';
|
|
65
|
-
const jobId = process.env.CI_JOB_ID
|
|
66
|
-
|| process.env.GITHUB_RUN_ID
|
|
67
|
-
|| process.env.BUILD_NUMBER
|
|
68
|
-
|| Date.now().toString(36);
|
|
69
|
-
return `ci:${branch}:${commit}:${jobId}`;
|
|
70
|
-
}
|
|
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";
|
|
71
18
|
function parseArgs(argv) {
|
|
72
19
|
const args = argv.slice(2);
|
|
73
20
|
const result = {
|
|
74
21
|
command: null,
|
|
22
|
+
positional: [],
|
|
75
23
|
message: null,
|
|
76
24
|
systemPrompt: null,
|
|
77
|
-
sessionId:
|
|
78
|
-
|
|
25
|
+
sessionId: null,
|
|
26
|
+
continueSession: false,
|
|
27
|
+
newSession: false,
|
|
79
28
|
json: false,
|
|
80
29
|
test: false,
|
|
81
30
|
images: [],
|
|
@@ -104,8 +53,19 @@ function parseArgs(argv) {
|
|
|
104
53
|
let i = 0;
|
|
105
54
|
while (i < args.length) {
|
|
106
55
|
const arg = args[i];
|
|
107
|
-
if (!arg.startsWith("-")
|
|
108
|
-
result.command
|
|
56
|
+
if (!arg.startsWith("-")) {
|
|
57
|
+
if (!result.command) {
|
|
58
|
+
result.command = arg;
|
|
59
|
+
// 子命令组:"session" / "workspace" 后所有剩余参数原样收集,
|
|
60
|
+
// 让子命令自己解析(避免顶层 dash 参数 switch 吞掉 --all 之类)
|
|
61
|
+
if (arg === "session" || arg === "workspace") {
|
|
62
|
+
result.positional.push(...args.slice(i + 1));
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
result.positional.push(arg);
|
|
68
|
+
}
|
|
109
69
|
i++;
|
|
110
70
|
continue;
|
|
111
71
|
}
|
|
@@ -120,8 +80,15 @@ function parseArgs(argv) {
|
|
|
120
80
|
break;
|
|
121
81
|
case "--session-id":
|
|
122
82
|
case "-s":
|
|
123
|
-
result.sessionId = args[++i] ??
|
|
124
|
-
|
|
83
|
+
result.sessionId = args[++i] ?? null;
|
|
84
|
+
break;
|
|
85
|
+
case "--continue":
|
|
86
|
+
case "-c":
|
|
87
|
+
result.continueSession = true;
|
|
88
|
+
break;
|
|
89
|
+
case "--new":
|
|
90
|
+
case "-n":
|
|
91
|
+
result.newSession = true;
|
|
125
92
|
break;
|
|
126
93
|
case "--json":
|
|
127
94
|
result.json = true;
|
|
@@ -265,33 +232,37 @@ function parseArgs(argv) {
|
|
|
265
232
|
}
|
|
266
233
|
return result;
|
|
267
234
|
}
|
|
268
|
-
async function initAgent(
|
|
269
|
-
return
|
|
270
|
-
config: cfg,
|
|
271
|
-
logger,
|
|
272
|
-
confirmMode,
|
|
273
|
-
thinking,
|
|
274
|
-
ciMode,
|
|
275
|
-
planMode,
|
|
276
|
-
overrides,
|
|
277
|
-
onStream,
|
|
278
|
-
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,
|
|
279
248
|
});
|
|
280
249
|
}
|
|
281
250
|
async function runAgentCommand(cliArgs) {
|
|
282
|
-
const logger = new
|
|
283
|
-
|
|
284
|
-
const cfg = await
|
|
251
|
+
const logger = new Logger({ jsonMode: cliArgs.json, level: cliArgs.verbose ? 'debug' : 'info' });
|
|
252
|
+
setGlobalLogger(logger);
|
|
253
|
+
const cfg = await loadConfig();
|
|
285
254
|
applyPermissionOverrides(cfg, cliArgs, logger);
|
|
286
255
|
const resolvedConfirmMode = cliArgs.yes ? 'never' : (cliArgs.confirmMode ?? cfg.tools.confirmMode ?? 'smart');
|
|
287
256
|
const resolvedThinking = resolveThinking(cliArgs, cfg);
|
|
288
257
|
// CI 模式:--yes 或 --json 时自动启用
|
|
289
258
|
const isCiMode = cliArgs.yes || cliArgs.json;
|
|
290
|
-
// ──
|
|
291
|
-
// CI
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
|
|
259
|
+
// ── Session 策略解析 ──
|
|
260
|
+
// 参见 resolveCliSessionStrategy;CI 模式默认新开+ephemeral,交互默认续接
|
|
261
|
+
const sessionStrategy = resolveCliSessionStrategy(cliArgs, isCiMode, logger);
|
|
262
|
+
if (sessionStrategy === null) {
|
|
263
|
+
// 参数冲突,已经打过日志,直接退出
|
|
264
|
+
process.exit(EXIT_CODES.CONFIG_ERROR);
|
|
265
|
+
}
|
|
295
266
|
const isEphemeral = cliArgs.ephemeral || isCiMode;
|
|
296
267
|
// ── 流式输出回调 ──
|
|
297
268
|
// JSON 模式下也使用流式请求(LLM 传输层),只是不输出到 stdout
|
|
@@ -327,12 +298,16 @@ async function runAgentCommand(cliArgs) {
|
|
|
327
298
|
break;
|
|
328
299
|
}
|
|
329
300
|
};
|
|
330
|
-
const { llm, contextBuilder, agent, effectiveMaxSteps, resolvedModel } = await initAgent(
|
|
331
|
-
|
|
332
|
-
contextWindow: cliArgs.contextWindow,
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
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();
|
|
308
|
+
const resolved = await sessionManager.resolveSession({ workspace, strategy: sessionStrategy });
|
|
309
|
+
const session = resolved.session;
|
|
310
|
+
const sessionUuidShort = resolved.session.ref?.sessionUuid.slice(0, 8) ?? "";
|
|
336
311
|
const userMessage = cliArgs.message;
|
|
337
312
|
if (!userMessage) {
|
|
338
313
|
if (cliArgs.json) {
|
|
@@ -340,7 +315,7 @@ async function runAgentCommand(cliArgs) {
|
|
|
340
315
|
version: 2,
|
|
341
316
|
runId: "",
|
|
342
317
|
status: "error",
|
|
343
|
-
exitCode:
|
|
318
|
+
exitCode: EXIT_CODES.CONFIG_ERROR,
|
|
344
319
|
summary: "缺少 --message 参数",
|
|
345
320
|
result: { payloads: [] },
|
|
346
321
|
};
|
|
@@ -349,10 +324,12 @@ async function runAgentCommand(cliArgs) {
|
|
|
349
324
|
else {
|
|
350
325
|
logger.error("❌ 缺少 --message 参数。用法: wukong agent --message \"你的消息\"");
|
|
351
326
|
}
|
|
352
|
-
process.exit(
|
|
327
|
+
process.exit(EXIT_CODES.CONFIG_ERROR);
|
|
353
328
|
}
|
|
354
329
|
logger.info(`📡 LLM: ${resolvedModel}`);
|
|
355
|
-
logger.info(`📂 会话: ${
|
|
330
|
+
logger.info(`📂 会话: ${sessionUuidShort} @ ${workspace.root}` +
|
|
331
|
+
` (${session.recordCount} 条记录${resolved.created ? ", 新建" : ""})` +
|
|
332
|
+
`${isEphemeral ? " [ephemeral]" : ""}`);
|
|
356
333
|
try {
|
|
357
334
|
let userContent;
|
|
358
335
|
if (cliArgs.images.length > 0) {
|
|
@@ -373,7 +350,7 @@ async function runAgentCommand(cliArgs) {
|
|
|
373
350
|
let smartContextText;
|
|
374
351
|
if (!cliArgs.noSmartContext && userMessage) {
|
|
375
352
|
try {
|
|
376
|
-
const sc = await
|
|
353
|
+
const sc = await collectSmartContext(userMessage, process.cwd());
|
|
377
354
|
if (sc.contextText && sc.files.length > 0) {
|
|
378
355
|
smartContextText = sc.contextText;
|
|
379
356
|
logger.info(`📂 Smart Context: 注入 ${sc.files.length} 个相关文件摘要 (~${sc.estimatedTokens} token)`);
|
|
@@ -390,14 +367,14 @@ async function runAgentCommand(cliArgs) {
|
|
|
390
367
|
...result.newMessages,
|
|
391
368
|
]);
|
|
392
369
|
const allMessages = contextBuilder.build(session, "");
|
|
393
|
-
await
|
|
370
|
+
await compactIfNeeded(session, allMessages, llm, cfg.sessions.compaction);
|
|
394
371
|
// ── 自动验证 ──
|
|
395
372
|
let verification;
|
|
396
373
|
const agentVerifyCfg = cfg.agent.verify;
|
|
397
374
|
const shouldVerify = !cliArgs.noVerify && (cliArgs.verify || agentVerifyCfg?.enabled);
|
|
398
375
|
if (shouldVerify && !result.aborted) {
|
|
399
376
|
const verifyConfig = {
|
|
400
|
-
...
|
|
377
|
+
...DEFAULT_VERIFY_CONFIG,
|
|
401
378
|
...(agentVerifyCfg ?? {}),
|
|
402
379
|
enabled: true,
|
|
403
380
|
// CLI 参数覆盖:验证超时
|
|
@@ -405,7 +382,7 @@ async function runAgentCommand(cliArgs) {
|
|
|
405
382
|
// CI 模式下默认包含 build 命令
|
|
406
383
|
includeBuild: isCiMode,
|
|
407
384
|
};
|
|
408
|
-
const verifier = new
|
|
385
|
+
const verifier = new AutoVerifier(verifyConfig, logger);
|
|
409
386
|
verification = await verifier.verifyAndFix(process.cwd(), async (fixPrompt) => {
|
|
410
387
|
// 让 Agent 执行修复
|
|
411
388
|
const fixMessages = contextBuilder.build(session, fixPrompt);
|
|
@@ -418,7 +395,7 @@ async function runAgentCommand(cliArgs) {
|
|
|
418
395
|
}, cliArgs.verifyCommand ?? undefined);
|
|
419
396
|
}
|
|
420
397
|
// ── 计算退出码 ──
|
|
421
|
-
const exitCode =
|
|
398
|
+
const exitCode = resolveExitCode({
|
|
422
399
|
aborted: result.aborted,
|
|
423
400
|
steps: result.steps,
|
|
424
401
|
maxSteps: effectiveMaxSteps,
|
|
@@ -441,8 +418,8 @@ async function runAgentCommand(cliArgs) {
|
|
|
441
418
|
const filesModified = extractModifiedFiles(result.trace);
|
|
442
419
|
// 推断 status
|
|
443
420
|
const status = result.aborted ? 'error'
|
|
444
|
-
: exitCode ===
|
|
445
|
-
: exitCode ===
|
|
421
|
+
: exitCode === EXIT_CODES.TASK_INCOMPLETE ? 'incomplete'
|
|
422
|
+
: exitCode === EXIT_CODES.VERIFY_FAILED ? 'verify_failed'
|
|
446
423
|
: 'success';
|
|
447
424
|
const metrics = result.metrics;
|
|
448
425
|
const durationMs = metrics?.durationMs ?? 0;
|
|
@@ -451,7 +428,7 @@ async function runAgentCommand(cliArgs) {
|
|
|
451
428
|
runId: `run_${Date.now()}`,
|
|
452
429
|
status,
|
|
453
430
|
exitCode,
|
|
454
|
-
summary:
|
|
431
|
+
summary: buildJsonSummary(result.response, {
|
|
455
432
|
steps: result.steps,
|
|
456
433
|
totalTokens: result.totalTokens.prompt + result.totalTokens.completion,
|
|
457
434
|
}),
|
|
@@ -545,7 +522,7 @@ async function runAgentCommand(cliArgs) {
|
|
|
545
522
|
if (isEphemeral) {
|
|
546
523
|
try {
|
|
547
524
|
await session.clear();
|
|
548
|
-
logger.info(`🗑️ Ephemeral session 已清理: ${
|
|
525
|
+
logger.info(`🗑️ Ephemeral session 已清理: ${sessionUuidShort}`);
|
|
549
526
|
}
|
|
550
527
|
catch {
|
|
551
528
|
// 清理失败不影响退出码
|
|
@@ -559,7 +536,7 @@ async function runAgentCommand(cliArgs) {
|
|
|
559
536
|
version: 2,
|
|
560
537
|
runId: `run_${Date.now()}`,
|
|
561
538
|
status: "error",
|
|
562
|
-
exitCode:
|
|
539
|
+
exitCode: EXIT_CODES.LLM_ERROR,
|
|
563
540
|
summary: error instanceof Error ? error.message : String(error),
|
|
564
541
|
result: { payloads: [] },
|
|
565
542
|
};
|
|
@@ -568,7 +545,7 @@ async function runAgentCommand(cliArgs) {
|
|
|
568
545
|
else {
|
|
569
546
|
logger.error("❌ Agent 执行出错:", error);
|
|
570
547
|
}
|
|
571
|
-
process.exit(
|
|
548
|
+
process.exit(EXIT_CODES.LLM_ERROR);
|
|
572
549
|
}
|
|
573
550
|
}
|
|
574
551
|
/** 从 trace 中提取被修改的文件路径 */
|
|
@@ -696,13 +673,67 @@ async function persistWukongConfig(setup, logger) {
|
|
|
696
673
|
logger.warn(`⚠️ 配置写入失败(本次运行仍可继续): ${String(err)}`);
|
|
697
674
|
}
|
|
698
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
|
+
}
|
|
699
730
|
async function runInteractive(cliArgs) {
|
|
700
|
-
const logger = new
|
|
701
|
-
|
|
731
|
+
const logger = new Logger({ jsonMode: false });
|
|
732
|
+
setGlobalLogger(logger);
|
|
702
733
|
logger.info("🐒 Wukong (悟空) v0.1.0");
|
|
703
734
|
logger.info("========================");
|
|
704
735
|
logger.info("");
|
|
705
|
-
const cfg = await
|
|
736
|
+
const cfg = await loadConfig();
|
|
706
737
|
applyPermissionOverrides(cfg, cliArgs, logger);
|
|
707
738
|
// ── 未配置模型时:TTY 下进入交互式向导,非 TTY 下打印手动配置指引 ──
|
|
708
739
|
if (!hasConfiguredProvider(cfg)) {
|
|
@@ -713,12 +744,12 @@ async function runInteractive(cliArgs) {
|
|
|
713
744
|
logger.error(" export LLM_BASE_URL=https://api.deepseek.com/v1 # OpenAI 兼容端点");
|
|
714
745
|
logger.error(" export LLM_MODEL=deepseek-chat");
|
|
715
746
|
logger.error(" 或编辑 ~/.wukong/wukong.json(详见 README 的「配置」章节)。");
|
|
716
|
-
process.exit(
|
|
747
|
+
process.exit(EXIT_CODES.CONFIG_ERROR);
|
|
717
748
|
}
|
|
718
749
|
const setup = await runSetupWizard(logger);
|
|
719
750
|
if (!setup) {
|
|
720
751
|
logger.error("❌ 配置未完成,无法启动。可参考 README 手动配置后重试。");
|
|
721
|
-
process.exit(
|
|
752
|
+
process.exit(EXIT_CODES.CONFIG_ERROR);
|
|
722
753
|
}
|
|
723
754
|
await persistWukongConfig(setup, logger);
|
|
724
755
|
// 直接注入内存配置,无需重新加载(避免本地 wukong.json 遮蔽全局配置的边界情况)
|
|
@@ -729,7 +760,90 @@ async function runInteractive(cliArgs) {
|
|
|
729
760
|
}
|
|
730
761
|
const resolvedConfirmMode = cliArgs.yes ? 'never' : (cliArgs.confirmMode ?? cfg.tools.confirmMode ?? 'smart');
|
|
731
762
|
const resolvedThinking = resolveThinking(cliArgs, cfg);
|
|
732
|
-
|
|
763
|
+
const sessionManager = new SessionManager(cfg.sessions.dir);
|
|
764
|
+
const workspace = resolveWorkspace();
|
|
765
|
+
// 交互模式不视为 CI(即使被 pipe,交互模式一定不会被 --yes/--json 触发)
|
|
766
|
+
const interactiveStrategy = resolveCliSessionStrategy(cliArgs, /* isCiMode */ false, logger);
|
|
767
|
+
if (interactiveStrategy === null) {
|
|
768
|
+
process.exit(EXIT_CODES.CONFIG_ERROR);
|
|
769
|
+
}
|
|
770
|
+
const resolvedInteractive = await sessionManager.resolveSession({
|
|
771
|
+
workspace,
|
|
772
|
+
strategy: interactiveStrategy,
|
|
773
|
+
});
|
|
774
|
+
const session = resolvedInteractive.session;
|
|
775
|
+
const sessionUuidShort = resolvedInteractive.session.ref?.sessionUuid.slice(0, 8) ?? "";
|
|
776
|
+
// ── 判定是否启用 TUI(§3.1 M1 降级矩阵) ──
|
|
777
|
+
const useTui = shouldUseTuiFromProcess();
|
|
778
|
+
if (!useTui) {
|
|
779
|
+
// 降级到 readline(readline 路径装 process SIGINT handler)
|
|
780
|
+
return runInteractiveReadline(cliArgs, cfg, logger, resolvedConfirmMode, resolvedThinking, session, workspace, sessionUuidShort, resolvedInteractive.created);
|
|
781
|
+
}
|
|
782
|
+
// ── TUI 路径(Ink raw mode 拦截 Ctrl+C,不装 process handler,由 TextInput onAbort 触发) ──
|
|
783
|
+
return runInteractiveTui(cliArgs, cfg, logger, resolvedConfirmMode, resolvedThinking, session, workspace, sessionUuidShort);
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* readline 降级路径(原 runInteractive 的实现,保持完全不变)。
|
|
787
|
+
*
|
|
788
|
+
* 触发条件(见 shouldUseTui):
|
|
789
|
+
* - WUKONG_TUI=off
|
|
790
|
+
* - CI=true
|
|
791
|
+
* - stdin/stdout 非 TTY
|
|
792
|
+
* - TERM=dumb
|
|
793
|
+
*/
|
|
794
|
+
async function runInteractiveReadline(cliArgs, cfg, logger, resolvedConfirmMode, resolvedThinking, session, workspace, sessionUuidShort, sessionCreated) {
|
|
795
|
+
// §3.3 M3:graceful shutdown(readline 路径装 process SIGINT handler)
|
|
796
|
+
const abortController = createAbortController({
|
|
797
|
+
onFirstAbort: () => {
|
|
798
|
+
process.stderr.write('\n\n⚠️ 收到中断信号,正在优雅退出(等当前步骤完成)...\n');
|
|
799
|
+
process.stderr.write(' 再按一次 Ctrl+C 立即退出(session 可能不完整)\n');
|
|
800
|
+
},
|
|
801
|
+
onSecondAbort: () => {
|
|
802
|
+
process.stderr.write('\n\n⏏️ 立即退出\n');
|
|
803
|
+
},
|
|
804
|
+
installProcessHandler: true,
|
|
805
|
+
});
|
|
806
|
+
const shouldAbort = abortController.shouldAbort;
|
|
807
|
+
// §3.6 M1:工具授权器(readline 路径用简单 question UI)
|
|
808
|
+
const authorizer = new ToolAuthorizer({
|
|
809
|
+
workspaceRoot: workspace.root,
|
|
810
|
+
ciMode: false,
|
|
811
|
+
uiCallback: async (req) => {
|
|
812
|
+
return new Promise((resolve) => {
|
|
813
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
|
|
814
|
+
process.stderr.write(`\n🔒 授权请求: ${req.toolName} (step ${req.step})\n`);
|
|
815
|
+
process.stderr.write(` 参数: ${req.argumentsJson.slice(0, 200)}\n`);
|
|
816
|
+
rl.question('[y]仅本次 [s]session [l]本项目 [g]全局 [n]拒绝 [x]永久拒绝: ', (ans) => {
|
|
817
|
+
rl.close();
|
|
818
|
+
const c = ans.trim().toLowerCase();
|
|
819
|
+
switch (c) {
|
|
820
|
+
case 's':
|
|
821
|
+
resolve({ approved: true, decision: 'session' });
|
|
822
|
+
break;
|
|
823
|
+
case 'l':
|
|
824
|
+
resolve({ approved: true, decision: 'always-local' });
|
|
825
|
+
break;
|
|
826
|
+
case 'g':
|
|
827
|
+
resolve({ approved: true, decision: 'always-global' });
|
|
828
|
+
break;
|
|
829
|
+
case 'n':
|
|
830
|
+
resolve({ approved: false, decision: 'no' });
|
|
831
|
+
break;
|
|
832
|
+
case 'x':
|
|
833
|
+
resolve({ approved: false, decision: 'no-always' });
|
|
834
|
+
break;
|
|
835
|
+
default:
|
|
836
|
+
resolve({ approved: true, decision: 'once' });
|
|
837
|
+
break;
|
|
838
|
+
}
|
|
839
|
+
});
|
|
840
|
+
});
|
|
841
|
+
},
|
|
842
|
+
});
|
|
843
|
+
const authorizeTool = async (req) => {
|
|
844
|
+
return authorizer.requestAuthorization(req);
|
|
845
|
+
};
|
|
846
|
+
// ── 流式输出回调(交互模式 readline 降级) ──
|
|
733
847
|
let interactiveStreamStarted = false;
|
|
734
848
|
const onStream = (event) => {
|
|
735
849
|
switch (event.type) {
|
|
@@ -754,23 +868,24 @@ async function runInteractive(cliArgs) {
|
|
|
754
868
|
break;
|
|
755
869
|
}
|
|
756
870
|
};
|
|
757
|
-
const { llm, tools, contextBuilder, agent, resolvedModel } = await initAgent(
|
|
758
|
-
|
|
759
|
-
contextWindow: cliArgs.contextWindow,
|
|
760
|
-
|
|
871
|
+
const { llm, tools, contextBuilder, agent, resolvedModel } = await initAgent({
|
|
872
|
+
cfg, logger, confirmMode: resolvedConfirmMode, thinking: resolvedThinking, planMode: cliArgs.planMode,
|
|
873
|
+
overrides: { maxSteps: cliArgs.maxSteps, contextWindow: cliArgs.contextWindow },
|
|
874
|
+
onStream, tracerOpts: { enabled: !cliArgs.noTrace },
|
|
875
|
+
shouldAbort, authorizeTool,
|
|
876
|
+
});
|
|
761
877
|
logger.info(`📡 LLM: ${resolvedModel}`);
|
|
762
878
|
logger.info(`🔧 已注册 ${tools.size} 个工具: ${tools.listNames().join(", ")}`);
|
|
763
|
-
const sessionManager = new wukong_agent_sdk_1.SessionManager(cfg.sessions.dir);
|
|
764
|
-
const session = await sessionManager.getOrCreate(cliArgs.sessionId);
|
|
765
879
|
logger.info(`📂 会话存储: ${cfg.sessions.dir}`);
|
|
880
|
+
logger.info(`📂 会话: ${sessionUuidShort} @ ${workspace.root}` +
|
|
881
|
+
` (${session.recordCount} 条记录${sessionCreated ? ", 新建" : ""})`);
|
|
766
882
|
logger.info(`📜 历史记录: ${session.recordCount} 条`);
|
|
767
883
|
logger.info("");
|
|
768
884
|
async function sendMessage(input) {
|
|
769
|
-
// ── Smart Context 收集(交互模式首条消息) ──
|
|
770
885
|
let smartContextText;
|
|
771
886
|
if (!cliArgs.noSmartContext && session.recordCount === 0) {
|
|
772
887
|
try {
|
|
773
|
-
const sc = await
|
|
888
|
+
const sc = await collectSmartContext(input, process.cwd());
|
|
774
889
|
if (sc.contextText && sc.files.length > 0) {
|
|
775
890
|
smartContextText = sc.contextText;
|
|
776
891
|
logger.info(`📂 Smart Context: 注入 ${sc.files.length} 个相关文件摘要 (~${sc.estimatedTokens} token)`);
|
|
@@ -781,9 +896,8 @@ async function runInteractive(cliArgs) {
|
|
|
781
896
|
}
|
|
782
897
|
}
|
|
783
898
|
const messages = contextBuilder.build(session, input, undefined, smartContextText);
|
|
784
|
-
interactiveStreamStarted = false;
|
|
899
|
+
interactiveStreamStarted = false;
|
|
785
900
|
const result = await agent.runWithMessages(messages);
|
|
786
|
-
// 如果已经流式输出了,不需要再重复打印
|
|
787
901
|
if (!interactiveStreamStarted) {
|
|
788
902
|
logger.info(`\nWukong: ${result.response}`);
|
|
789
903
|
}
|
|
@@ -793,8 +907,7 @@ async function runInteractive(cliArgs) {
|
|
|
793
907
|
...result.newMessages,
|
|
794
908
|
]);
|
|
795
909
|
const allMessages = contextBuilder.build(session, "");
|
|
796
|
-
await
|
|
797
|
-
return result;
|
|
910
|
+
await compactIfNeeded(session, allMessages, llm, cfg.sessions.compaction);
|
|
798
911
|
}
|
|
799
912
|
if (cliArgs.test) {
|
|
800
913
|
logger.info("🧪 测试模式:发送一条测试消息...");
|
|
@@ -825,6 +938,7 @@ async function runInteractive(cliArgs) {
|
|
|
825
938
|
if (trimmed.toLowerCase() === "exit") {
|
|
826
939
|
logger.info("👋 再见!");
|
|
827
940
|
rl.close();
|
|
941
|
+
abortController.uninstall();
|
|
828
942
|
return;
|
|
829
943
|
}
|
|
830
944
|
if (trimmed.toLowerCase() === "clear") {
|
|
@@ -844,6 +958,161 @@ async function runInteractive(cliArgs) {
|
|
|
844
958
|
};
|
|
845
959
|
ask();
|
|
846
960
|
}
|
|
961
|
+
/**
|
|
962
|
+
* TUI 路径(Ink 渲染)。
|
|
963
|
+
*
|
|
964
|
+
* 与 readline 路径的差异:
|
|
965
|
+
* - onStream 通过 streamSinkRef 桥接给 App,由 App 渲染流式文本(不走 stdout)
|
|
966
|
+
* - sendMessage 返回 CompletedRun,供 App 展示统计信息
|
|
967
|
+
* - 输入用 Ink TextInput(多行 + 历史 + Ctrl+R)
|
|
968
|
+
*
|
|
969
|
+
* M1 简化:model 用 cfg.agent.defaultModel 作为初始显示值(initAgent 内部的
|
|
970
|
+
* resolvedModel 通常与之一致;即便不一致也不影响功能,只影响 Header 显示)。
|
|
971
|
+
* M2 引入 step counter 时会顺便修这个 —— 届时通过 EventBus 的 run_start 事件
|
|
972
|
+
* 携带真实 model,App 订阅后更新即可。
|
|
973
|
+
*/
|
|
974
|
+
async function runInteractiveTui(cliArgs, cfg, logger, resolvedConfirmMode, resolvedThinking, session, workspace, sessionUuidShort) {
|
|
975
|
+
// §3.3 M3:graceful shutdown(TUI 路径不装 process handler,Ink raw mode 拦截 Ctrl+C)
|
|
976
|
+
// TextInput 的 onAbort → requestAbort,实现两次 Ctrl+C 语义
|
|
977
|
+
const abortController = createAbortController({
|
|
978
|
+
onFirstAbort: () => {
|
|
979
|
+
// 提示通过 App 状态显示(不直接写 stderr,避免打乱 Ink 渲染)
|
|
980
|
+
},
|
|
981
|
+
onSecondAbort: () => {
|
|
982
|
+
// 二次直接退出,Ink unmount 由 process.exit 触发
|
|
983
|
+
},
|
|
984
|
+
installProcessHandler: false,
|
|
985
|
+
});
|
|
986
|
+
const shouldAbort = abortController.shouldAbort;
|
|
987
|
+
// §3.6 M1:工具授权器(交互模式;CI 模式 runAgentCommand 不创建)
|
|
988
|
+
const authorizer = new ToolAuthorizer({
|
|
989
|
+
workspaceRoot: workspace.root,
|
|
990
|
+
ciMode: false,
|
|
991
|
+
});
|
|
992
|
+
const authorizeTool = async (req) => {
|
|
993
|
+
return authorizer.requestAuthorization(req);
|
|
994
|
+
};
|
|
995
|
+
// 读取持久化输入历史(可选,失败不阻塞)
|
|
996
|
+
const initialHistory = await loadInputHistory().catch(() => []);
|
|
997
|
+
// agent 引用:sendMessage 通过它跑 agent;init 完成前为 null
|
|
998
|
+
const agentRef = {
|
|
999
|
+
llm: null,
|
|
1000
|
+
contextBuilder: null,
|
|
1001
|
+
agent: null,
|
|
1002
|
+
};
|
|
1003
|
+
const handlers = {
|
|
1004
|
+
eventBus: getDefaultEventBus(),
|
|
1005
|
+
clearSession: async () => {
|
|
1006
|
+
await session.clear();
|
|
1007
|
+
},
|
|
1008
|
+
// 与 initAgent 内部逻辑一致(effectiveMaxSteps = overrides?.maxSteps ?? cfg.agent.maxSteps)
|
|
1009
|
+
maxSteps: cliArgs.maxSteps ?? cfg.agent.maxSteps,
|
|
1010
|
+
// §3.3 M3:Ctrl+C 两次语义(TextInput onAbort → requestAbort)
|
|
1011
|
+
requestAbort: abortController.requestAbort,
|
|
1012
|
+
isAbortRequested: abortController.isFirstAbortReceived,
|
|
1013
|
+
// §3.6 M1:工具授权器(App 把 uiCallback 桥接到 PermissionCard)
|
|
1014
|
+
authorizer,
|
|
1015
|
+
sendMessage: async (input) => {
|
|
1016
|
+
if (!agentRef.agent || !agentRef.contextBuilder || !agentRef.llm) {
|
|
1017
|
+
throw new Error('Agent 未初始化');
|
|
1018
|
+
}
|
|
1019
|
+
// 持久化输入历史(异步,不阻塞)
|
|
1020
|
+
void appendInputHistory(input);
|
|
1021
|
+
let smartContextText;
|
|
1022
|
+
if (!cliArgs.noSmartContext && session.recordCount === 0) {
|
|
1023
|
+
try {
|
|
1024
|
+
const sc = await collectSmartContext(input, process.cwd());
|
|
1025
|
+
if (sc.contextText && sc.files.length > 0) {
|
|
1026
|
+
smartContextText = sc.contextText;
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
catch {
|
|
1030
|
+
// Smart Context 失败不影响主流程
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
const messages = agentRef.contextBuilder.build(session, input, undefined, smartContextText);
|
|
1034
|
+
const result = await agentRef.agent.runWithMessages(messages);
|
|
1035
|
+
await session.appendMessages([
|
|
1036
|
+
{ role: "user", content: input },
|
|
1037
|
+
...result.newMessages,
|
|
1038
|
+
]);
|
|
1039
|
+
const allMessages = agentRef.contextBuilder.build(session, "");
|
|
1040
|
+
await compactIfNeeded(session, allMessages, agentRef.llm, cfg.sessions.compaction);
|
|
1041
|
+
const durationMs = result.metrics?.durationMs ?? 0;
|
|
1042
|
+
return {
|
|
1043
|
+
response: result.response,
|
|
1044
|
+
steps: result.steps,
|
|
1045
|
+
totalTokens: result.totalTokens,
|
|
1046
|
+
durationMs,
|
|
1047
|
+
};
|
|
1048
|
+
},
|
|
1049
|
+
};
|
|
1050
|
+
// 启动 Ink(App 会立即渲染,但 agent 还没 init;用户输入会被 sendMessage 拦截)
|
|
1051
|
+
const inkHandle = runInk({
|
|
1052
|
+
model: cfg.agent.defaultModel ?? 'unknown',
|
|
1053
|
+
sessionUuidShort,
|
|
1054
|
+
workspaceRoot: workspace.root,
|
|
1055
|
+
handlers,
|
|
1056
|
+
initialHistory,
|
|
1057
|
+
});
|
|
1058
|
+
// init agent,把 Ink 的 onStream 传进去
|
|
1059
|
+
const { llm, contextBuilder, agent } = await initAgent({
|
|
1060
|
+
cfg, logger, confirmMode: resolvedConfirmMode, thinking: resolvedThinking, planMode: cliArgs.planMode,
|
|
1061
|
+
overrides: { maxSteps: cliArgs.maxSteps, contextWindow: cliArgs.contextWindow },
|
|
1062
|
+
onStream: inkHandle.onStream, tracerOpts: { enabled: !cliArgs.noTrace },
|
|
1063
|
+
shouldAbort, authorizeTool,
|
|
1064
|
+
});
|
|
1065
|
+
agentRef.llm = llm;
|
|
1066
|
+
agentRef.contextBuilder = contextBuilder;
|
|
1067
|
+
agentRef.agent = agent;
|
|
1068
|
+
// test 模式:发一条消息验证后退出
|
|
1069
|
+
if (cliArgs.test) {
|
|
1070
|
+
try {
|
|
1071
|
+
await handlers.sendMessage('请列出当前目录下有哪些文件和文件夹。');
|
|
1072
|
+
process.stderr.write("\n✅ 测试通过,AI 对话正常!\n");
|
|
1073
|
+
inkHandle.unmount();
|
|
1074
|
+
process.exit(0);
|
|
1075
|
+
}
|
|
1076
|
+
catch (error) {
|
|
1077
|
+
process.stderr.write(`\n❌ 测试失败: ${String(error)}\n`);
|
|
1078
|
+
inkHandle.unmount();
|
|
1079
|
+
process.exit(1);
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
/**
|
|
1084
|
+
* 把 CLI 参数(--session-id / --continue / --new)与运行模式(CI/交互)综合起来,
|
|
1085
|
+
* 得到 SessionManager.resolveSession() 需要的 strategy 对象。
|
|
1086
|
+
*
|
|
1087
|
+
* 三种 session 参数互斥,冲突时返回 null,调用方应以 CONFIG_ERROR 退出。
|
|
1088
|
+
*
|
|
1089
|
+
* 决策规则:
|
|
1090
|
+
* 显式参数(--session-id / --continue / --new) → 按参数
|
|
1091
|
+
* 无显式参数 + CI 模式 → new(不复用旧 session)
|
|
1092
|
+
* 无显式参数 + 交互模式 → continue(自动续接 latest)
|
|
1093
|
+
*
|
|
1094
|
+
* @returns 解析出的 strategy;若参数冲突返回 null(已通过 logger 报错)
|
|
1095
|
+
*/
|
|
1096
|
+
function resolveCliSessionStrategy(cliArgs, isCiMode, logger) {
|
|
1097
|
+
const flags = [
|
|
1098
|
+
cliArgs.sessionId != null,
|
|
1099
|
+
cliArgs.continueSession,
|
|
1100
|
+
cliArgs.newSession,
|
|
1101
|
+
].filter(Boolean).length;
|
|
1102
|
+
if (flags > 1) {
|
|
1103
|
+
logger.error("❌ --session-id / --continue / --new 三者互斥,只能指定其中一个");
|
|
1104
|
+
return null;
|
|
1105
|
+
}
|
|
1106
|
+
if (cliArgs.sessionId != null) {
|
|
1107
|
+
return { kind: "specific", sessionUuid: cliArgs.sessionId };
|
|
1108
|
+
}
|
|
1109
|
+
if (cliArgs.continueSession)
|
|
1110
|
+
return { kind: "continue" };
|
|
1111
|
+
if (cliArgs.newSession)
|
|
1112
|
+
return { kind: "new" };
|
|
1113
|
+
// 无显式参数:CI 默认新开,交互默认续接
|
|
1114
|
+
return isCiMode ? { kind: "new" } : { kind: "continue" };
|
|
1115
|
+
}
|
|
847
1116
|
/**
|
|
848
1117
|
* 应用 CLI 权限参数到 cfg(覆盖 cfg.tools.profile/allow/deny)
|
|
849
1118
|
*
|
|
@@ -882,24 +1151,53 @@ function resolveThinking(cliArgs, cfg) {
|
|
|
882
1151
|
}
|
|
883
1152
|
async function main() {
|
|
884
1153
|
const cliArgs = parseArgs(process.argv);
|
|
1154
|
+
// 把 --yes / --json 反射到 env,让 SDK 层的 config loader 也能识别
|
|
1155
|
+
// CI 模式(§1.3)。参见 core 里 isCiMode()。
|
|
1156
|
+
if (cliArgs.yes)
|
|
1157
|
+
process.env.WUKONG_YES = "1";
|
|
1158
|
+
if (cliArgs.json)
|
|
1159
|
+
process.env.WUKONG_JSON = "1";
|
|
885
1160
|
// 优先处理 --help 和 --version
|
|
886
1161
|
if (cliArgs.help) {
|
|
887
|
-
process.stdout.write(
|
|
1162
|
+
process.stdout.write(HELP_TEXT + '\n');
|
|
888
1163
|
process.exit(0);
|
|
889
1164
|
}
|
|
890
1165
|
if (cliArgs.version) {
|
|
891
|
-
process.stdout.write(
|
|
1166
|
+
process.stdout.write(formatVersion() + '\n');
|
|
892
1167
|
process.exit(0);
|
|
893
1168
|
}
|
|
894
1169
|
switch (cliArgs.command) {
|
|
895
1170
|
case "agent":
|
|
896
1171
|
await runAgentCommand(cliArgs);
|
|
897
1172
|
break;
|
|
1173
|
+
case "session":
|
|
1174
|
+
await runSessionSubcommand(cliArgs);
|
|
1175
|
+
break;
|
|
1176
|
+
case "workspace":
|
|
1177
|
+
await runWorkspaceSubcommand(cliArgs);
|
|
1178
|
+
break;
|
|
898
1179
|
default:
|
|
899
1180
|
await runInteractive(cliArgs);
|
|
900
1181
|
break;
|
|
901
1182
|
}
|
|
902
1183
|
}
|
|
1184
|
+
async function runSessionSubcommand(cliArgs) {
|
|
1185
|
+
const logger = new Logger({ jsonMode: false });
|
|
1186
|
+
setGlobalLogger(logger);
|
|
1187
|
+
const cfg = await loadConfig();
|
|
1188
|
+
const manager = new SessionManager(cfg.sessions.dir);
|
|
1189
|
+
const workspace = resolveWorkspace();
|
|
1190
|
+
const code = await runSessionCommand({ manager, workspace, args: cliArgs.positional });
|
|
1191
|
+
process.exit(code);
|
|
1192
|
+
}
|
|
1193
|
+
async function runWorkspaceSubcommand(cliArgs) {
|
|
1194
|
+
const logger = new Logger({ jsonMode: false });
|
|
1195
|
+
setGlobalLogger(logger);
|
|
1196
|
+
const cfg = await loadConfig();
|
|
1197
|
+
const manager = new SessionManager(cfg.sessions.dir);
|
|
1198
|
+
const code = await runWorkspaceCommand({ manager, args: cliArgs.positional });
|
|
1199
|
+
process.exit(code);
|
|
1200
|
+
}
|
|
903
1201
|
main().catch((err) => {
|
|
904
1202
|
process.stderr.write(String(err) + '\n');
|
|
905
1203
|
process.exit(1);
|