jinzd-ai-cli 0.4.88 → 0.4.90

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 (32) hide show
  1. package/dist/{batch-7XCYSPJU.js → batch-3MJ56YAA.js} +2 -2
  2. package/dist/chat-index-QKFH7ZP6.js +17 -0
  3. package/dist/chat-index-W2UZ34ZI.js +18 -0
  4. package/dist/{chunk-PFYAAX2S.js → chunk-2DXY7UGF.js} +16 -63
  5. package/dist/chunk-5S3PIG5O.js +453 -0
  6. package/dist/{chunk-QT2KNL3V.js → chunk-AB2LA33A.js} +1 -1
  7. package/dist/chunk-ANYYM4CF.js +460 -0
  8. package/dist/{chunk-P6EQZKKG.js → chunk-BJXGZFE6.js} +1 -1
  9. package/dist/{chunk-L3MBIO36.js → chunk-DJGP7AR6.js} +5 -106
  10. package/dist/{chunk-V3NMERIB.js → chunk-EEEAFWNK.js} +1 -1
  11. package/dist/{chunk-YDHIU24C.js → chunk-G65IDWVP.js} +76 -3
  12. package/dist/chunk-JV5N65KN.js +50 -0
  13. package/dist/chunk-KHYD3WXE.js +52 -0
  14. package/dist/{chunk-CQQQFNND.js → chunk-KJLJPUY2.js} +6 -4
  15. package/dist/{chunk-GTKJUEBS.js → chunk-MO7MWNWC.js} +6 -4
  16. package/dist/{chunk-XMA222FQ.js → chunk-PASCDYMH.js} +17 -63
  17. package/dist/{chunk-VGXNE37B.js → chunk-WPQ4D6T3.js} +1 -1
  18. package/dist/electron-server.js +187 -104
  19. package/dist/{hub-IR4INXSU.js → hub-B7NJSCWF.js} +1 -1
  20. package/dist/index.js +158 -19
  21. package/dist/{run-tests-FQHDUYOG.js → run-tests-2DYVHTIH.js} +2 -2
  22. package/dist/{run-tests-JVWIGY7P.js → run-tests-37FEBJTR.js} +1 -1
  23. package/dist/{semantic-MYAXLDCZ.js → semantic-3KJPAUW6.js} +3 -2
  24. package/dist/{semantic-ICJ536BG.js → semantic-YDRPPVWK.js} +3 -2
  25. package/dist/{server-UWKRV5DK.js → server-FCTPLKGO.js} +121 -13
  26. package/dist/{server-HTVVWKFN.js → server-S6JYNMMF.js} +7 -5
  27. package/dist/{task-orchestrator-6MI6LD7T.js → task-orchestrator-K6HDX4YE.js} +7 -5
  28. package/dist/{vector-store-UR7IARXB.js → vector-store-NDUFLNGN.js} +2 -1
  29. package/dist/{vector-store-YTVHACBV.js → vector-store-QARQ2P6D.js} +2 -1
  30. package/dist/web/client/app.js +201 -0
  31. package/dist/web/client/index.html +24 -0
  32. package/package.json +1 -1
@@ -36,15 +36,21 @@ import {
36
36
  VERSION,
37
37
  buildUserIdentityPrompt,
38
38
  runTestsTool
39
- } from "./chunk-P6EQZKKG.js";
39
+ } from "./chunk-BJXGZFE6.js";
40
40
  import {
41
41
  hasSemanticIndex,
42
42
  semanticSearch
43
- } from "./chunk-GTKJUEBS.js";
43
+ } from "./chunk-MO7MWNWC.js";
44
44
  import {
45
45
  loadIndex
46
46
  } from "./chunk-BJAT4GNC.js";
47
- import "./chunk-XMA222FQ.js";
47
+ import "./chunk-PASCDYMH.js";
48
+ import {
49
+ loadChatIndex,
50
+ redactJson,
51
+ searchChatMemory
52
+ } from "./chunk-5S3PIG5O.js";
53
+ import "./chunk-JV5N65KN.js";
48
54
 
49
55
  // src/web/server.ts
50
56
  import express from "express";
@@ -3054,104 +3060,6 @@ var Session = class _Session {
3054
3060
  }
3055
3061
  };
3056
3062
 
