jinzd-ai-cli 0.4.87 → 0.4.89
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/{batch-FNHSLCMV.js → batch-UMQYXVKG.js} +2 -2
- package/dist/chat-index-W2UZ34ZI.js +18 -0
- package/dist/{chunk-PFYAAX2S.js → chunk-2DXY7UGF.js} +16 -63
- package/dist/{chunk-Y75YPB5F.js → chunk-3O3U3L5W.js} +19 -1
- package/dist/{chunk-AQX3GYRD.js → chunk-4WVXTADR.js} +1 -1
- package/dist/{chunk-SP6RFAKW.js → chunk-ABPT6XCI.js} +76 -3
- package/dist/chunk-ANYYM4CF.js +460 -0
- package/dist/{chunk-BAOIXQHD.js → chunk-E7YC4GWV.js} +1 -1
- package/dist/{chunk-3DGNN4RM.js → chunk-FVRLRIKC.js} +28 -3
- package/dist/chunk-KHYD3WXE.js +52 -0
- package/dist/{chunk-CQQQFNND.js → chunk-KJLJPUY2.js} +6 -4
- package/dist/{chunk-TFLBQRQM.js → chunk-TKYNTXKB.js} +1 -1
- package/dist/electron-server.js +310 -14
- package/dist/{hub-HEC4GYKR.js → hub-4P2BH57W.js} +1 -1
- package/dist/index.js +247 -17
- package/dist/{run-tests-7PTRRO4D.js → run-tests-5TO5G3YH.js} +2 -2
- package/dist/{run-tests-P7GIZ6UH.js → run-tests-TGGXTOFF.js} +1 -1
- package/dist/{semantic-ICJ536BG.js → semantic-YDRPPVWK.js} +3 -2
- package/dist/{server-TRTN3RVO.js → server-NG7AEAD5.js} +15 -13
- package/dist/{server-GJRBVTTQ.js → server-U2BBLP4Y.js} +7 -5
- package/dist/{task-orchestrator-36SFPCP7.js → task-orchestrator-ODU45UQG.js} +7 -5
- package/dist/{vector-store-YTVHACBV.js → vector-store-QARQ2P6D.js} +2 -1
- package/package.json +1 -1
package/dist/electron-server.js
CHANGED
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
VERSION,
|
|
37
37
|
buildUserIdentityPrompt,
|
|
38
38
|
runTestsTool
|
|
39
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-4WVXTADR.js";
|
|
40
40
|
import {
|
|
41
41
|
hasSemanticIndex,
|
|
42
42
|
semanticSearch
|
|
@@ -44,7 +44,10 @@ import {
|
|
|
44
44
|
import {
|
|
45
45
|
loadIndex
|
|
46
46
|
} from "./chunk-BJAT4GNC.js";
|
|
47
|
-
import
|
|
47
|
+
import {
|
|
48
|
+
EMBEDDING_DIM,
|
|
49
|
+
embedOne
|
|
50
|
+
} from "./chunk-XMA222FQ.js";
|
|
48
51
|
|
|
49
52
|
// src/web/server.ts
|
|
50
53
|
import express from "express";
|
|
@@ -223,6 +226,24 @@ var ConfigSchema = z.object({
|
|
|
223
226
|
// 必须确认插件来源可信后,再设为 true 启用。
|
|
224
227
|
// 可通过 /config 命令或直接编辑 ~/.aicli/config.json 开启。
|
|
225
228
|
allowPlugins: z.boolean().default(false),
|
|
229
|
+
// 敏感信息脱敏(v0.4.88+,2026-04 凭据泄漏事件后引入)
|
|
230
|
+
// 会话保存到磁盘 / 发送给 Provider 前,自动将 API key、密码、PEM 私钥等
|
|
231
|
+
// 按正则替换为 [REDACTED:kind] 占位符。模式定义在 src/security/redactor.ts。
|
|
232
|
+
//
|
|
233
|
+
// redactOnSave 默认 true:保存到 ~/.aicli/history/*.json 时脱敏(推荐开启)
|
|
234
|
+
// redactOnSend 默认 false:发送到 Provider 时脱敏(激进,可能影响工具结果)
|
|
235
|
+
// mode:
|
|
236
|
+
// 'default' — 使用内置 13 条模式(推荐)
|
|
237
|
+
// 'strict' — 同 default,但阈值更低(更激进,可能误报)
|
|
238
|
+
// 'off' — 禁用所有模式(等同 redactOnSave=false)
|
|
239
|
+
// customPatterns — 用户补充正则字符串(支持 /pattern/flags 形式),
|
|
240
|
+
// 无效正则会静默跳过,不会中断保存。
|
|
241
|
+
security: z.object({
|
|
242
|
+
redactOnSave: z.boolean().default(true),
|
|
243
|
+
redactOnSend: z.boolean().default(false),
|
|
244
|
+
mode: z.enum(["default", "strict", "off"]).default("default"),
|
|
245
|
+
customPatterns: z.array(z.string()).default([])
|
|
246
|
+
}).default({}),
|
|
226
247
|
// 智能模型路由(v0.4.68+)
|
|
227
248
|
// 按用户每轮输入的内容/标签/长度动态选择模型,在同一 provider 内切换,
|
|
228
249
|
// 例:短问题走 haiku(省钱),planning 走 opus(质量)。
|
|
@@ -414,8 +435,8 @@ ${err}`
|
|
|
414
435
|
return EnvLoader.getDefaultProvider() ?? this.config.defaultProvider;
|
|
415
436
|
}
|
|
416
437
|
/** 点分路径读取配置值,如 `ui.theme` → config.ui.theme */
|
|
417
|
-
getByPath(
|
|
418
|
-
const keys =
|
|
438
|
+
getByPath(path4) {
|
|
439
|
+
const keys = path4.split(".");
|
|
419
440
|
let current = this.config;
|
|
420
441
|
for (const key of keys) {
|
|
421
442
|
if (current == null || typeof current !== "object") return void 0;
|
|
@@ -424,8 +445,8 @@ ${err}`
|
|
|
424
445
|
return current;
|
|
425
446
|
}
|
|
426
447
|
/** 点分路径写入配置值,自动类型转换(boolean/number/string)并持久化 */
|
|
427
|
-
setByPath(
|
|
428
|
-
const keys =
|
|
448
|
+
setByPath(path4, rawValue) {
|
|
449
|
+
const keys = path4.split(".");
|
|
429
450
|
if (keys.length === 0) return;
|
|
430
451
|
let value = rawValue;
|
|
431
452
|
if (rawValue === "true") value = true;
|
|
@@ -444,7 +465,7 @@ ${err}`
|
|
|
444
465
|
const result = ConfigSchema.safeParse(draft);
|
|
445
466
|
if (!result.success) {
|
|
446
467
|
const firstErr = result.error.errors[0];
|
|
447
|
-
throw new ConfigError(`Invalid config value for "${
|
|
468
|
+
throw new ConfigError(`Invalid config value for "${path4}": ${firstErr?.message ?? "validation failed"}`);
|
|
448
469
|
}
|
|
449
470
|
this.config = result.data;
|
|
450
471
|
this.save();
|
|
@@ -3036,6 +3057,104 @@ var Session = class _Session {
|
|
|
3036
3057
|
}
|
|
3037
3058
|
};
|
|
3038
3059
|
|
|
3060
|
+
// src/security/redactor.ts
|
|
3061
|
+
var DEFAULT_PATTERNS = [
|
|
3062
|
+
// password: xxx / password = xxx / password="xxx"
|
|
3063
|
+
// Covers YAML / JSON / shell-ish / env-file forms.
|
|
3064
|
+
{ kind: "password", regex: /\b(password|passwd|pwd)\s*[:=]\s*["']?([^\s"',;{}]{4,200})["']?/gi },
|
|
3065
|
+
// PGPASSWORD=xxx (explicit bash env-var form, separate rule because no quotes usually)
|
|
3066
|
+
{ kind: "pgpassword-env", regex: /\b(PGPASSWORD)=([^\s"']{4,200})/g },
|
|
3067
|
+
// JDBC/PG/MySQL/Mongo connection strings with inline credentials
|
|
3068
|
+
// postgresql://user:pass@host/db → redact pass
|
|
3069
|
+
{ kind: "db-uri-password", regex: /(\b(?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?|redis|amqp|mssql):\/\/[^:\s]+:)([^@\s]+)(@)/gi },
|
|
3070
|
+
// Anthropic API keys
|
|
3071
|
+
{ kind: "anthropic-key", regex: /(sk-ant-[a-zA-Z0-9_-]{90,})/g },
|
|
3072
|
+
// OpenAI / generic sk- keys — requires length ≥32 to avoid eating short identifiers
|
|
3073
|
+
{ kind: "openai-key", regex: /(sk-(?:proj-)?[a-zA-Z0-9_-]{32,})/g },
|
|
3074
|
+
// GitHub personal access tokens
|
|
3075
|
+
{ kind: "github-pat", regex: /\b(ghp_[a-zA-Z0-9]{36})\b/g },
|
|
3076
|
+
{ kind: "github-oauth", regex: /\b(gho_[a-zA-Z0-9]{36})\b/g },
|
|
3077
|
+
{ kind: "github-install", regex: /\b(ghs_[a-zA-Z0-9]{36})\b/g },
|
|
3078
|
+
// Slack tokens
|
|
3079
|
+
{ kind: "slack-bot", regex: /\b(xoxb-\d+-\d+-[a-zA-Z0-9]+)\b/g },
|
|
3080
|
+
{ kind: "slack-user", regex: /\b(xoxp-\d+-\d+-\d+-[a-zA-Z0-9]+)\b/g },
|
|
3081
|
+
// AWS access key IDs (AKIA...) and secret access keys are context-dependent;
|
|
3082
|
+
// we only catch the ID because secret key alone is indistinguishable from random base64.
|
|
3083
|
+
{ kind: "aws-access-key-id", regex: /\b(AKIA[0-9A-Z]{16})\b/g },
|
|
3084
|
+
// Google API keys
|
|
3085
|
+
{ kind: "google-api-key", regex: /\b(AIza[0-9A-Za-z_-]{35})\b/g },
|
|
3086
|
+
// Generic "api_key": "..." / "apiKey": "..." / api-key=xxx
|
|
3087
|
+
{ kind: "api-key", regex: /\b(api[_-]?key)\s*[:=]\s*["']?([a-zA-Z0-9_\-.]{16,200})["']?/gi },
|
|
3088
|
+
// Generic token: xxx (only when value looks token-shaped; avoids eating human prose)
|
|
3089
|
+
{ kind: "token", regex: /\b(token|access[_-]?token|bearer[_-]?token)\s*[:=]\s*["']?([a-zA-Z0-9_\-.]{20,300})["']?/gi },
|
|
3090
|
+
// Bearer <token> in Authorization headers
|
|
3091
|
+
{ kind: "bearer", regex: /\b(Authorization:\s*Bearer\s+)([a-zA-Z0-9_\-.=]{20,500})/g },
|
|
3092
|
+
// Private key PEM blocks — catch the header+footer together
|
|
3093
|
+
{ kind: "private-key", regex: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g }
|
|
3094
|
+
];
|
|
3095
|
+
function render(placeholder, kind) {
|
|
3096
|
+
return placeholder.replace("{kind}", kind);
|
|
3097
|
+
}
|
|
3098
|
+
function redactString(input, options) {
|
|
3099
|
+
if (!options.enabled || !input) return { redacted: input, hits: [] };
|
|
3100
|
+
const placeholder = options.placeholder ?? "[REDACTED:{kind}]";
|
|
3101
|
+
const patterns = [
|
|
3102
|
+
...options.patterns ?? DEFAULT_PATTERNS,
|
|
3103
|
+
...(options.customRegexes ?? []).flatMap((src, i) => {
|
|
3104
|
+
try {
|
|
3105
|
+
const flags = src.match(/^\/.*\/([gimsuy]*)$/)?.[1] ?? "";
|
|
3106
|
+
const body = src.replace(/^\/(.*)\/[gimsuy]*$/, "$1");
|
|
3107
|
+
const regex = new RegExp(body, flags.includes("g") ? flags : flags + "g");
|
|
3108
|
+
return [{ kind: `custom-${i}`, regex }];
|
|
3109
|
+
} catch {
|
|
3110
|
+
return [];
|
|
3111
|
+
}
|
|
3112
|
+
})
|
|
3113
|
+
];
|
|
3114
|
+
let redacted = input;
|
|
3115
|
+
const hits = [];
|
|
3116
|
+
for (const { kind, regex } of patterns) {
|
|
3117
|
+
const rx = new RegExp(regex.source, regex.flags);
|
|
3118
|
+
redacted = redacted.replace(rx, (...args) => {
|
|
3119
|
+
const match = args[0];
|
|
3120
|
+
const probe = new RegExp(rx.source).exec(match);
|
|
3121
|
+
const captureCount = probe ? probe.length - 1 : 0;
|
|
3122
|
+
const g1 = captureCount >= 1 ? args[1] : void 0;
|
|
3123
|
+
const g2 = captureCount >= 2 ? args[2] : void 0;
|
|
3124
|
+
const offset = args[1 + captureCount];
|
|
3125
|
+
if (captureCount >= 2 && typeof g2 === "string") {
|
|
3126
|
+
hits.push({ kind, start: offset + (g1?.length ?? 0), length: g2.length, secret: g2 });
|
|
3127
|
+
return `${g1}${render(placeholder, kind)}`;
|
|
3128
|
+
}
|
|
3129
|
+
hits.push({ kind, start: offset, length: match.length, secret: g1 ?? match });
|
|
3130
|
+
return render(placeholder, kind);
|
|
3131
|
+
});
|
|
3132
|
+
}
|
|
3133
|
+
return { redacted, hits };
|
|
3134
|
+
}
|
|
3135
|
+
function redactJson(value, options) {
|
|
3136
|
+
if (!options.enabled) return { value, hits: [] };
|
|
3137
|
+
const allHits = [];
|
|
3138
|
+
function walk(v) {
|
|
3139
|
+
if (typeof v === "string") {
|
|
3140
|
+
const r = redactString(v, options);
|
|
3141
|
+
allHits.push(...r.hits);
|
|
3142
|
+
return r.redacted;
|
|
3143
|
+
}
|
|
3144
|
+
if (Array.isArray(v)) return v.map(walk);
|
|
3145
|
+
if (v && typeof v === "object") {
|
|
3146
|
+
const out = {};
|
|
3147
|
+
for (const [k, vv] of Object.entries(v)) {
|
|
3148
|
+
out[k] = walk(vv);
|
|
3149
|
+
}
|
|
3150
|
+
return out;
|
|
3151
|
+
}
|
|
3152
|
+
return v;
|
|
3153
|
+
}
|
|
3154
|
+
const redacted = walk(value);
|
|
3155
|
+
return { value: redacted, hits: allHits };
|
|
3156
|
+
}
|
|
3157
|
+
|
|
3039
3158
|
// src/session/session-manager.ts
|
|
3040
3159
|
function safeDate(value) {
|
|
3041
3160
|
const d = new Date(value);
|
|
@@ -3049,9 +3168,27 @@ function extractJsonField(header, field) {
|
|
|
3049
3168
|
var SessionManager = class {
|
|
3050
3169
|
_current = null;
|
|
3051
3170
|
historyDir;
|
|
3171
|
+
config;
|
|
3172
|
+
/** Last save's redaction hit count — exposed for /security status reporting */
|
|
3173
|
+
lastRedactionHits = 0;
|
|
3052
3174
|
constructor(config) {
|
|
3175
|
+
this.config = config;
|
|
3053
3176
|
this.historyDir = config.getHistoryDir();
|
|
3054
3177
|
}
|
|
3178
|
+
/**
|
|
3179
|
+
* Build redaction options from config. Returns `{ enabled: false }` when
|
|
3180
|
+
* `security.redactOnSave` is off or `security.mode` is 'off'.
|
|
3181
|
+
*/
|
|
3182
|
+
redactOptionsForSave() {
|
|
3183
|
+
const security = this.config.get("security");
|
|
3184
|
+
if (!security || !security.redactOnSave || security.mode === "off") {
|
|
3185
|
+
return { enabled: false };
|
|
3186
|
+
}
|
|
3187
|
+
return {
|
|
3188
|
+
enabled: true,
|
|
3189
|
+
customRegexes: security.customPatterns ?? []
|
|
3190
|
+
};
|
|
3191
|
+
}
|
|
3055
3192
|
get current() {
|
|
3056
3193
|
return this._current;
|
|
3057
3194
|
}
|
|
@@ -3077,8 +3214,12 @@ var SessionManager = class {
|
|
|
3077
3214
|
if (!this._current) return;
|
|
3078
3215
|
mkdirSync2(this.historyDir, { recursive: true });
|
|
3079
3216
|
const filePath = join2(this.historyDir, `${this._current.id}.json`);
|
|
3217
|
+
const raw = this._current.toJSON();
|
|
3218
|
+
const opts = this.redactOptionsForSave();
|
|
3219
|
+
const { value: payload, hits } = redactJson(raw, opts);
|
|
3220
|
+
this.lastRedactionHits = hits.length;
|
|
3080
3221
|
const tmpPath = filePath + ".tmp";
|
|
3081
|
-
writeFileSync2(tmpPath, JSON.stringify(
|
|
3222
|
+
writeFileSync2(tmpPath, JSON.stringify(payload, null, 2), "utf-8");
|
|
3082
3223
|
renameSync(tmpPath, filePath);
|
|
3083
3224
|
}
|
|
3084
3225
|
loadSession(id) {
|
|
@@ -4217,8 +4358,8 @@ function checkPermission(toolName, args, dangerLevel, rules, defaultAction = "co
|
|
|
4217
4358
|
if (rule.when) {
|
|
4218
4359
|
if (rule.when.dangerLevel && rule.when.dangerLevel !== dangerLevel) continue;
|
|
4219
4360
|
if (rule.when.pathPattern) {
|
|
4220
|
-
const
|
|
4221
|
-
if (!
|
|
4361
|
+
const path4 = String(args["path"] ?? args["command"] ?? "");
|
|
4362
|
+
if (!path4.includes(rule.when.pathPattern)) continue;
|
|
4222
4363
|
}
|
|
4223
4364
|
}
|
|
4224
4365
|
return rule.action;
|
|
@@ -7718,6 +7859,160 @@ ${lines.join("\n")}`;
|
|
|
7718
7859
|
}
|
|
7719
7860
|
};
|
|
7720
7861
|
|
|
7862
|
+
// src/memory/chat-index.ts
|
|
7863
|
+
import fs2 from "fs";
|
|
7864
|
+
import path3 from "path";
|
|
7865
|
+
import os from "os";
|
|
7866
|
+
import crypto from "crypto";
|
|
7867
|
+
var MEMORY_DIR_NAME = "memory-index";
|
|
7868
|
+
var CHUNKS_FILE = "chunks.json";
|
|
7869
|
+
var VECTORS_FILE = "vectors.vec";
|
|
7870
|
+
var VEC_MAGIC = 1094929750;
|
|
7871
|
+
var VEC_VERSION = 1;
|
|
7872
|
+
var VEC_HEADER_BYTES = 16;
|
|
7873
|
+
function memoryIndexDir() {
|
|
7874
|
+
return path3.join(os.homedir(), ".aicli", MEMORY_DIR_NAME);
|
|
7875
|
+
}
|
|
7876
|
+
function chunksPath() {
|
|
7877
|
+
return path3.join(memoryIndexDir(), CHUNKS_FILE);
|
|
7878
|
+
}
|
|
7879
|
+
function vectorsPath() {
|
|
7880
|
+
return path3.join(memoryIndexDir(), VECTORS_FILE);
|
|
7881
|
+
}
|
|
7882
|
+
function readVectorsFile(expectedCount) {
|
|
7883
|
+
const p = vectorsPath();
|
|
7884
|
+
if (!fs2.existsSync(p)) return null;
|
|
7885
|
+
let buf;
|
|
7886
|
+
try {
|
|
7887
|
+
buf = fs2.readFileSync(p);
|
|
7888
|
+
} catch {
|
|
7889
|
+
return null;
|
|
7890
|
+
}
|
|
7891
|
+
if (buf.length < VEC_HEADER_BYTES) return null;
|
|
7892
|
+
const magic = buf.readUInt32LE(0);
|
|
7893
|
+
const version = buf.readUInt32LE(4);
|
|
7894
|
+
const count = buf.readUInt32LE(8);
|
|
7895
|
+
const dim = buf.readUInt32LE(12);
|
|
7896
|
+
if (magic !== VEC_MAGIC || version !== VEC_VERSION || dim !== EMBEDDING_DIM) return null;
|
|
7897
|
+
if (count !== expectedCount) return null;
|
|
7898
|
+
const expected = VEC_HEADER_BYTES + count * dim * 4;
|
|
7899
|
+
if (buf.length !== expected) return null;
|
|
7900
|
+
return new Float32Array(
|
|
7901
|
+
buf.buffer.slice(buf.byteOffset + VEC_HEADER_BYTES, buf.byteOffset + expected)
|
|
7902
|
+
);
|
|
7903
|
+
}
|
|
7904
|
+
function readIndexFile() {
|
|
7905
|
+
const p = chunksPath();
|
|
7906
|
+
if (!fs2.existsSync(p)) return null;
|
|
7907
|
+
try {
|
|
7908
|
+
const raw = fs2.readFileSync(p, "utf-8");
|
|
7909
|
+
const data = JSON.parse(raw);
|
|
7910
|
+
if (data.version !== 1) return null;
|
|
7911
|
+
return data;
|
|
7912
|
+
} catch {
|
|
7913
|
+
return null;
|
|
7914
|
+
}
|
|
7915
|
+
}
|
|
7916
|
+
function loadChatIndex() {
|
|
7917
|
+
const idx = readIndexFile();
|
|
7918
|
+
if (!idx) return null;
|
|
7919
|
+
const vectors = readVectorsFile(idx.chunks.length);
|
|
7920
|
+
if (!vectors) return null;
|
|
7921
|
+
return { idx, vectors };
|
|
7922
|
+
}
|
|
7923
|
+
async function searchChatMemory(query, options = {}) {
|
|
7924
|
+
const topK = options.topK ?? 5;
|
|
7925
|
+
const minScore = options.minScore ?? 0.25;
|
|
7926
|
+
const loaded = loadChatIndex();
|
|
7927
|
+
if (!loaded || loaded.idx.chunks.length === 0) return [];
|
|
7928
|
+
const { idx, vectors } = loaded;
|
|
7929
|
+
const { redacted } = redactString(query, { enabled: true });
|
|
7930
|
+
const qvec = await embedOne(redacted);
|
|
7931
|
+
const candidates = [];
|
|
7932
|
+
for (let i = 0; i < idx.chunks.length; i++) {
|
|
7933
|
+
const c = idx.chunks[i];
|
|
7934
|
+
if (options.sessionId && c.sessionId !== options.sessionId) continue;
|
|
7935
|
+
if (options.excludeSessionId && c.sessionId === options.excludeSessionId) continue;
|
|
7936
|
+
let score = 0;
|
|
7937
|
+
const base = i * EMBEDDING_DIM;
|
|
7938
|
+
for (let d = 0; d < EMBEDDING_DIM; d++) {
|
|
7939
|
+
score += vectors[base + d] * qvec[d];
|
|
7940
|
+
}
|
|
7941
|
+
if (score < minScore) continue;
|
|
7942
|
+
candidates.push({ chunk: c, score });
|
|
7943
|
+
}
|
|
7944
|
+
candidates.sort((a, b) => b.score - a.score);
|
|
7945
|
+
return candidates.slice(0, topK);
|
|
7946
|
+
}
|
|
7947
|
+
|
|
7948
|
+
// src/tools/builtin/recall-memory.ts
|
|
7949
|
+
function formatHit(h, i) {
|
|
7950
|
+
const ts = h.chunk.timestamp.slice(0, 16).replace("T", " ");
|
|
7951
|
+
const title = h.chunk.sessionTitle ? ` \xB7 ${h.chunk.sessionTitle}` : "";
|
|
7952
|
+
const sid = h.chunk.sessionId.slice(0, 8);
|
|
7953
|
+
const score = h.score.toFixed(3);
|
|
7954
|
+
const body = h.chunk.text.length > 600 ? h.chunk.text.slice(0, 600) + "\u2026" : h.chunk.text;
|
|
7955
|
+
return `\u2500\u2500\u2500 Hit ${i + 1} (score ${score}, session ${sid}${title}, ${ts}) \u2500\u2500\u2500
|
|
7956
|
+
` + body;
|
|
7957
|
+
}
|
|
7958
|
+
var recallMemoryTool = {
|
|
7959
|
+
definition: {
|
|
7960
|
+
name: "recall_memory",
|
|
7961
|
+
description: 'Semantic search over past chat sessions. Call this whenever the user references something that may have been discussed before ("last time", "remember", "\u4E4B\u524D", "\u4E0A\u6B21"), when context is ambiguous and continuity matters, or when you want to check what decisions or preferences have been established across prior conversations. Returns up to `topK` relevant snippets with session id, timestamp, and cosine similarity score. Prefer this over asking the user "can you remind me".',
|
|
7962
|
+
parameters: {
|
|
7963
|
+
query: {
|
|
7964
|
+
type: "string",
|
|
7965
|
+
description: "Natural-language description of what to recall. Chinese or English both work.",
|
|
7966
|
+
required: true
|
|
7967
|
+
},
|
|
7968
|
+
topK: {
|
|
7969
|
+
type: "number",
|
|
7970
|
+
description: "Max number of snippets to return (default 5, max 20).",
|
|
7971
|
+
required: false
|
|
7972
|
+
},
|
|
7973
|
+
excludeCurrentSession: {
|
|
7974
|
+
type: "boolean",
|
|
7975
|
+
description: "If true, exclude the current session from results (avoid echoing what you just said). Default false.",
|
|
7976
|
+
required: false
|
|
7977
|
+
},
|
|
7978
|
+
currentSessionId: {
|
|
7979
|
+
type: "string",
|
|
7980
|
+
description: "Session ID to exclude when excludeCurrentSession=true. Usually the active session.",
|
|
7981
|
+
required: false
|
|
7982
|
+
},
|
|
7983
|
+
minScore: {
|
|
7984
|
+
type: "number",
|
|
7985
|
+
description: "Drop hits below this cosine score. Default 0.25. Raise to 0.35+ for stricter matches.",
|
|
7986
|
+
required: false
|
|
7987
|
+
}
|
|
7988
|
+
},
|
|
7989
|
+
dangerous: false
|
|
7990
|
+
},
|
|
7991
|
+
async execute(args) {
|
|
7992
|
+
const query = String(args["query"] ?? "").trim();
|
|
7993
|
+
if (!query) throw new ToolError("recall_memory", "query is required");
|
|
7994
|
+
const topK = Math.max(1, Math.min(20, Number(args["topK"] ?? 5)));
|
|
7995
|
+
const excludeCurrent = Boolean(args["excludeCurrentSession"]);
|
|
7996
|
+
const currentId = args["currentSessionId"] ? String(args["currentSessionId"]) : void 0;
|
|
7997
|
+
const minScore = args["minScore"] !== void 0 ? Number(args["minScore"]) : 0.25;
|
|
7998
|
+
const status = loadChatIndex();
|
|
7999
|
+
if (!status) {
|
|
8000
|
+
return "No chat memory index found. The index is built on REPL startup, or run `/memory rebuild` manually. If you have no past sessions yet, this is expected.";
|
|
8001
|
+
}
|
|
8002
|
+
const hits = await searchChatMemory(query, {
|
|
8003
|
+
topK,
|
|
8004
|
+
minScore,
|
|
8005
|
+
excludeSessionId: excludeCurrent ? currentId : void 0
|
|
8006
|
+
});
|
|
8007
|
+
if (hits.length === 0) {
|
|
8008
|
+
return `No memories matched "${query}" above score ${minScore}. Index has ${status.idx.chunks.length} chunks across ${Object.keys(status.idx.sessionMtimes).length} sessions. Consider lowering minScore to 0.15 or rephrasing the query.`;
|
|
8009
|
+
}
|
|
8010
|
+
const header = `Found ${hits.length} memory hit(s) for "${query}" (min-score ${minScore}):
|
|
8011
|
+
`;
|
|
8012
|
+
return header + "\n" + hits.map(formatHit).join("\n\n");
|
|
8013
|
+
}
|
|
8014
|
+
};
|
|
8015
|
+
|
|
7721
8016
|
// src/core/token-estimator.ts
|
|
7722
8017
|
var CJK_REGEX = /[\u2E80-\u9FFF\uA000-\uA4FF\uAC00-\uD7FF\uF900-\uFAFF\uFE30-\uFE4F\uFF00-\uFFEF]/g;
|
|
7723
8018
|
function estimateTokens(text) {
|
|
@@ -7777,6 +8072,7 @@ var ToolRegistry = class {
|
|
|
7777
8072
|
this.register(getOutlineTool);
|
|
7778
8073
|
this.register(findReferencesTool);
|
|
7779
8074
|
this.register(searchCodeTool);
|
|
8075
|
+
this.register(recallMemoryTool);
|
|
7780
8076
|
}
|
|
7781
8077
|
register(tool) {
|
|
7782
8078
|
this.tools.set(tool.definition.name, tool);
|
|
@@ -8944,9 +9240,9 @@ function getDevStatePath() {
|
|
|
8944
9240
|
return join10(homedir4(), CONFIG_DIR_NAME, DEV_STATE_FILE_NAME);
|
|
8945
9241
|
}
|
|
8946
9242
|
function loadDevState() {
|
|
8947
|
-
const
|
|
8948
|
-
if (!existsSync17(
|
|
8949
|
-
const content = readFileSync11(
|
|
9243
|
+
const path4 = getDevStatePath();
|
|
9244
|
+
if (!existsSync17(path4)) return null;
|
|
9245
|
+
const content = readFileSync11(path4, "utf-8").trim();
|
|
8950
9246
|
return content || null;
|
|
8951
9247
|
}
|
|
8952
9248
|
|
|
@@ -11108,7 +11404,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
11108
11404
|
case "test": {
|
|
11109
11405
|
this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
|
|
11110
11406
|
try {
|
|
11111
|
-
const { executeTests } = await import("./run-tests-
|
|
11407
|
+
const { executeTests } = await import("./run-tests-TGGXTOFF.js");
|
|
11112
11408
|
const argStr = args.join(" ").trim();
|
|
11113
11409
|
let testArgs = {};
|
|
11114
11410
|
if (argStr) {
|
|
@@ -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-ODU45UQG.js");
|
|
389
389
|
const orchestrator = new TaskOrchestrator(config, providers, configManager);
|
|
390
390
|
let interrupted = false;
|
|
391
391
|
const onSigint = () => {
|