jinzd-ai-cli 0.4.232 → 0.4.234

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.
Files changed (37) hide show
  1. package/dist/{batch-D6XYKHHA.js → batch-T3FRX4B6.js} +2 -2
  2. package/dist/{chat-index-O56HDGFI.js → chat-index-7HXBWQFH.js} +2 -2
  3. package/dist/{chat-index-WSI7ULRQ.js → chat-index-WYXYD7YP.js} +1 -1
  4. package/dist/{chunk-4KM53GLG.js → chunk-2QQKS56X.js} +1 -1
  5. package/dist/{chunk-EPDW73H7.js → chunk-3KTSJ2EL.js} +1 -1
  6. package/dist/{chunk-KIQ2ICMV.js → chunk-5DTEEWYC.js} +4 -4
  7. package/dist/{chunk-NVUCDUXE.js → chunk-5MYPIQ3Y.js} +11 -5
  8. package/dist/{chunk-YDGKT6RI.js → chunk-5VPRZMZB.js} +2 -2
  9. package/dist/{chunk-RRMLRCPW.js → chunk-6MJMHHRK.js} +4 -4
  10. package/dist/{chunk-2FZACYUE.js → chunk-AFT6VFMI.js} +1 -1
  11. package/dist/{chunk-TJVM535A.js → chunk-E5RLXMSU.js} +6 -6
  12. package/dist/{chunk-XVR4YKRC.js → chunk-ETJ5X6V7.js} +1 -1
  13. package/dist/{chunk-6VSAKQTN.js → chunk-II6RZRTF.js} +1 -1
  14. package/dist/{chunk-ZWU7FWHO.js → chunk-JMP3LJC2.js} +38 -91
  15. package/dist/chunk-KCEO2XJ4.js +420 -0
  16. package/dist/{chunk-LKTYTDEJ.js → chunk-L5PNAQ6O.js} +1 -1
  17. package/dist/{chunk-ODYTXQ26.js → chunk-O3XSFPYH.js} +35 -3
  18. package/dist/{chunk-62R7CACG.js → chunk-QYQI7ZWK.js} +1 -1
  19. package/dist/{chunk-2HWL45PG.js → chunk-TAXIG7HH.js} +1 -1
  20. package/dist/{chunk-LF3OUJHK.js → chunk-UO3XWZFE.js} +302 -4
  21. package/dist/{ci-OR57Z7QF.js → ci-GJ53GPLA.js} +6 -6
  22. package/dist/{ci-format-DMMS2ZFK.js → ci-format-AXE4QCI6.js} +2 -2
  23. package/dist/{constants-7GZKINR5.js → constants-DC4EXFSB.js} +1 -1
  24. package/dist/{doctor-cli-ZD37GAZD.js → doctor-cli-HKREND6Q.js} +6 -6
  25. package/dist/electron-server.js +78 -99
  26. package/dist/{hub-VFYFUSM6.js → hub-LTTKRGZC.js} +2 -2
  27. package/dist/index.js +24 -24
  28. package/dist/{persist-I3JI27OK.js → persist-AMH5SQ4W.js} +3 -3
  29. package/dist/{pr-YF6TXJYE.js → pr-4VODJVRV.js} +6 -6
  30. package/dist/{run-tests-HEDDC6DJ.js → run-tests-G5BNTMWQ.js} +2 -2
  31. package/dist/{run-tests-BNZZDCTU.js → run-tests-QR2PYR2P.js} +1 -1
  32. package/dist/{server-HCDI2EID.js → server-64WZVZZN.js} +17 -17
  33. package/dist/{server-C52P6IDO.js → server-EO6VQHKL.js} +7 -7
  34. package/dist/{task-orchestrator-QIEHYRGK.js → task-orchestrator-4INCZMLT.js} +7 -7
  35. package/dist/{usage-4AAMJBBM.js → usage-T3HFMNNV.js} +2 -2
  36. package/package.json +1 -1
  37. package/dist/chunk-B5TYJO7V.js +0 -123
@@ -128,6 +128,293 @@ function scanString(input, options) {
128
128
  return hits;
129
129
  }
130
130
 
