@ai-zen/air 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -6
- package/README.zh.md +31 -6
- package/dist/agent-factory.js +1 -1
- package/dist/agent-factory.js.map +1 -1
- package/dist/agent-runtime.d.ts.map +1 -1
- package/dist/agent-runtime.js +241 -228
- package/dist/agent-runtime.js.map +1 -1
- package/dist/chat/commands/back.d.ts +3 -0
- package/dist/chat/commands/back.d.ts.map +1 -0
- package/dist/chat/commands/back.js +95 -0
- package/dist/chat/commands/back.js.map +1 -0
- package/dist/chat/commands/editor.d.ts +3 -0
- package/dist/chat/commands/editor.d.ts.map +1 -0
- package/dist/chat/commands/editor.js +20 -0
- package/dist/chat/commands/editor.js.map +1 -0
- package/dist/chat/commands/exit.d.ts +3 -0
- package/dist/chat/commands/exit.d.ts.map +1 -0
- package/dist/chat/commands/exit.js +5 -0
- package/dist/chat/commands/exit.js.map +1 -0
- package/dist/chat/commands/help.d.ts +2 -0
- package/dist/chat/commands/help.d.ts.map +1 -0
- package/dist/chat/commands/help.js +4 -0
- package/dist/chat/commands/help.js.map +1 -0
- package/dist/chat/commands/index.d.ts +3 -0
- package/dist/chat/commands/index.d.ts.map +1 -0
- package/dist/chat/commands/index.js +37 -0
- package/dist/chat/commands/index.js.map +1 -0
- package/dist/chat/commands/load.d.ts +3 -0
- package/dist/chat/commands/load.d.ts.map +1 -0
- package/dist/chat/commands/load.js +37 -0
- package/dist/chat/commands/load.js.map +1 -0
- package/dist/chat/commands/message.d.ts +5 -0
- package/dist/chat/commands/message.d.ts.map +1 -0
- package/dist/chat/commands/message.js +53 -0
- package/dist/chat/commands/message.js.map +1 -0
- package/dist/chat/commands/new.d.ts +3 -0
- package/dist/chat/commands/new.d.ts.map +1 -0
- package/dist/chat/commands/new.js +11 -0
- package/dist/chat/commands/new.js.map +1 -0
- package/dist/chat/commands/save.d.ts +3 -0
- package/dist/chat/commands/save.d.ts.map +1 -0
- package/dist/chat/commands/save.js +5 -0
- package/dist/chat/commands/save.js.map +1 -0
- package/dist/chat/message.d.ts +3 -0
- package/dist/chat/message.d.ts.map +1 -0
- package/dist/chat/message.js +31 -0
- package/dist/chat/message.js.map +1 -0
- package/dist/chat/print.d.ts +3 -0
- package/dist/chat/print.d.ts.map +1 -0
- package/dist/chat/print.js +24 -0
- package/dist/chat/print.js.map +1 -0
- package/dist/chat/runtime.d.ts +2 -0
- package/dist/chat/runtime.d.ts.map +1 -0
- package/dist/chat/runtime.js +64 -0
- package/dist/chat/runtime.js.map +1 -0
- package/dist/chat/session.d.ts +8 -0
- package/dist/chat/session.d.ts.map +1 -0
- package/dist/chat/session.js +77 -0
- package/dist/chat/session.js.map +1 -0
- package/dist/chat/shared.d.ts +6 -0
- package/dist/chat/shared.d.ts.map +1 -0
- package/dist/chat/shared.js +16 -0
- package/dist/chat/shared.js.map +1 -0
- package/dist/cli.js +3 -3
- package/dist/cli.js.map +1 -1
- package/dist/config.js +1 -1
- package/dist/config.js.map +1 -1
- package/dist/session/commands/back.d.ts +3 -0
- package/dist/session/commands/back.d.ts.map +1 -0
- package/dist/session/commands/back.js +95 -0
- package/dist/session/commands/back.js.map +1 -0
- package/dist/session/commands/editor.d.ts +3 -0
- package/dist/session/commands/editor.d.ts.map +1 -0
- package/dist/session/commands/editor.js +20 -0
- package/dist/session/commands/editor.js.map +1 -0
- package/dist/session/commands/exit.d.ts +3 -0
- package/dist/session/commands/exit.d.ts.map +1 -0
- package/dist/session/commands/exit.js +5 -0
- package/dist/session/commands/exit.js.map +1 -0
- package/dist/session/commands/help.d.ts +2 -0
- package/dist/session/commands/help.d.ts.map +1 -0
- package/dist/session/commands/help.js +4 -0
- package/dist/session/commands/help.js.map +1 -0
- package/dist/session/commands/load.d.ts +3 -0
- package/dist/session/commands/load.d.ts.map +1 -0
- package/dist/session/commands/load.js +37 -0
- package/dist/session/commands/load.js.map +1 -0
- package/dist/session/commands/new.d.ts +3 -0
- package/dist/session/commands/new.d.ts.map +1 -0
- package/dist/session/commands/new.js +6 -0
- package/dist/session/commands/new.js.map +1 -0
- package/dist/session/commands/save.d.ts +3 -0
- package/dist/session/commands/save.d.ts.map +1 -0
- package/dist/session/commands/save.js +5 -0
- package/dist/session/commands/save.js.map +1 -0
- package/dist/session/message.d.ts +3 -0
- package/dist/session/message.d.ts.map +1 -0
- package/dist/session/message.js +31 -0
- package/dist/session/message.js.map +1 -0
- package/dist/session/print.d.ts +3 -0
- package/dist/session/print.d.ts.map +1 -0
- package/dist/session/print.js +24 -0
- package/dist/session/print.js.map +1 -0
- package/dist/session/runtime.d.ts +2 -0
- package/dist/session/runtime.d.ts.map +1 -0
- package/dist/session/runtime.js +46 -0
- package/dist/session/runtime.js.map +1 -0
- package/dist/session/shared.d.ts +8 -0
- package/dist/session/shared.d.ts.map +1 -0
- package/dist/session/shared.js +77 -0
- package/dist/session/shared.js.map +1 -0
- package/package.json +1 -1
- package/src/__tests__/chat.test.ts +147 -0
- package/src/__tests__/e2e.test.ts +187 -0
- package/src/agent-factory.ts +1 -1
- package/src/chat/commands/back.ts +90 -0
- package/src/chat/commands/editor.ts +18 -0
- package/src/chat/commands/exit.ts +6 -0
- package/src/chat/commands/help.ts +3 -0
- package/src/chat/commands/index.ts +38 -0
- package/src/chat/commands/load.ts +38 -0
- package/src/chat/commands/new.ts +12 -0
- package/src/chat/commands/save.ts +6 -0
- package/src/chat/message.ts +27 -0
- package/src/chat/print.ts +27 -0
- package/src/chat/runtime.ts +65 -0
- package/src/chat/shared.ts +21 -0
- package/src/cli.ts +4 -4
- package/src/config.ts +1 -1
- package/src/agent-runtime.ts +0 -265
package/README.md
CHANGED
|
@@ -85,20 +85,35 @@ $ gred "hello" file.txt
|
|
|
85
85
|
```
|
|
86
86
|
src/
|
|
87
87
|
├── cli.ts # CLI entry, commander
|
|
88
|
-
├── config.ts # Config read/write
|
|
89
|
-
├── delta-renderer.ts # Stream renderer
|
|
88
|
+
├── config.ts # Config, context, snapshot read/write
|
|
89
|
+
├── delta-renderer.ts # Stream renderer
|
|
90
|
+
├── hook.ts # Fallback terminal hook (install/uninstall)
|
|
91
|
+
├── migration.ts # Context counting & migration
|
|
90
92
|
├── tools.ts # Tool definitions — shell
|
|
91
93
|
├── agent-factory.ts # Agent factory — build model & agent
|
|
92
|
-
├──
|
|
93
|
-
├──
|
|
94
|
-
├──
|
|
94
|
+
├── chat/
|
|
95
|
+
│ ├── shared.ts # ChatCtx type & SYSTEM_PROMPT
|
|
96
|
+
│ ├── runtime.ts # runChat() & chatLoop() — core runtime
|
|
97
|
+
│ ├── message.ts # handleMessage() — send & migrate
|
|
98
|
+
│ ├── print.ts # sendAndPrint() — stream output
|
|
99
|
+
│ └── commands/
|
|
100
|
+
│ ├── index.ts # dispatchCommand() — command router
|
|
101
|
+
│ ├── back.ts # /back — recall & resend
|
|
102
|
+
│ ├── editor.ts # /editor — multi-line input
|
|
103
|
+
│ ├── exit.ts # /exit — quit
|
|
104
|
+
│ ├── help.ts # /help
|
|
105
|
+
│ ├── load.ts # /load — load snapshot
|
|
106
|
+
│ ├── new.ts # /new — new session
|
|
107
|
+
│ └── save.ts # /save — save snapshot
|
|
95
108
|
└── __tests__/
|
|
109
|
+
├── chat.test.ts # Chat session tests
|
|
96
110
|
├── config.test.ts # Config/context/snapshot tests
|
|
97
111
|
├── main.test.ts # contextSize/shouldMigrate tests
|
|
112
|
+
├── e2e.test.ts # End-to-end tests
|
|
98
113
|
└── tools.test.ts # Shell tool structure tests
|
|
99
114
|
```
|
|
100
115
|
|
|
101
|
-
~
|
|
116
|
+
~49 KB, 881 lines.
|
|
102
117
|
|
|
103
118
|
## Tests
|
|
104
119
|
|
package/README.zh.md
CHANGED
|
@@ -52,6 +52,16 @@ air hook uninstall
|
|
|
52
52
|
| `/editor` | 打开系统编辑器输入多行文本 |
|
|
53
53
|
| `/help` | 帮助 |
|
|
54
54
|
|
|
55
|
+
### 兜底终端钩子
|
|
56
|
+
|
|
57
|
+
安装后,任何 shell 中不存在的命令都会自动转发到 `air`,AI 会解读你的意图并帮助你。
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
$ gred "hello" file.txt
|
|
61
|
+
# → command not found → 自动转发到 air
|
|
62
|
+
# → AI: "你是不是想用 grep?"
|
|
63
|
+
```
|
|
64
|
+
|
|
55
65
|
## 设计
|
|
56
66
|
|
|
57
67
|
```
|
|
@@ -75,20 +85,35 @@ air hook uninstall
|
|
|
75
85
|
```
|
|
76
86
|
src/
|
|
77
87
|
├── cli.ts # CLI 入口,commander
|
|
78
|
-
├── config.ts #
|
|
79
|
-
├── delta-renderer.ts #
|
|
88
|
+
├── config.ts # 配置、上下文、快照读写
|
|
89
|
+
├── delta-renderer.ts # 流式渲染器
|
|
90
|
+
├── hook.ts # 兜底终端钩子(install/uninstall)
|
|
91
|
+
├── migration.ts # 上下文计数与迁移
|
|
80
92
|
├── tools.ts # 工具定义——shell
|
|
81
93
|
├── agent-factory.ts # Agent 工厂——构建模型与 Agent
|
|
82
|
-
├──
|
|
83
|
-
├──
|
|
84
|
-
├──
|
|
94
|
+
├── chat/
|
|
95
|
+
│ ├── shared.ts # ChatCtx 类型 & SYSTEM_PROMPT
|
|
96
|
+
│ ├── runtime.ts # runChat() & chatLoop() — 核心运行时
|
|
97
|
+
│ ├── message.ts # handleMessage() — 发送与迁移
|
|
98
|
+
│ ├── print.ts # sendAndPrint() — 流式输出
|
|
99
|
+
│ └── commands/
|
|
100
|
+
│ ├── index.ts # dispatchCommand() — 命令分发入口
|
|
101
|
+
│ ├── back.ts # /back — 撤回消息
|
|
102
|
+
│ ├── editor.ts # /editor — 多行编辑器输入
|
|
103
|
+
│ ├── exit.ts # /exit — 退出
|
|
104
|
+
│ ├── help.ts # /help
|
|
105
|
+
│ ├── load.ts # /load — 加载快照
|
|
106
|
+
│ ├── new.ts # /new — 新会话
|
|
107
|
+
│ └── save.ts # /save — 保存快照
|
|
85
108
|
└── __tests__/
|
|
109
|
+
├── chat.test.ts # 聊天测试
|
|
86
110
|
├── config.test.ts # 配置/上下文/快照测试
|
|
87
111
|
├── main.test.ts # contextSize/shouldMigrate 测试
|
|
112
|
+
├── e2e.test.ts # 端到端测试
|
|
88
113
|
└── tools.test.ts # shell 工具结构测试
|
|
89
114
|
```
|
|
90
115
|
|
|
91
|
-
共
|
|
116
|
+
共 49 KB,881 行。
|
|
92
117
|
|
|
93
118
|
## 测试
|
|
94
119
|
|
package/dist/agent-factory.js
CHANGED
|
@@ -2,7 +2,7 @@ import { Agent, Message, OpenAI, ChatGPT } from "@ai-zen/agents-core";
|
|
|
2
2
|
import { readConfig } from "./config.js";
|
|
3
3
|
import { shellTool } from "./tools.js";
|
|
4
4
|
const MODEL_NAME = "deepseek-v4-flash";
|
|
5
|
-
const API_ENDPOINT = "https://api.deepseek.com/v1";
|
|
5
|
+
const API_ENDPOINT = process.env.AIR_API_ENDPOINT || "https://api.deepseek.com/v1";
|
|
6
6
|
async function buildModel(apiKey) {
|
|
7
7
|
const endpoint = new OpenAI({ openai_endpoint: API_ENDPOINT, api_key: apiKey });
|
|
8
8
|
return new ChatGPT({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-factory.js","sourceRoot":"","sources":["../src/agent-factory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAEtE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC,MAAM,UAAU,GAAG,mBAAmB,CAAC;AACvC,MAAM,YAAY,GAAG,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"agent-factory.js","sourceRoot":"","sources":["../src/agent-factory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAEtE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC,MAAM,UAAU,GAAG,mBAAmB,CAAC;AACvC,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,6BAA6B,CAAC;AAEnF,KAAK,UAAU,UAAU,CAAC,MAAc;IACtC,MAAM,QAAQ,GAAG,IAAI,MAAM,CAAC,EAAE,eAAe,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;IAChF,OAAO,IAAI,OAAO,CAAC;QACjB,YAAY,EAAE,EAAE;QAChB,cAAc,EAAE,MAAM,QAAQ,CAAC,cAAc,CAAC,UAAU,CAAC;KAC1D,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,aAAoB;IACnD,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,MAAM,KAAK,GAAG,MAAM,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC9C,MAAM,QAAQ,GAAsB,EAAE,CAAC;IACvC,KAAK,MAAM,CAAC,IAAI,aAAa;QAAE,QAAQ,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7D,OAAO,IAAI,KAAK,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;AAC5D,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-runtime.d.ts","sourceRoot":"","sources":["../src/agent-runtime.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"agent-runtime.d.ts","sourceRoot":"","sources":["../src/agent-runtime.ts"],"names":[],"mappings":"AA0RA,wBAAsB,eAAe,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAyC5E"}
|
package/dist/agent-runtime.js
CHANGED
|
@@ -2,7 +2,7 @@ import chalk from "chalk";
|
|
|
2
2
|
import { Message } from "@ai-zen/agents-core";
|
|
3
3
|
import { DeltaRenderer } from "./delta-renderer.js";
|
|
4
4
|
import inquirer from "inquirer";
|
|
5
|
-
import { readConfig, readMessages, saveMessages,
|
|
5
|
+
import { readConfig, readMessages, saveMessages, saveSnapshot, listSnapshots, loadSnapshot } from "./config.js";
|
|
6
6
|
import { buildAgent } from "./agent-factory.js";
|
|
7
7
|
import { contextSize, shouldMigrate, generateMigrationDoc, MAX_CONTEXT_CHARS } from "./migration.js";
|
|
8
8
|
const SYSTEM_PROMPT = [
|
|
@@ -20,8 +20,9 @@ const SYSTEM_PROMPT = [
|
|
|
20
20
|
"- 项目记忆: $(cwd)/.ai-zen/air/temp/*.md",
|
|
21
21
|
"下次启动时用 shell 读取即可。这是你唯一的记忆方式。",
|
|
22
22
|
].join("\n");
|
|
23
|
-
// 流式发送
|
|
23
|
+
// ==================== 流式发送 ====================
|
|
24
24
|
async function sendAndPrint(agent, text) {
|
|
25
|
+
console.log(chalk.green.bold("\n🤖 AI:"));
|
|
25
26
|
const renderer = new DeltaRenderer({
|
|
26
27
|
reasoningHeader: "\n\n💭 思考中...\n",
|
|
27
28
|
contentHeader: "\n\n💭 回答中...\n",
|
|
@@ -41,261 +42,273 @@ async function sendAndPrint(agent, text) {
|
|
|
41
42
|
process.stdout.write("\n\n");
|
|
42
43
|
console.log();
|
|
43
44
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
// ==================== 公共操作 ====================
|
|
46
|
+
async function saveAndNew(ctx) {
|
|
47
|
+
const msgs = [Message.System(SYSTEM_PROMPT)];
|
|
48
|
+
saveMessages(msgs);
|
|
49
|
+
ctx.agent = await buildAgent(msgs);
|
|
47
50
|
}
|
|
48
|
-
// 对话循环
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (!config.apiKey) {
|
|
52
|
-
console.error("❌ 请先设置 API Key: air key <your-key>");
|
|
53
|
-
console.error(" 获取 Key: https://platform.deepseek.com/api_keys");
|
|
54
|
-
process.exit(1);
|
|
55
|
-
}
|
|
56
|
-
let msgs = readMessages();
|
|
57
|
-
if (msgs.length === 0) {
|
|
58
|
-
msgs = [Message.System(SYSTEM_PROMPT)];
|
|
59
|
-
saveMessages(msgs);
|
|
60
|
-
}
|
|
61
|
-
let agent = await buildAgent(msgs);
|
|
62
|
-
if (initialMessage) {
|
|
63
|
-
// 有旧对话则先存档,再开新对话
|
|
64
|
-
const hasHistory = agent.messages.some(m => m.role === "user" || m.role === "assistant");
|
|
65
|
-
if (hasHistory) {
|
|
66
|
-
const snap = saveSnapshot(agent.messages);
|
|
67
|
-
console.log(`💾 已存档旧对话: ${snap}\n`);
|
|
68
|
-
}
|
|
69
|
-
msgs = [Message.System(SYSTEM_PROMPT)];
|
|
70
|
-
saveMessages(msgs);
|
|
71
|
-
agent = await buildAgent(msgs);
|
|
72
|
-
console.log(`💬 你: ${initialMessage}`);
|
|
73
|
-
try {
|
|
74
|
-
await sendAndSave(agent, initialMessage);
|
|
75
|
-
saveMessages(agent.messages);
|
|
76
|
-
console.log("💾 已保存\n");
|
|
77
|
-
}
|
|
78
|
-
catch (err) {
|
|
79
|
-
console.error(`\n❌ ${err.message}`);
|
|
80
|
-
}
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
const msgCount = agent.messages.filter((m) => m.role !== "system").length;
|
|
84
|
-
console.log(msgCount > 0
|
|
85
|
-
? `\n💬 继续上次对话 (${msgCount} 条,/${contextSize(agent.messages)} 字符,输入 /new 重新开始)\n`
|
|
86
|
-
: "\n💬 air — 极简 AI 助手 (输入 /help 查看命令)\n");
|
|
87
|
-
async function ask() {
|
|
51
|
+
// ==================== 对话循环 ====================
|
|
52
|
+
async function ask(ctx) {
|
|
53
|
+
while (true) {
|
|
88
54
|
const { input } = await inquirer.prompt([
|
|
89
55
|
{ type: "input", name: "input", message: "你:" },
|
|
90
56
|
]);
|
|
91
57
|
const t = input.trim();
|
|
92
|
-
if (!t)
|
|
93
|
-
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
58
|
+
if (!t)
|
|
59
|
+
continue;
|
|
96
60
|
if (t.startsWith("/")) {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
61
|
+
await dispatchCommand(ctx, t.toLowerCase());
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
await handleMessage(ctx, t);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async function dispatchCommand(ctx, cmd) {
|
|
68
|
+
switch (cmd) {
|
|
69
|
+
case "/exit":
|
|
70
|
+
case "/quit":
|
|
71
|
+
cmdExit(ctx);
|
|
72
|
+
break;
|
|
73
|
+
case "/save":
|
|
74
|
+
await cmdSave(ctx);
|
|
75
|
+
break;
|
|
76
|
+
case "/new":
|
|
77
|
+
await cmdNew(ctx);
|
|
78
|
+
break;
|
|
79
|
+
case "/load":
|
|
80
|
+
await cmdLoad(ctx);
|
|
81
|
+
break;
|
|
82
|
+
case "/back":
|
|
83
|
+
await handleBack(ctx);
|
|
84
|
+
break;
|
|
85
|
+
case "/editor":
|
|
86
|
+
await cmdEditor(ctx);
|
|
87
|
+
break;
|
|
88
|
+
case "/help":
|
|
89
|
+
cmdHelp();
|
|
90
|
+
break;
|
|
91
|
+
default:
|
|
92
|
+
console.log(`\n❌ 未知命令: ${cmd}\n`);
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
async function cmdSave(ctx) {
|
|
97
|
+
console.log(`\n✅ 快照: ${saveSnapshot(ctx.agent.messages)}\n`);
|
|
98
|
+
}
|
|
99
|
+
async function cmdNew(ctx) {
|
|
100
|
+
await saveAndNew(ctx);
|
|
101
|
+
console.log("\n🆕 重新开始\n");
|
|
102
|
+
}
|
|
103
|
+
function cmdHelp() {
|
|
104
|
+
console.log("\n/exit /quit 退出\n/save 保存快照\n/load 加载快照\n/new 重新开始\n/back 撤回消息\n/help 帮助\n");
|
|
105
|
+
}
|
|
106
|
+
async function cmdExit(ctx) {
|
|
107
|
+
console.log("\n👋 再见!");
|
|
108
|
+
process.exit(0);
|
|
109
|
+
}
|
|
110
|
+
async function cmdLoad(ctx) {
|
|
111
|
+
const snapshots = listSnapshots();
|
|
112
|
+
if (snapshots.length === 0) {
|
|
113
|
+
console.log("\n📭 没有可用的快照\n");
|
|
114
|
+
ctx.agent = await buildAgent([]);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const { selectedName } = await inquirer.prompt([
|
|
118
|
+
{
|
|
119
|
+
type: "list",
|
|
120
|
+
name: "selectedName",
|
|
121
|
+
message: "选择要加载的快照:",
|
|
122
|
+
pageSize: 15,
|
|
123
|
+
choices: [
|
|
124
|
+
{ name: "\u21a9\ufe0f 取消操作", value: "" },
|
|
125
|
+
...snapshots.map((s) => ({ name: s.date, value: s.name })),
|
|
126
|
+
],
|
|
127
|
+
},
|
|
128
|
+
]);
|
|
129
|
+
if (!selectedName) {
|
|
130
|
+
console.log("\n已取消\n");
|
|
131
|
+
ctx.agent = await buildAgent([]);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
let loaded = loadSnapshot(selectedName);
|
|
135
|
+
if (loaded.length === 0) {
|
|
136
|
+
loaded = [Message.System(SYSTEM_PROMPT)];
|
|
137
|
+
}
|
|
138
|
+
saveMessages(loaded);
|
|
139
|
+
const snap = snapshots.find((s) => s.name === selectedName);
|
|
140
|
+
console.log("\n✅ 已加载快照: " + (snap ? snap.date : selectedName) + "\n");
|
|
141
|
+
ctx.agent = await buildAgent(loaded);
|
|
142
|
+
}
|
|
143
|
+
async function cmdEditor(ctx) {
|
|
144
|
+
const { content } = await inquirer.prompt([
|
|
145
|
+
{ type: "editor", name: "content", message: "编辑消息:" },
|
|
146
|
+
]);
|
|
147
|
+
if (!content.trim()) {
|
|
148
|
+
console.log("\n已取消\n");
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
try {
|
|
152
|
+
await sendAndPrint(ctx.agent, content.trim());
|
|
153
|
+
saveMessages(ctx.agent.messages);
|
|
154
|
+
}
|
|
155
|
+
catch (err) {
|
|
156
|
+
console.error(`\n❌ ${err.message}`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
async function handleMessage(ctx, text) {
|
|
160
|
+
try {
|
|
161
|
+
await sendAndPrint(ctx.agent, text);
|
|
162
|
+
saveMessages(ctx.agent.messages);
|
|
163
|
+
if (shouldMigrate(ctx.agent.messages)) {
|
|
164
|
+
console.log(`🔄 上下文 ${contextSize(ctx.agent.messages)}/${MAX_CONTEXT_CHARS},准备迁移...`);
|
|
165
|
+
try {
|
|
166
|
+
const snap = saveSnapshot(ctx.agent.messages);
|
|
167
|
+
console.log(` 💾 快照: ${snap}`);
|
|
168
|
+
const summary = await generateMigrationDoc(ctx.agent.messages);
|
|
169
|
+
const msgs = [Message.System(SYSTEM_PROMPT), Message.User(summary)];
|
|
144
170
|
saveMessages(msgs);
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
console.log("\n✅ 已加载快照: " + (snap ? snap.date : selectedName) + "\n");
|
|
148
|
-
await ask();
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
if (c === "/back") {
|
|
152
|
-
await handleBack();
|
|
153
|
-
await ask();
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
if (c === "/editor") {
|
|
157
|
-
const { content } = await inquirer.prompt([
|
|
158
|
-
{ type: "editor", name: "content", message: "编辑消息:" },
|
|
159
|
-
]);
|
|
160
|
-
if (!content.trim()) {
|
|
161
|
-
console.log("\n已取消\n");
|
|
162
|
-
await ask();
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
try {
|
|
166
|
-
await sendAndSave(agent, content.trim());
|
|
167
|
-
saveMessages(agent.messages);
|
|
168
|
-
}
|
|
169
|
-
catch (err) {
|
|
170
|
-
console.error(`\n❌ ${err.message}`);
|
|
171
|
-
}
|
|
172
|
-
await ask();
|
|
173
|
-
return;
|
|
171
|
+
console.log("✅ 迁移完成\n");
|
|
172
|
+
ctx.agent = await buildAgent(msgs);
|
|
174
173
|
}
|
|
175
|
-
|
|
176
|
-
console.
|
|
177
|
-
await ask();
|
|
178
|
-
return;
|
|
174
|
+
catch (err) {
|
|
175
|
+
console.error(`❌ 迁移失败: ${err.message}\n`);
|
|
179
176
|
}
|
|
180
|
-
console.log(`\n❌ 未知命令: ${t}\n`);
|
|
181
|
-
await ask();
|
|
182
|
-
return;
|
|
183
177
|
}
|
|
184
|
-
try {
|
|
185
|
-
await sendAndSave(agent, t);
|
|
186
|
-
saveMessages(agent.messages);
|
|
187
|
-
if (shouldMigrate(agent.messages)) {
|
|
188
|
-
console.log(`🔄 上下文 ${contextSize(agent.messages)}/${MAX_CONTEXT_CHARS},准备迁移...`);
|
|
189
|
-
try {
|
|
190
|
-
const snap = saveSnapshot(agent.messages);
|
|
191
|
-
console.log(` 💾 快照: ${snap}`);
|
|
192
|
-
const summary = await generateMigrationDoc(agent.messages);
|
|
193
|
-
const msgs = [Message.System(SYSTEM_PROMPT), Message.User(summary)];
|
|
194
|
-
saveMessages(msgs);
|
|
195
|
-
agent = await buildAgent(msgs);
|
|
196
|
-
console.log("✅ 迁移完成\n");
|
|
197
|
-
}
|
|
198
|
-
catch (err) {
|
|
199
|
-
console.error(`❌ 迁移失败: ${err.message}\n`);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
catch (err) {
|
|
204
|
-
console.error(`\n❌ ${err.message}`);
|
|
205
|
-
}
|
|
206
|
-
await ask();
|
|
207
178
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
if (text) {
|
|
221
|
-
targets.push({ index: i, role: msg.role, label: "🔧 工具", preview: text.substring(0, 60) + (text.length > 60 ? "..." : "") });
|
|
222
|
-
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
console.error(`\n❌ ${err.message}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
async function handleBack(ctx) {
|
|
184
|
+
const targets = [];
|
|
185
|
+
for (let i = 0; i < ctx.agent.messages.length; i++) {
|
|
186
|
+
const msg = ctx.agent.messages[i];
|
|
187
|
+
if (msg.role === "user") {
|
|
188
|
+
const text = typeof msg.content === "string" ? msg.content : "";
|
|
189
|
+
if (text) {
|
|
190
|
+
targets.push({ index: i, role: "user", label: "\u{1F464} 用户", preview: text.substring(0, 60) + (text.length > 60 ? "..." : "") });
|
|
223
191
|
}
|
|
224
192
|
}
|
|
225
|
-
if (
|
|
226
|
-
|
|
227
|
-
|
|
193
|
+
else if (msg.role === "tool" || msg.role === "function") {
|
|
194
|
+
const text = typeof msg.content === "string" ? msg.content : "";
|
|
195
|
+
if (text) {
|
|
196
|
+
targets.push({ index: i, role: msg.role, label: "\u{1F527} 工具", preview: text.substring(0, 60) + (text.length > 60 ? "..." : "") });
|
|
197
|
+
}
|
|
228
198
|
}
|
|
229
|
-
|
|
230
|
-
|
|
199
|
+
}
|
|
200
|
+
if (targets.length === 0) {
|
|
201
|
+
console.log("\n❌ 还没有消息可以撤回\n");
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
console.log("\n📋 选择要撤回到哪条消息(将删除所选及其之后的所有内容):\n");
|
|
205
|
+
const { selectedIndex } = await inquirer.prompt([
|
|
206
|
+
{
|
|
207
|
+
type: "list",
|
|
208
|
+
name: "selectedIndex",
|
|
209
|
+
message: "撤回到:",
|
|
210
|
+
pageSize: 15,
|
|
211
|
+
choices: [
|
|
212
|
+
{ name: "\u21a9\ufe0f 取消操作", value: -1 },
|
|
213
|
+
...targets.map((t) => ({ name: t.label + " " + t.preview, value: t.index })),
|
|
214
|
+
],
|
|
215
|
+
},
|
|
216
|
+
]);
|
|
217
|
+
if (selectedIndex === -1) {
|
|
218
|
+
console.log("\n已取消\n");
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
const selectedMsg = ctx.agent.messages[selectedIndex];
|
|
222
|
+
const isUserMsg = selectedMsg.role === "user";
|
|
223
|
+
const originalText = typeof selectedMsg.content === "string" ? selectedMsg.content : "";
|
|
224
|
+
const sliceEnd = isUserMsg ? selectedIndex : selectedIndex + 1;
|
|
225
|
+
ctx.agent.messages = ctx.agent.messages.slice(0, sliceEnd);
|
|
226
|
+
if (isUserMsg) {
|
|
227
|
+
console.log("\n原内容: " + originalText.substring(0, 200) + (originalText.length > 200 ? "..." : "") + "\n");
|
|
228
|
+
const { editChoice } = await inquirer.prompt([
|
|
231
229
|
{
|
|
232
230
|
type: "list",
|
|
233
|
-
name: "
|
|
234
|
-
message: "
|
|
235
|
-
pageSize: 15,
|
|
231
|
+
name: "editChoice",
|
|
232
|
+
message: "请选择:",
|
|
236
233
|
choices: [
|
|
237
|
-
{ name: "
|
|
238
|
-
|
|
234
|
+
{ name: "\u270f\ufe0f 修改后重新发送", value: "edit" },
|
|
235
|
+
{ name: "\u{1F504} 直接重新发送", value: "resend" },
|
|
236
|
+
{ name: "\u21a9\ufe0f 取消操作", value: "cancel" },
|
|
239
237
|
],
|
|
240
238
|
},
|
|
241
239
|
]);
|
|
242
|
-
if (
|
|
240
|
+
if (editChoice === "cancel") {
|
|
243
241
|
console.log("\n已取消\n");
|
|
244
242
|
return;
|
|
245
243
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
agent.messages = agent.messages.slice(0, sliceEnd);
|
|
251
|
-
if (isUserMsg) {
|
|
252
|
-
console.log("\n原内容: " + originalText.substring(0, 200) + (originalText.length > 200 ? "..." : "") + "\n");
|
|
253
|
-
const { editChoice } = await inquirer.prompt([
|
|
254
|
-
{
|
|
255
|
-
type: "list",
|
|
256
|
-
name: "editChoice",
|
|
257
|
-
message: "请选择:",
|
|
258
|
-
choices: [
|
|
259
|
-
{ name: "✏️ 修改后重新发送", value: "edit" },
|
|
260
|
-
{ name: "🔄 直接重新发送", value: "resend" },
|
|
261
|
-
{ name: "↩️ 取消操作", value: "cancel" },
|
|
262
|
-
],
|
|
263
|
-
},
|
|
244
|
+
let textToSend = originalText;
|
|
245
|
+
if (editChoice === "edit") {
|
|
246
|
+
const { editedContent } = await inquirer.prompt([
|
|
247
|
+
{ type: "input", name: "editedContent", message: "修改消息:", default: originalText },
|
|
264
248
|
]);
|
|
265
|
-
if (
|
|
266
|
-
console.log("\n
|
|
249
|
+
if (!editedContent.trim()) {
|
|
250
|
+
console.log("\n❌ 消息不能为空\n");
|
|
267
251
|
return;
|
|
268
252
|
}
|
|
269
|
-
|
|
270
|
-
if (editChoice === "edit") {
|
|
271
|
-
const { editedContent } = await inquirer.prompt([
|
|
272
|
-
{ type: "input", name: "editedContent", message: "修改消息:", default: originalText },
|
|
273
|
-
]);
|
|
274
|
-
if (!editedContent.trim()) {
|
|
275
|
-
console.log("\n❌ 消息不能为空\n");
|
|
276
|
-
return;
|
|
277
|
-
}
|
|
278
|
-
textToSend = editedContent.trim();
|
|
279
|
-
}
|
|
280
|
-
agent = await buildAgent(agent.messages);
|
|
281
|
-
await sendAndSave(agent, textToSend);
|
|
282
|
-
saveMessages(agent.messages);
|
|
253
|
+
textToSend = editedContent.trim();
|
|
283
254
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
255
|
+
ctx.agent = await buildAgent(ctx.agent.messages);
|
|
256
|
+
await sendAndPrint(ctx.agent, textToSend);
|
|
257
|
+
saveMessages(ctx.agent.messages);
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
console.log("\n💡 请输入一条新消息继续对话\n");
|
|
261
|
+
const { newMessage } = await inquirer.prompt([
|
|
262
|
+
{ type: "input", name: "newMessage", message: "新消息:" },
|
|
263
|
+
]);
|
|
264
|
+
if (!newMessage.trim()) {
|
|
265
|
+
console.log("\n已取消\n");
|
|
266
|
+
return;
|
|
296
267
|
}
|
|
268
|
+
ctx.agent = await buildAgent(ctx.agent.messages);
|
|
269
|
+
await sendAndPrint(ctx.agent, newMessage.trim());
|
|
270
|
+
saveMessages(ctx.agent.messages);
|
|
297
271
|
}
|
|
298
|
-
|
|
272
|
+
}
|
|
273
|
+
export async function runConversation(initialMessage) {
|
|
274
|
+
const config = readConfig();
|
|
275
|
+
if (!config.apiKey) {
|
|
276
|
+
console.error("❌ 请先设置 API Key: air key <your-key>");
|
|
277
|
+
console.error(" 获取 Key: https://platform.deepseek.com/api_keys");
|
|
278
|
+
process.exit(1);
|
|
279
|
+
}
|
|
280
|
+
let msgs = readMessages();
|
|
281
|
+
if (msgs.length === 0) {
|
|
282
|
+
msgs = [Message.System(SYSTEM_PROMPT)];
|
|
283
|
+
saveMessages(msgs);
|
|
284
|
+
}
|
|
285
|
+
const agent = await buildAgent(msgs);
|
|
286
|
+
const ctx = { agent };
|
|
287
|
+
if (initialMessage) {
|
|
288
|
+
// 有旧对话则先存档,再开新对话
|
|
289
|
+
const hasHistory = ctx.agent.messages.some(m => m.role === "user" || m.role === "assistant");
|
|
290
|
+
if (hasHistory) {
|
|
291
|
+
const snap = saveSnapshot(ctx.agent.messages);
|
|
292
|
+
console.log(`💾 已存档旧对话: ${snap}\n`);
|
|
293
|
+
}
|
|
294
|
+
await saveAndNew(ctx);
|
|
295
|
+
console.log(`💬 你: ${initialMessage}`);
|
|
296
|
+
try {
|
|
297
|
+
await sendAndPrint(ctx.agent, initialMessage);
|
|
298
|
+
saveMessages(ctx.agent.messages);
|
|
299
|
+
}
|
|
300
|
+
catch (err) {
|
|
301
|
+
console.error(`\n❌ ${err.message}`);
|
|
302
|
+
}
|
|
303
|
+
console.log("\n💬 继续对话 (输入 /exit 退出)\n");
|
|
304
|
+
await ask(ctx);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
const msgCount = ctx.agent.messages.filter((m) => m.role !== "system").length;
|
|
308
|
+
console.log(msgCount > 0
|
|
309
|
+
? `\n💬 继续上次对话 (${msgCount} 条,/${contextSize(ctx.agent.messages)} 字符,输入 /new 重新开始)\n`
|
|
310
|
+
: "\n💬 air — 极简 AI 助手 (输入 /help 查看命令)\n");
|
|
311
|
+
await ask(ctx);
|
|
299
312
|
process.on("SIGINT", () => { console.log("\n\n👋 再见!"); process.exit(0); });
|
|
300
313
|
}
|
|
301
314
|
//# sourceMappingURL=agent-runtime.js.map
|