jinzd-ai-cli 0.4.72 → 0.4.74
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 +28 -3
- package/README.zh-CN.md +28 -3
- package/dist/{chunk-OKNKH5D7.js → chunk-265H5S5H.js} +1 -1
- package/dist/chunk-2ZD3YTVM.js +114 -0
- package/dist/chunk-BT2TCINO.js +155 -0
- package/dist/{chunk-XH65H3BT.js → chunk-D5ZDVEJJ.js} +10 -111
- package/dist/{chunk-73UI5AH7.js → chunk-FKVJRBPO.js} +3 -152
- package/dist/{chunk-57HHY5ZX.js → chunk-PLJUAA3J.js} +242 -368
- package/dist/chunk-VG3MFZYG.js +318 -0
- package/dist/{hub-NQADIYTS.js → hub-ABCHM2OR.js} +1 -1
- package/dist/index.js +182 -7
- package/dist/{run-tests-X2P5BOWV.js → run-tests-FMEFXUGO.js} +1 -1
- package/dist/{run-tests-4565WOUK.js → run-tests-HBLD2R6B.js} +2 -1
- package/dist/{server-Y4CT5IJJ.js → server-NMMRIWT2.js} +106 -5
- package/dist/{task-orchestrator-R33SWTHO.js → task-orchestrator-24UUKJW5.js} +4 -2
- package/dist/web/client/app.js +32 -0
- package/package.json +1 -1
|
@@ -1,330 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
schemaToJsonSchema,
|
|
4
|
+
truncateForPersist
|
|
5
|
+
} from "./chunk-D5ZDVEJJ.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-XH65H3BT.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-BT2TCINO.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
|
|
@@ -2438,6 +2132,9 @@ function getContentText(content) {
|
|
|
2438
2132
|
}
|
|
2439
2133
|
|
|
2440
2134
|
// src/session/session.ts
|
|
2135
|
+
function makeBranchId() {
|
|
2136
|
+
return Math.random().toString(16).slice(2, 8);
|
|
2137
|
+
}
|
|
2441
2138
|
var Session = class _Session {
|
|
2442
2139
|
id;
|
|
2443
2140
|
provider;
|
|
@@ -2453,12 +2150,32 @@ var Session = class _Session {
|
|
|
2453
2150
|
cacheReadTokens: 0
|
|
2454
2151
|
};
|
|
2455
2152
|
checkpoints = [];
|
|
2153
|
+
// ── B2 Branches (v0.4.74+) ──────────────────────────────────────
|
|
2154
|
+
/**
|
|
2155
|
+
* All branches in this session. The 'main' branch is auto-created and
|
|
2156
|
+
* represents the linear conversation for pre-B2 sessions.
|
|
2157
|
+
*/
|
|
2158
|
+
branches = [];
|
|
2159
|
+
/** Currently active branch — its messages live in `this.messages`. */
|
|
2160
|
+
activeBranchId = "main";
|
|
2161
|
+
/**
|
|
2162
|
+
* Stashed message arrays for INACTIVE branches. The active branch's
|
|
2163
|
+
* messages are always in `this.messages`, never duplicated here.
|
|
2164
|
+
*/
|
|
2165
|
+
_inactiveBranchMessages = /* @__PURE__ */ new Map();
|
|
2456
2166
|
constructor(id, provider, model) {
|
|
2457
2167
|
this.id = id;
|
|
2458
2168
|
this.provider = provider;
|
|
2459
2169
|
this.model = model;
|
|
2460
2170
|
this.created = /* @__PURE__ */ new Date();
|
|
2461
2171
|
this.updated = /* @__PURE__ */ new Date();
|
|
2172
|
+
this.branches.push({
|
|
2173
|
+
id: "main",
|
|
2174
|
+
title: "main",
|
|
2175
|
+
parentBranchId: null,
|
|
2176
|
+
parentMessageIndex: 0,
|
|
2177
|
+
created: this.created
|
|
2178
|
+
});
|
|
2462
2179
|
}
|
|
2463
2180
|
/**
|
|
2464
2181
|
* 更新 session 关联的 provider 和 model(在 /provider 或 /model 切换时调用)。
|
|
@@ -2545,6 +2262,120 @@ var Session = class _Session {
|
|
|
2545
2262
|
this.checkpoints = this.checkpoints.filter((c) => c.name !== name);
|
|
2546
2263
|
return this.checkpoints.length < len;
|
|
2547
2264
|
}
|
|
2265
|
+
// ── B2 Branch operations ────────────────────────────────────────
|
|
2266
|
+
/** Deep-clone a messages array (matches `Session.fork` semantics). */
|
|
2267
|
+
static cloneMessages(msgs) {
|
|
2268
|
+
return msgs.map((m) => {
|
|
2269
|
+
const cloned = { ...m };
|
|
2270
|
+
if (Array.isArray(cloned.content)) {
|
|
2271
|
+
cloned.content = cloned.content.map(
|
|
2272
|
+
(part) => typeof part === "object" && part !== null ? { ...part } : part
|
|
2273
|
+
);
|
|
2274
|
+
}
|
|
2275
|
+
if (cloned.toolCalls) {
|
|
2276
|
+
cloned.toolCalls = cloned.toolCalls.map((tc) => ({ ...tc }));
|
|
2277
|
+
}
|
|
2278
|
+
return cloned;
|
|
2279
|
+
});
|
|
2280
|
+
}
|
|
2281
|
+
/** List all branches (metadata only). */
|
|
2282
|
+
listBranches() {
|
|
2283
|
+
return this.branches.map((b) => ({ ...b }));
|
|
2284
|
+
}
|
|
2285
|
+
/** Current active branch metadata. */
|
|
2286
|
+
getActiveBranch() {
|
|
2287
|
+
const b = this.branches.find((b2) => b2.id === this.activeBranchId);
|
|
2288
|
+
if (!b) {
|
|
2289
|
+
this.activeBranchId = this.branches[0]?.id ?? "main";
|
|
2290
|
+
return this.branches[0] ?? { id: "main", title: "main", parentBranchId: null, parentMessageIndex: 0, created: /* @__PURE__ */ new Date() };
|
|
2291
|
+
}
|
|
2292
|
+
return b;
|
|
2293
|
+
}
|
|
2294
|
+
/**
|
|
2295
|
+
* Create a new branch by forking the active branch at message index
|
|
2296
|
+
* `fromIndex`. Copies `messages[0..fromIndex]` into the new branch
|
|
2297
|
+
* and switches to it. The original active branch is preserved intact
|
|
2298
|
+
* in the stash.
|
|
2299
|
+
*
|
|
2300
|
+
* @returns new branch id
|
|
2301
|
+
* @throws if fromIndex is out of range
|
|
2302
|
+
*/
|
|
2303
|
+
createBranch(fromIndex, title) {
|
|
2304
|
+
if (fromIndex < 0 || fromIndex > this.messages.length) {
|
|
2305
|
+
throw new Error(
|
|
2306
|
+
`createBranch: fromIndex ${fromIndex} out of range [0, ${this.messages.length}]`
|
|
2307
|
+
);
|
|
2308
|
+
}
|
|
2309
|
+
this._inactiveBranchMessages.set(this.activeBranchId, this.messages);
|
|
2310
|
+
const id = makeBranchId();
|
|
2311
|
+
const meta = {
|
|
2312
|
+
id,
|
|
2313
|
+
title: title || `branch-${this.branches.length + 1}`,
|
|
2314
|
+
parentBranchId: this.activeBranchId,
|
|
2315
|
+
parentMessageIndex: fromIndex,
|
|
2316
|
+
created: /* @__PURE__ */ new Date()
|
|
2317
|
+
};
|
|
2318
|
+
this.branches.push(meta);
|
|
2319
|
+
this.messages = _Session.cloneMessages(this.messages.slice(0, fromIndex));
|
|
2320
|
+
this.activeBranchId = id;
|
|
2321
|
+
this.updated = /* @__PURE__ */ new Date();
|
|
2322
|
+
return id;
|
|
2323
|
+
}
|
|
2324
|
+
/**
|
|
2325
|
+
* Switch the active branch. Stashes current messages under the old
|
|
2326
|
+
* active id and loads the target branch's messages into `this.messages`.
|
|
2327
|
+
*
|
|
2328
|
+
* @returns true if switched, false if id not found or already active
|
|
2329
|
+
*/
|
|
2330
|
+
switchBranch(id) {
|
|
2331
|
+
if (id === this.activeBranchId) return false;
|
|
2332
|
+
if (!this.branches.some((b) => b.id === id)) return false;
|
|
2333
|
+
this._inactiveBranchMessages.set(this.activeBranchId, this.messages);
|
|
2334
|
+
const target = this._inactiveBranchMessages.get(id) ?? [];
|
|
2335
|
+
this._inactiveBranchMessages.delete(id);
|
|
2336
|
+
this.messages = target;
|
|
2337
|
+
this.activeBranchId = id;
|
|
2338
|
+
this.updated = /* @__PURE__ */ new Date();
|
|
2339
|
+
return true;
|
|
2340
|
+
}
|
|
2341
|
+
/**
|
|
2342
|
+
* Delete a branch by id. Cannot delete the active branch or the last
|
|
2343
|
+
* remaining branch. If other branches list this one as parent, their
|
|
2344
|
+
* parent pointer is retargeted to this branch's parent (transparent
|
|
2345
|
+
* to callers — branches still form a valid forest).
|
|
2346
|
+
*
|
|
2347
|
+
* @returns true if deleted
|
|
2348
|
+
*/
|
|
2349
|
+
deleteBranch(id) {
|
|
2350
|
+
if (id === this.activeBranchId) return false;
|
|
2351
|
+
if (this.branches.length <= 1) return false;
|
|
2352
|
+
const idx = this.branches.findIndex((b) => b.id === id);
|
|
2353
|
+
if (idx === -1) return false;
|
|
2354
|
+
const deleted = this.branches[idx];
|
|
2355
|
+
for (const b of this.branches) {
|
|
2356
|
+
if (b.parentBranchId === id) {
|
|
2357
|
+
b.parentBranchId = deleted.parentBranchId;
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2360
|
+
this.branches.splice(idx, 1);
|
|
2361
|
+
this._inactiveBranchMessages.delete(id);
|
|
2362
|
+
this.updated = /* @__PURE__ */ new Date();
|
|
2363
|
+
return true;
|
|
2364
|
+
}
|
|
2365
|
+
/** Rename a branch (affects only display title). */
|
|
2366
|
+
renameBranch(id, newTitle) {
|
|
2367
|
+
const b = this.branches.find((b2) => b2.id === id);
|
|
2368
|
+
if (!b) return false;
|
|
2369
|
+
b.title = newTitle;
|
|
2370
|
+
this.updated = /* @__PURE__ */ new Date();
|
|
2371
|
+
return true;
|
|
2372
|
+
}
|
|
2373
|
+
/** Messages of any branch (active or inactive) — read-only copy. */
|
|
2374
|
+
getBranchMessages(id) {
|
|
2375
|
+
if (id === this.activeBranchId) return this.messages.slice();
|
|
2376
|
+
const m = this._inactiveBranchMessages.get(id);
|
|
2377
|
+
return m ? m.slice() : null;
|
|
2378
|
+
}
|
|
2548
2379
|
getMeta() {
|
|
2549
2380
|
return {
|
|
2550
2381
|
id: this.id,
|
|
@@ -2557,6 +2388,23 @@ var Session = class _Session {
|
|
|
2557
2388
|
};
|
|
2558
2389
|
}
|
|
2559
2390
|
toJSON() {
|
|
2391
|
+
const serializeMessages = (msgs) => msgs.map((m) => {
|
|
2392
|
+
const out = {
|
|
2393
|
+
role: m.role,
|
|
2394
|
+
content: m.content,
|
|
2395
|
+
timestamp: m.timestamp.toISOString()
|
|
2396
|
+
};
|
|
2397
|
+
if (m.toolCalls) out.toolCalls = m.toolCalls;
|
|
2398
|
+
if (m.reasoningContent !== void 0) out.reasoningContent = m.reasoningContent;
|
|
2399
|
+
if (m.toolCallId) out.toolCallId = m.toolCallId;
|
|
2400
|
+
if (m.toolName) out.toolName = m.toolName;
|
|
2401
|
+
if (m.isError !== void 0) out.isError = m.isError;
|
|
2402
|
+
return out;
|
|
2403
|
+
});
|
|
2404
|
+
const branchMessages = {};
|
|
2405
|
+
for (const [id, msgs] of this._inactiveBranchMessages.entries()) {
|
|
2406
|
+
branchMessages[id] = serializeMessages(msgs);
|
|
2407
|
+
}
|
|
2560
2408
|
return {
|
|
2561
2409
|
id: this.id,
|
|
2562
2410
|
provider: this.provider,
|
|
@@ -2570,19 +2418,21 @@ var Session = class _Session {
|
|
|
2570
2418
|
messageIndex: c.messageIndex,
|
|
2571
2419
|
timestamp: c.timestamp.toISOString()
|
|
2572
2420
|
})),
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2421
|
+
// B2 Branches (v0.4.74+). Omitted for sessions with only the default
|
|
2422
|
+
// 'main' branch and no stashed messages (keeps file size identical
|
|
2423
|
+
// to pre-B2 for the common case).
|
|
2424
|
+
...this.branches.length > 1 || this._inactiveBranchMessages.size > 0 ? {
|
|
2425
|
+
activeBranchId: this.activeBranchId,
|
|
2426
|
+
branches: this.branches.map((b) => ({
|
|
2427
|
+
id: b.id,
|
|
2428
|
+
title: b.title,
|
|
2429
|
+
parentBranchId: b.parentBranchId,
|
|
2430
|
+
parentMessageIndex: b.parentMessageIndex,
|
|
2431
|
+
created: b.created.toISOString()
|
|
2432
|
+
})),
|
|
2433
|
+
branchMessages
|
|
2434
|
+
} : {},
|
|
2435
|
+
messages: serializeMessages(this.messages)
|
|
2586
2436
|
};
|
|
2587
2437
|
}
|
|
2588
2438
|
/**
|
|
@@ -2650,7 +2500,7 @@ var Session = class _Session {
|
|
|
2650
2500
|
timestamp: new Date(c.timestamp)
|
|
2651
2501
|
}));
|
|
2652
2502
|
}
|
|
2653
|
-
|
|
2503
|
+
const deserializeMessages = (arr) => arr.map((m) => {
|
|
2654
2504
|
const ts = new Date(m.timestamp);
|
|
2655
2505
|
const msg = {
|
|
2656
2506
|
role: m.role ?? "user",
|
|
@@ -2664,6 +2514,31 @@ var Session = class _Session {
|
|
|
2664
2514
|
if (typeof m.isError === "boolean") msg.isError = m.isError;
|
|
2665
2515
|
return msg;
|
|
2666
2516
|
});
|
|
2517
|
+
session.messages = deserializeMessages(d.messages);
|
|
2518
|
+
if (Array.isArray(d.branches) && d.branches.length > 0) {
|
|
2519
|
+
session.branches = d.branches.map((b) => {
|
|
2520
|
+
const ts = new Date(b.created);
|
|
2521
|
+
return {
|
|
2522
|
+
id: String(b.id ?? "main"),
|
|
2523
|
+
title: String(b.title ?? b.id ?? "main"),
|
|
2524
|
+
parentBranchId: typeof b.parentBranchId === "string" ? b.parentBranchId : null,
|
|
2525
|
+
parentMessageIndex: typeof b.parentMessageIndex === "number" ? b.parentMessageIndex : 0,
|
|
2526
|
+
created: isNaN(ts.getTime()) ? /* @__PURE__ */ new Date() : ts
|
|
2527
|
+
};
|
|
2528
|
+
});
|
|
2529
|
+
session.activeBranchId = typeof d.activeBranchId === "string" ? d.activeBranchId : session.branches[0]?.id ?? "main";
|
|
2530
|
+
const bm = d.branchMessages;
|
|
2531
|
+
if (bm && typeof bm === "object") {
|
|
2532
|
+
for (const [id, arr] of Object.entries(bm)) {
|
|
2533
|
+
if (id === session.activeBranchId) continue;
|
|
2534
|
+
if (!Array.isArray(arr)) continue;
|
|
2535
|
+
session._inactiveBranchMessages.set(
|
|
2536
|
+
id,
|
|
2537
|
+
deserializeMessages(arr)
|
|
2538
|
+
);
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
2667
2542
|
return session;
|
|
2668
2543
|
}
|
|
2669
2544
|
};
|
|
@@ -2707,20 +2582,20 @@ var SessionManager = class {
|
|
|
2707
2582
|
}
|
|
2708
2583
|
async save() {
|
|
2709
2584
|
if (!this._current) return;
|
|
2710
|
-
|
|
2711
|
-
const filePath =
|
|
2585
|
+
mkdirSync(this.historyDir, { recursive: true });
|
|
2586
|
+
const filePath = join(this.historyDir, `${this._current.id}.json`);
|
|
2712
2587
|
const tmpPath = filePath + ".tmp";
|
|
2713
|
-
|
|
2588
|
+
writeFileSync(tmpPath, JSON.stringify(this._current.toJSON(), null, 2), "utf-8");
|
|
2714
2589
|
renameSync(tmpPath, filePath);
|
|
2715
2590
|
}
|
|
2716
2591
|
loadSession(id) {
|
|
2717
|
-
const filePath =
|
|
2718
|
-
if (!
|
|
2592
|
+
const filePath = join(this.historyDir, `${id}.json`);
|
|
2593
|
+
if (!existsSync(filePath)) {
|
|
2719
2594
|
throw new Error(`Session ${id} not found`);
|
|
2720
2595
|
}
|
|
2721
2596
|
let data;
|
|
2722
2597
|
try {
|
|
2723
|
-
data = JSON.parse(
|
|
2598
|
+
data = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
2724
2599
|
} catch (err) {
|
|
2725
2600
|
throw new Error(`Session ${id} is corrupted: ${err instanceof Error ? err.message : String(err)}`);
|
|
2726
2601
|
}
|
|
@@ -2729,12 +2604,12 @@ var SessionManager = class {
|
|
|
2729
2604
|
return session;
|
|
2730
2605
|
}
|
|
2731
2606
|
listSessions() {
|
|
2732
|
-
if (!
|
|
2607
|
+
if (!existsSync(this.historyDir)) return [];
|
|
2733
2608
|
const files = readdirSync(this.historyDir).filter((f) => f.endsWith(".json"));
|
|
2734
2609
|
const metas = [];
|
|
2735
2610
|
for (const file of files) {
|
|
2736
2611
|
try {
|
|
2737
|
-
const meta = this.readSessionMeta(
|
|
2612
|
+
const meta = this.readSessionMeta(join(this.historyDir, file));
|
|
2738
2613
|
if (meta) metas.push(meta);
|
|
2739
2614
|
} catch (err) {
|
|
2740
2615
|
process.stderr.write(
|
|
@@ -2772,7 +2647,7 @@ var SessionManager = class {
|
|
|
2772
2647
|
if (id && provider && model) {
|
|
2773
2648
|
let messageCount = 0;
|
|
2774
2649
|
try {
|
|
2775
|
-
const full =
|
|
2650
|
+
const full = readFileSync(filePath, "utf-8");
|
|
2776
2651
|
const matches = full.match(/"role"\s*:/g);
|
|
2777
2652
|
messageCount = matches ? matches.length : 0;
|
|
2778
2653
|
} catch {
|
|
@@ -2787,7 +2662,7 @@ var SessionManager = class {
|
|
|
2787
2662
|
title: title || void 0
|
|
2788
2663
|
};
|
|
2789
2664
|
}
|
|
2790
|
-
const data = JSON.parse(
|
|
2665
|
+
const data = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
2791
2666
|
return {
|
|
2792
2667
|
id: data.id,
|
|
2793
2668
|
provider: data.provider,
|
|
@@ -2799,8 +2674,8 @@ var SessionManager = class {
|
|
|
2799
2674
|
};
|
|
2800
2675
|
}
|
|
2801
2676
|
deleteSession(id) {
|
|
2802
|
-
const filePath =
|
|
2803
|
-
if (!
|
|
2677
|
+
const filePath = join(this.historyDir, `${id}.json`);
|
|
2678
|
+
if (!existsSync(filePath)) return false;
|
|
2804
2679
|
try {
|
|
2805
2680
|
unlinkSync(filePath);
|
|
2806
2681
|
if (this._current && this._current.id === id) {
|
|
@@ -2837,14 +2712,14 @@ var SessionManager = class {
|
|
|
2837
2712
|
* 每个 session 最多返回 3 条匹配片段,全局最多 maxResults 个 session。
|
|
2838
2713
|
*/
|
|
2839
2714
|
searchMessages(query, maxResults = 20) {
|
|
2840
|
-
if (!
|
|
2715
|
+
if (!existsSync(this.historyDir)) return [];
|
|
2841
2716
|
const q = query.toLowerCase();
|
|
2842
|
-
const files = readdirSync(this.historyDir).filter((f) => f.endsWith(".json")).map((f) =>
|
|
2717
|
+
const files = readdirSync(this.historyDir).filter((f) => f.endsWith(".json")).map((f) => join(this.historyDir, f));
|
|
2843
2718
|
const results = [];
|
|
2844
2719
|
for (const filePath of files) {
|
|
2845
2720
|
if (results.length >= maxResults) break;
|
|
2846
2721
|
try {
|
|
2847
|
-
const data = JSON.parse(
|
|
2722
|
+
const data = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
2848
2723
|
const messages = data.messages ?? [];
|
|
2849
2724
|
const matches = [];
|
|
2850
2725
|
for (const msg of messages) {
|
|
@@ -2891,8 +2766,8 @@ var SessionManager = class {
|
|
|
2891
2766
|
|
|
2892
2767
|
// src/tools/git-context.ts
|
|
2893
2768
|
import { execSync } from "child_process";
|
|
2894
|
-
import { existsSync as
|
|
2895
|
-
import { join as
|
|
2769
|
+
import { existsSync as existsSync2 } from "fs";
|
|
2770
|
+
import { join as join2 } from "path";
|
|
2896
2771
|
function runGit(cmd, cwd) {
|
|
2897
2772
|
try {
|
|
2898
2773
|
return execSync(`git ${cmd}`, {
|
|
@@ -2909,7 +2784,7 @@ function getGitRoot(cwd = process.cwd()) {
|
|
|
2909
2784
|
return runGit("rev-parse --show-toplevel", cwd);
|
|
2910
2785
|
}
|
|
2911
2786
|
function getGitContext(cwd = process.cwd()) {
|
|
2912
|
-
if (!
|
|
2787
|
+
if (!existsSync2(join2(cwd, ".git"))) {
|
|
2913
2788
|
const result = runGit("rev-parse --git-dir", cwd);
|
|
2914
2789
|
if (!result) return null;
|
|
2915
2790
|
}
|
|
@@ -3489,11 +3364,11 @@ var McpManager = class {
|
|
|
3489
3364
|
};
|
|
3490
3365
|
|
|
3491
3366
|
// src/skills/manager.ts
|
|
3492
|
-
import { existsSync as
|
|
3493
|
-
import { join as
|
|
3367
|
+
import { existsSync as existsSync3, readdirSync as readdirSync2, mkdirSync as mkdirSync2, statSync } from "fs";
|
|
3368
|
+
import { join as join3 } from "path";
|
|
3494
3369
|
|
|
3495
3370
|
// src/skills/types.ts
|
|
3496
|
-
import { readFileSync as
|
|
3371
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
3497
3372
|
import { basename } from "path";
|
|
3498
3373
|
function parseSimpleYaml(yaml) {
|
|
3499
3374
|
const result = {};
|
|
@@ -3515,7 +3390,7 @@ function parseYamlArray(value) {
|
|
|
3515
3390
|
function parseSkillFile(filePath) {
|
|
3516
3391
|
let raw;
|
|
3517
3392
|
try {
|
|
3518
|
-
raw =
|
|
3393
|
+
raw = readFileSync2(filePath, "utf-8");
|
|
3519
3394
|
} catch {
|
|
3520
3395
|
return null;
|
|
3521
3396
|
}
|
|
@@ -3555,9 +3430,9 @@ var SkillManager = class {
|
|
|
3555
3430
|
/** 发现并加载 skillsDir 下所有 .md 文件,返回加载数量 */
|
|
3556
3431
|
loadSkills() {
|
|
3557
3432
|
this.skills.clear();
|
|
3558
|
-
if (!
|
|
3433
|
+
if (!existsSync3(this.skillsDir)) {
|
|
3559
3434
|
try {
|
|
3560
|
-
|
|
3435
|
+
mkdirSync2(this.skillsDir, { recursive: true });
|
|
3561
3436
|
} catch {
|
|
3562
3437
|
}
|
|
3563
3438
|
return 0;
|
|
@@ -3570,14 +3445,14 @@ var SkillManager = class {
|
|
|
3570
3445
|
}
|
|
3571
3446
|
for (const entry of entries) {
|
|
3572
3447
|
let filePath;
|
|
3573
|
-
const fullPath =
|
|
3448
|
+
const fullPath = join3(this.skillsDir, entry);
|
|
3574
3449
|
if (entry.endsWith(".md")) {
|
|
3575
3450
|
filePath = fullPath;
|
|
3576
3451
|
} else {
|
|
3577
3452
|
try {
|
|
3578
3453
|
if (statSync(fullPath).isDirectory()) {
|
|
3579
|
-
const skillMd =
|
|
3580
|
-
if (
|
|
3454
|
+
const skillMd = join3(fullPath, "SKILL.md");
|
|
3455
|
+
if (existsSync3(skillMd)) {
|
|
3581
3456
|
filePath = skillMd;
|
|
3582
3457
|
} else {
|
|
3583
3458
|
continue;
|
|
@@ -3880,9 +3755,9 @@ function autoTrimSessionIfNeeded(session, sizeLimit = SESSION_SIZE_LIMIT) {
|
|
|
3880
3755
|
}
|
|
3881
3756
|
|
|
3882
3757
|
// src/repl/dev-state.ts
|
|
3883
|
-
import { existsSync as
|
|
3884
|
-
import { join as
|
|
3885
|
-
import { homedir
|
|
3758
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2, unlinkSync as unlinkSync2, mkdirSync as mkdirSync3 } from "fs";
|
|
3759
|
+
import { join as join4 } from "path";
|
|
3760
|
+
import { homedir } from "os";
|
|
3886
3761
|
var DEV_STATE_MAX_CHARS = 6e3;
|
|
3887
3762
|
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
3763
|
|
|
@@ -3935,12 +3810,12 @@ function sessionHasMeaningfulContent(messages) {
|
|
|
3935
3810
|
return hasUser && hasAssistant;
|
|
3936
3811
|
}
|
|
3937
3812
|
function getDevStatePath() {
|
|
3938
|
-
return
|
|
3813
|
+
return join4(homedir(), CONFIG_DIR_NAME, DEV_STATE_FILE_NAME);
|
|
3939
3814
|
}
|
|
3940
3815
|
function saveDevState(content) {
|
|
3941
|
-
const configDir =
|
|
3942
|
-
if (!
|
|
3943
|
-
|
|
3816
|
+
const configDir = join4(homedir(), CONFIG_DIR_NAME);
|
|
3817
|
+
if (!existsSync4(configDir)) {
|
|
3818
|
+
mkdirSync3(configDir, { recursive: true });
|
|
3944
3819
|
}
|
|
3945
3820
|
let trimmed = content.trim();
|
|
3946
3821
|
if (trimmed.length > DEV_STATE_MAX_CHARS) {
|
|
@@ -3951,17 +3826,17 @@ function saveDevState(content) {
|
|
|
3951
3826
|
}
|
|
3952
3827
|
trimmed += "\n\n[...truncated]";
|
|
3953
3828
|
}
|
|
3954
|
-
|
|
3829
|
+
writeFileSync2(getDevStatePath(), trimmed, "utf-8");
|
|
3955
3830
|
}
|
|
3956
3831
|
function loadDevState() {
|
|
3957
3832
|
const path = getDevStatePath();
|
|
3958
|
-
if (!
|
|
3959
|
-
const content =
|
|
3833
|
+
if (!existsSync4(path)) return null;
|
|
3834
|
+
const content = readFileSync3(path, "utf-8").trim();
|
|
3960
3835
|
return content || null;
|
|
3961
3836
|
}
|
|
3962
3837
|
function clearDevState() {
|
|
3963
3838
|
const path = getDevStatePath();
|
|
3964
|
-
if (
|
|
3839
|
+
if (existsSync4(path)) {
|
|
3965
3840
|
try {
|
|
3966
3841
|
unlinkSync2(path);
|
|
3967
3842
|
} catch {
|
|
@@ -3970,7 +3845,6 @@ function clearDevState() {
|
|
|
3970
3845
|
}
|
|
3971
3846
|
|
|
3972
3847
|
export {
|
|
3973
|
-
ConfigManager,
|
|
3974
3848
|
detectsHallucinatedFileOp,
|
|
3975
3849
|
hadPreviousWriteToolCalls,
|
|
3976
3850
|
TOOL_CALL_REMINDER,
|