3057
- // src/security/redactor.ts
3058
- var DEFAULT_PATTERNS = [
3059
- // password: xxx / password = xxx / password="xxx"
3060
- // Covers YAML / JSON / shell-ish / env-file forms.
3061
- { kind: "password", regex: /\b(password|passwd|pwd)\s*[:=]\s*["']?([^\s"',;{}]{4,200})["']?/gi },
3062
- // PGPASSWORD=xxx (explicit bash env-var form, separate rule because no quotes usually)
3063
- { kind: "pgpassword-env", regex: /\b(PGPASSWORD)=([^\s"']{4,200})/g },
3064
- // JDBC/PG/MySQL/Mongo connection strings with inline credentials
3065
- // postgresql://user:pass@host/db → redact pass
3066
- { kind: "db-uri-password", regex: /(\b(?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?|redis|amqp|mssql):\/\/[^:\s]+:)([^@\s]+)(@)/gi },
3067
- // Anthropic API keys
3068
- { kind: "anthropic-key", regex: /(sk-ant-[a-zA-Z0-9_-]{90,})/g },
3069
- // OpenAI / generic sk- keys — requires length ≥32 to avoid eating short identifiers
3070
- { kind: "openai-key", regex: /(sk-(?:proj-)?[a-zA-Z0-9_-]{32,})/g },
3071
- // GitHub personal access tokens
3072
- { kind: "github-pat", regex: /\b(ghp_[a-zA-Z0-9]{36})\b/g },
3073
- { kind: "github-oauth", regex: /\b(gho_[a-zA-Z0-9]{36})\b/g },
3074
- { kind: "github-install", regex: /\b(ghs_[a-zA-Z0-9]{36})\b/g },
3075
- // Slack tokens
3076
- { kind: "slack-bot", regex: /\b(xoxb-\d+-\d+-[a-zA-Z0-9]+)\b/g },
3077
- { kind: "slack-user", regex: /\b(xoxp-\d+-\d+-\d+-[a-zA-Z0-9]+)\b/g },
3078
- // AWS access key IDs (AKIA...) and secret access keys are context-dependent;
3079
- // we only catch the ID because secret key alone is indistinguishable from random base64.
3080
- { kind: "aws-access-key-id", regex: /\b(AKIA[0-9A-Z]{16})\b/g },
3081
- // Google API keys
3082
- { kind: "google-api-key", regex: /\b(AIza[0-9A-Za-z_-]{35})\b/g },
3083
- // Generic "api_key": "..." / "apiKey": "..." / api-key=xxx
3084
- { kind: "api-key", regex: /\b(api[_-]?key)\s*[:=]\s*["']?([a-zA-Z0-9_\-.]{16,200})["']?/gi },
3085
- // Generic token: xxx (only when value looks token-shaped; avoids eating human prose)
3086
- { kind: "token", regex: /\b(token|access[_-]?token|bearer[_-]?token)\s*[:=]\s*["']?([a-zA-Z0-9_\-.]{20,300})["']?/gi },
3087
- // Bearer <token> in Authorization headers
3088
- { kind: "bearer", regex: /\b(Authorization:\s*Bearer\s+)([a-zA-Z0-9_\-.=]{20,500})/g },
3089
- // Private key PEM blocks — catch the header+footer together
3090
- { kind: "private-key", regex: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g }
3091
- ];
3092
- function render(placeholder, kind) {
3093
- return placeholder.replace("{kind}", kind);
3094
- }
3095
- function redactString(input, options) {
3096
- if (!options.enabled || !input) return { redacted: input, hits: [] };
3097
- const placeholder = options.placeholder ?? "[REDACTED:{kind}]";
3098
- const patterns = [
3099
- ...options.patterns ?? DEFAULT_PATTERNS,
3100
- ...(options.customRegexes ?? []).flatMap((src, i) => {
3101
- try {
3102
- const flags = src.match(/^\/.*\/([gimsuy]*)$/)?.[1] ?? "";
3103
- const body = src.replace(/^\/(.*)\/[gimsuy]*$/, "$1");
3104
- const regex = new RegExp(body, flags.includes("g") ? flags : flags + "g");
3105
- return [{ kind: `custom-${i}`, regex }];
3106
- } catch {
3107
- return [];
3108
- }
3109
- })
3110
- ];
3111
- let redacted = input;
3112
- const hits = [];
3113
- for (const { kind, regex } of patterns) {
3114
- const rx = new RegExp(regex.source, regex.flags);
3115
- redacted = redacted.replace(rx, (...args) => {
3116
- const match = args[0];
3117
- const probe = new RegExp(rx.source).exec(match);
3118
- const captureCount = probe ? probe.length - 1 : 0;
3119
- const g1 = captureCount >= 1 ? args[1] : void 0;
3120
- const g2 = captureCount >= 2 ? args[2] : void 0;
3121
- const offset = args[1 + captureCount];
3122
- if (captureCount >= 2 && typeof g2 === "string") {
3123
- hits.push({ kind, start: offset + (g1?.length ?? 0), length: g2.length, secret: g2 });
3124
- return `${g1}${render(placeholder, kind)}`;
3125
- }
3126
- hits.push({ kind, start: offset, length: match.length, secret: g1 ?? match });
3127
- return render(placeholder, kind);
3128
- });
3129
- }
3130
- return { redacted, hits };
3131
- }
3132
- function redactJson(value, options) {
3133
- if (!options.enabled) return { value, hits: [] };
3134
- const allHits = [];
3135
- function walk(v) {
3136
- if (typeof v === "string") {
3137
- const r = redactString(v, options);
3138
- allHits.push(...r.hits);
3139
- return r.redacted;
3140
- }
3141
- if (Array.isArray(v)) return v.map(walk);
3142
- if (v && typeof v === "object") {
3143
- const out = {};
3144
- for (const [k, vv] of Object.entries(v)) {
3145
- out[k] = walk(vv);
3146
- }
3147
- return out;
3148
- }
3149
- return v;
3150
- }
3151
- const redacted = walk(value);
3152
- return { value: redacted, hits: allHits };
3153
- }
3154
-
3155
3063
  // src/session/session-manager.ts
3156
3064
  function safeDate(value) {
3157
3065
  const d = new Date(value);
@@ -7856,6 +7764,74 @@ ${lines.join("\n")}`;
7856
7764
  }
7857
7765
  };
7858
7766
 
7767
+ // src/tools/builtin/recall-memory.ts
7768
+ function formatHit(h, i) {
7769
+ const ts = h.chunk.timestamp.slice(0, 16).replace("T", " ");
7770
+ const title = h.chunk.sessionTitle ? ` \xB7 ${h.chunk.sessionTitle}` : "";
7771
+ const sid = h.chunk.sessionId.slice(0, 8);
7772
+ const score = h.score.toFixed(3);
7773
+ const body = h.chunk.text.length > 600 ? h.chunk.text.slice(0, 600) + "\u2026" : h.chunk.text;
7774
+ return `\u2500\u2500\u2500 Hit ${i + 1} (score ${score}, session ${sid}${title}, ${ts}) \u2500\u2500\u2500
7775
+ ` + body;
7776
+ }
7777
+ var recallMemoryTool = {
7778
+ definition: {
7779
+ name: "recall_memory",
7780
+ 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".',
7781
+ parameters: {
7782
+ query: {
7783
+ type: "string",
7784
+ description: "Natural-language description of what to recall. Chinese or English both work.",
7785
+ required: true
7786
+ },
7787
+ topK: {
7788
+ type: "number",
7789
+ description: "Max number of snippets to return (default 5, max 20).",
7790
+ required: false
7791
+ },
7792
+ excludeCurrentSession: {
7793
+ type: "boolean",
7794
+ description: "If true, exclude the current session from results (avoid echoing what you just said). Default false.",
7795
+ required: false
7796
+ },
7797
+ currentSessionId: {
7798
+ type: "string",
7799
+ description: "Session ID to exclude when excludeCurrentSession=true. Usually the active session.",
7800
+ required: false
7801
+ },
7802
+ minScore: {
7803
+ type: "number",
7804
+ description: "Drop hits below this cosine score. Default 0.25. Raise to 0.35+ for stricter matches.",
7805
+ required: false
7806
+ }
7807
+ },
7808
+ dangerous: false
7809
+ },
7810
+ async execute(args) {
7811
+ const query = String(args["query"] ?? "").trim();
7812
+ if (!query) throw new ToolError("recall_memory", "query is required");
7813
+ const topK = Math.max(1, Math.min(20, Number(args["topK"] ?? 5)));
7814
+ const excludeCurrent = Boolean(args["excludeCurrentSession"]);
7815
+ const currentId = args["currentSessionId"] ? String(args["currentSessionId"]) : void 0;
7816
+ const minScore = args["minScore"] !== void 0 ? Number(args["minScore"]) : 0.25;
7817
+ const status = loadChatIndex();
7818
+ if (!status) {
7819
+ 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.";
7820
+ }
7821
+ const hits = await searchChatMemory(query, {
7822
+ topK,
7823
+ minScore,
7824
+ excludeSessionId: excludeCurrent ? currentId : void 0
7825
+ });
7826
+ if (hits.length === 0) {
7827
+ 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.`;
7828
+ }
7829
+ const header = `Found ${hits.length} memory hit(s) for "${query}" (min-score ${minScore}):
7830
+ `;
7831
+ return header + "\n" + hits.map(formatHit).join("\n\n");
7832
+ }
7833
+ };
7834
+
7859
7835
  // src/core/token-estimator.ts
7860
7836
  var CJK_REGEX = /[\u2E80-\u9FFF\uA000-\uA4FF\uAC00-\uD7FF\uF900-\uFAFF\uFE30-\uFE4F\uFF00-\uFFEF]/g;
7861
7837
  function estimateTokens(text) {
@@ -7915,6 +7891,7 @@ var ToolRegistry = class {
7915
7891
  this.register(getOutlineTool);
7916
7892
  this.register(findReferencesTool);
7917
7893
  this.register(searchCodeTool);
7894
+ this.register(recallMemoryTool);
7918
7895
  }
7919
7896
  register(tool) {
7920
7897
  this.tools.set(tool.definition.name, tool);
@@ -9637,6 +9614,12 @@ var SessionHandler = class _SessionHandler {
9637
9614
  }
9638
9615
  return;
9639
9616
  }
9617
+ case "memory_search":
9618
+ return this.handleMemorySearch(msg.query, msg.topK, msg.minScore, msg.excludeCurrentSession);
9619
+ case "memory_status_request":
9620
+ return this.handleMemoryStatus();
9621
+ case "memory_rebuild":
9622
+ return this.handleMemoryRebuild(Boolean(msg.full));
9640
9623
  case "auto_pause_response": {
9641
9624
  const resolve6 = this.pendingAutoPause.get(msg.requestId);
9642
9625
  if (resolve6) {
@@ -11010,7 +10993,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
11010
10993
  const root = process.cwd();
11011
10994
  const { loadIndex: loadIndex2, clearIndex } = await import("./store-247B3TAU.js");
11012
10995
  const { indexProject: indexProject2 } = await import("./indexer-O5FCGFBJ.js");
11013
- const { loadVectorStore, clearVectorStore } = await import("./vector-store-UR7IARXB.js");
10996
+ const { loadVectorStore, clearVectorStore } = await import("./vector-store-NDUFLNGN.js");
11014
10997
  if (sub === "status") {
11015
10998
  const idx = loadIndex2(root);
11016
10999
  const vec = loadVectorStore(root);
@@ -11059,7 +11042,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
11059
11042
  message: `Building semantic index for ${idx.symbolCount} symbols\u2026 (first run downloads ~117 MB model)`
11060
11043
  });
11061
11044
  try {
11062
- const { rebuildSemanticIndex } = await import("./semantic-MYAXLDCZ.js");
11045
+ const { rebuildSemanticIndex } = await import("./semantic-3KJPAUW6.js");
11063
11046
  const stats = await rebuildSemanticIndex(root);
11064
11047
  const first = stats.modelFirstLoadMs ? ` (model load+first batch ${stats.modelFirstLoadMs}ms)` : "";
11065
11048
  this.send({
@@ -11246,7 +11229,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
11246
11229
  case "test": {
11247
11230
  this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
11248
11231
  try {
11249
- const { executeTests } = await import("./run-tests-JVWIGY7P.js");
11232
+ const { executeTests } = await import("./run-tests-37FEBJTR.js");
11250
11233
  const argStr = args.join(" ").trim();
11251
11234
  let testArgs = {};
11252
11235
  if (argStr) {
@@ -11760,6 +11743,106 @@ Add .md files to create commands.` });
11760
11743
  this.send({ type: "error", message: `Failed to clear memory: ${err.message}` });
11761
11744
  }
11762
11745
  }
11746
+ // ── B4 chat memory recall (v0.4.90+) ──────────────────────────────
11747
+ // Lazy-imported so the 117 MB embedder stays out of the load path for
11748
+ // clients that never open the Memory panel.
11749
+ async handleMemorySearch(query, topK, minScore, excludeCurrent) {
11750
+ const q = (query ?? "").trim();
11751
+ if (!q) {
11752
+ this.send({ type: "error", message: "Memory search: query is empty." });
11753
+ return;
11754
+ }
11755
+ try {
11756
+ const { searchChatMemory: searchChatMemory2, loadChatIndex: loadChatIndex2 } = await import("./chat-index-QKFH7ZP6.js");
11757
+ const loaded = loadChatIndex2();
11758
+ if (!loaded || loaded.idx.chunks.length === 0) {
11759
+ this.send({ type: "memory_hits", query: q, hits: [], indexMissing: true });
11760
+ return;
11761
+ }
11762
+ const hits = await searchChatMemory2(q, {
11763
+ topK: topK ?? 8,
11764
+ minScore: minScore ?? 0.2,
11765
+ excludeSessionId: excludeCurrent ? this.sessions.current?.id : void 0
11766
+ });
11767
+ this.send({
11768
+ type: "memory_hits",
11769
+ query: q,
11770
+ hits: hits.map((h) => ({
11771
+ id: h.chunk.id,
11772
+ sessionId: h.chunk.sessionId,
11773
+ sessionTitle: h.chunk.sessionTitle,
11774
+ provider: h.chunk.provider,
11775
+ model: h.chunk.model,
11776
+ timestamp: h.chunk.timestamp,
11777
+ score: h.score,
11778
+ text: h.chunk.text,
11779
+ startMessageIdx: h.chunk.startMessageIdx,
11780
+ endMessageIdx: h.chunk.endMessageIdx
11781
+ }))
11782
+ });
11783
+ } catch (err) {
11784
+ this.send({
11785
+ type: "error",
11786
+ message: `Memory search failed: ${err instanceof Error ? err.message : String(err)}`
11787
+ });
11788
+ }
11789
+ }
11790
+ async handleMemoryStatus() {
11791
+ try {
11792
+ const { getChatIndexStatus } = await import("./chat-index-QKFH7ZP6.js");
11793
+ const s = getChatIndexStatus();
11794
+ this.send({
11795
+ type: "memory_status",
11796
+ exists: s.exists,
11797
+ chunks: s.chunks,
11798
+ sessions: s.sessions,
11799
+ built: s.built,
11800
+ model: s.model,
11801
+ vecFileSizeBytes: s.vecFileSizeBytes,
11802
+ chunksFileSizeBytes: s.chunksFileSizeBytes
11803
+ });
11804
+ } catch (err) {
11805
+ this.send({
11806
+ type: "error",
11807
+ message: `Memory status failed: ${err instanceof Error ? err.message : String(err)}`
11808
+ });
11809
+ }
11810
+ }
11811
+ async handleMemoryRebuild(full) {
11812
+ try {
11813
+ this.send({
11814
+ type: "info",
11815
+ 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"
11816
+ });
11817
+ const { buildChatIndex } = await import("./chat-index-QKFH7ZP6.js");
11818
+ const stats = await buildChatIndex({
11819
+ full,
11820
+ onProgress: (p) => {
11821
+ this.send({
11822
+ type: "memory_rebuild_progress",
11823
+ stage: p.stage,
11824
+ processed: p.processed,
11825
+ total: p.total
11826
+ });
11827
+ }
11828
+ });
11829
+ this.send({
11830
+ type: "memory_rebuild_done",
11831
+ chunksTotal: stats.chunksTotal,
11832
+ chunksAdded: stats.chunksAdded,
11833
+ chunksRemoved: stats.chunksRemoved,
11834
+ sessionsIndexed: stats.sessionsIndexed,
11835
+ sessionsSkipped: stats.sessionsSkipped,
11836
+ durationMs: stats.durationMs
11837
+ });
11838
+ await this.handleMemoryStatus();
11839
+ } catch (err) {
11840
+ this.send({
11841
+ type: "error",
11842
+ message: `Memory rebuild failed: ${err instanceof Error ? err.message : String(err)}`
11843
+ });
11844
+ }
11845
+ }
11763
11846
  sendSessionMessages() {
11764
11847
  const session = this.sessions.current;
11765
11848
  if (!session) return;
@@ -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-6MI6LD7T.js");
388
+ const { TaskOrchestrator } = await import("./task-orchestrator-K6HDX4YE.js");
389
389
  const orchestrator = new TaskOrchestrator(config, providers, configManager);
390
390
  let interrupted = false;
391
391
  const onSigint = () => {
package/dist/index.js CHANGED
@@ -1,6 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- DEFAULT_PATTERNS,
4
3
  HALLUCINATION_CORRECTION_MESSAGE,
5
4
  McpManager,
6
5
  ProviderRegistry,
@@ -29,13 +28,12 @@ import {
29
28
  persistToolRound,
30
29
  rebuildExtraMessages,
31
30
  saveDevState,
32
- scanString,
33
31
  sessionHasMeaningfulContent,
34
32
  setupProxy
35
- } from "./chunk-L3MBIO36.js";
33
+ } from "./chunk-DJGP7AR6.js";
36
34
  import {
37
35
  ConfigManager
38
- } from "./chunk-QT2KNL3V.js";
36
+ } from "./chunk-AB2LA33A.js";
39
37
  import {
40
38
  ToolExecutor,
41
39
  ToolRegistry,
@@ -51,16 +49,25 @@ import {
51
49
  spawnAgentContext,
52
50
  theme,
53
51
  undoStack
54
- } from "./chunk-YDHIU24C.js";
52
+ } from "./chunk-G65IDWVP.js";
55
53
  import "./chunk-2ZD3YTVM.js";
56
54
  import {
57
55
  fileCheckpoints
58
56
  } from "./chunk-4BKXL7SM.js";
57
+ import {
58
+ DEFAULT_PATTERNS,
59
+ buildChatIndex,
60
+ clearChatIndex,
61
+ getChatIndexStatus,
62
+ scanString,
63
+ searchChatMemory
64
+ } from "./chunk-ANYYM4CF.js";
59
65
  import "./chunk-NHNWUBXB.js";
60
- import "./chunk-CQQQFNND.js";
66
+ import "./chunk-KJLJPUY2.js";
61
67
  import "./chunk-6VRJGH25.js";
62
- import "./chunk-PFYAAX2S.js";
63
- import "./chunk-V3NMERIB.js";
68
+ import "./chunk-2DXY7UGF.js";
69
+ import "./chunk-KHYD3WXE.js";
70
+ import "./chunk-EEEAFWNK.js";
64
71
  import {
65
72
  AGENTIC_BEHAVIOR_GUIDELINE,
66
73
  AUTHOR,
@@ -82,7 +89,7 @@ import {
82
89
  SKILLS_DIR_NAME,
83
90
  VERSION,
84
91
  buildUserIdentityPrompt
85
- } from "./chunk-VGXNE37B.js";
92
+ } from "./chunk-WPQ4D6T3.js";
86
93
 
87
94
  // src/index.ts
88
95
  import { program } from "commander";
@@ -2470,7 +2477,7 @@ ${hint}` : "")
2470
2477
  const root = process.cwd();
2471
2478
  const { loadIndex, clearIndex } = await import("./store-S24SPPDZ.js");
2472
2479
  const { indexProject } = await import("./indexer-C7QYYHSZ.js");
2473
- const { loadVectorStore, clearVectorStore } = await import("./vector-store-YTVHACBV.js");
2480
+ const { loadVectorStore, clearVectorStore } = await import("./vector-store-QARQ2P6D.js");
2474
2481
  if (sub === "status") {
2475
2482
  const idx = loadIndex(root);
2476
2483
  const vec = loadVectorStore(root);
@@ -2529,7 +2536,7 @@ ${hint}` : "")
2529
2536
  }
2530
2537
  console.log(theme.dim(` Building semantic index for ${idx.symbolCount} symbols\u2026`));
2531
2538
  console.log(theme.dim(" (First run downloads ~117 MB embedding model to ~/.aicli/models/)"));
2532
- const { rebuildSemanticIndex } = await import("./semantic-ICJ536BG.js");
2539
+ const { rebuildSemanticIndex } = await import("./semantic-YDRPPVWK.js");
2533
2540
  try {
2534
2541
  const stats = await rebuildSemanticIndex(root, {
2535
2542
  onProgress: (done, total) => {
@@ -2595,7 +2602,7 @@ ${hint}` : "")
2595
2602
  usage: "/test [command|filter]",
2596
2603
  async execute(args, ctx) {
2597
2604
  try {
2598
- const { executeTests } = await import("./run-tests-FQHDUYOG.js");
2605
+ const { executeTests } = await import("./run-tests-2DYVHTIH.js");
2599
2606
  const argStr = args.join(" ").trim();
2600
2607
  let testArgs = {};
2601
2608
  if (argStr) {
@@ -2750,11 +2757,92 @@ ${hint}` : "")
2750
2757
  // ── /memory ────────────────────────────────────────────────────────────────
2751
2758
  {
2752
2759
  name: "memory",
2753
- description: "View, add to, or clear persistent memory (memory.md)",
2754
- usage: "/memory [show|add <text>|clear|path]",
2755
- execute(args, ctx) {
2760
+ description: "Persistent memory (memory.md) + chat memory recall index (v0.4.89+)",
2761
+ usage: "/memory [show|add <text>|clear|path|rebuild|refresh|status|recall <query>|index-clear]",
2762
+ async execute(args, ctx) {
2756
2763
  const memoryFile = join2(ctx.config.getConfigDir(), MEMORY_FILE_NAME);
2757
2764
  const sub = args[0] ?? "show";
2765
+ if (sub === "rebuild" || sub === "refresh") {
2766
+ const full = sub === "rebuild";
2767
+ ctx.renderer.printInfo(
2768
+ full ? "\u{1F9E0} Rebuilding chat memory index from scratch (this may take a while on first run \u2014 embedding model ~117 MB)\u2026" : "\u{1F9E0} Refreshing chat memory index (incremental)\u2026"
2769
+ );
2770
+ try {
2771
+ let lastStage = "";
2772
+ const stats = await buildChatIndex({
2773
+ full,
2774
+ onProgress: (p) => {
2775
+ if (p.stage !== lastStage) {
2776
+ lastStage = p.stage;
2777
+ if (p.stage === "embedding" && p.total) {
2778
+ process.stdout.write(theme.dim(` embedding ${p.total} chunk(s)\u2026
2779
+ `));
2780
+ } else if (p.stage !== "done") {
2781
+ process.stdout.write(theme.dim(` ${p.stage}\u2026
2782
+ `));
2783
+ }
2784
+ }
2785
+ }
2786
+ });
2787
+ ctx.renderer.printSuccess(
2788
+ `\u2713 Chat index: ${stats.chunksTotal} chunks (${stats.chunksAdded} new, ${stats.chunksRemoved} removed) across ${stats.sessionsIndexed + stats.sessionsSkipped} sessions (${stats.sessionsIndexed} re-indexed, ${stats.sessionsSkipped} cached) in ${stats.durationMs}ms`
2789
+ );
2790
+ } catch (err) {
2791
+ ctx.renderer.renderError(`Rebuild failed: ${err instanceof Error ? err.message : String(err)}`);
2792
+ }
2793
+ return;
2794
+ }
2795
+ if (sub === "status") {
2796
+ const s = getChatIndexStatus();
2797
+ console.log(theme.heading("\n\u{1F9E0} Chat Memory Index"));
2798
+ if (!s.exists) {
2799
+ console.log(theme.dim(" not built yet \u2014 run /memory rebuild\n"));
2800
+ return;
2801
+ }
2802
+ const mb = (n) => `${(n / 1024 / 1024).toFixed(2)} MB`;
2803
+ console.log(` chunks : ${s.chunks}`);
2804
+ console.log(` sessions : ${s.sessions}`);
2805
+ console.log(` built : ${s.built?.replace("T", " ").slice(0, 19) ?? "-"}`);
2806
+ console.log(` model : ${s.model ?? "-"}`);
2807
+ console.log(` vec file : ${mb(s.vecFileSizeBytes)}`);
2808
+ console.log(` chunks file : ${mb(s.chunksFileSizeBytes)}`);
2809
+ console.log();
2810
+ return;
2811
+ }
2812
+ if (sub === "recall") {
2813
+ const query = args.slice(1).join(" ").trim();
2814
+ if (!query) {
2815
+ ctx.renderer.printInfo("Usage: /memory recall <query>");
2816
+ return;
2817
+ }
2818
+ try {
2819
+ const hits = await searchChatMemory(query, { topK: 5, minScore: 0.2 });
2820
+ if (hits.length === 0) {
2821
+ console.log(theme.warning(` No memories matched "${query}" above score 0.20.`));
2822
+ console.log();
2823
+ return;
2824
+ }
2825
+ console.log(theme.heading(`
2826
+ \u{1F9E0} Memory recall for "${query}" \u2014 ${hits.length} hit(s):
2827
+ `));
2828
+ for (const [i, h] of hits.entries()) {
2829
+ const ts = h.chunk.timestamp.slice(0, 16).replace("T", " ");
2830
+ const title = h.chunk.sessionTitle ?? h.chunk.sessionId.slice(0, 8);
2831
+ console.log(theme.accent(` [${i + 1}] ${ts} \xB7 ${title} \xB7 score ${h.score.toFixed(3)}`));
2832
+ const body = h.chunk.text.length > 400 ? h.chunk.text.slice(0, 400) + "\u2026" : h.chunk.text;
2833
+ console.log(theme.dim(" ") + body.replace(/\n/g, "\n "));
2834
+ console.log();
2835
+ }
2836
+ } catch (err) {
2837
+ ctx.renderer.renderError(`Recall failed: ${err instanceof Error ? err.message : String(err)}`);
2838
+ }
2839
+ return;
2840
+ }
2841
+ if (sub === "index-clear") {
2842
+ clearChatIndex();
2843
+ ctx.renderer.printSuccess("Chat memory index cleared (memory.md untouched).");
2844
+ return;
2845
+ }
2758
2846
  if (sub === "show" || sub === "view") {
2759
2847
  if (!existsSync2(memoryFile)) {
2760
2848
  ctx.renderer.printInfo("Memory is empty (memory.md not found)");
@@ -4123,6 +4211,12 @@ var Repl = class {
4123
4211
  toolExecutor;
4124
4212
  /** 运行时有效的 system prompt(合并了项目上下文 + 用户配置) */
4125
4213
  activeSystemPrompt;
4214
+ /** v0.4.89: cached chat memory index status, refreshed at startup and after /memory rebuild. */
4215
+ chatMemoryStatus = {
4216
+ exists: false,
4217
+ chunks: 0,
4218
+ sessions: 0
4219
+ };
4126
4220
  /** 当前加载的层级上下文(全局/项目/子目录) */
4127
4221
  contextLayers = [];
4128
4222
  /** 本次会话累计 token 用量 */
@@ -4580,6 +4674,23 @@ You have a maximum of ${effectiveMaxRounds} tool call rounds per conversation tu
4580
4674
  if (memory) stableParts.push(`# Persistent Memory
4581
4675
 
4582
4676
  ${memory.content}`);
4677
+ if (this.chatMemoryStatus.exists && this.chatMemoryStatus.chunks > 0) {
4678
+ stableParts.push(
4679
+ `# Long-term Memory Recall
4680
+
4681
+ You have a semantic index over ${this.chatMemoryStatus.chunks} chunks from ${this.chatMemoryStatus.sessions} past sessions. Call \`recall_memory({ query: "\u2026" })\` whenever:
4682
+ - The user references something that may have been discussed before ("\u4E0A\u6B21", "\u4E4B\u524D", "last time", "remember")
4683
+ - A pronoun/demonstrative is ambiguous ("\u90A3\u4E2A", "it", "that project") and current context doesn't resolve it
4684
+ - The user asks about preferences, decisions, or conventions that might have been established earlier
4685
+ - Continuity across sessions would clearly help (e.g. user returns after days to the same project)
4686
+
4687
+ Do NOT call it for:
4688
+ - Queries fully answerable from the current conversation
4689
+ - Obviously-new topics with no continuity signal
4690
+
4691
+ Prefer recall over asking the user "can you remind me". When a hit is relevant, cite it briefly ("we established last session that\u2026") so the user sees you remembered.`
4692
+ );
4693
+ }
4583
4694
  const devState = loadDevState();
4584
4695
  if (devState) {
4585
4696
  stableParts.push(
@@ -4913,6 +5024,34 @@ Session '${this.resumeSessionId}' not found.
4913
5024
  } catch {
4914
5025
  }
4915
5026
  })();
5027
+ void (async () => {
5028
+ try {
5029
+ const { getChatIndexStatus: getChatIndexStatus2, buildChatIndex: buildChatIndex2 } = await import("./chat-index-W2UZ34ZI.js");
5030
+ const initial = getChatIndexStatus2();
5031
+ this.chatMemoryStatus = {
5032
+ exists: initial.exists,
5033
+ chunks: initial.chunks,
5034
+ sessions: initial.sessions
5035
+ };
5036
+ if (!initial.exists) return;
5037
+ const stats = await buildChatIndex2({ full: false });
5038
+ const after = getChatIndexStatus2();
5039
+ this.chatMemoryStatus = {
5040
+ exists: after.exists,
5041
+ chunks: after.chunks,
5042
+ sessions: after.sessions
5043
+ };
5044
+ if (stats.sessionsIndexed > 0 || stats.chunksRemoved > 0) {
5045
+ process.stdout.write(
5046
+ theme.dim(
5047
+ ` \u{1F9E0} Chat memory refreshed: ${stats.chunksAdded} new \xB7 ${stats.chunksRemoved} removed \xB7 ${stats.chunksTotal} total
5048
+ `
5049
+ )
5050
+ );
5051
+ }
5052
+ } catch {
5053
+ }
5054
+ })();
4916
5055
  const globalMcpServers = this.config.get("mcpServers") ?? {};
4917
5056
  const projectMcpResult = this.loadProjectMcpConfig();
4918
5057
  const projectMcpServers = projectMcpResult?.servers ?? {};
@@ -6576,7 +6715,7 @@ program.command("web").description("Start Web UI server with browser-based chat
6576
6715
  console.error("Error: Invalid port number. Must be between 1 and 65535.");
6577
6716
  process.exit(1);
6578
6717
  }
6579
- const { startWebServer } = await import("./server-UWKRV5DK.js");
6718
+ const { startWebServer } = await import("./server-FCTPLKGO.js");
6580
6719
  await startWebServer({ port, host: options.host });
6581
6720
  });
6582
6721
  program.command("user [action] [username]").description("Manage Web UI users (list | create <name> | delete <name> | reset-password <name> | migrate <name>)").action(async (action, username) => {
@@ -6699,7 +6838,7 @@ program.command("sessions").description("List recent conversation sessions").act
6699
6838
  });
6700
6839
  program.command("batch <action> [arg] [arg2]").description("Anthropic Message Batches: submit | list | status <id> | results <id> [out] | cancel <id>").option("--dry-run", "Parse and validate input without submitting (submit only)").action(async (action, arg, arg2, options) => {
6701
6840
  try {
6702
- const batch = await import("./batch-7XCYSPJU.js");
6841
+ const batch = await import("./batch-3MJ56YAA.js");
6703
6842
  switch (action) {
6704
6843
  case "submit":
6705
6844
  if (!arg) {
@@ -6742,7 +6881,7 @@ program.command("batch <action> [arg] [arg2]").description("Anthropic Message Ba
6742
6881
  }
6743
6882
  });
6744
6883
  program.command("mcp-serve").description("Start an MCP server over STDIO, exposing aicli's built-in tools to Claude Desktop / Cursor / other MCP clients").option("--allow-destructive", "Allow bash / run_interactive / task_create (always destructive in MCP mode)").option("--allow-outside-cwd", "Allow tool path arguments to escape the sandbox root \u2014 disabled by default").option("--tools <list>", "Comma-separated whitelist of tools to expose (default: all eligible tools)").option("--cwd <path>", "Working directory AND sandbox root (default: current directory)").action(async (options) => {
6745
- const { startMcpServer } = await import("./server-HTVVWKFN.js");
6884
+ const { startMcpServer } = await import("./server-S6JYNMMF.js");
6746
6885
  await startMcpServer({
6747
6886
  allowDestructive: !!options.allowDestructive,
6748
6887
  allowOutsideCwd: !!options.allowOutsideCwd,
@@ -6869,7 +7008,7 @@ program.command("hub [topic]").description("Start multi-agent hub (discuss / bra
6869
7008
  }),
6870
7009
  config.get("customProviders")
6871
7010
  );
6872
- const { startHub } = await import("./hub-IR4INXSU.js");
7011
+ const { startHub } = await import("./hub-B7NJSCWF.js");
6873
7012
  await startHub(
6874
7013
  {
6875
7014
  topic: topic ?? "",
@@ -2,8 +2,8 @@
2
2
  import {
3
3
  executeTests,
4
4
  runTestsTool
5
- } from "./chunk-V3NMERIB.js";
6
- import "./chunk-VGXNE37B.js";
5
+ } from "./chunk-EEEAFWNK.js";
6
+ import "./chunk-WPQ4D6T3.js";
7
7
  export {
8
8
  executeTests,
9
9
  runTestsTool
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  executeTests,
3
3
  runTestsTool
4
- } from "./chunk-P6EQZKKG.js";
4
+ } from "./chunk-BJXGZFE6.js";
5
5
  export {
6
6
  executeTests,
7
7
  runTestsTool