131
+ // src/session/session-index.ts
132
+ import { existsSync, readFileSync as readFileSync2, readdirSync, statSync } from "fs";
133
+ import { join } from "path";
134
+
135
+ // src/session/session-meta.ts
136
+ import { readFileSync, openSync, readSync, closeSync } from "fs";
137
+ function safeDate(value) {
138
+ const d = new Date(value);
139
+ return isNaN(d.getTime()) ? /* @__PURE__ */ new Date(0) : d;
140
+ }
141
+ function extractJsonField(header, field) {
142
+ const re = new RegExp(`"${field}"\\s*:\\s*"([^"]*)"`, "i");
143
+ const m = header.match(re);
144
+ return m ? m[1] : void 0;
145
+ }
146
+ function extractJsonNumberField(header, field) {
147
+ const m = header.match(new RegExp(`"${field}"\\s*:\\s*(\\d+)`));
148
+ return m ? Number(m[1]) : void 0;
149
+ }
150
+ function readSessionMeta(filePath) {
151
+ const HEADER_SIZE = 1024;
152
+ let header;
153
+ try {
154
+ const fd = openSync(filePath, "r");
155
+ const buf = Buffer.alloc(HEADER_SIZE);
156
+ const bytesRead = readSync(fd, buf, 0, HEADER_SIZE, 0);
157
+ closeSync(fd);
158
+ header = buf.toString("utf-8", 0, bytesRead);
159
+ } catch {
160
+ return null;
161
+ }
162
+ const id = extractJsonField(header, "id");
163
+ const provider = extractJsonField(header, "provider");
164
+ const model = extractJsonField(header, "model");
165
+ const created = extractJsonField(header, "created");
166
+ const updated = extractJsonField(header, "updated");
167
+ const title = extractJsonField(header, "title");
168
+ if (id && provider && model) {
169
+ let messageCount = extractJsonNumberField(header, "messageCount");
170
+ if (messageCount === void 0) {
171
+ messageCount = 0;
172
+ try {
173
+ const full = readFileSync(filePath, "utf-8");
174
+ const matches = full.match(/"role"\s*:/g);
175
+ messageCount = matches ? matches.length : 0;
176
+ } catch {
177
+ }
178
+ }
179
+ let branchCount;
180
+ let activeBranch;
181
+ try {
182
+ const brMatch = header.match(/"branches"\s*:\s*\[/);
183
+ if (brMatch) {
184
+ const parentMatches = header.match(/"parentBranchId"/g);
185
+ branchCount = parentMatches ? parentMatches.length + 1 : 1;
186
+ }
187
+ activeBranch = extractJsonField(header, "activeBranchId");
188
+ } catch {
189
+ }
190
+ return {
191
+ id,
192
+ provider,
193
+ model,
194
+ messageCount,
195
+ created: safeDate(created),
196
+ updated: safeDate(updated),
197
+ title: title || void 0,
198
+ branchCount,
199
+ activeBranch
200
+ };
201
+ }
202
+ const data = JSON.parse(readFileSync(filePath, "utf-8"));
203
+ return {
204
+ id: data.id,
205
+ provider: data.provider,
206
+ model: data.model,
207
+ messageCount: data.messages?.length ?? 0,
208
+ created: safeDate(data.created),
209
+ updated: safeDate(data.updated),
210
+ title: data.title,
211
+ branchCount: data.branches?.length ?? 1,
212
+ activeBranch: data.activeBranchId
213
+ };
214
+ }
215
+
216
+ // src/session/session-index.ts
217
+ var INDEX_FILE = "sessions.index.json";
218
+ function indexFilePath(historyDir2) {
219
+ return join(historyDir2, INDEX_FILE);
220
+ }
221
+ function loadIndex(historyDir2) {
222
+ const fp = indexFilePath(historyDir2);
223
+ if (!existsSync(fp)) return null;
224
+ try {
225
+ const data = JSON.parse(readFileSync2(fp, "utf-8"));
226
+ if (data?.version === 1 && data.sessions && data.invertedIndex) {
227
+ return data;
228
+ }
229
+ return null;
230
+ } catch {
231
+ return null;
232
+ }
233
+ }
234
+ function saveIndex(historyDir2, idx) {
235
+ idx.built = (/* @__PURE__ */ new Date()).toISOString();
236
+ const fp = indexFilePath(historyDir2);
237
+ atomicWriteFileSync(fp, JSON.stringify(idx));
238
+ }
239
+ function rebuildIndex(historyDir2) {
240
+ if (!existsSync(historyDir2)) {
241
+ const empty = { version: 1, built: (/* @__PURE__ */ new Date()).toISOString(), sessions: {}, invertedIndex: {} };
242
+ return empty;
243
+ }
244
+ const files = readdirSync(historyDir2).filter((f) => f.endsWith(".json"));
245
+ const sessions = {};
246
+ const invertedIndex = /* @__PURE__ */ new Map();
247
+ for (const file of files) {
248
+ const filePath = join(historyDir2, file);
249
+ const meta = readSessionMeta(filePath);
250
+ if (!meta) continue;
251
+ let fileSize = 0;
252
+ try {
253
+ fileSize = statSync(filePath).size;
254
+ } catch {
255
+ }
256
+ let mtimeMs = 0;
257
+ try {
258
+ mtimeMs = statSync(filePath).mtimeMs;
259
+ } catch {
260
+ }
261
+ const entry = {
262
+ id: meta.id,
263
+ provider: meta.provider,
264
+ model: meta.model,
265
+ title: meta.title,
266
+ messageCount: meta.messageCount,
267
+ fileSize,
268
+ created: meta.created.toISOString(),
269
+ updated: meta.updated.toISOString(),
270
+ branchCount: meta.branchCount,
271
+ activeBranch: meta.activeBranch,
272
+ _mtimeMs: mtimeMs
273
+ };
274
+ sessions[meta.id] = entry;
275
+ addTokensToIndex(invertedIndex, meta.title ?? "", meta.id);
276
+ }
277
+ const serializedIndex = {};
278
+ for (const [token, ids] of invertedIndex) {
279
+ serializedIndex[token] = [...ids];
280
+ }
281
+ const idx = {
282
+ version: 1,
283
+ built: (/* @__PURE__ */ new Date()).toISOString(),
284
+ sessions,
285
+ invertedIndex: serializedIndex
286
+ };
287
+ saveIndex(historyDir2, idx);
288
+ return idx;
289
+ }
290
+ function updateSessionInIndex(historyDir2, meta, filePath) {
291
+ let idx = loadIndex(historyDir2);
292
+ if (!idx) {
293
+ rebuildIndex(historyDir2);
294
+ return;
295
+ }
296
+ let fileSize = 0;
297
+ try {
298
+ fileSize = statSync(filePath).size;
299
+ } catch {
300
+ }
301
+ let mtimeMs = 0;
302
+ try {
303
+ mtimeMs = statSync(filePath).mtimeMs;
304
+ } catch {
305
+ }
306
+ const id = meta.id;
307
+ for (const [token, ids] of Object.entries(idx.invertedIndex)) {
308
+ const filtered = ids.filter((sid) => sid !== id);
309
+ if (filtered.length === 0) {
310
+ delete idx.invertedIndex[token];
311
+ } else {
312
+ idx.invertedIndex[token] = filtered;
313
+ }
314
+ }
315
+ idx.sessions[id] = {
316
+ id,
317
+ provider: meta.provider,
318
+ model: meta.model,
319
+ title: meta.title,
320
+ messageCount: meta.messageCount,
321
+ fileSize,
322
+ created: meta.created.toISOString(),
323
+ updated: meta.updated.toISOString(),
324
+ _mtimeMs: mtimeMs
325
+ };
326
+ const tokens = tokenize(meta.title ?? "");
327
+ for (const token of tokens) {
328
+ const existing = idx.invertedIndex[token] ?? [];
329
+ if (!existing.includes(id)) {
330
+ existing.push(id);
331
+ idx.invertedIndex[token] = existing;
332
+ }
333
+ }
334
+ saveIndex(historyDir2, idx);
335
+ }
336
+ function removeSessionFromIndex(historyDir2, id) {
337
+ const idx = loadIndex(historyDir2);
338
+ if (!idx) return;
339
+ delete idx.sessions[id];
340
+ for (const [token, ids] of Object.entries(idx.invertedIndex)) {
341
+ const filtered = ids.filter((sid) => sid !== id);
342
+ if (filtered.length === 0) {
343
+ delete idx.invertedIndex[token];
344
+ } else {
345
+ idx.invertedIndex[token] = filtered;
346
+ }
347
+ }
348
+ saveIndex(historyDir2, idx);
349
+ }
350
+ function listFromIndex(historyDir2, opts) {
351
+ let idx = loadIndex(historyDir2);
352
+ if (!idx) {
353
+ idx = rebuildIndex(historyDir2);
354
+ }
355
+ return Object.values(idx.sessions).map((entry) => ({
356
+ id: entry.id,
357
+ provider: entry.provider,
358
+ model: entry.model,
359
+ title: entry.title,
360
+ messageCount: entry.messageCount,
361
+ created: new Date(entry.created),
362
+ updated: new Date(entry.updated),
363
+ fileSize: opts?.includeFileSize ? entry.fileSize : void 0,
364
+ branchCount: entry.branchCount,
365
+ activeBranch: entry.activeBranch
366
+ })).sort((a, b) => b.updated.getTime() - a.updated.getTime());
367
+ }
368
+ function searchCandidates(historyDir2, query) {
369
+ const idx = loadIndex(historyDir2);
370
+ if (!idx) return null;
371
+ const queryTokens = tokenize(query.toLowerCase());
372
+ if (queryTokens.length === 0) return null;
373
+ const scoreMap = /* @__PURE__ */ new Map();
374
+ for (const token of queryTokens) {
375
+ const matchingIds = idx.invertedIndex[token];
376
+ if (!matchingIds) continue;
377
+ for (const sid of matchingIds) {
378
+ scoreMap.set(sid, (scoreMap.get(sid) ?? 0) + 1);
379
+ }
380
+ }
381
+ const candidates = [...scoreMap.entries()].filter(([, score]) => score > 0).sort((a, b) => {
382
+ const scoreDiff = b[1] - a[1];
383
+ if (scoreDiff !== 0) return scoreDiff;
384
+ const aMeta = idx.sessions[a[0]];
385
+ const bMeta = idx.sessions[b[0]];
386
+ return (bMeta?.updated ?? "").localeCompare(aMeta?.updated ?? "");
387
+ }).map(([sid]) => sid);
388
+ return candidates.length > 0 ? candidates : null;
389
+ }
390
+ function getIndexMtimeMap(historyDir2) {
391
+ const idx = loadIndex(historyDir2);
392
+ if (!idx) return null;
393
+ const map = {};
394
+ for (const entry of Object.values(idx.sessions)) {
395
+ map[entry.id] = entry._mtimeMs;
396
+ }
397
+ return map;
398
+ }
399
+ function addTokensToIndex(invertedIndex, text, sessionId) {
400
+ for (const token of tokenize(text)) {
401
+ const s = invertedIndex.get(token) ?? /* @__PURE__ */ new Set();
402
+ s.add(sessionId);
403
+ invertedIndex.set(token, s);
404
+ }
405
+ }
406
+ function tokenize(text) {
407
+ const tokens = /* @__PURE__ */ new Set();
408
+ const cjkOnly = text.replace(/[^\u4e00-\u9fff\u3400-\u4dbf]/g, "");
409
+ for (let i = 0; i < cjkOnly.length - 1; i++) {
410
+ tokens.add(cjkOnly.slice(i, i + 2));
411
+ }
412
+ const english = text.replace(/[\u4e00-\u9fff\u3400-\u4dbf]/g, " ").toLowerCase();
413
+ const words = english.split(/[^a-z0-9]+/).filter((w) => w.length >= 2);
414
+ for (const w of words) tokens.add(w);
415
+ return [...tokens];
416
+ }
417
+
131
418
  // src/memory/chat-index.ts
132
419
  var MEMORY_DIR_NAME = "memory-index";
133
420
  var CHUNKS_FILE = "chunks.json";
@@ -285,14 +572,19 @@ function listSessionFiles() {
285
572
  const dir = historyDir();
286
573
  if (!fs.existsSync(dir)) return [];
287
574
  const out = [];
575
+ const indexMtimes = getIndexMtimeMap(dir);
288
576
  for (const name of fs.readdirSync(dir)) {
289
577
  if (!name.endsWith(".json")) continue;
290
578
  const id = name.replace(/\.json$/, "");
291
579
  const p = path.join(dir, name);
292
- try {
293
- const st = fs.statSync(p);
294
- out.push({ id, path: p, mtime: st.mtimeMs });
295
- } catch {
580
+ if (indexMtimes && indexMtimes[id] !== void 0) {
581
+ out.push({ id, path: p, mtime: indexMtimes[id] });
582
+ } else {
583
+ try {
584
+ const st = fs.statSync(p);
585
+ out.push({ id, path: p, mtime: st.mtimeMs });
586
+ } catch {
587
+ }
296
588
  }
297
589
  }
298
590
  return out;
@@ -461,6 +753,12 @@ export {
461
753
  redactString,
462
754
  redactJson,
463
755
  scanString,
756
+ safeDate,
757
+ readSessionMeta,
758
+ updateSessionInIndex,
759
+ removeSessionFromIndex,
760
+ listFromIndex,
761
+ searchCandidates,
464
762
  chunkSession,
465
763
  loadChatIndex,
466
764
  clearChatIndex,
@@ -3,14 +3,14 @@ import {
3
3
  CI_COMMENT_MARKER,
4
4
  countSeverity,
5
5
  runCi
6
- } from "./chunk-RRMLRCPW.js";
7
- import "./chunk-2HWL45PG.js";
6
+ } from "./chunk-6MJMHHRK.js";
7
+ import "./chunk-TAXIG7HH.js";
8
8
  import "./chunk-HLWUDRBO.js";
9
- import "./chunk-62R7CACG.js";
10
- import "./chunk-ODYTXQ26.js";
11
- import "./chunk-LKTYTDEJ.js";
9
+ import "./chunk-QYQI7ZWK.js";
10
+ import "./chunk-O3XSFPYH.js";
11
+ import "./chunk-L5PNAQ6O.js";
12
12
  import "./chunk-5ULLIOVC.js";
13
- import "./chunk-EPDW73H7.js";
13
+ import "./chunk-3KTSJ2EL.js";
14
14
  import "./chunk-IW3Q7AE5.js";
15
15
  export {
16
16
  CI_COMMENT_MARKER,
@@ -6,8 +6,8 @@ import {
6
6
  formatCiResult,
7
7
  formatCiSarif,
8
8
  normalizeCiThresholds
9
- } from "./chunk-2HWL45PG.js";
10
- import "./chunk-EPDW73H7.js";
9
+ } from "./chunk-TAXIG7HH.js";
10
+ import "./chunk-3KTSJ2EL.js";
11
11
  export {
12
12
  DEFAULT_CI_THRESHOLDS,
13
13
  detectCiGateSignals,
@@ -38,7 +38,7 @@ import {
38
38
  TEST_TIMEOUT,
39
39
  VERSION,
40
40
  buildUserIdentityPrompt
41
- } from "./chunk-EPDW73H7.js";
41
+ } from "./chunk-3KTSJ2EL.js";
42
42
  export {
43
43
  AGENTIC_BEHAVIOR_GUIDELINE,
44
44
  APP_NAME,
@@ -2,14 +2,14 @@
2
2
  import {
3
3
  formatDoctorReport,
4
4
  runDoctorCli
5
- } from "./chunk-KIQ2ICMV.js";
6
- import "./chunk-62R7CACG.js";
7
- import "./chunk-6VSAKQTN.js";
8
- import "./chunk-ODYTXQ26.js";
9
- import "./chunk-LKTYTDEJ.js";
5
+ } from "./chunk-5DTEEWYC.js";
6
+ import "./chunk-QYQI7ZWK.js";
7
+ import "./chunk-II6RZRTF.js";
8
+ import "./chunk-O3XSFPYH.js";
9
+ import "./chunk-L5PNAQ6O.js";
10
10
  import "./chunk-5ULLIOVC.js";
11
11
  import "./chunk-HOSJZMQS.js";
12
- import "./chunk-EPDW73H7.js";
12
+ import "./chunk-3KTSJ2EL.js";
13
13
  import "./chunk-IW3Q7AE5.js";
14
14
  export {
15
15
  formatDoctorReport,
@@ -38,7 +38,7 @@ import {
38
38
  VERSION,
39
39
  buildUserIdentityPrompt,
40
40
  runTestsTool
41
- } from "./chunk-XVR4YKRC.js";
41
+ } from "./chunk-ETJ5X6V7.js";
42
42
  import {
43
43
  hasSemanticIndex,
44
44
  semanticSearch
@@ -49,12 +49,18 @@ import {
49
49
  import "./chunk-IEQAE3QG.js";
50
50
  import {
51
51
  DEFAULT_PATTERNS,
52
+ listFromIndex,
52
53
  loadChatIndex,
54
+ readSessionMeta,
53
55
  redactJson,
54
56
  redactString,
57
+ removeSessionFromIndex,
58
+ safeDate,
55
59
  scanString,
56
- searchChatMemory
57
- } from "./chunk-LF3OUJHK.js";
60
+ searchCandidates,
61
+ searchChatMemory,
62
+ updateSessionInIndex
63
+ } from "./chunk-UO3XWZFE.js";
58
64
  import "./chunk-JV5N65KN.js";
59
65
  import {
60
66
  atomicWriteFileSync
@@ -2053,7 +2059,10 @@ function peelMetaNarration(content) {
2053
2059
  var META_NARRATION_HARD_MARKERS = [
2054
2060
  /\[⚠️\s*CONTENT GENERATION MODE\]/,
2055
2061
  /CONTENT_ONLY_STREAM_REMINDER\b/,
2056
- /<system-reminder>/i
2062
+ /<system-reminder>/i,
2063
+ // → `path` — AI wraps content with "已写好 → `file.md`" before the document body.
2064
+ // Documents never use this conversational file-path-pointer syntax.
2065
+ /→\s*`[^`]{2,}`/u
2057
2066
  ];
2058
2067
  var META_NARRATION_HEURISTICS = [
2059
2068
  /\bthe user (?:is asking me|wants me|is requesting|expects me)\b/i,
@@ -2064,7 +2073,24 @@ var META_NARRATION_HEURISTICS = [
2064
2073
  /\bActually,?\s+I\b/i,
2065
2074
  /\bI need to be honest with the user\b/i,
2066
2075
  /\bI(?:'m| am) in a special mode\b/i,
2067
- /\bGiven that I cannot\b/i
2076
+ /\bGiven that I cannot\b/i,
2077
+ // ── 中文 ── (need 2+ total hits across all patterns to trigger)
2078
+ /(?:已经?|已)\s*(?:写[好完]|生成[好完]|创建[好完]|准备[好完]|搞[好定]|弄[好完])[了啦]?/u,
2079
+ /(?:需要你|请你|麻烦你|还要你|你看)\s*(?:确认|决定|选择|帮忙|看[一看下]|过目)/u,
2080
+ /(?:总结|概括|归纳|整理)[一下]?(?:核心|主要|关键)?(?:思路|要点|内容|设计)/u,
2081
+ /(?:随时|有问题|有需要|需要调整|需要修改|需要补充)\s*(?:联系|找我|告诉|沟通|调整|修改|补充|说)/u,
2082
+ /^(?:.{0,40})(?:好的[,,,!\s]|嗯[,,,!\s]|收到[了]?[,,,!\s]|了解[了]?[,,,!\s]|明白[了]?[,,,!\s])/mu,
2083
+ /(?:我来|让我|容我)\s*(?:整理|梳理|归纳|想想|看看|重新|检查|核实)/u,
2084
+ // 过渡语(需要 2+ hits):"以下是:" / "如下:" 作为独立行。文档中可作小节标题,
2085
+ // 但配合另一个对话模式(如"好的")时是典型的 AI 内容包装。
2086
+ /^.{0,60}(?:以下是|下面是|如下)\s*[::,,]\s*$/mu
2087
+ ];
2088
+ var CHINESE_TAIL_CLOSING_MARKERS = [
2089
+ /(?:需要你|请你|麻烦你|你看)\s*(?:确认|决定|选择|帮忙|看[一看下]|过目|觉得|认为)/u,
2090
+ /(?:随时|有问题|有需要|需要调整|需要修改)\s*(?:联系|找我|告诉|沟通|调整|修改)/u,
2091
+ /(?:如果|假如|假设)\s*(?:有[没缺][有]?|需要|觉得|认为).{0,30}?(?:随时|可以|直接|尽管)\s*(?:联系|找我|告诉|说|调整|修改)/u,
2092
+ /(?:以上|先这样|暂时|目前).{0,10}$/mu,
2093
+ /(?:几个|一些|以下)\s*(?:关键)?(?:问题|疑问|想法|建议).{0,30}$/mu
2068
2094
  ];
2069
2095
  function detectMetaNarration(content) {
2070
2096
  if (!content) return null;
@@ -2072,7 +2098,19 @@ function detectMetaNarration(content) {
2072
2098
  for (const re of META_NARRATION_HARD_MARKERS) {
2073
2099
  if (re.test(head)) return re.source;
2074
2100
  }
2075
- if (/^#{1,3}\s+\S/m.test(head)) return null;
2101
+ if (content.length > 500) {
2102
+ const tail = content.slice(-500);
2103
+ let tailHits = 0;
2104
+ let tailFirst = "";
2105
+ for (const re of CHINESE_TAIL_CLOSING_MARKERS) {
2106
+ if (re.test(tail)) {
2107
+ tailHits++;
2108
+ if (!tailFirst) tailFirst = re.source;
2109
+ if (tailHits >= 2) return `meta-narration-tail:${tailFirst}`;
2110
+ }
2111
+ }
2112
+ }
2113
+ if (/^#{1,3}\s+\S/m.test(content.slice(0, 200))) return null;
2076
2114
  let hits = 0;
2077
2115
  let firstMatch = "";
2078
2116
  for (const re of META_NARRATION_HEURISTICS) {
@@ -3915,7 +3953,7 @@ var ProviderRegistry = class {
3915
3953
  };
3916
3954
 
3917
3955
  // src/session/session-manager.ts
3918
- import { readFileSync as readFileSync3, existsSync as existsSync3, mkdirSync as mkdirSync3, readdirSync, unlinkSync as unlinkSync2, openSync, readSync, closeSync, statSync } from "fs";
3956
+ import { readFileSync as readFileSync3, existsSync as existsSync3, mkdirSync as mkdirSync3, readdirSync, unlinkSync as unlinkSync2, statSync } from "fs";
3919
3957
  import { join as join3 } from "path";
3920
3958
  import { v4 as uuidv4 } from "uuid";
3921
3959
 
@@ -4451,19 +4489,6 @@ var Session = class _Session {
4451
4489
  };
4452
4490
 
4453
4491
  // src/session/session-manager.ts
4454
- function safeDate(value) {
4455
- const d = new Date(value);
4456
- return isNaN(d.getTime()) ? /* @__PURE__ */ new Date(0) : d;
4457
- }
4458
- function extractJsonField(header, field) {
4459
- const re = new RegExp(`"${field}"\\s*:\\s*"([^"]*)"`, "i");
4460
- const m = header.match(re);
4461
- return m ? m[1] : void 0;
4462
- }
4463
- function extractJsonNumberField(header, field) {
4464
- const m = header.match(new RegExp(`"${field}"\\s*:\\s*(\\d+)`));
4465
- return m ? Number(m[1]) : void 0;
4466
- }
4467
4492
  var SessionManager = class {
4468
4493
  _current = null;
4469
4494
  historyDir;
@@ -4521,6 +4546,18 @@ var SessionManager = class {
4521
4546
  this.lastRedactionHits = hits.length;
4522
4547
  atomicWriteFileSync(filePath, JSON.stringify(payload, null, 2));
4523
4548
  this._current.dirty = false;
4549
+ try {
4550
+ updateSessionInIndex(this.historyDir, {
4551
+ id: this._current.id,
4552
+ provider: this._current.provider,
4553
+ model: this._current.model,
4554
+ title: this._current.title,
4555
+ messageCount: this._current.messages.length,
4556
+ created: this._current.created,
4557
+ updated: this._current.updated
4558
+ }, filePath);
4559
+ } catch {
4560
+ }
4524
4561
  }
4525
4562
  loadSession(id) {
4526
4563
  const filePath = join3(this.historyDir, `${id}.json`);
@@ -4539,12 +4576,16 @@ var SessionManager = class {
4539
4576
  }
4540
4577
  listSessions(opts) {
4541
4578
  if (!existsSync3(this.historyDir)) return [];
4579
+ try {
4580
+ return listFromIndex(this.historyDir, opts);
4581
+ } catch {
4582
+ }
4542
4583
  const files = readdirSync(this.historyDir).filter((f) => f.endsWith(".json"));
4543
4584
  const metas = [];
4544
4585
  for (const file of files) {
4545
4586
  try {
4546
4587
  const filePath = join3(this.historyDir, file);
4547
- const meta = this.readSessionMeta(filePath);
4588
+ const meta = readSessionMeta(filePath);
4548
4589
  if (!meta) continue;
4549
4590
  if (opts?.includeFileSize) {
4550
4591
  try {
@@ -4562,78 +4603,6 @@ var SessionManager = class {
4562
4603
  }
4563
4604
  return metas.sort((a, b) => b.updated.getTime() - a.updated.getTime());
4564
4605
  }
4565
- /**
4566
- * P1-B: Read only the first ~1KB of a session file to extract metadata fields.
4567
- * Session JSON format puts id/provider/model/created/updated/title before the
4568
- * large "messages" array, so a small header read suffices for metadata extraction.
4569
- * Falls back to full file read if header parsing fails.
4570
- */
4571
- readSessionMeta(filePath) {
4572
- const HEADER_SIZE = 1024;
4573
- let header;
4574
- try {
4575
- const fd = openSync(filePath, "r");
4576
- const buf = Buffer.alloc(HEADER_SIZE);
4577
- const bytesRead = readSync(fd, buf, 0, HEADER_SIZE, 0);
4578
- closeSync(fd);
4579
- header = buf.toString("utf-8", 0, bytesRead);
4580
- } catch {
4581
- return null;
4582
- }
4583
- const id = extractJsonField(header, "id");
4584
- const provider = extractJsonField(header, "provider");
4585
- const model = extractJsonField(header, "model");
4586
- const created = extractJsonField(header, "created");
4587
- const updated = extractJsonField(header, "updated");
4588
- const title = extractJsonField(header, "title");
4589
- if (id && provider && model) {
4590
- let messageCount = extractJsonNumberField(header, "messageCount");
4591
- if (messageCount === void 0) {
4592
- messageCount = 0;
4593
- try {
4594
- const full = readFileSync3(filePath, "utf-8");
4595
- const matches = full.match(/"role"\s*:/g);
4596
- messageCount = matches ? matches.length : 0;
4597
- } catch {
4598
- }
4599
- }
4600
- let branchCount;
4601
- let activeBranch;
4602
- try {
4603
- const brMatch = header.match(/"branches"\s*:\s*\[/);
4604
- if (brMatch) {
4605
- const brIds = header.match(/"id"\s*:\s*"[^"]*"/g);
4606
- const parentMatches = header.match(/"parentBranchId"/g);
4607
- branchCount = parentMatches ? parentMatches.length + 1 : 1;
4608
- }
4609
- activeBranch = extractJsonField(header, "activeBranchId");
4610
- } catch {
4611
- }
4612
- return {
4613
- id,
4614
- provider,
4615
- model,
4616
- messageCount,
4617
- created: safeDate(created),
4618
- updated: safeDate(updated),
4619
- title: title || void 0,
4620
- branchCount,
4621
- activeBranch
4622
- };
4623
- }
4624
- const data = JSON.parse(readFileSync3(filePath, "utf-8"));
4625
- return {
4626
- id: data.id,
4627
- provider: data.provider,
4628
- model: data.model,
4629
- messageCount: data.messages?.length ?? 0,
4630
- created: safeDate(data.created),
4631
- updated: safeDate(data.updated),
4632
- title: data.title,
4633
- branchCount: data.branches?.length ?? 1,
4634
- activeBranch: data.activeBranchId
4635
- };
4636
- }
4637
4606
  deleteSession(id) {
4638
4607
  const filePath = join3(this.historyDir, `${id}.json`);
4639
4608
  if (!existsSync3(filePath)) return false;
@@ -4642,6 +4611,10 @@ var SessionManager = class {
4642
4611
  if (this._current && this._current.id === id) {
4643
4612
  this._current = null;
4644
4613
  }
4614
+ try {
4615
+ removeSessionFromIndex(this.historyDir, id);
4616
+ } catch {
4617
+ }
4645
4618
  return true;
4646
4619
  } catch {
4647
4620
  return false;
@@ -4675,9 +4648,15 @@ var SessionManager = class {
4675
4648
  searchMessages(query, maxResults = 20) {
4676
4649
  if (!existsSync3(this.historyDir)) return [];
4677
4650
  const q = query.toLowerCase();
4678
- const files = readdirSync(this.historyDir).filter((f) => f.endsWith(".json")).map((f) => join3(this.historyDir, f));
4651
+ const candidateIds = searchCandidates(this.historyDir, q);
4652
+ let filesToSearch;
4653
+ if (candidateIds !== null) {
4654
+ filesToSearch = candidateIds.map((id) => join3(this.historyDir, `${id}.json`)).filter((fp) => existsSync3(fp));
4655
+ } else {
4656
+ filesToSearch = readdirSync(this.historyDir).filter((f) => f.endsWith(".json")).map((f) => join3(this.historyDir, f));
4657
+ }
4679
4658
  const results = [];
4680
- for (const filePath of files) {
4659
+ for (const filePath of filesToSearch) {
4681
4660
  if (results.length >= maxResults) break;
4682
4661
  try {
4683
4662
  const data = JSON.parse(readFileSync3(filePath, "utf-8"));
@@ -15876,7 +15855,7 @@ async function handleSecurityReview(args, ctx) {
15876
15855
  async function handleTest(args, ctx) {
15877
15856
  ctx.send({ type: "info", message: "\u{1F9EA} Running tests..." });
15878
15857
  try {
15879
- const { executeTests } = await import("./run-tests-BNZZDCTU.js");
15858
+ const { executeTests } = await import("./run-tests-QR2PYR2P.js");
15880
15859
  const argStr = args.join(" ").trim();
15881
15860
  let testArgs = {};
15882
15861
  if (argStr) {
@@ -17739,7 +17718,7 @@ ${entry.content}`;
17739
17718
  return;
17740
17719
  }
17741
17720
  try {
17742
- const { searchChatMemory: searchChatMemory2, loadChatIndex: loadChatIndex2 } = await import("./chat-index-WSI7ULRQ.js");
17721
+ const { searchChatMemory: searchChatMemory2, loadChatIndex: loadChatIndex2 } = await import("./chat-index-WYXYD7YP.js");
17743
17722
  const loaded = loadChatIndex2();
17744
17723
  if (!loaded || loaded.idx.chunks.length === 0) {
17745
17724
  this.send({ type: "memory_hits", query: q, hits: [], indexMissing: true });
@@ -17775,7 +17754,7 @@ ${entry.content}`;
17775
17754
  }
17776
17755
  async handleMemoryStatus() {
17777
17756
  try {
17778
- const { getChatIndexStatus } = await import("./chat-index-WSI7ULRQ.js");
17757
+ const { getChatIndexStatus } = await import("./chat-index-WYXYD7YP.js");
17779
17758
  const s = getChatIndexStatus();
17780
17759
  this.send({
17781
17760
  type: "memory_status",
@@ -17800,7 +17779,7 @@ ${entry.content}`;
17800
17779
  type: "info",
17801
17780
  message: full ? "\u{1F9E0} Rebuilding chat memory index (this may take a while on first run \u2014 ~117 MB embedder)." : "\u{1F9E0} Refreshing chat memory index (incremental)\u2026"
17802
17781
  });
17803
- const { buildChatIndex } = await import("./chat-index-WSI7ULRQ.js");
17782
+ const { buildChatIndex } = await import("./chat-index-WYXYD7YP.js");
17804
17783
  const stats = await buildChatIndex({
17805
17784
  full,
17806
17785
  onProgress: (p) => {