jinzd-ai-cli 0.4.71 → 0.4.73
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/chunk-2ZD3YTVM.js +114 -0
- package/dist/{chunk-6QRHSSJB.js → chunk-76KBSQHA.js} +1 -1
- package/dist/{chunk-UHZ6YANH.js → chunk-H65WPFDO.js} +164 -114
- package/dist/{chunk-6OKAZIY7.js → chunk-HAOCJWW2.js} +3 -152
- package/dist/chunk-LR7IV4SK.js +318 -0
- package/dist/chunk-T2OUKQOX.js +155 -0
- package/dist/{chunk-IZK6GNT4.js → chunk-TIGB5ADX.js} +47 -354
- package/dist/{hub-DUCBBK3Y.js → hub-AFXKRJ5D.js} +1 -1
- package/dist/index.js +56 -7
- package/dist/{run-tests-CKMD75Y2.js → run-tests-5BUD2CL4.js} +1 -1
- package/dist/{run-tests-A42NM2XQ.js → run-tests-6JUSVL4W.js} +2 -1
- package/dist/{server-BOAYC5O3.js → server-QQGBLS42.js} +9 -5
- package/dist/{task-orchestrator-WX5NZZJR.js → task-orchestrator-M7Y32LNH.js} +4 -2
- package/package.json +1 -1
|
@@ -1,330 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
schemaToJsonSchema,
|
|
4
|
+
truncateForPersist
|
|
5
|
+
} from "./chunk-H65WPFDO.js";
|
|
2
6
|
import {
|
|
3
7
|
AuthError,
|
|
4
|
-
ConfigError,
|
|
5
|
-
EnvLoader,
|
|
6
8
|
ProviderError,
|
|
7
9
|
ProviderNotFoundError,
|
|
8
|
-
RateLimitError
|
|
9
|
-
|
|
10
|
-
truncateForPersist
|
|
11
|
-
} from "./chunk-UHZ6YANH.js";
|
|
10
|
+
RateLimitError
|
|
11
|
+
} from "./chunk-2ZD3YTVM.js";
|
|
12
12
|
import {
|
|
13
13
|
APP_NAME,
|
|
14
14
|
CONFIG_DIR_NAME,
|
|
15
|
-
CONFIG_FILE_NAME,
|
|
16
15
|
DEV_STATE_FILE_NAME,
|
|
17
|
-
HISTORY_DIR_NAME,
|
|
18
16
|
MCP_CALL_TIMEOUT,
|
|
19
17
|
MCP_CONNECT_TIMEOUT,
|
|
20
18
|
MCP_PROTOCOL_VERSION,
|
|
21
19
|
MCP_TOOL_PREFIX,
|
|
22
|
-
PLUGINS_DIR_NAME,
|
|
23
20
|
VERSION
|
|
24
|
-
} from "./chunk-
|
|
25
|
-
|
|
26
|
-
// src/config/config-manager.ts
|
|
27
|
-
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
28
|
-
import { join } from "path";
|
|
29
|
-
import { homedir } from "os";
|
|
30
|
-
|
|
31
|
-
// src/config/schema.ts
|
|
32
|
-
import { z } from "zod";
|
|
33
|
-
var CustomModelSchema = z.object({
|
|
34
|
-
id: z.string(),
|
|
35
|
-
displayName: z.string().optional(),
|
|
36
|
-
contextWindow: z.number().optional()
|
|
37
|
-
});
|
|
38
|
-
var CustomProviderConfigSchema = z.object({
|
|
39
|
-
id: z.string(),
|
|
40
|
-
// 唯一 ID,不能与内置 provider 重名
|
|
41
|
-
displayName: z.string(),
|
|
42
|
-
// 显示名称
|
|
43
|
-
apiKey: z.string().optional(),
|
|
44
|
-
// 可选:直接在此写 key(也可通过 apiKeys 字段或环境变量提供)
|
|
45
|
-
baseUrl: z.string(),
|
|
46
|
-
// OpenAI 兼容 API 的 base URL(必填)
|
|
47
|
-
defaultModel: z.string(),
|
|
48
|
-
// 默认使用的模型 ID
|
|
49
|
-
models: z.array(CustomModelSchema).default([]),
|
|
50
|
-
timeout: z.number().optional()
|
|
51
|
-
// 请求超时(ms),覆盖全局默认值
|
|
52
|
-
});
|
|
53
|
-
var ModelParamsSchema = z.object({
|
|
54
|
-
temperature: z.number().min(0).max(2).optional(),
|
|
55
|
-
maxTokens: z.number().int().positive().optional(),
|
|
56
|
-
timeout: z.number().int().positive().optional(),
|
|
57
|
-
/** 是否启用深度思考(thinking)模式,Claude Sonnet/Opus、GLM-5 等 */
|
|
58
|
-
thinking: z.boolean().optional(),
|
|
59
|
-
/** thinking 模式的 token 预算(最小 1024,仅 Claude Extended Thinking 使用) */
|
|
60
|
-
thinkingBudget: z.number().int().min(1024).optional()
|
|
61
|
-
});
|
|
62
|
-
var UserProfileSchema = z.object({
|
|
63
|
-
/** 真实姓名(如 "Jin Zhengdong") */
|
|
64
|
-
name: z.string().optional(),
|
|
65
|
-
/** 昵称/称呼偏好(如 "东叔"),AI 会以此称呼你 */
|
|
66
|
-
nickname: z.string().optional(),
|
|
67
|
-
/** 职业角色(如 "Full-stack developer"、"Data scientist") */
|
|
68
|
-
role: z.string().optional(),
|
|
69
|
-
/** 个人简介 / 专长描述(1-3 句话) */
|
|
70
|
-
bio: z.string().optional(),
|
|
71
|
-
/** 兴趣领域或技术栈(如 ["TypeScript", "Rust", "AI/ML"]) */
|
|
72
|
-
interests: z.array(z.string()).default([]),
|
|
73
|
-
/** 语言偏好(如 "zh-CN"、"en"),AI 将据此选择交流语言 */
|
|
74
|
-
locale: z.string().optional(),
|
|
75
|
-
/** 自定义人设补充(自由格式,直接注入 system prompt) */
|
|
76
|
-
extra: z.string().optional()
|
|
77
|
-
}).default({});
|
|
78
|
-
var ConfigSchema = z.object({
|
|
79
|
-
version: z.string().default("1.0.0"),
|
|
80
|
-
// 用户身份档案 — 跨 Provider 的 "灵魂"
|
|
81
|
-
userProfile: UserProfileSchema,
|
|
82
|
-
defaultProvider: z.string().default("claude"),
|
|
83
|
-
// 每个 provider 的默认模型(key 为 provider ID)
|
|
84
|
-
defaultModels: z.record(z.string()).default({}),
|
|
85
|
-
// API Keys:放宽为任意 record,支持自定义 provider ID
|
|
86
|
-
apiKeys: z.record(z.string()).default({}),
|
|
87
|
-
customBaseUrls: z.record(z.string()).default({}),
|
|
88
|
-
// Per-provider timeout in ms (e.g. { deepseek: 60000 })
|
|
89
|
-
// ⚠️ Timeout 优先级说明(L5):
|
|
90
|
-
// 1. modelParams[modelId].timeout — 最高优先级,精确到具体模型(如 deepseek-reasoner 需要更长超时)
|
|
91
|
-
// 2. timeouts[providerId] — Provider 级默认,覆盖内置默认值
|
|
92
|
-
// 3. 内置 Provider 的硬编码默认值 — 最低兜底(如 OpenAI 兼容系列默认 60000ms)
|
|
93
|
-
// 实现位置:src/providers/registry.ts initialize(),将 timeouts[id] 注入 provider
|
|
94
|
-
timeouts: z.record(z.number()).default({}),
|
|
95
|
-
// HTTP/HTTPS 代理地址(Node.js 不自动使用系统代理,需显式配置)
|
|
96
|
-
// 例:http://127.0.0.1:10809
|
|
97
|
-
// 也可通过环境变量 HTTPS_PROXY / HTTP_PROXY 覆盖
|
|
98
|
-
proxy: z.string().optional(),
|
|
99
|
-
// 自定义 Provider 列表(OpenAI 兼容接口,无需改代码)
|
|
100
|
-
customProviders: z.array(CustomProviderConfigSchema).default([]),
|
|
101
|
-
// 按模型 ID 存储的推理参数(key 为模型 ID,如 "deepseek-chat")
|
|
102
|
-
modelParams: z.record(ModelParamsSchema).default({}),
|
|
103
|
-
ui: z.object({
|
|
104
|
-
streaming: z.boolean().default(true),
|
|
105
|
-
markdownRendering: z.boolean().default(true),
|
|
106
|
-
showTokenCount: z.boolean().default(true),
|
|
107
|
-
/** 桌面通知阈值(毫秒):AI 任务耗时超过此值时发送系统通知。0 = 禁用。默认 10000 (10s) */
|
|
108
|
-
notificationThreshold: z.number().default(1e4),
|
|
109
|
-
/** 终端输出折行宽度。0 = 自动(使用终端宽度),>0 = 固定列宽。默认 0 */
|
|
110
|
-
wordWrap: z.number().int().min(0).default(0),
|
|
111
|
-
/** 颜色主题:'dark'(默认)| 'light' | 'custom' */
|
|
112
|
-
theme: z.enum(["dark", "light", "custom"]).default("dark"),
|
|
113
|
-
/** 自定义颜色覆盖(仅在 theme='custom' 时生效)。值为 chalk 颜色名或 '#hex',支持 'bold.cyan' 组合 */
|
|
114
|
-
colors: z.object({
|
|
115
|
-
prompt: z.string().optional(),
|
|
116
|
-
info: z.string().optional(),
|
|
117
|
-
warning: z.string().optional(),
|
|
118
|
-
error: z.string().optional(),
|
|
119
|
-
success: z.string().optional(),
|
|
120
|
-
dim: z.string().optional(),
|
|
121
|
-
accent: z.string().optional(),
|
|
122
|
-
toolCall: z.string().optional(),
|
|
123
|
-
toolResult: z.string().optional(),
|
|
124
|
-
heading: z.string().optional()
|
|
125
|
-
}).optional()
|
|
126
|
-
}).default({}),
|
|
127
|
-
session: z.object({
|
|
128
|
-
autoSave: z.boolean().default(true),
|
|
129
|
-
maxHistoryDays: z.number().default(30),
|
|
130
|
-
systemPrompt: z.string().optional()
|
|
131
|
-
}).default({}),
|
|
132
|
-
// 项目上下文文件配置
|
|
133
|
-
// 启动时自动读取并注入 system prompt,类似 Claude Code 的 CLAUDE.md 机制
|
|
134
|
-
// 默认按顺序查找:AICLI.md → CLAUDE.md → .aicli/context.md
|
|
135
|
-
// 设为 false 可禁用此功能
|
|
136
|
-
contextFile: z.union([z.string(), z.literal(false)]).default("auto"),
|
|
137
|
-
// Google Custom Search API 的 Search Engine ID (cx 参数)
|
|
138
|
-
// API Key 通过 apiKeys['google-search'] 或 AICLI_API_KEY_GOOGLESEARCH 环境变量配置
|
|
139
|
-
// CX 也可通过 AICLI_GOOGLE_CX 环境变量覆盖
|
|
140
|
-
googleSearchEngineId: z.string().optional(),
|
|
141
|
-
// MCP (Model Context Protocol) 服务器配置
|
|
142
|
-
// 声明外部 MCP 服务器,启动时自动连接、发现工具并注册
|
|
143
|
-
// 配置格式兼容 Claude Desktop(command + args + env)
|
|
144
|
-
// 示例:{ "filesystem": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "/path"] } }
|
|
145
|
-
mcpServers: z.record(z.object({
|
|
146
|
-
command: z.string(),
|
|
147
|
-
args: z.array(z.string()).default([]),
|
|
148
|
-
env: z.record(z.string()).optional(),
|
|
149
|
-
timeout: z.number().default(3e4)
|
|
150
|
-
})).default({}),
|
|
151
|
-
// 工具执行钩子(shell 命令,模板变量:{tool} {dangerLevel} {args} {status})
|
|
152
|
-
hooks: z.object({
|
|
153
|
-
preToolExecution: z.string().optional(),
|
|
154
|
-
postToolExecution: z.string().optional()
|
|
155
|
-
}).optional(),
|
|
156
|
-
// 工具权限规则(按顺序匹配第一个生效)
|
|
157
|
-
permissionRules: z.array(z.object({
|
|
158
|
-
tool: z.string(),
|
|
159
|
-
action: z.enum(["auto-approve", "deny", "confirm"]),
|
|
160
|
-
when: z.object({
|
|
161
|
-
dangerLevel: z.enum(["safe", "write", "destructive"]).optional(),
|
|
162
|
-
pathPattern: z.string().optional()
|
|
163
|
-
}).optional()
|
|
164
|
-
})).default([]),
|
|
165
|
-
// 无规则匹配时的默认权限动作
|
|
166
|
-
defaultPermission: z.enum(["auto-approve", "deny", "confirm"]).default("confirm"),
|
|
167
|
-
// 自动上下文压缩开关
|
|
168
|
-
// 当对话估算 token 数超过模型 contextWindow 的 80% 时,自动触发 compact 压缩旧消息
|
|
169
|
-
// 默认开启。设为 false 则仅在手动 /compact 时压缩
|
|
170
|
-
autoCompact: z.boolean().default(true),
|
|
171
|
-
// Agentic 工具调用循环单次对话最大轮次(默认 200)。
|
|
172
|
-
// 超过此值后 AI 被强制停止调用工具并生成总结。
|
|
173
|
-
// 建议范围:25(保守)~ 1000(宽松,接近 Claude Code)。
|
|
174
|
-
// CLI `--max-tool-rounds <n>` 可覆盖此值。
|
|
175
|
-
maxToolRounds: z.number().int().min(1).max(1e4).default(200),
|
|
176
|
-
// Auto-pause checkpoint:Agentic 循环每隔多少轮暂停一次,让用户确认方向或中途介入。
|
|
177
|
-
// 默认 50;设为 0 禁用(完全自动执行到完成或 maxToolRounds 用尽)。
|
|
178
|
-
// CLI 与 Web UI 行为一致:CLI 用 readline question 提示,Web UI 弹出对话框。
|
|
179
|
-
autoPauseInterval: z.number().int().min(0).max(1e4).default(50),
|
|
180
|
-
// 单次工具输出(如 read_file、bash、grep_files)返回给 AI 的最大字符数上限。
|
|
181
|
-
// 默认 500_000 (~500K chars ≈ 6000-8000 行代码)。
|
|
182
|
-
// 实际上限还会受模型 contextWindow 动态约束(取 contextWindow/4 作为下限)。
|
|
183
|
-
// 设置为 0 或未配置时使用默认值;不建议设为小于 12_000 或大于模型 contextWindow/2。
|
|
184
|
-
maxToolOutputChars: z.number().int().min(0).default(5e5),
|
|
185
|
-
// 月度成本预算(USD)。
|
|
186
|
-
// 设置后,每次 AI 回复后会跟踪成本,接近或超过预算时在 /status 和 /cost 中显示警告。
|
|
187
|
-
// 默认 0 = 不限制。例:50 表示每月最多花 $50。
|
|
188
|
-
monthlyBudget: z.number().min(0).default(0),
|
|
189
|
-
// 插件加载开关(安全控制)
|
|
190
|
-
// 默认 false:不自动加载 ~/.aicli/plugins/ 中的插件文件。
|
|
191
|
-
// 插件以完整 Node.js 权限在主进程中执行(可读写文件、访问网络、执行命令),
|
|
192
|
-
// 必须确认插件来源可信后,再设为 true 启用。
|
|
193
|
-
// 可通过 /config 命令或直接编辑 ~/.aicli/config.json 开启。
|
|
194
|
-
allowPlugins: z.boolean().default(false),
|
|
195
|
-
// 智能模型路由(v0.4.68+)
|
|
196
|
-
// 按用户每轮输入的内容/标签/长度动态选择模型,在同一 provider 内切换,
|
|
197
|
-
// 例:短问题走 haiku(省钱),planning 走 opus(质量)。
|
|
198
|
-
// enabled=false 时永远返回当前模型。rules 按顺序匹配,首个命中的规则生效。
|
|
199
|
-
// 每个 rule 的 match 必须至少有一个条件(tag/contains/maxLength/minLength)。
|
|
200
|
-
// 详见 src/core/model-router.ts。
|
|
201
|
-
routing: z.object({
|
|
202
|
-
enabled: z.boolean().default(false),
|
|
203
|
-
rules: z.array(z.object({
|
|
204
|
-
match: z.object({
|
|
205
|
-
contains: z.array(z.string()).optional(),
|
|
206
|
-
maxLength: z.number().int().positive().optional(),
|
|
207
|
-
minLength: z.number().int().positive().optional(),
|
|
208
|
-
tag: z.string().optional()
|
|
209
|
-
}),
|
|
210
|
-
model: z.string(),
|
|
211
|
-
name: z.string().optional()
|
|
212
|
-
})).default([]),
|
|
213
|
-
fallback: z.string().optional()
|
|
214
|
-
}).default({ enabled: false, rules: [] })
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
// src/config/config-manager.ts
|
|
218
|
-
var ConfigManager = class {
|
|
219
|
-
configDir;
|
|
220
|
-
configPath;
|
|
221
|
-
config;
|
|
222
|
-
constructor(configDir) {
|
|
223
|
-
this.configDir = configDir ?? join(homedir(), CONFIG_DIR_NAME);
|
|
224
|
-
this.configPath = join(this.configDir, CONFIG_FILE_NAME);
|
|
225
|
-
this.config = this.load();
|
|
226
|
-
}
|
|
227
|
-
load() {
|
|
228
|
-
if (!existsSync(this.configPath)) {
|
|
229
|
-
return ConfigSchema.parse({});
|
|
230
|
-
}
|
|
231
|
-
try {
|
|
232
|
-
const raw = JSON.parse(readFileSync(this.configPath, "utf-8"));
|
|
233
|
-
return ConfigSchema.parse(raw);
|
|
234
|
-
} catch (err) {
|
|
235
|
-
throw new ConfigError(
|
|
236
|
-
`Config file at ${this.configPath} is invalid. Delete it and run 'ai-cli config' to recreate.
|
|
237
|
-
${err}`
|
|
238
|
-
);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
save() {
|
|
242
|
-
mkdirSync(this.configDir, { recursive: true });
|
|
243
|
-
writeFileSync(this.configPath, JSON.stringify(this.config, null, 2), "utf-8");
|
|
244
|
-
}
|
|
245
|
-
getApiKey(providerId) {
|
|
246
|
-
const envKey = EnvLoader.getApiKey(providerId);
|
|
247
|
-
if (envKey) return envKey;
|
|
248
|
-
return this.config.apiKeys[providerId];
|
|
249
|
-
}
|
|
250
|
-
setApiKey(providerId, key) {
|
|
251
|
-
this.config.apiKeys[providerId] = key;
|
|
252
|
-
this.save();
|
|
253
|
-
}
|
|
254
|
-
get(key) {
|
|
255
|
-
return this.config[key];
|
|
256
|
-
}
|
|
257
|
-
set(key, value) {
|
|
258
|
-
this.config[key] = value;
|
|
259
|
-
this.save();
|
|
260
|
-
}
|
|
261
|
-
/**
|
|
262
|
-
* 仅修改内存中的配置值,不持久化到磁盘。
|
|
263
|
-
* 用于 CLI 命令行参数覆盖(-p, -m, --no-stream),使其只对当前进程生效。
|
|
264
|
-
*/
|
|
265
|
-
setTransient(key, value) {
|
|
266
|
-
this.config[key] = value;
|
|
267
|
-
}
|
|
268
|
-
isFirstRun() {
|
|
269
|
-
return !existsSync(this.configPath);
|
|
270
|
-
}
|
|
271
|
-
getConfigDir() {
|
|
272
|
-
return this.configDir;
|
|
273
|
-
}
|
|
274
|
-
getHistoryDir() {
|
|
275
|
-
return join(this.configDir, HISTORY_DIR_NAME);
|
|
276
|
-
}
|
|
277
|
-
getPluginsDir() {
|
|
278
|
-
return join(this.configDir, PLUGINS_DIR_NAME);
|
|
279
|
-
}
|
|
280
|
-
getDefaultProvider() {
|
|
281
|
-
return EnvLoader.getDefaultProvider() ?? this.config.defaultProvider;
|
|
282
|
-
}
|
|
283
|
-
/** 点分路径读取配置值,如 `ui.theme` → config.ui.theme */
|
|
284
|
-
getByPath(path) {
|
|
285
|
-
const keys = path.split(".");
|
|
286
|
-
let current = this.config;
|
|
287
|
-
for (const key of keys) {
|
|
288
|
-
if (current == null || typeof current !== "object") return void 0;
|
|
289
|
-
current = current[key];
|
|
290
|
-
}
|
|
291
|
-
return current;
|
|
292
|
-
}
|
|
293
|
-
/** 点分路径写入配置值,自动类型转换(boolean/number/string)并持久化 */
|
|
294
|
-
setByPath(path, rawValue) {
|
|
295
|
-
const keys = path.split(".");
|
|
296
|
-
if (keys.length === 0) return;
|
|
297
|
-
let value = rawValue;
|
|
298
|
-
if (rawValue === "true") value = true;
|
|
299
|
-
else if (rawValue === "false") value = false;
|
|
300
|
-
else if (rawValue !== "" && !isNaN(Number(rawValue))) value = Number(rawValue);
|
|
301
|
-
const draft = JSON.parse(JSON.stringify(this.config));
|
|
302
|
-
let current = draft;
|
|
303
|
-
for (let i = 0; i < keys.length - 1; i++) {
|
|
304
|
-
const key = keys[i];
|
|
305
|
-
if (current[key] == null || typeof current[key] !== "object") {
|
|
306
|
-
current[key] = {};
|
|
307
|
-
}
|
|
308
|
-
current = current[key];
|
|
309
|
-
}
|
|
310
|
-
current[keys[keys.length - 1]] = value;
|
|
311
|
-
const result = ConfigSchema.safeParse(draft);
|
|
312
|
-
if (!result.success) {
|
|
313
|
-
const firstErr = result.error.errors[0];
|
|
314
|
-
throw new ConfigError(`Invalid config value for "${path}": ${firstErr?.message ?? "validation failed"}`);
|
|
315
|
-
}
|
|
316
|
-
this.config = result.data;
|
|
317
|
-
this.save();
|
|
318
|
-
}
|
|
319
|
-
/** 获取完整配置对象的格式化 JSON 字符串(用于 /config show 等展示) */
|
|
320
|
-
toFormattedJSON() {
|
|
321
|
-
return JSON.stringify(this.config, null, 2);
|
|
322
|
-
}
|
|
323
|
-
/** 获取完整配置对象(JSON 兼容的原始对象) */
|
|
324
|
-
toJSON() {
|
|
325
|
-
return structuredClone(this.config);
|
|
326
|
-
}
|
|
327
|
-
};
|
|
21
|
+
} from "./chunk-T2OUKQOX.js";
|
|
328
22
|
|
|
329
23
|
// src/providers/claude.ts
|
|
330
24
|
import Anthropic from "@anthropic-ai/sdk";
|
|
@@ -2427,8 +2121,8 @@ var ProviderRegistry = class {
|
|
|
2427
2121
|
};
|
|
2428
2122
|
|
|
2429
2123
|
// src/session/session-manager.ts
|
|
2430
|
-
import { readFileSync
|
|
2431
|
-
import { join
|
|
2124
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, unlinkSync, renameSync, openSync, readSync, closeSync } from "fs";
|
|
2125
|
+
import { join } from "path";
|
|
2432
2126
|
import { v4 as uuidv4 } from "uuid";
|
|
2433
2127
|
|
|
2434
2128
|
// src/core/types.ts
|
|
@@ -2707,20 +2401,20 @@ var SessionManager = class {
|
|
|
2707
2401
|
}
|
|
2708
2402
|
async save() {
|
|
2709
2403
|
if (!this._current) return;
|
|
2710
|
-
|
|
2711
|
-
const filePath =
|
|
2404
|
+
mkdirSync(this.historyDir, { recursive: true });
|
|
2405
|
+
const filePath = join(this.historyDir, `${this._current.id}.json`);
|
|
2712
2406
|
const tmpPath = filePath + ".tmp";
|
|
2713
|
-
|
|
2407
|
+
writeFileSync(tmpPath, JSON.stringify(this._current.toJSON(), null, 2), "utf-8");
|
|
2714
2408
|
renameSync(tmpPath, filePath);
|
|
2715
2409
|
}
|
|
2716
2410
|
loadSession(id) {
|
|
2717
|
-
const filePath =
|
|
2718
|
-
if (!
|
|
2411
|
+
const filePath = join(this.historyDir, `${id}.json`);
|
|
2412
|
+
if (!existsSync(filePath)) {
|
|
2719
2413
|
throw new Error(`Session ${id} not found`);
|
|
2720
2414
|
}
|
|
2721
2415
|
let data;
|
|
2722
2416
|
try {
|
|
2723
|
-
data = JSON.parse(
|
|
2417
|
+
data = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
2724
2418
|
} catch (err) {
|
|
2725
2419
|
throw new Error(`Session ${id} is corrupted: ${err instanceof Error ? err.message : String(err)}`);
|
|
2726
2420
|
}
|
|
@@ -2729,12 +2423,12 @@ var SessionManager = class {
|
|
|
2729
2423
|
return session;
|
|
2730
2424
|
}
|
|
2731
2425
|
listSessions() {
|
|
2732
|
-
if (!
|
|
2426
|
+
if (!existsSync(this.historyDir)) return [];
|
|
2733
2427
|
const files = readdirSync(this.historyDir).filter((f) => f.endsWith(".json"));
|
|
2734
2428
|
const metas = [];
|
|
2735
2429
|
for (const file of files) {
|
|
2736
2430
|
try {
|
|
2737
|
-
const meta = this.readSessionMeta(
|
|
2431
|
+
const meta = this.readSessionMeta(join(this.historyDir, file));
|
|
2738
2432
|
if (meta) metas.push(meta);
|
|
2739
2433
|
} catch (err) {
|
|
2740
2434
|
process.stderr.write(
|
|
@@ -2772,7 +2466,7 @@ var SessionManager = class {
|
|
|
2772
2466
|
if (id && provider && model) {
|
|
2773
2467
|
let messageCount = 0;
|
|
2774
2468
|
try {
|
|
2775
|
-
const full =
|
|
2469
|
+
const full = readFileSync(filePath, "utf-8");
|
|
2776
2470
|
const matches = full.match(/"role"\s*:/g);
|
|
2777
2471
|
messageCount = matches ? matches.length : 0;
|
|
2778
2472
|
} catch {
|
|
@@ -2787,7 +2481,7 @@ var SessionManager = class {
|
|
|
2787
2481
|
title: title || void 0
|
|
2788
2482
|
};
|
|
2789
2483
|
}
|
|
2790
|
-
const data = JSON.parse(
|
|
2484
|
+
const data = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
2791
2485
|
return {
|
|
2792
2486
|
id: data.id,
|
|
2793
2487
|
provider: data.provider,
|
|
@@ -2799,8 +2493,8 @@ var SessionManager = class {
|
|
|
2799
2493
|
};
|
|
2800
2494
|
}
|
|
2801
2495
|
deleteSession(id) {
|
|
2802
|
-
const filePath =
|
|
2803
|
-
if (!
|
|
2496
|
+
const filePath = join(this.historyDir, `${id}.json`);
|
|
2497
|
+
if (!existsSync(filePath)) return false;
|
|
2804
2498
|
try {
|
|
2805
2499
|
unlinkSync(filePath);
|
|
2806
2500
|
if (this._current && this._current.id === id) {
|
|
@@ -2837,14 +2531,14 @@ var SessionManager = class {
|
|
|
2837
2531
|
* 每个 session 最多返回 3 条匹配片段,全局最多 maxResults 个 session。
|
|
2838
2532
|
*/
|
|
2839
2533
|
searchMessages(query, maxResults = 20) {
|
|
2840
|
-
if (!
|
|
2534
|
+
if (!existsSync(this.historyDir)) return [];
|
|
2841
2535
|
const q = query.toLowerCase();
|
|
2842
|
-
const files = readdirSync(this.historyDir).filter((f) => f.endsWith(".json")).map((f) =>
|
|
2536
|
+
const files = readdirSync(this.historyDir).filter((f) => f.endsWith(".json")).map((f) => join(this.historyDir, f));
|
|
2843
2537
|
const results = [];
|
|
2844
2538
|
for (const filePath of files) {
|
|
2845
2539
|
if (results.length >= maxResults) break;
|
|
2846
2540
|
try {
|
|
2847
|
-
const data = JSON.parse(
|
|
2541
|
+
const data = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
2848
2542
|
const messages = data.messages ?? [];
|
|
2849
2543
|
const matches = [];
|
|
2850
2544
|
for (const msg of messages) {
|
|
@@ -2891,8 +2585,8 @@ var SessionManager = class {
|
|
|
2891
2585
|
|
|
2892
2586
|
// src/tools/git-context.ts
|
|
2893
2587
|
import { execSync } from "child_process";
|
|
2894
|
-
import { existsSync as
|
|
2895
|
-
import { join as
|
|
2588
|
+
import { existsSync as existsSync2 } from "fs";
|
|
2589
|
+
import { join as join2 } from "path";
|
|
2896
2590
|
function runGit(cmd, cwd) {
|
|
2897
2591
|
try {
|
|
2898
2592
|
return execSync(`git ${cmd}`, {
|
|
@@ -2909,7 +2603,7 @@ function getGitRoot(cwd = process.cwd()) {
|
|
|
2909
2603
|
return runGit("rev-parse --show-toplevel", cwd);
|
|
2910
2604
|
}
|
|
2911
2605
|
function getGitContext(cwd = process.cwd()) {
|
|
2912
|
-
if (!
|
|
2606
|
+
if (!existsSync2(join2(cwd, ".git"))) {
|
|
2913
2607
|
const result = runGit("rev-parse --git-dir", cwd);
|
|
2914
2608
|
if (!result) return null;
|
|
2915
2609
|
}
|
|
@@ -3489,11 +3183,11 @@ var McpManager = class {
|
|
|
3489
3183
|
};
|
|
3490
3184
|
|
|
3491
3185
|
// src/skills/manager.ts
|
|
3492
|
-
import { existsSync as
|
|
3493
|
-
import { join as
|
|
3186
|
+
import { existsSync as existsSync3, readdirSync as readdirSync2, mkdirSync as mkdirSync2, statSync } from "fs";
|
|
3187
|
+
import { join as join3 } from "path";
|
|
3494
3188
|
|
|
3495
3189
|
// src/skills/types.ts
|
|
3496
|
-
import { readFileSync as
|
|
3190
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
3497
3191
|
import { basename } from "path";
|
|
3498
3192
|
function parseSimpleYaml(yaml) {
|
|
3499
3193
|
const result = {};
|
|
@@ -3515,7 +3209,7 @@ function parseYamlArray(value) {
|
|
|
3515
3209
|
function parseSkillFile(filePath) {
|
|
3516
3210
|
let raw;
|
|
3517
3211
|
try {
|
|
3518
|
-
raw =
|
|
3212
|
+
raw = readFileSync2(filePath, "utf-8");
|
|
3519
3213
|
} catch {
|
|
3520
3214
|
return null;
|
|
3521
3215
|
}
|
|
@@ -3555,9 +3249,9 @@ var SkillManager = class {
|
|
|
3555
3249
|
/** 发现并加载 skillsDir 下所有 .md 文件,返回加载数量 */
|
|
3556
3250
|
loadSkills() {
|
|
3557
3251
|
this.skills.clear();
|
|
3558
|
-
if (!
|
|
3252
|
+
if (!existsSync3(this.skillsDir)) {
|
|
3559
3253
|
try {
|
|
3560
|
-
|
|
3254
|
+
mkdirSync2(this.skillsDir, { recursive: true });
|
|
3561
3255
|
} catch {
|
|
3562
3256
|
}
|
|
3563
3257
|
return 0;
|
|
@@ -3570,14 +3264,14 @@ var SkillManager = class {
|
|
|
3570
3264
|
}
|
|
3571
3265
|
for (const entry of entries) {
|
|
3572
3266
|
let filePath;
|
|
3573
|
-
const fullPath =
|
|
3267
|
+
const fullPath = join3(this.skillsDir, entry);
|
|
3574
3268
|
if (entry.endsWith(".md")) {
|
|
3575
3269
|
filePath = fullPath;
|
|
3576
3270
|
} else {
|
|
3577
3271
|
try {
|
|
3578
3272
|
if (statSync(fullPath).isDirectory()) {
|
|
3579
|
-
const skillMd =
|
|
3580
|
-
if (
|
|
3273
|
+
const skillMd = join3(fullPath, "SKILL.md");
|
|
3274
|
+
if (existsSync3(skillMd)) {
|
|
3581
3275
|
filePath = skillMd;
|
|
3582
3276
|
} else {
|
|
3583
3277
|
continue;
|
|
@@ -3880,9 +3574,9 @@ function autoTrimSessionIfNeeded(session, sizeLimit = SESSION_SIZE_LIMIT) {
|
|
|
3880
3574
|
}
|
|
3881
3575
|
|
|
3882
3576
|
// src/repl/dev-state.ts
|
|
3883
|
-
import { existsSync as
|
|
3884
|
-
import { join as
|
|
3885
|
-
import { homedir
|
|
3577
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2, unlinkSync as unlinkSync2, mkdirSync as mkdirSync3 } from "fs";
|
|
3578
|
+
import { join as join4 } from "path";
|
|
3579
|
+
import { homedir } from "os";
|
|
3886
3580
|
var DEV_STATE_MAX_CHARS = 6e3;
|
|
3887
3581
|
var SNAPSHOT_PROMPT = `You are about to be replaced by a different AI model. Please generate a structured development state snapshot so the next model can continue seamlessly.
|
|
3888
3582
|
|
|
@@ -3935,12 +3629,12 @@ function sessionHasMeaningfulContent(messages) {
|
|
|
3935
3629
|
return hasUser && hasAssistant;
|
|
3936
3630
|
}
|
|
3937
3631
|
function getDevStatePath() {
|
|
3938
|
-
return
|
|
3632
|
+
return join4(homedir(), CONFIG_DIR_NAME, DEV_STATE_FILE_NAME);
|
|
3939
3633
|
}
|
|
3940
3634
|
function saveDevState(content) {
|
|
3941
|
-
const configDir =
|
|
3942
|
-
if (!
|
|
3943
|
-
|
|
3635
|
+
const configDir = join4(homedir(), CONFIG_DIR_NAME);
|
|
3636
|
+
if (!existsSync4(configDir)) {
|
|
3637
|
+
mkdirSync3(configDir, { recursive: true });
|
|
3944
3638
|
}
|
|
3945
3639
|
let trimmed = content.trim();
|
|
3946
3640
|
if (trimmed.length > DEV_STATE_MAX_CHARS) {
|
|
@@ -3951,17 +3645,17 @@ function saveDevState(content) {
|
|
|
3951
3645
|
}
|
|
3952
3646
|
trimmed += "\n\n[...truncated]";
|
|
3953
3647
|
}
|
|
3954
|
-
|
|
3648
|
+
writeFileSync2(getDevStatePath(), trimmed, "utf-8");
|
|
3955
3649
|
}
|
|
3956
3650
|
function loadDevState() {
|
|
3957
3651
|
const path = getDevStatePath();
|
|
3958
|
-
if (!
|
|
3959
|
-
const content =
|
|
3652
|
+
if (!existsSync4(path)) return null;
|
|
3653
|
+
const content = readFileSync3(path, "utf-8").trim();
|
|
3960
3654
|
return content || null;
|
|
3961
3655
|
}
|
|
3962
3656
|
function clearDevState() {
|
|
3963
3657
|
const path = getDevStatePath();
|
|
3964
|
-
if (
|
|
3658
|
+
if (existsSync4(path)) {
|
|
3965
3659
|
try {
|
|
3966
3660
|
unlinkSync2(path);
|
|
3967
3661
|
} catch {
|
|
@@ -3970,7 +3664,6 @@ function clearDevState() {
|
|
|
3970
3664
|
}
|
|
3971
3665
|
|
|
3972
3666
|
export {
|
|
3973
|
-
ConfigManager,
|
|
3974
3667
|
detectsHallucinatedFileOp,
|
|
3975
3668
|
hadPreviousWriteToolCalls,
|
|
3976
3669
|
TOOL_CALL_REMINDER,
|
|
@@ -385,7 +385,7 @@ ${content}`);
|
|
|
385
385
|
}
|
|
386
386
|
}
|
|
387
387
|
async function runTaskMode(config, providers, configManager, topic) {
|
|
388
|
-
const { TaskOrchestrator } = await import("./task-orchestrator-
|
|
388
|
+
const { TaskOrchestrator } = await import("./task-orchestrator-M7Y32LNH.js");
|
|
389
389
|
const orchestrator = new TaskOrchestrator(config, providers, configManager);
|
|
390
390
|
let interrupted = false;
|
|
391
391
|
const onSigint = () => {
|