zapmyco 0.1.0

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.
@@ -0,0 +1,1428 @@
1
+ #!/usr/bin/env node
2
+ import { E as __VERSION__, T as APP_NAME, h as createLlmBasedAgent, o as logger, t as loadConfig, w as eventBus } from "../loader-D1dTKHK4.mjs";
3
+ import chalk, { Chalk } from "chalk";
4
+ import { Command } from "commander";
5
+ import { getModel } from "@mariozechner/pi-ai";
6
+ import { Container, Editor, Key, ProcessTerminal, TUI, Text, matchesKey, wrapTextWithAnsi } from "@mariozechner/pi-tui";
7
+
8
+ //#region src/cli/repl/command-registry.ts
9
+ const log$1 = logger.child("repl:command-registry");
10
+ /**
11
+ * 命令注册表
12
+ */
13
+ var CommandRegistry = class {
14
+ commands = /* @__PURE__ */ new Map();
15
+ aliasMap = /* @__PURE__ */ new Map();
16
+ constructor(session) {
17
+ this.session = session;
18
+ }
19
+ /**
20
+ * 注册一个命令
21
+ */
22
+ register(cmd) {
23
+ const canonicalName = cmd.name.toLowerCase();
24
+ if (this.commands.has(canonicalName)) log$1.warn(`命令 "${canonicalName}" 已存在,将被覆盖`);
25
+ this.commands.set(canonicalName, cmd);
26
+ for (const alias of cmd.aliases) {
27
+ const lowerAlias = alias.toLowerCase();
28
+ this.aliasMap.set(lowerAlias, canonicalName);
29
+ }
30
+ }
31
+ /**
32
+ * 根据名称或别名查找命令
33
+ */
34
+ getCommand(name) {
35
+ const lowerName = name.toLowerCase();
36
+ const direct = this.commands.get(lowerName);
37
+ if (direct) return direct;
38
+ const canonical = this.aliasMap.get(lowerName);
39
+ if (canonical) return this.commands.get(canonical);
40
+ }
41
+ /**
42
+ * 列出所有已注册的命令
43
+ */
44
+ listCommands() {
45
+ return Array.from(this.commands.values());
46
+ }
47
+ /**
48
+ * 分发并执行命令
49
+ */
50
+ async dispatch(parsed) {
51
+ if (parsed.kind !== "command") {
52
+ log$1.warn("dispatch 收到了非 command 类型的输入");
53
+ return;
54
+ }
55
+ const cmd = this.getCommand(parsed.name);
56
+ if (!cmd) {
57
+ console.log(`\n 未知命令: /${parsed.name},输入 /help 查看可用命令\n`);
58
+ return;
59
+ }
60
+ try {
61
+ await cmd.handler(parsed.args, this.session);
62
+ } catch (error) {
63
+ const message = error instanceof Error ? error.message : String(error);
64
+ log$1.error(`命令 /${cmd.name} 执行出错`, {}, error);
65
+ console.log(`\n 命令执行出错: ${message}\n`);
66
+ }
67
+ }
68
+ };
69
+
70
+ //#endregion
71
+ //#region src/cli/repl/commands/agents-cmd.ts
72
+ /**
73
+ * 创建 agents 命令定义
74
+ */
75
+ function createAgentsCommand() {
76
+ return {
77
+ name: "agents",
78
+ aliases: ["ag"],
79
+ description: "列出已注册 Agent 及其状态",
80
+ usage: "/agents",
81
+ handler(_args, session) {
82
+ const agents = buildAgentList(session.config);
83
+ const lines = session.getRenderer().renderAgents(agents);
84
+ session.appendOutput(lines);
85
+ }
86
+ };
87
+ }
88
+ /**
89
+ * 从配置构建 AgentRegistration 列表
90
+ *
91
+ * 当前阶段:引擎尚未完全实现,从配置中的 agents 列表构造基本信息。
92
+ * 未来:从 Agent 注册中心获取实时状态。
93
+ */
94
+ function buildAgentList(config) {
95
+ return config.agents.filter((agent) => agent.enabled).map((agent) => {
96
+ const result = {
97
+ agentId: agent.id,
98
+ displayName: agent.id,
99
+ capabilities: [],
100
+ status: "online",
101
+ currentLoad: 0,
102
+ maxConcurrency: 3
103
+ };
104
+ if (agent.endpoint !== void 0) result.endpoint = agent.endpoint;
105
+ return result;
106
+ });
107
+ }
108
+
109
+ //#endregion
110
+ //#region src/cli/repl/commands/clear.ts
111
+ /**
112
+ * 创建 clear 命令定义
113
+ */
114
+ function createClearCommand() {
115
+ return {
116
+ name: "clear",
117
+ aliases: ["cl"],
118
+ description: "清除屏幕",
119
+ usage: "/clear",
120
+ handler(_args, session) {
121
+ session.clearOutput();
122
+ session.getInputParser().reset();
123
+ }
124
+ };
125
+ }
126
+
127
+ //#endregion
128
+ //#region src/cli/repl/commands/config-cmd.ts
129
+ /**
130
+ * 创建 config 命令定义
131
+ */
132
+ function createConfigCommand() {
133
+ return {
134
+ name: "config",
135
+ aliases: ["cfg"],
136
+ description: "查看配置 [show | get <key>]",
137
+ usage: "/config [show | get <key>]",
138
+ handler(args, session) {
139
+ const config = session.config;
140
+ const renderer = session.getRenderer();
141
+ if (args.length === 0 || args[0] === "show") {
142
+ const lines = renderer.renderConfig(config);
143
+ session.appendOutput(lines);
144
+ return;
145
+ }
146
+ if (args[0] === "get" && args[1]) {
147
+ const value = getByDotPath(config, args[1]);
148
+ if (value !== void 0) {
149
+ const displayValue = args[1].toLowerCase().includes("apikey") || args[1].toLowerCase().includes("api_key") ? "***已配置***" : JSON.stringify(value, null, 2);
150
+ session.appendOutput([
151
+ ``,
152
+ ` ${args[1]}: ${displayValue}`,
153
+ ``
154
+ ]);
155
+ } else session.appendOutput([
156
+ "",
157
+ ` 未找到配置项: ${args[1]}`,
158
+ " 使用 /config show 查看所有可用配置项",
159
+ ""
160
+ ]);
161
+ return;
162
+ }
163
+ session.appendOutput([
164
+ "",
165
+ " 用法: /config [show | get <key>]",
166
+ ""
167
+ ]);
168
+ }
169
+ };
170
+ }
171
+ /**
172
+ * 通过 dot-path 获取嵌套对象属性
173
+ *
174
+ * 例如: getByPath(config, 'llm.provider') → config.llm.provider
175
+ */
176
+ function getByDotPath(obj, path) {
177
+ const keys = path.split(".");
178
+ let current = obj;
179
+ for (const key of keys) {
180
+ if (current === null || current === void 0 || typeof current !== "object") return;
181
+ current = current[key];
182
+ }
183
+ return current;
184
+ }
185
+
186
+ //#endregion
187
+ //#region src/cli/repl/commands/help.ts
188
+ /**
189
+ * /help 命令
190
+ *
191
+ * 显示所有可用命令及用法摘要。
192
+ */
193
+ /**
194
+ * 创建 help 命令定义
195
+ */
196
+ function createHelpCommand() {
197
+ return {
198
+ name: "help",
199
+ aliases: ["h", "?"],
200
+ description: "显示帮助信息",
201
+ usage: "/help",
202
+ handler(_args, session) {
203
+ const { c } = getColorEnabled(session);
204
+ const commands = getCommandRegistry(session).listCommands();
205
+ const lines = [
206
+ "",
207
+ c.bold(" 可用命令:"),
208
+ ""
209
+ ];
210
+ for (const cmd of commands) {
211
+ const aliasStr = cmd.aliases.length > 0 ? `, /${cmd.aliases.join(", /")}` : "";
212
+ lines.push(` ${c.cyan.bold(`/${cmd.name}`)}${c.gray(aliasStr)}`);
213
+ lines.push(` ${c.gray(cmd.description)}`);
214
+ if (cmd.usage && cmd.usage !== `/${cmd.name}`) lines.push(` ${c.gray(`用法: ${cmd.usage}`)}`);
215
+ lines.push("");
216
+ }
217
+ lines.push(c.gray(" ───────────────────────────────────────"));
218
+ lines.push(c.gray(" 提示: 直接输入自然语言即可提交目标给 AI 总管执行。"));
219
+ lines.push(c.gray(" 多行输入请在行末加 \"\\\" 续行。按 Ctrl+C 取消当前任务。"));
220
+ lines.push("");
221
+ session.appendOutput(lines);
222
+ }
223
+ };
224
+ }
225
+ function getColorEnabled(session) {
226
+ return { c: session.replOptions.color ? chalk : new Chalk({ level: 0 }) };
227
+ }
228
+ function getCommandRegistry(session) {
229
+ return session.getCommandRegistry();
230
+ }
231
+
232
+ //#endregion
233
+ //#region src/cli/repl/commands/history.ts
234
+ /** 默认显示条数 */
235
+ const DEFAULT_COUNT = 10;
236
+ /**
237
+ * 创建 history 命令定义
238
+ */
239
+ function createHistoryCommand() {
240
+ return {
241
+ name: "history",
242
+ aliases: ["hi"],
243
+ description: "查看历史记录 [n]",
244
+ usage: "/history [n]",
245
+ handler(args, session) {
246
+ const store = session.getHistoryStore();
247
+ const count = args[0] ? parseInt(args[0], 10) : DEFAULT_COUNT;
248
+ if (Number.isNaN(count) || count <= 0) {
249
+ session.appendOutput([
250
+ "",
251
+ " 参数错误: 请输入有效的数字,如 /history 20",
252
+ ""
253
+ ]);
254
+ return;
255
+ }
256
+ const entries = store.getLast(count);
257
+ const lines = session.getRenderer().renderHistory(entries);
258
+ session.appendOutput(lines);
259
+ }
260
+ };
261
+ }
262
+
263
+ //#endregion
264
+ //#region src/cli/repl/commands/quit.ts
265
+ /**
266
+ * 创建 quit 命令定义
267
+ */
268
+ function createQuitCommand() {
269
+ return {
270
+ name: "quit",
271
+ aliases: [
272
+ "exit",
273
+ "q",
274
+ "x"
275
+ ],
276
+ description: "退出 REPL",
277
+ usage: "/quit",
278
+ async handler(_args, session) {
279
+ await session.shutdown("用户主动退出");
280
+ }
281
+ };
282
+ }
283
+
284
+ //#endregion
285
+ //#region src/cli/repl/commands/status.ts
286
+ /**
287
+ * 创建 status 命令定义
288
+ */
289
+ function createStatusCommand() {
290
+ return {
291
+ name: "status",
292
+ aliases: ["st"],
293
+ description: "查看会话状态统计",
294
+ usage: "/status",
295
+ handler(_args, session) {
296
+ const stats = session.getStats();
297
+ const lines = session.getRenderer().renderStatus(stats);
298
+ session.appendOutput(lines);
299
+ }
300
+ };
301
+ }
302
+
303
+ //#endregion
304
+ //#region src/cli/repl/components/custom-editor.ts
305
+ /**
306
+ * 自定义编辑器组件
307
+ *
308
+ * 继承自 pi-tui 的 Editor,添加 zapmyco 特有的快捷键处理:
309
+ * - Ctrl+C: 取消任务 / 二次退出
310
+ * - Ctrl+D: 退出
311
+ * - Escape: 取消当前输入
312
+ */
313
+ var ZapmycoEditor = class extends Editor {
314
+ /** Escape 键回调 */
315
+ onEscape;
316
+ /** Ctrl+C 回调 */
317
+ onCtrlC;
318
+ /** Ctrl+D 回调 */
319
+ onCtrlD;
320
+ handleInput(data) {
321
+ if (matchesKey(data, Key.escape) && this.onEscape) {
322
+ this.onEscape();
323
+ return;
324
+ }
325
+ if (matchesKey(data, Key.ctrl("c")) && this.onCtrlC) {
326
+ this.onCtrlC();
327
+ return;
328
+ }
329
+ if (matchesKey(data, Key.ctrl("d"))) {
330
+ if (this.getText().length === 0 && this.onCtrlD) this.onCtrlD();
331
+ return;
332
+ }
333
+ super.handleInput(data);
334
+ }
335
+ };
336
+
337
+ //#endregion
338
+ //#region src/cli/repl/history-store.ts
339
+ /** 默认最大历史条数 */
340
+ const DEFAULT_MAX_SIZE = 100;
341
+ /**
342
+ * 历史存储类
343
+ */
344
+ var HistoryStore = class {
345
+ entries = [];
346
+ nextId = 1;
347
+ maxSize;
348
+ constructor(maxSize = DEFAULT_MAX_SIZE) {
349
+ this.maxSize = maxSize;
350
+ }
351
+ /** 添加条目 */
352
+ push(entry) {
353
+ const newEntry = {
354
+ ...entry,
355
+ id: this.nextId++
356
+ };
357
+ this.entries.push(newEntry);
358
+ if (this.entries.length > this.maxSize) this.entries.shift();
359
+ return newEntry;
360
+ }
361
+ /** 获取所有条目 */
362
+ getAll() {
363
+ return [...this.entries];
364
+ }
365
+ /** 获取最近 n 条 */
366
+ getLast(n) {
367
+ const count = Math.min(n, this.entries.length);
368
+ return this.entries.slice(-count);
369
+ }
370
+ /** 清空所有条目 */
371
+ clear() {
372
+ this.entries = [];
373
+ }
374
+ /** 搜索条目(按输入内容模糊匹配) */
375
+ search(query) {
376
+ const lowerQuery = query.toLowerCase();
377
+ return this.entries.filter((entry) => entry.input.toLowerCase().includes(lowerQuery));
378
+ }
379
+ };
380
+
381
+ //#endregion
382
+ //#region src/cli/repl/input-parser.ts
383
+ /**
384
+ * 输入解析器
385
+ */
386
+ var InputParser = class {
387
+ buffer = "";
388
+ /**
389
+ * 解析单行输入
390
+ *
391
+ * 规则:
392
+ * 1. 空行 → empty
393
+ * 2. 以 / 开头 → command
394
+ * 3. 以 \ 结尾 → incomplete(多行续行)
395
+ * 4. 其他 → goal(拼接 buffer 后)
396
+ */
397
+ parse(line) {
398
+ const trimmed = line.trimEnd();
399
+ if (trimmed.length === 0 && this.buffer.length === 0) return { kind: "empty" };
400
+ if (trimmed.startsWith("/")) {
401
+ this.buffer = "";
402
+ return this.parseCommand(trimmed);
403
+ }
404
+ if (trimmed.endsWith("\\")) {
405
+ const content = trimmed.slice(0, -1);
406
+ this.buffer += this.buffer ? `\n${content}` : content;
407
+ return {
408
+ kind: "incomplete",
409
+ buffer: this.buffer
410
+ };
411
+ }
412
+ const fullInput = this.buffer ? `${this.buffer}\n${trimmed}` : trimmed;
413
+ this.buffer = "";
414
+ if (fullInput.trim().length === 0) return { kind: "empty" };
415
+ return {
416
+ kind: "goal",
417
+ rawInput: fullInput
418
+ };
419
+ }
420
+ /** 重置解析状态(清空多行缓冲) */
421
+ reset() {
422
+ this.buffer = "";
423
+ }
424
+ /** 获取当前缓冲内容 */
425
+ getBuffer() {
426
+ return this.buffer;
427
+ }
428
+ /**
429
+ * 解析命令行
430
+ *
431
+ * 支持引号包裹的参数:/config set key "value with spaces"
432
+ */
433
+ parseCommand(line) {
434
+ const withoutSlash = line.slice(1);
435
+ const tokens = this.tokenize(withoutSlash);
436
+ if (tokens.length === 0) return { kind: "empty" };
437
+ return {
438
+ kind: "command",
439
+ name: tokens[0]?.toLowerCase() ?? "",
440
+ args: tokens.slice(1)
441
+ };
442
+ }
443
+ /**
444
+ * 分词器
445
+ *
446
+ * 支持双引号包裹的参数(引号内空格不分割)。
447
+ */
448
+ tokenize(input) {
449
+ const tokens = [];
450
+ let current = "";
451
+ let inQuotes = false;
452
+ for (let i = 0; i < input.length; i++) {
453
+ const char = input[i];
454
+ if (char === "\"") {
455
+ inQuotes = !inQuotes;
456
+ continue;
457
+ }
458
+ if (char === " " && !inQuotes) {
459
+ if (current.length > 0) {
460
+ tokens.push(current);
461
+ current = "";
462
+ }
463
+ continue;
464
+ }
465
+ current += char;
466
+ }
467
+ if (current.length > 0) tokens.push(current);
468
+ return tokens;
469
+ }
470
+ };
471
+
472
+ //#endregion
473
+ //#region src/cli/repl/components/output-area.ts
474
+ /**
475
+ * 输出区域组件
476
+ *
477
+ * 负责渲染 REPL 中的所有输出内容:
478
+ * 欢迎信息、执行结果、错误信息、系统消息等。
479
+ */
480
+ /**
481
+ * 格式化输出内容的工具类
482
+ *
483
+ * 从原 Renderer 中提取的纯格式化逻辑,不依赖 console.log。
484
+ */
485
+ var OutputFormatter = class {
486
+ c;
487
+ cDisabled;
488
+ constructor(color) {
489
+ this.color = color;
490
+ this.c = chalk;
491
+ this.cDisabled = new Chalk({ level: 0 });
492
+ }
493
+ /** 获取 chalk 实例 */
494
+ getColor(colorEnabled = this.color) {
495
+ return colorEnabled ? this.c : this.cDisabled;
496
+ }
497
+ /** 格式化欢迎信息 */
498
+ formatWelcome(version) {
499
+ const c = this.getColor();
500
+ return [
501
+ "",
502
+ ` 🍄 ${c.bold(`zapmyco@${version}`)}`,
503
+ "",
504
+ " 欢迎回来!",
505
+ "",
506
+ c.gray("─".repeat(90)),
507
+ ""
508
+ ];
509
+ }
510
+ /** 格式化错误信息 */
511
+ formatError(error) {
512
+ const c = this.getColor();
513
+ const lines = ["", ""];
514
+ const zapmycoError = error;
515
+ if (zapmycoError.code) {
516
+ lines.push(`${c.red.bold(` ✗ [${zapmycoError.code}]`)} ${error.message}`);
517
+ if (zapmycoError.context && Object.keys(zapmycoError.context).length > 0) lines.push(c.gray(` 详情: ${JSON.stringify(zapmycoError.context)}`));
518
+ } else lines.push(`${c.red.bold(" ✗ 执行失败:")} ${error.message}`);
519
+ lines.push("");
520
+ return lines;
521
+ }
522
+ /** 格式化执行结果 */
523
+ formatResult(result) {
524
+ const c = this.getColor();
525
+ const statusIcon = result.overallStatus === "success" ? "✅" : result.overallStatus === "partial-failure" ? "⚠️" : "❌";
526
+ const lines = [
527
+ "",
528
+ c.gray(" ┌────────────────────────────────────────────┐"),
529
+ ` │ ${statusIcon} ${c.bold("执行完成")}`,
530
+ c.gray(" ├────────────────────────────────────────────┤"),
531
+ ` │ ${c.gray("目标:")} ${result.summary.slice(0, 40)}`,
532
+ ` │ ${c.gray("状态:")} ${result.overallStatus === "success" ? c.green("成功") : result.overallStatus === "partial-failure" ? c.yellow("部分成功") : c.red("失败")}`,
533
+ ` │ ${c.gray("耗时:")} ${(result.totalDuration / 1e3).toFixed(1)}s · ${c.gray("Token:")} ${result.totalTokenUsage.totalTokens.toLocaleString()}`,
534
+ ` │ ${c.gray("成本:")} $${result.totalTokenUsage.estimatedCostUsd.toFixed(4)}`
535
+ ];
536
+ if (result.taskResults.length > 0) {
537
+ lines.push(c.gray(" ├────────────────────────────────────────────┤"));
538
+ lines.push(` │ ${c.bold("任务拆分")} (${result.taskResults.length} 个子任务):`);
539
+ for (const tr of result.taskResults) {
540
+ const icon = tr.status === "success" ? c.green("✓") : tr.status === "partial" ? c.yellow("~") : c.red("✗");
541
+ lines.push(` │ ${icon} ${tr.taskId.slice(0, 12)}...`);
542
+ }
543
+ }
544
+ if (result.allArtifacts.length > 0) {
545
+ lines.push(c.gray(" ├────────────────────────────────────────────┤"));
546
+ lines.push(` │ ${c.bold("制品:")}`);
547
+ for (const artifact of result.allArtifacts) {
548
+ const icon = artifact.type === "pull-request" ? "🔗" : "📄";
549
+ lines.push(` │ ${icon} ${artifact.description} (${artifact.reference})`);
550
+ }
551
+ }
552
+ if (result.nextSteps && result.nextSteps.length > 0) {
553
+ lines.push(c.gray(" ├────────────────────────────────────────────┤"));
554
+ lines.push(` │ ${c.bold("建议:")}`);
555
+ for (let i = 0; i < result.nextSteps.length; i++) lines.push(` │ ${i + 1}. ${result.nextSteps[i]}`);
556
+ }
557
+ lines.push(c.gray(" └────────────────────────────────────────────┘"));
558
+ lines.push("");
559
+ return lines;
560
+ }
561
+ /** 格式化任务拆分概览 */
562
+ formatTaskGraph(graph) {
563
+ const c = this.getColor();
564
+ const lines = [
565
+ "",
566
+ c.bold(" 📋 任务拆分概览"),
567
+ c.gray(` 共 ${graph.nodes.size} 个子任务,${graph.layers.length} 层并行`),
568
+ ""
569
+ ];
570
+ for (let layerIdx = 0; layerIdx < graph.layers.length; layerIdx++) {
571
+ const layer = graph.layers[layerIdx];
572
+ if (!layer) continue;
573
+ lines.push(c.gray(` 第 ${layerIdx + 1} 层 (可并行):`));
574
+ for (const taskId of layer) {
575
+ const task = graph.nodes.get(taskId);
576
+ if (task) {
577
+ const statusIcon = this.statusToIcon(task.status, c);
578
+ lines.push(` ${statusIcon} ${c.cyan(task.name)} - ${task.description.slice(0, 50)}`);
579
+ }
580
+ }
581
+ lines.push("");
582
+ }
583
+ return lines;
584
+ }
585
+ /** 格式化 Agent 列表 */
586
+ formatAgents(agents) {
587
+ const c = this.getColor();
588
+ const lines = [
589
+ "",
590
+ c.bold(" 🤖 已注册 Agent"),
591
+ ""
592
+ ];
593
+ if (agents.length === 0) {
594
+ lines.push(c.gray(" 暂无已注册的 Agent"));
595
+ lines.push("");
596
+ return lines;
597
+ }
598
+ lines.push(` ${c.bold("ID").padEnd(20)} ${c.bold("状态").padEnd(10)} ${c.bold("负载").padEnd(8)} ${c.bold("能力")}`);
599
+ lines.push(c.gray(` ${"─".repeat(60)}`));
600
+ for (const agent of agents) {
601
+ const statusDot = agent.status === "online" ? c.green("●") : agent.status === "busy" ? c.yellow("●") : c.gray("○");
602
+ const capabilities = agent.capabilities.map((cap) => cap.name).join(", ");
603
+ lines.push(` ${agent.agentId.padEnd(20)} ${statusDot} ${String(agent.status).padEnd(8)} ${String(agent.currentLoad).padEnd(8)} ${capabilities}`);
604
+ }
605
+ lines.push("");
606
+ return lines;
607
+ }
608
+ /** 格式化配置信息 */
609
+ formatConfig(config) {
610
+ const c = this.getColor();
611
+ const lines = [
612
+ "",
613
+ c.bold(" ⚙️ 当前配置"),
614
+ "",
615
+ c.bold(" LLM:")
616
+ ];
617
+ lines.push(` 默认模型: ${config.llm.defaultModel}`);
618
+ const defaultModelConfig = config.llm.models[config.llm.defaultModel];
619
+ if (defaultModelConfig) {
620
+ lines.push(` 提供商: ${defaultModelConfig.provider}`);
621
+ lines.push(` 模型 ID: ${defaultModelConfig.modelId}`);
622
+ }
623
+ const auth = config.llm.providers[defaultModelConfig?.provider ?? "anthropic"];
624
+ lines.push(` API Key: ${auth?.apiKey ? c.gray("***已配置***") : c.red("(未配置)")}`);
625
+ lines.push(c.bold(" 调度器:"));
626
+ lines.push(` 最大并行: ${config.scheduler.maxConcurrency}`);
627
+ lines.push(` 单 Agent 最大并发: ${config.scheduler.maxPerAgent}`);
628
+ lines.push(` 任务超时: ${(config.scheduler.taskTimeoutMs / 1e3 / 60).toFixed(0)} 分钟`);
629
+ lines.push(` 最大重试: ${config.scheduler.maxRetries}`);
630
+ lines.push(c.bold(" CLI:"));
631
+ lines.push(` 颜色输出: ${config.cli.color ? c.green("开启") : c.gray("关闭")}`);
632
+ lines.push(` 调试模式: ${config.cli.debug ? c.green("开启") : c.gray("关闭")}`);
633
+ lines.push(` 输出格式: ${config.cli.outputFormat}`);
634
+ lines.push(c.bold(" Agents:"));
635
+ for (const agent of config.agents) {
636
+ const statusIcon = agent.enabled ? c.green("✓") : c.gray("✗");
637
+ lines.push(` ${statusIcon} ${agent.id}`);
638
+ }
639
+ lines.push("");
640
+ return lines;
641
+ }
642
+ /** 格式化历史记录 */
643
+ formatHistory(entries) {
644
+ const c = this.getColor();
645
+ const lines = [
646
+ "",
647
+ c.bold(" 📜 会话历史"),
648
+ ""
649
+ ];
650
+ if (entries.length === 0) {
651
+ lines.push(c.gray(" 暂无历史记录"));
652
+ lines.push("");
653
+ return lines;
654
+ }
655
+ for (const entry of entries) {
656
+ const time = new Date(entry.timestamp).toTimeString().slice(0, 8);
657
+ const inputPreview = entry.input.length > 50 ? `${entry.input.slice(0, 47)}...` : entry.input;
658
+ let line = ` #${String(entry.id).padStart(3)} ${c.gray(`[${time}]`)} ${inputPreview}`;
659
+ if (entry.durationMs !== void 0) line += c.gray(` (${(entry.durationMs / 1e3).toFixed(1)}s)`);
660
+ lines.push(line);
661
+ }
662
+ lines.push("");
663
+ return lines;
664
+ }
665
+ /** 格式化会话状态 */
666
+ formatStatus(stats) {
667
+ const c = this.getColor();
668
+ const lines = [
669
+ "",
670
+ c.bold(" 📊 会话状态"),
671
+ ""
672
+ ];
673
+ const stateLabel = stats.state === "idle" ? c.green("空闲") : stats.state === "executing" ? c.magenta("执行中") : c.gray("关闭中");
674
+ lines.push(` 状态: ${stateLabel}`);
675
+ lines.push(` 总请求数: ${stats.totalRequests}`);
676
+ lines.push(` 成功: ${c.green(String(stats.successCount))}`);
677
+ lines.push(` 失败: ${stats.failureCount > 0 ? c.red(String(stats.failureCount)) : String(stats.failureCount)}`);
678
+ lines.push(` Token 消耗: ${stats.totalTokens.toLocaleString()}`);
679
+ lines.push(` 总成本: $${stats.totalCostUsd.toFixed(4)}`);
680
+ lines.push("");
681
+ return lines;
682
+ }
683
+ statusToIcon(status, c) {
684
+ switch (status) {
685
+ case "succeeded": return `${c.green(" ✓")}`;
686
+ case "running": return `${c.magenta(" ⟳")}`;
687
+ case "failed": return `${c.red(" ✗")}`;
688
+ case "cancelled": return `${c.gray(" ⊘")}`;
689
+ case "skipped": return `${c.gray(" ⊘")}`;
690
+ default: return `${c.gray(" ○")}`;
691
+ }
692
+ }
693
+ };
694
+
695
+ //#endregion
696
+ //#region src/cli/repl/renderer.ts
697
+ /**
698
+ * 终端输出渲染器(TUI 适配版)
699
+ *
700
+ * 在 pi-tui 架构下,Renderer 不再直接 console.log,
701
+ * 而是将格式化后的内容追加到 OutputArea 组件中。
702
+ */
703
+ /**
704
+ * 渲染器实现
705
+ *
706
+ * 协调 OutputFormatter 和 OutputArea 之间的内容输出。
707
+ */
708
+ var Renderer = class {
709
+ formatter;
710
+ constructor(opts) {
711
+ this.formatter = new OutputFormatter(opts.color);
712
+ }
713
+ /** 获取底层格式化器(供 OutputArea 直接使用) */
714
+ getFormatter() {
715
+ return this.formatter;
716
+ }
717
+ /** 渲染欢迎信息 → 返回格式化行 */
718
+ renderWelcome(version) {
719
+ return this.formatter.formatWelcome(version);
720
+ }
721
+ /** 渲染错误信息 → 返回格式化行 */
722
+ renderError(error) {
723
+ return this.formatter.formatError(error);
724
+ }
725
+ /** 渲染最终执行结果 → 返回格式化行 */
726
+ renderResult(result) {
727
+ return this.formatter.formatResult(result);
728
+ }
729
+ /** 渲染任务拆分概览 → 返回格式化行 */
730
+ renderTaskGraph(graph) {
731
+ return this.formatter.formatTaskGraph(graph);
732
+ }
733
+ /** 渲染 Agent 列表 → 返回格式化行 */
734
+ renderAgents(agents) {
735
+ return this.formatter.formatAgents(agents);
736
+ }
737
+ /** 渲染配置信息 → 返回格式化行 */
738
+ renderConfig(config) {
739
+ return this.formatter.formatConfig(config);
740
+ }
741
+ /** 渲染历史记录 → 返回格式化行 */
742
+ renderHistory(entries) {
743
+ return this.formatter.formatHistory(entries);
744
+ }
745
+ /** 渲染会话状态 → 返回格式化行 */
746
+ renderStatus(stats) {
747
+ return this.formatter.formatStatus(stats);
748
+ }
749
+ };
750
+
751
+ //#endregion
752
+ //#region src/cli/repl/repl-agent-tools.ts
753
+ /**
754
+ * 创建 REPL 基础工具集
755
+ *
756
+ * 第一阶段工具:验证 Agent 工具调用链路的端到端连通性。
757
+ * 后续阶段在此基础扩展:文件读写、Shell 执行、Git 操作等。
758
+ */
759
+ function createReplBuiltinTools() {
760
+ return [
761
+ {
762
+ id: "get_current_time",
763
+ label: "获取当前时间",
764
+ description: "获取当前日期和时间。当用户询问时间、需要时间戳、或需要时间相关上下文时调用此工具。",
765
+ execute: async () => ({
766
+ content: [{
767
+ type: "text",
768
+ text: (/* @__PURE__ */ new Date()).toISOString()
769
+ }],
770
+ details: { timestamp: Date.now() }
771
+ })
772
+ },
773
+ {
774
+ id: "get_workdir_info",
775
+ label: "获取工作目录信息",
776
+ description: "获取当前工作目录路径和系统平台信息。当需要了解当前项目位置或运行环境时调用。",
777
+ execute: async () => ({
778
+ content: [{
779
+ type: "text",
780
+ text: JSON.stringify({
781
+ cwd: process.cwd(),
782
+ platform: process.platform,
783
+ arch: process.arch,
784
+ nodeVersion: process.version
785
+ }, null, 2)
786
+ }],
787
+ details: {
788
+ cwd: process.cwd(),
789
+ platform: process.platform
790
+ }
791
+ })
792
+ },
793
+ {
794
+ id: "read_file",
795
+ label: "读取文件",
796
+ description: "读取指定路径的文本文件内容。参数 path 为文件绝对或相对路径。",
797
+ parameters: {
798
+ type: "object",
799
+ properties: { path: {
800
+ type: "string",
801
+ description: "文件路径"
802
+ } },
803
+ required: ["path"]
804
+ },
805
+ execute: async (_toolCallId, params) => {
806
+ const fs = await import("node:fs/promises");
807
+ try {
808
+ const content = await fs.readFile(params.path, "utf-8");
809
+ return {
810
+ content: [{
811
+ type: "text",
812
+ text: content
813
+ }],
814
+ details: {
815
+ path: params.path,
816
+ size: content.length
817
+ }
818
+ };
819
+ } catch (error) {
820
+ return {
821
+ content: [{
822
+ type: "text",
823
+ text: `读取失败: ${error instanceof Error ? error.message : String(error)}`
824
+ }],
825
+ details: {
826
+ path: params.path,
827
+ error: true
828
+ }
829
+ };
830
+ }
831
+ }
832
+ }
833
+ ];
834
+ }
835
+
836
+ //#endregion
837
+ //#region src/cli/repl/theme.ts
838
+ /** 根据颜色开关获取 chalk 实例 */
839
+ function makeColor(colorEnabled) {
840
+ return colorEnabled ? chalk : new Chalk({ level: 0 });
841
+ }
842
+ /**
843
+ * 创建 zapmyco pi-tui 主题
844
+ */
845
+ function createTheme(colorEnabled) {
846
+ const c = makeColor(colorEnabled);
847
+ /** 基础 selectList 主题 */
848
+ const baseSelectListTheme = {
849
+ selectedPrefix: (text) => c.cyan(text),
850
+ selectedText: (text) => c.bold(c.cyan(text)),
851
+ description: (text) => c.gray(text),
852
+ scrollInfo: (text) => c.gray(text),
853
+ noMatch: (text) => c.gray(text)
854
+ };
855
+ return {
856
+ /** 主文本色 */
857
+ text: (s) => s,
858
+ /** 加粗 */
859
+ bold: (s) => c.bold(s),
860
+ /** 灰色/弱化文本 */
861
+ dim: (s) => c.gray(s),
862
+ /** 强调色 - 青色 */
863
+ accent: (s) => c.cyan(s),
864
+ /** 成功 - 绿色 */
865
+ success: (s) => c.green(s),
866
+ /** 错误 - 红色 */
867
+ error: (s) => c.red(s),
868
+ /** 警告 - 黄色 */
869
+ warning: (s) => c.yellow(s),
870
+ /** 边框色 - 灰色 */
871
+ border: (s) => c.gray(s),
872
+ /** Header 文本 */
873
+ heading: (s) => c.bold(s),
874
+ editorTheme: {
875
+ borderColor: (text) => c.gray(text),
876
+ selectList: baseSelectListTheme
877
+ },
878
+ selectListTheme: baseSelectListTheme
879
+ };
880
+ }
881
+
882
+ //#endregion
883
+ //#region src/llm/pi-ai-provider.ts
884
+ /**
885
+ * 解析模型标识符
886
+ *
887
+ * 支持格式:provider/modelId(如 anthropic/claude-sonnet-4-20250514)
888
+ * 导出供其他模块复用(如 REPL Session 为 Agent 解析 Model 对象)
889
+ */
890
+ function parseModelKey(key) {
891
+ const slashIndex = key.indexOf("/");
892
+ if (slashIndex <= 0 || slashIndex >= key.length - 1) return null;
893
+ return {
894
+ provider: key.slice(0, slashIndex),
895
+ modelId: key.slice(slashIndex + 1)
896
+ };
897
+ }
898
+
899
+ //#endregion
900
+ //#region src/cli/repl/session.ts
901
+ const log = logger.child("repl:session");
902
+ /**
903
+ * 输出区域组件
904
+ *
905
+ * 管理所有输出内容的行缓冲,实现 pi-tui 的 render 接口。
906
+ */
907
+ var OutputArea = class extends Container {
908
+ lines = [];
909
+ render(width) {
910
+ const result = [];
911
+ for (const line of this.lines) result.push(...wrapTextWithAnsi(line, width));
912
+ return result;
913
+ }
914
+ /** 追加多行内容 */
915
+ append(lines) {
916
+ this.lines.push(...lines);
917
+ this.invalidate();
918
+ }
919
+ /** 追加文本到当前行末尾(用于流式输出) */
920
+ appendText(text) {
921
+ if (this.lines.length === 0) this.lines.push(text);
922
+ else this.lines[this.lines.length - 1] += text;
923
+ this.invalidate();
924
+ }
925
+ /** 清空所有内容 */
926
+ clear() {
927
+ this.lines = [];
928
+ this.invalidate();
929
+ }
930
+ };
931
+ /** 默认提示符(用于显示/格式化) */
932
+ const DEFAULT_PROMPT = "❯ ";
933
+ /** 默认续行提示符 */
934
+ const DEFAULT_CONTINUATION_PROMPT = "... ";
935
+ /**
936
+ * REPL 会话实现
937
+ */
938
+ var ReplSession = class {
939
+ tui;
940
+ editor;
941
+ outputArea;
942
+ header;
943
+ footer;
944
+ options;
945
+ _state = "idle";
946
+ parser;
947
+ registry;
948
+ renderer;
949
+ history;
950
+ currentTaskAbort = null;
951
+ /** Agent 实例(会话级复用,替代直接 LLM 调用) */
952
+ agent;
953
+ /** 当前正在执行的 taskId(用于取消操作) */
954
+ currentTaskId = null;
955
+ /** 多轮对话上下文(兼容保留,Agent 内部也维护历史) */
956
+ conversationHistory = [];
957
+ stats = {
958
+ totalRequests: 0,
959
+ successCount: 0,
960
+ failureCount: 0,
961
+ totalTokens: 0,
962
+ totalCostUsd: 0,
963
+ state: "idle"
964
+ };
965
+ constructor(config) {
966
+ this.config = config;
967
+ this.options = {
968
+ color: config.cli.color,
969
+ debug: config.cli.debug,
970
+ maxHistorySize: 100,
971
+ prompt: DEFAULT_PROMPT,
972
+ continuationPrompt: DEFAULT_CONTINUATION_PROMPT
973
+ };
974
+ const theme = createTheme(this.options.color);
975
+ const terminal = new ProcessTerminal();
976
+ this.tui = new TUI(terminal);
977
+ this.header = new Text("", 1, 0);
978
+ this.outputArea = new OutputArea();
979
+ this.footer = new Text("", 1, 0);
980
+ this.editor = new ZapmycoEditor(this.tui, theme.editorTheme);
981
+ const root = new Container();
982
+ root.addChild(this.header);
983
+ root.addChild(this.outputArea);
984
+ root.addChild(this.footer);
985
+ root.addChild(this.editor);
986
+ this.tui.addChild(root);
987
+ this.tui.setFocus(this.editor);
988
+ this.parser = new InputParser();
989
+ this.registry = new CommandRegistry(this);
990
+ this.renderer = new Renderer(this.options);
991
+ this.history = new HistoryStore(this.options.maxHistorySize);
992
+ this.agent = this.createReplAgent();
993
+ this.registerBuiltinCommands();
994
+ this.registerBuiltinTools();
995
+ this.setupEditorHandlers();
996
+ this.setupSignalHandlers();
997
+ this.setupEventListeners();
998
+ }
999
+ get currentState() {
1000
+ return this._state;
1001
+ }
1002
+ get replOptions() {
1003
+ return this.options;
1004
+ }
1005
+ /** 启动 REPL 循环 */
1006
+ async start() {
1007
+ this._state = "idle";
1008
+ this.updateStatsState();
1009
+ const welcomeLines = this.renderer.renderWelcome(__VERSION__);
1010
+ this.outputArea.append(welcomeLines);
1011
+ this.updateHeader();
1012
+ this.updateFooter();
1013
+ this.tui.start();
1014
+ }
1015
+ /** 优雅关闭会话 */
1016
+ async shutdown(reason) {
1017
+ if (this._state === "shutting-down") return;
1018
+ this._state = "shutting-down";
1019
+ this.updateStatsState();
1020
+ log.info("REPL 关闭", { reason: reason ?? "未知" });
1021
+ this.cancelCurrentTask();
1022
+ eventBus.emit("system:shutdown", { reason });
1023
+ this.tui.stop();
1024
+ }
1025
+ /** 获取渲染器引用 */
1026
+ getRenderer() {
1027
+ return this.renderer;
1028
+ }
1029
+ /** 获取历史存储引用 */
1030
+ getHistoryStore() {
1031
+ return this.history;
1032
+ }
1033
+ /** 获取会话统计 */
1034
+ getStats() {
1035
+ return { ...this.stats };
1036
+ }
1037
+ /** 将内容追加到输出区域 */
1038
+ appendOutput(lines) {
1039
+ this.outputArea.append(lines);
1040
+ this.tui.requestRender();
1041
+ }
1042
+ /** 清空输出区域 */
1043
+ clearOutput() {
1044
+ this.outputArea.clear();
1045
+ this.tui.requestRender();
1046
+ }
1047
+ /** 请求 TUI 重绘 */
1048
+ requestRender() {
1049
+ this.tui.requestRender();
1050
+ }
1051
+ /**
1052
+ * 执行用户目标 — 通过 Agent 执行并流式输出回复
1053
+ */
1054
+ async executeGoal(rawInput) {
1055
+ const startTime = Date.now();
1056
+ let historyEntry;
1057
+ const taskId = `task-${Date.now()}`;
1058
+ try {
1059
+ this._state = "executing";
1060
+ this.updateStatsState();
1061
+ this.updateFooter();
1062
+ this.currentTaskAbort = new AbortController();
1063
+ historyEntry = this.history.push({
1064
+ timestamp: Date.now(),
1065
+ input: rawInput
1066
+ });
1067
+ eventBus.emit("goal:submitted", {
1068
+ goalId: `goal-${startTime}`,
1069
+ rawInput
1070
+ });
1071
+ const goalLines = [
1072
+ "",
1073
+ ` 🎯 ${rawInput}`,
1074
+ "",
1075
+ " 💬 "
1076
+ ];
1077
+ this.outputArea.append(goalLines);
1078
+ const outputHandler = (event) => {
1079
+ if (event.taskId === taskId) {
1080
+ this.outputArea.appendText(event.text);
1081
+ this.tui.requestRender();
1082
+ }
1083
+ };
1084
+ const errorHandler = (event) => {
1085
+ if (event.taskId === taskId) log.error("Agent 执行中收到 error 事件", { error: event.error.message });
1086
+ };
1087
+ this.agent.on(this.agent.EVENT_OUTPUT, outputHandler);
1088
+ this.agent.on(this.agent.EVENT_ERROR, errorHandler);
1089
+ this.currentTaskId = taskId;
1090
+ log.debug("开始通过 Agent 执行目标", {
1091
+ taskId,
1092
+ taskDescription: rawInput.slice(0, 100)
1093
+ });
1094
+ const taskResult = await this.agent.execute({
1095
+ taskId,
1096
+ taskDescription: rawInput,
1097
+ workdir: process.cwd(),
1098
+ options: {
1099
+ timeout: this.config.scheduler.taskTimeoutMs,
1100
+ verbose: this.options.debug
1101
+ }
1102
+ });
1103
+ this.agent.off(this.agent.EVENT_OUTPUT, outputHandler);
1104
+ this.agent.off(this.agent.EVENT_ERROR, errorHandler);
1105
+ log.debug("Agent 执行完成", {
1106
+ taskId,
1107
+ status: taskResult.status,
1108
+ hasOutput: taskResult.output != null,
1109
+ duration: Date.now() - startTime
1110
+ });
1111
+ const outputText = typeof taskResult.output === "string" ? taskResult.output : taskResult.output != null ? JSON.stringify(taskResult.output) : null;
1112
+ if (taskResult.status !== "success") {
1113
+ const errorMsg = taskResult.error?.message ?? "Agent 执行失败(无详细错误信息)";
1114
+ this.outputArea.appendText(`[错误] ${errorMsg}`);
1115
+ log.error("Agent 执行返回 failure", {
1116
+ taskId,
1117
+ error: taskResult.error,
1118
+ status: taskResult.status
1119
+ });
1120
+ } else if (outputText) this.outputArea.appendText(outputText);
1121
+ this.outputArea.append(["", ""]);
1122
+ const duration = Date.now() - startTime;
1123
+ const result = {
1124
+ goalId: `goal-${startTime}`,
1125
+ overallStatus: taskResult.status === "success" ? "success" : "failure",
1126
+ summary: outputText?.slice(0, 200) ?? "(无输出)",
1127
+ taskResults: [taskResult],
1128
+ allArtifacts: taskResult.artifacts ?? [],
1129
+ totalDuration: duration,
1130
+ totalTokenUsage: taskResult.tokenUsage ?? {
1131
+ inputTokens: 0,
1132
+ outputTokens: 0,
1133
+ totalTokens: 0,
1134
+ estimatedCostUsd: 0
1135
+ }
1136
+ };
1137
+ this.stats.totalRequests++;
1138
+ if (taskResult.status === "success") this.stats.successCount++;
1139
+ else this.stats.failureCount++;
1140
+ this.conversationHistory.push({
1141
+ role: "user",
1142
+ content: rawInput
1143
+ });
1144
+ if (outputText) this.conversationHistory.push({
1145
+ role: "assistant",
1146
+ content: outputText
1147
+ });
1148
+ eventBus.emit("goal:completed", {
1149
+ goalId: result.goalId,
1150
+ result
1151
+ });
1152
+ if (historyEntry) {
1153
+ historyEntry.goalId = result.goalId;
1154
+ historyEntry.durationMs = duration;
1155
+ }
1156
+ return result;
1157
+ } catch (error) {
1158
+ const err = error instanceof Error ? error : new Error(String(error));
1159
+ log.error("目标执行失败", { input: rawInput }, err);
1160
+ this.stats.totalRequests++;
1161
+ this.stats.failureCount++;
1162
+ eventBus.emit("goal:failed", {
1163
+ goalId: `goal-${startTime}`,
1164
+ error: err
1165
+ });
1166
+ const errorLines = this.renderer.renderError(err);
1167
+ this.outputArea.append(errorLines);
1168
+ const duration = Date.now() - startTime;
1169
+ return {
1170
+ goalId: `goal-${startTime}`,
1171
+ overallStatus: "failure",
1172
+ summary: `执行失败: ${err.message}`,
1173
+ taskResults: [],
1174
+ allArtifacts: [],
1175
+ totalDuration: duration,
1176
+ totalTokenUsage: {
1177
+ inputTokens: 0,
1178
+ outputTokens: 0,
1179
+ totalTokens: 0,
1180
+ estimatedCostUsd: 0
1181
+ }
1182
+ };
1183
+ } finally {
1184
+ this._state = "idle";
1185
+ this.updateStatsState();
1186
+ this.updateFooter();
1187
+ this.currentTaskAbort = null;
1188
+ this.currentTaskId = null;
1189
+ }
1190
+ }
1191
+ /** 获取命令注册表(供 help 命令使用) */
1192
+ getCommandRegistry() {
1193
+ return this.registry;
1194
+ }
1195
+ /** 获取输入解析器(供 clear 命令使用) */
1196
+ getInputParser() {
1197
+ return this.parser;
1198
+ }
1199
+ /**
1200
+ * 处理用户提交的输入
1201
+ *
1202
+ * 由 editor.onSubmit 触发。
1203
+ */
1204
+ async handleSubmit(line) {
1205
+ if (this._state === "shutting-down") return;
1206
+ const parsed = this.parser.parse(line);
1207
+ switch (parsed.kind) {
1208
+ case "empty": break;
1209
+ case "incomplete": break;
1210
+ case "command":
1211
+ await this.registry.dispatch(parsed);
1212
+ break;
1213
+ case "goal":
1214
+ await this.executeGoal(parsed.rawInput);
1215
+ break;
1216
+ }
1217
+ }
1218
+ /** 注册所有内置命令 */
1219
+ registerBuiltinCommands() {
1220
+ this.registry.register(createHelpCommand());
1221
+ this.registry.register(createQuitCommand());
1222
+ this.registry.register(createClearCommand());
1223
+ this.registry.register(createHistoryCommand());
1224
+ this.registry.register(createConfigCommand());
1225
+ this.registry.register(createAgentsCommand());
1226
+ this.registry.register(createStatusCommand());
1227
+ }
1228
+ /**
1229
+ * 创建 REPL 专用的 Agent 实例
1230
+ *
1231
+ * Agent 复用 pi-ai 的 Model 对象进行 LLM 调用,
1232
+ * 因此需要从 config.llm 解析 model 并注入到 Agent state。
1233
+ */
1234
+ createReplAgent() {
1235
+ const agent = createLlmBasedAgent({
1236
+ agentId: "repl-chat-agent",
1237
+ displayName: "Zapmyco AI 助手",
1238
+ capabilities: [{
1239
+ id: "chat",
1240
+ name: "对话",
1241
+ description: "自然语言对话、问答、任务编排",
1242
+ category: "chat"
1243
+ }],
1244
+ runtimeConfig: this.config.agentRuntime ?? {}
1245
+ });
1246
+ agent.innerAgent.state.model = this.resolveModelForAgent();
1247
+ agent.innerAgent.getApiKey = (_provider) => {
1248
+ const modelKey = this.config.llm.defaultModel;
1249
+ const providerName = this.config.llm.models[modelKey]?.provider;
1250
+ if (providerName) return this.config.llm.providers[providerName]?.apiKey;
1251
+ };
1252
+ return agent;
1253
+ }
1254
+ /**
1255
+ * 为 Agent 解析 pi-ai Model 对象
1256
+ *
1257
+ * 复用 PiAiProvider 的模型解析逻辑(parseModelKey + getModel),
1258
+ * 但不依赖 chat/chatStream 方法。
1259
+ */
1260
+ resolveModelForAgent() {
1261
+ const modelKey = this.config.llm.defaultModel;
1262
+ const parsed = parseModelKey(modelKey);
1263
+ if (!parsed) throw new Error(`无效的模型标识符: ${modelKey}`);
1264
+ const modelConfig = this.config.llm.models[modelKey];
1265
+ const provider = modelConfig?.provider ?? parsed.provider;
1266
+ const modelId = modelConfig?.modelId ?? parsed.modelId;
1267
+ const baseModelId = provider === "anthropic" ? "claude-sonnet-4-20250514" : modelId;
1268
+ let model;
1269
+ try {
1270
+ model = getModel(provider, baseModelId);
1271
+ } catch {
1272
+ model = getModel("anthropic", "claude-sonnet-4-20250514");
1273
+ }
1274
+ if (!model) throw new Error(`无法初始化模型 ${modelKey}:pi-ai 返回了无效的模型对象`);
1275
+ model.name = modelKey;
1276
+ model.id = modelId;
1277
+ if (modelConfig?.baseUrl) model.baseUrl = modelConfig.baseUrl;
1278
+ return model;
1279
+ }
1280
+ /**
1281
+ * 注册 REPL 场景下的基础工具
1282
+ */
1283
+ registerBuiltinTools() {
1284
+ this.agent.registerTools(createReplBuiltinTools());
1285
+ }
1286
+ /** 设置编辑器事件绑定 */
1287
+ setupEditorHandlers() {
1288
+ this.editor.onSubmit = (text) => void this.handleSubmit(text);
1289
+ let ctrlCPressCount = 0;
1290
+ let ctrlCTimer;
1291
+ this.editor.onCtrlC = () => {
1292
+ if (this._state === "executing") {
1293
+ this.cancelCurrentTask();
1294
+ this.outputArea.append([
1295
+ "",
1296
+ " 任务已取消",
1297
+ ""
1298
+ ]);
1299
+ return;
1300
+ }
1301
+ ctrlCPressCount++;
1302
+ if (ctrlCPressCount >= 2) {
1303
+ this.outputArea.append([
1304
+ "",
1305
+ " 再见!",
1306
+ ""
1307
+ ]);
1308
+ this.shutdown("用户连续按下 Ctrl+C");
1309
+ return;
1310
+ }
1311
+ this.outputArea.append([
1312
+ "",
1313
+ " (再次按下 Ctrl+C 可强制退出)",
1314
+ ""
1315
+ ]);
1316
+ clearTimeout(ctrlCTimer);
1317
+ ctrlCTimer = setTimeout(() => {
1318
+ ctrlCPressCount = 0;
1319
+ }, 3e3);
1320
+ };
1321
+ this.editor.onCtrlD = () => {
1322
+ this.outputArea.append([
1323
+ "",
1324
+ " 再见!",
1325
+ ""
1326
+ ]);
1327
+ this.shutdown("收到 EOF (Ctrl+D)");
1328
+ };
1329
+ }
1330
+ /** 设置信号处理 */
1331
+ setupSignalHandlers() {
1332
+ process.on("SIGINT", () => {});
1333
+ }
1334
+ /** 设置事件监听 */
1335
+ setupEventListeners() {
1336
+ eventBus.on("system:shutdown", ({ reason }) => {
1337
+ log.debug(`收到系统关闭信号: ${reason ?? "未知"}`);
1338
+ });
1339
+ }
1340
+ /** 取消当前正在执行的任务 */
1341
+ cancelCurrentTask() {
1342
+ if (this.currentTaskId !== null) {
1343
+ this.agent.cancel(this.currentTaskId);
1344
+ this.currentTaskId = null;
1345
+ }
1346
+ if (this.currentTaskAbort !== null) {
1347
+ this.currentTaskAbort.abort();
1348
+ this.currentTaskAbort = null;
1349
+ }
1350
+ }
1351
+ /** 更新统计中的状态字段 */
1352
+ updateStatsState() {
1353
+ this.stats.state = this._state;
1354
+ }
1355
+ /** 更新 header 文本 */
1356
+ updateHeader() {
1357
+ const theme = createTheme(this.options.color);
1358
+ this.header.setText(theme.heading(` zapmyco@${__VERSION__}`));
1359
+ }
1360
+ /** 更新 footer 文本 */
1361
+ updateFooter() {
1362
+ const theme = createTheme(this.options.color);
1363
+ const stateLabel = this._state === "idle" ? theme.success("空闲") : this._state === "executing" ? theme.warning("执行中") : theme.dim("关闭中");
1364
+ this.footer.setText(` ${stateLabel}`);
1365
+ }
1366
+ };
1367
+
1368
+ //#endregion
1369
+ //#region src/cli/repl/index.ts
1370
+ /**
1371
+ * REPL 模块入口
1372
+ *
1373
+ * 导出 startRepl() 函数作为 REPL 交互模式的启动入口。
1374
+ */
1375
+ /**
1376
+ * 启动 REPL 交互模式
1377
+ *
1378
+ * 加载配置 → 创建会话 → 进入输入循环
1379
+ */
1380
+ async function startRepl() {
1381
+ await new ReplSession(await loadConfig()).start();
1382
+ }
1383
+
1384
+ //#endregion
1385
+ //#region src/cli/index.ts
1386
+ /**
1387
+ * zapmyco CLI 入口
1388
+ *
1389
+ * AI 总管命令行界面。
1390
+ *
1391
+ * 使用方式:
1392
+ * zapmyco 进入交互式 REPL 模式
1393
+ * zapmyco run <goal> 直接执行单次目标
1394
+ * zapmyco agents 列出可用 Agent
1395
+ * zapmyco config 管理配置
1396
+ * zapmyco version 显示版本号
1397
+ */
1398
+ const program = new Command();
1399
+ program.name(APP_NAME).description("AI 原生并行任务编排系统 -- AI 总管").version(__VERSION__, "-v, --version", "显示版本号").helpOption("-h, --help", "显示帮助信息");
1400
+ program.action(async () => {
1401
+ try {
1402
+ await startRepl();
1403
+ } catch (error) {
1404
+ const message = error instanceof Error ? error.message : String(error);
1405
+ console.error(chalk.red("REPL 启动失败:"), message);
1406
+ process.exit(1);
1407
+ }
1408
+ });
1409
+ program.command("run").description("直接执行一个目标(非交互模式)").argument("<goal>", "要执行的目标描述").option("-j, --json", "以 JSON 格式输出结果").option("--no-color", "禁用颜色输出").action((goal, _options) => {
1410
+ console.log(`🎯 目标: ${goal}`);
1411
+ console.log(chalk.yellow("\n 任务执行引擎即将推出,敬请期待!\n"));
1412
+ });
1413
+ program.command("agents").description("列出已注册的 Agent 及其状态").action(() => {
1414
+ console.log(chalk.cyan("\n 已注册 Agent:\n"));
1415
+ console.log(chalk.gray(" · code-agent 代码专家"));
1416
+ console.log(chalk.gray(" · security-scanner 安全扫描"));
1417
+ console.log(chalk.gray(" · research-agent 信息搜集"));
1418
+ console.log(chalk.gray(" · planning-agent 规划安排"));
1419
+ console.log(chalk.yellow("\n Agent 注册中心即将完全启用\n"));
1420
+ });
1421
+ program.command("config").description("管理配置").action(() => {
1422
+ console.log(chalk.cyan("配置管理功能即将推出"));
1423
+ });
1424
+ program.parse();
1425
+
1426
+ //#endregion
1427
+ export { };
1428
+ //# sourceMappingURL=index.mjs.map