jinzd-ai-cli 0.4.89 → 0.4.91

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/index.js CHANGED
@@ -30,10 +30,10 @@ import {
30
30
  saveDevState,
31
31
  sessionHasMeaningfulContent,
32
32
  setupProxy
33
- } from "./chunk-FVRLRIKC.js";
33
+ } from "./chunk-MYQANQ6F.js";
34
34
  import {
35
35
  ConfigManager
36
- } from "./chunk-3O3U3L5W.js";
36
+ } from "./chunk-US6MQO6W.js";
37
37
  import {
38
38
  ToolExecutor,
39
39
  ToolRegistry,
@@ -49,7 +49,7 @@ import {
49
49
  spawnAgentContext,
50
50
  theme,
51
51
  undoStack
52
- } from "./chunk-ABPT6XCI.js";
52
+ } from "./chunk-Z2V6EYPQ.js";
53
53
  import "./chunk-2ZD3YTVM.js";
54
54
  import {
55
55
  fileCheckpoints
@@ -67,7 +67,7 @@ import "./chunk-KJLJPUY2.js";
67
67
  import "./chunk-6VRJGH25.js";
68
68
  import "./chunk-2DXY7UGF.js";
69
69
  import "./chunk-KHYD3WXE.js";
70
- import "./chunk-TKYNTXKB.js";
70
+ import "./chunk-IGNYJUZU.js";
71
71
  import {
72
72
  AGENTIC_BEHAVIOR_GUIDELINE,
73
73
  AUTHOR,
@@ -89,7 +89,7 @@ import {
89
89
  SKILLS_DIR_NAME,
90
90
  VERSION,
91
91
  buildUserIdentityPrompt
92
- } from "./chunk-E7YC4GWV.js";
92
+ } from "./chunk-AKCXRW2Q.js";
93
93
 
94
94
  // src/index.ts
95
95
  import { program } from "commander";
@@ -227,7 +227,7 @@ var Renderer = class {
227
227
  console.log(theme.dim(" Gemini (Google) \xB7 Zhipu (GLM) \xB7 OpenRouter \xB7 Ollama (Local, no API key)"));
228
228
  console.log(HR);
229
229
  const mcpToolCount = mcpInfo?.tools ?? 0;
230
- const toolTotal = 28 + pluginCount + mcpToolCount;
230
+ const toolTotal = 29 + pluginCount + mcpToolCount;
231
231
  const extras = [];
232
232
  if (pluginCount > 0) extras.push(`${pluginCount} plugin(s)`);
233
233
  if (mcpToolCount > 0) extras.push(`${mcpToolCount} MCP`);
@@ -261,14 +261,15 @@ var Renderer = class {
261
261
  console.log(tool("get_outline", "Enumerate all top-level declarations in one source file"));
262
262
  console.log(tool("find_references", "Search indexed files for references to a symbol name"));
263
263
  console.log(tool("search_code", 'Semantic (meaning-based) code search via local embeddings \u2014 "grep by meaning", bilingual'));
264
+ console.log(tool("recall_memory", 'Semantic recall over past chat sessions \u2014 "remember when we discussed X" across history (v0.4.89+)'));
264
265
  console.log(HR);
265
- console.log(theme.dim(" REPL Commands (42):"));
266
+ console.log(theme.dim(" REPL Commands (43):"));
266
267
  console.log(theme.dim(" /help /about /provider /model /clear /compact /plan /session"));
267
268
  console.log(theme.dim(" /system /context /status /search /undo /export /copy /paste"));
268
269
  console.log(theme.dim(" /cost /init /skill /tools /plugins /mcp /config /checkpoint"));
269
- console.log(theme.dim(" /review /security-review /rewind /commands /test /scaffold"));
270
- console.log(theme.dim(" /add-dir /memory /profile /doctor /bug /think /diff /fork"));
271
- console.log(theme.dim(" /branch /index /yolo /exit"));
270
+ console.log(theme.dim(" /review /security-review /security /rewind /commands /test"));
271
+ console.log(theme.dim(" /scaffold /add-dir /memory /profile /doctor /bug /think"));
272
+ console.log(theme.dim(" /diff /fork /branch /index /yolo /exit"));
272
273
  console.log(HR);
273
274
  console.log(theme.dim(" Key Features:"));
274
275
  console.log(feat("Agentic loop (up to 200 tool-call rounds, configurable via config/CLI, final answer streamed)"));
@@ -338,6 +339,12 @@ var Renderer = class {
338
339
  console.log(feat("Anthropic Batches API (A3, v0.4.73+): aicli batch submit/list/status/results/cancel \u2014 50% off, 24h window"));
339
340
  console.log(feat("Session Replay (B1, v0.4.71+): Web UI \u{1F3AC} button \u2014 timeline view of every message, tool call, reasoning, and cache-aware token usage"));
340
341
  console.log(feat("Conversation Branching (B2, v0.4.74+): /branch list/new/switch/delete/rename \u2014 fork the conversation at any message; Web UI replay \u{1F33F} fork-here button"));
342
+ console.log(feat("Branch tree sidebar (B2 polish, v0.4.75+): Web UI \u{1F33F} Branches tab with tree-indented picker, click to switch, hover to rename/delete, + Fork button"));
343
+ console.log(feat("Cross-branch ops (B3, v0.4.80+): /branch diff and /branch cherry-pick \u2014 compare branches and pick messages across forks; v0.4.81 accepts id/title/prefix"));
344
+ console.log(feat("MCP Server mode (E1, v0.4.84+): aicli mcp-serve \u2014 reverse aicli into an MCP server so Claude Desktop/Cursor/any MCP client can use its 26 built-in tools (including find_symbol/search_code)"));
345
+ console.log(feat("Session sensitive-data redaction (v0.4.88+): unified redactor scrubs passwords/tokens/keys from every message before it hits disk; /security status and scan to audit"));
346
+ console.log(feat('Chat memory recall (B4, v0.4.89+): "human-like long-term memory" \u2014 semantic index over all past sessions + recall_memory AI tool + /memory rebuild|refresh|status|recall, AI auto-recalls on "last time"/"\u4E0A\u6B21"'));
347
+ console.log(feat("Web UI Memory panel (B4, v0.4.90+): \u{1F9E0} Memory sidebar tab \u2014 cross-session semantic search with \u2795 Inject-to-input (quotes hit into chat box for user-reviewed recall) and \u2197 jump-to-session"));
341
348
  console.log();
342
349
  }
343
350
  printPrompt(provider, _model) {
@@ -982,7 +989,7 @@ function createDefaultCommands() {
982
989
  " /scaffold <description> - Generate project scaffolding with AI",
983
990
  " /add-dir [path|remove] - Add/remove a directory from AI context",
984
991
  " /profile [show|set|clear] - View or edit your identity (AI knows who you are)",
985
- " /memory [show|add|clear] - View or edit persistent memory (memory.md)",
992
+ " /memory [show|add|clear|path|rebuild|refresh|status|recall <q>|index-clear] - Persistent memory + chat memory recall index (v0.4.89+)",
986
993
  " /doctor - Health check (API keys, config, MCP status)",
987
994
  " /bug [--copy] - Generate bug report template (--copy to clipboard)",
988
995
  " /diff [--stats] - Show all file modifications in this session",
@@ -2602,7 +2609,7 @@ ${hint}` : "")
2602
2609
  usage: "/test [command|filter]",
2603
2610
  async execute(args, ctx) {
2604
2611
  try {
2605
- const { executeTests } = await import("./run-tests-5TO5G3YH.js");
2612
+ const { executeTests } = await import("./run-tests-MKBVRMBA.js");
2606
2613
  const argStr = args.join(" ").trim();
2607
2614
  let testArgs = {};
2608
2615
  if (argStr) {
@@ -6715,7 +6722,7 @@ program.command("web").description("Start Web UI server with browser-based chat
6715
6722
  console.error("Error: Invalid port number. Must be between 1 and 65535.");
6716
6723
  process.exit(1);
6717
6724
  }
6718
- const { startWebServer } = await import("./server-NG7AEAD5.js");
6725
+ const { startWebServer } = await import("./server-J7BPHI7D.js");
6719
6726
  await startWebServer({ port, host: options.host });
6720
6727
  });
6721
6728
  program.command("user [action] [username]").description("Manage Web UI users (list | create <name> | delete <name> | reset-password <name> | migrate <name>)").action(async (action, username) => {
@@ -6838,7 +6845,7 @@ program.command("sessions").description("List recent conversation sessions").act
6838
6845
  });
6839
6846
  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) => {
6840
6847
  try {
6841
- const batch = await import("./batch-UMQYXVKG.js");
6848
+ const batch = await import("./batch-KZQHKPE5.js");
6842
6849
  switch (action) {
6843
6850
  case "submit":
6844
6851
  if (!arg) {
@@ -6881,7 +6888,7 @@ program.command("batch <action> [arg] [arg2]").description("Anthropic Message Ba
6881
6888
  }
6882
6889
  });
6883
6890
  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) => {
6884
- const { startMcpServer } = await import("./server-U2BBLP4Y.js");
6891
+ const { startMcpServer } = await import("./server-B6U5GMZ7.js");
6885
6892
  await startMcpServer({
6886
6893
  allowDestructive: !!options.allowDestructive,
6887
6894
  allowOutsideCwd: !!options.allowOutsideCwd,
@@ -7008,7 +7015,7 @@ program.command("hub [topic]").description("Start multi-agent hub (discuss / bra
7008
7015
  }),
7009
7016
  config.get("customProviders")
7010
7017
  );
7011
- const { startHub } = await import("./hub-4P2BH57W.js");
7018
+ const { startHub } = await import("./hub-A66CLTFF.js");
7012
7019
  await startHub(
7013
7020
  {
7014
7021
  topic: topic ?? "",
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  executeTests,
3
3
  runTestsTool
4
- } from "./chunk-4WVXTADR.js";
4
+ } from "./chunk-XQHCCV3A.js";
5
5
  export {
6
6
  executeTests,
7
7
  runTestsTool
@@ -2,8 +2,8 @@
2
2
  import {
3
3
  executeTests,
4
4
  runTestsTool
5
- } from "./chunk-TKYNTXKB.js";
6
- import "./chunk-E7YC4GWV.js";
5
+ } from "./chunk-IGNYJUZU.js";
6
+ import "./chunk-AKCXRW2Q.js";
7
7
  export {
8
8
  executeTests,
9
9
  runTestsTool
@@ -4,9 +4,10 @@ import {
4
4
  pathTokens,
5
5
  rebuildSemanticIndex,
6
6
  semanticSearch
7
- } from "./chunk-GTKJUEBS.js";
7
+ } from "./chunk-MO7MWNWC.js";
8
8
  import "./chunk-BJAT4GNC.js";
9
- import "./chunk-XMA222FQ.js";
9
+ import "./chunk-PASCDYMH.js";
10
+ import "./chunk-JV5N65KN.js";
10
11
  export {
11
12
  buildEmbeddingText,
12
13
  hasSemanticIndex,
@@ -3,7 +3,7 @@ import {
3
3
  ToolRegistry,
4
4
  getDangerLevel,
5
5
  schemaToJsonSchema
6
- } from "./chunk-ABPT6XCI.js";
6
+ } from "./chunk-Z2V6EYPQ.js";
7
7
  import "./chunk-2ZD3YTVM.js";
8
8
  import "./chunk-4BKXL7SM.js";
9
9
  import "./chunk-ANYYM4CF.js";
@@ -12,10 +12,10 @@ import "./chunk-KJLJPUY2.js";
12
12
  import "./chunk-6VRJGH25.js";
13
13
  import "./chunk-2DXY7UGF.js";
14
14
  import "./chunk-KHYD3WXE.js";
15
- import "./chunk-TKYNTXKB.js";
15
+ import "./chunk-IGNYJUZU.js";
16
16
  import {
17
17
  VERSION
18
- } from "./chunk-E7YC4GWV.js";
18
+ } from "./chunk-AKCXRW2Q.js";
19
19
 
20
20
  // src/mcp/server.ts
21
21
  import { createInterface } from "readline";
@@ -23,10 +23,10 @@ import {
23
23
  persistToolRound,
24
24
  rebuildExtraMessages,
25
25
  setupProxy
26
- } from "./chunk-FVRLRIKC.js";
26
+ } from "./chunk-MYQANQ6F.js";
27
27
  import {
28
28
  ConfigManager
29
- } from "./chunk-3O3U3L5W.js";
29
+ } from "./chunk-US6MQO6W.js";
30
30
  import {
31
31
  ToolExecutor,
32
32
  ToolRegistry,
@@ -44,7 +44,7 @@ import {
44
44
  spawnAgentContext,
45
45
  truncateOutput,
46
46
  undoStack
47
- } from "./chunk-ABPT6XCI.js";
47
+ } from "./chunk-Z2V6EYPQ.js";
48
48
  import "./chunk-2ZD3YTVM.js";
49
49
  import "./chunk-4BKXL7SM.js";
50
50
  import "./chunk-ANYYM4CF.js";
@@ -53,7 +53,7 @@ import "./chunk-KJLJPUY2.js";
53
53
  import "./chunk-6VRJGH25.js";
54
54
  import "./chunk-2DXY7UGF.js";
55
55
  import "./chunk-KHYD3WXE.js";
56
- import "./chunk-TKYNTXKB.js";
56
+ import "./chunk-IGNYJUZU.js";
57
57
  import {
58
58
  AGENTIC_BEHAVIOR_GUIDELINE,
59
59
  AUTHOR,
@@ -72,7 +72,7 @@ import {
72
72
  SKILLS_DIR_NAME,
73
73
  VERSION,
74
74
  buildUserIdentityPrompt
75
- } from "./chunk-E7YC4GWV.js";
75
+ } from "./chunk-AKCXRW2Q.js";
76
76
 
77
77
  // src/web/server.ts
78
78
  import express from "express";
@@ -622,6 +622,12 @@ var SessionHandler = class _SessionHandler {
622
622
  }
623
623
  return;
624
624
  }
625
+ case "memory_search":
626
+ return this.handleMemorySearch(msg.query, msg.topK, msg.minScore, msg.excludeCurrentSession);
627
+ case "memory_status_request":
628
+ return this.handleMemoryStatus();
629
+ case "memory_rebuild":
630
+ return this.handleMemoryRebuild(Boolean(msg.full));
625
631
  case "auto_pause_response": {
626
632
  const resolve3 = this.pendingAutoPause.get(msg.requestId);
627
633
  if (resolve3) {
@@ -2231,7 +2237,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
2231
2237
  case "test": {
2232
2238
  this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
2233
2239
  try {
2234
- const { executeTests } = await import("./run-tests-5TO5G3YH.js");
2240
+ const { executeTests } = await import("./run-tests-MKBVRMBA.js");
2235
2241
  const argStr = args.join(" ").trim();
2236
2242
  let testArgs = {};
2237
2243
  if (argStr) {
@@ -2745,6 +2751,106 @@ Add .md files to create commands.` });
2745
2751
  this.send({ type: "error", message: `Failed to clear memory: ${err.message}` });
2746
2752
  }
2747
2753
  }
2754
+ // ── B4 chat memory recall (v0.4.90+) ──────────────────────────────
2755
+ // Lazy-imported so the 117 MB embedder stays out of the load path for
2756
+ // clients that never open the Memory panel.
2757
+ async handleMemorySearch(query, topK, minScore, excludeCurrent) {
2758
+ const q = (query ?? "").trim();
2759
+ if (!q) {
2760
+ this.send({ type: "error", message: "Memory search: query is empty." });
2761
+ return;
2762
+ }
2763
+ try {
2764
+ const { searchChatMemory, loadChatIndex } = await import("./chat-index-W2UZ34ZI.js");
2765
+ const loaded = loadChatIndex();
2766
+ if (!loaded || loaded.idx.chunks.length === 0) {
2767
+ this.send({ type: "memory_hits", query: q, hits: [], indexMissing: true });
2768
+ return;
2769
+ }
2770
+ const hits = await searchChatMemory(q, {
2771
+ topK: topK ?? 8,
2772
+ minScore: minScore ?? 0.2,
2773
+ excludeSessionId: excludeCurrent ? this.sessions.current?.id : void 0
2774
+ });
2775
+ this.send({
2776
+ type: "memory_hits",
2777
+ query: q,
2778
+ hits: hits.map((h) => ({
2779
+ id: h.chunk.id,
2780
+ sessionId: h.chunk.sessionId,
2781
+ sessionTitle: h.chunk.sessionTitle,
2782
+ provider: h.chunk.provider,
2783
+ model: h.chunk.model,
2784
+ timestamp: h.chunk.timestamp,
2785
+ score: h.score,
2786
+ text: h.chunk.text,
2787
+ startMessageIdx: h.chunk.startMessageIdx,
2788
+ endMessageIdx: h.chunk.endMessageIdx
2789
+ }))
2790
+ });
2791
+ } catch (err) {
2792
+ this.send({
2793
+ type: "error",
2794
+ message: `Memory search failed: ${err instanceof Error ? err.message : String(err)}`
2795
+ });
2796
+ }
2797
+ }
2798
+ async handleMemoryStatus() {
2799
+ try {
2800
+ const { getChatIndexStatus } = await import("./chat-index-W2UZ34ZI.js");
2801
+ const s = getChatIndexStatus();
2802
+ this.send({
2803
+ type: "memory_status",
2804
+ exists: s.exists,
2805
+ chunks: s.chunks,
2806
+ sessions: s.sessions,
2807
+ built: s.built,
2808
+ model: s.model,
2809
+ vecFileSizeBytes: s.vecFileSizeBytes,
2810
+ chunksFileSizeBytes: s.chunksFileSizeBytes
2811
+ });
2812
+ } catch (err) {
2813
+ this.send({
2814
+ type: "error",
2815
+ message: `Memory status failed: ${err instanceof Error ? err.message : String(err)}`
2816
+ });
2817
+ }
2818
+ }
2819
+ async handleMemoryRebuild(full) {
2820
+ try {
2821
+ this.send({
2822
+ type: "info",
2823
+ 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"
2824
+ });
2825
+ const { buildChatIndex } = await import("./chat-index-W2UZ34ZI.js");
2826
+ const stats = await buildChatIndex({
2827
+ full,
2828
+ onProgress: (p) => {
2829
+ this.send({
2830
+ type: "memory_rebuild_progress",
2831
+ stage: p.stage,
2832
+ processed: p.processed,
2833
+ total: p.total
2834
+ });
2835
+ }
2836
+ });
2837
+ this.send({
2838
+ type: "memory_rebuild_done",
2839
+ chunksTotal: stats.chunksTotal,
2840
+ chunksAdded: stats.chunksAdded,
2841
+ chunksRemoved: stats.chunksRemoved,
2842
+ sessionsIndexed: stats.sessionsIndexed,
2843
+ sessionsSkipped: stats.sessionsSkipped,
2844
+ durationMs: stats.durationMs
2845
+ });
2846
+ await this.handleMemoryStatus();
2847
+ } catch (err) {
2848
+ this.send({
2849
+ type: "error",
2850
+ message: `Memory rebuild failed: ${err instanceof Error ? err.message : String(err)}`
2851
+ });
2852
+ }
2853
+ }
2748
2854
  sendSessionMessages() {
2749
2855
  const session = this.sessions.current;
2750
2856
  if (!session) return;
@@ -4,7 +4,7 @@ import {
4
4
  getDangerLevel,
5
5
  googleSearchContext,
6
6
  truncateOutput
7
- } from "./chunk-ABPT6XCI.js";
7
+ } from "./chunk-Z2V6EYPQ.js";
8
8
  import "./chunk-2ZD3YTVM.js";
9
9
  import "./chunk-4BKXL7SM.js";
10
10
  import "./chunk-ANYYM4CF.js";
@@ -13,10 +13,10 @@ import "./chunk-KJLJPUY2.js";
13
13
  import "./chunk-6VRJGH25.js";
14
14
  import "./chunk-2DXY7UGF.js";
15
15
  import "./chunk-KHYD3WXE.js";
16
- import "./chunk-TKYNTXKB.js";
16
+ import "./chunk-IGNYJUZU.js";
17
17
  import {
18
18
  SUBAGENT_ALLOWED_TOOLS
19
- } from "./chunk-E7YC4GWV.js";
19
+ } from "./chunk-AKCXRW2Q.js";
20
20
 
21
21
  // src/hub/task-orchestrator.ts
22
22
  import { createInterface } from "readline";
@@ -4,7 +4,8 @@ import {
4
4
  loadVectorStore,
5
5
  saveVectorStore,
6
6
  searchVectorStore
7
- } from "./chunk-XMA222FQ.js";
7
+ } from "./chunk-PASCDYMH.js";
8
+ import "./chunk-JV5N65KN.js";
8
9
  export {
9
10
  clearVectorStore,
10
11
  emptyVectorStore,
@@ -187,6 +187,10 @@ function handleServerMessage(msg) {
187
187
  case 'tools_list': renderToolsList(msg); switchSidebarTab('tools'); break;
188
188
  case 'export_data': handleExportData(msg); break;
189
189
  case 'memory_content': handleMemoryContent(msg); break;
190
+ case 'memory_hits': renderMemoryHits(msg); break;
191
+ case 'memory_status': renderMemoryStatus(msg); break;
192
+ case 'memory_rebuild_progress': renderMemoryRebuildProgress(msg); break;
193
+ case 'memory_rebuild_done': renderMemoryRebuildDone(msg); break;
190
194
  case 'auth_required': showAuthScreen(msg.hasUsers); break;
191
195
  case 'auth_result': handleAuthResult(msg); break;
192
196
  case 'info': addInfoMessage(msg.message); break;
@@ -2869,3 +2873,200 @@ function showEnableAuthDialog() {
2869
2873
  })
2870
2874
  .catch(err => alert('Failed: ' + err.message));
2871
2875
  }
2876
+
2877
+ // ── B4 Chat Memory recall panel (v0.4.90+) ────────────────────────────
2878
+ // UI for the semantic index built from all past sessions. The tab is
2879
+ // opt-in: we only request a status refresh + wire handlers when the
2880
+ // user actually clicks 🧠 Memory, so unused sessions pay zero cost.
2881
+
2882
+ let memoryPanelInit = false;
2883
+ let memoryStatusCache = null;
2884
+
2885
+ function initMemoryPanel() {
2886
+ if (memoryPanelInit) return;
2887
+ memoryPanelInit = true;
2888
+
2889
+ const input = document.getElementById('memory-search-input');
2890
+ const btnSearch = document.getElementById('btn-memory-search');
2891
+ const btnRefresh = document.getElementById('btn-memory-refresh');
2892
+ const btnRebuild = document.getElementById('btn-memory-rebuild');
2893
+
2894
+ const runSearch = () => {
2895
+ const q = (input?.value || '').trim();
2896
+ if (!q) return;
2897
+ const excl = document.getElementById('memory-exclude-current')?.checked;
2898
+ const hitsEl = document.getElementById('memory-hits');
2899
+ if (hitsEl) {
2900
+ hitsEl.innerHTML = '<div class="text-xs opacity-40 text-center py-4">Searching…</div>';
2901
+ }
2902
+ send({
2903
+ type: 'memory_search',
2904
+ query: q,
2905
+ topK: 8,
2906
+ minScore: 0.2,
2907
+ excludeCurrentSession: !!excl,
2908
+ });
2909
+ };
2910
+
2911
+ btnSearch?.addEventListener('click', runSearch);
2912
+ input?.addEventListener('keydown', (e) => {
2913
+ if (e.key === 'Enter') {
2914
+ e.preventDefault();
2915
+ runSearch();
2916
+ }
2917
+ });
2918
+
2919
+ btnRefresh?.addEventListener('click', () => {
2920
+ send({ type: 'memory_rebuild', full: false });
2921
+ });
2922
+ btnRebuild?.addEventListener('click', () => {
2923
+ if (!confirm('Full rebuild re-embeds every chat chunk. On first run this downloads ~117 MB and can take minutes. Continue?')) return;
2924
+ send({ type: 'memory_rebuild', full: true });
2925
+ });
2926
+
2927
+ send({ type: 'memory_status_request' });
2928
+ }
2929
+
2930
+ function renderMemoryStatus(msg) {
2931
+ memoryStatusCache = msg;
2932
+ const el = document.getElementById('memory-status');
2933
+ if (!el) return;
2934
+ if (!msg.exists) {
2935
+ el.textContent = 'Index not built — click ⟳ Rebuild to create it.';
2936
+ el.className = 'text-xs opacity-70 truncate';
2937
+ return;
2938
+ }
2939
+ const built = msg.built ? msg.built.replace('T', ' ').slice(0, 16) : '-';
2940
+ const mb = ((msg.vecFileSizeBytes + msg.chunksFileSizeBytes) / 1024 / 1024).toFixed(2);
2941
+ el.textContent = `${msg.chunks} chunks · ${msg.sessions} sessions · ${mb} MB · built ${built}`;
2942
+ el.className = 'text-xs opacity-50 truncate';
2943
+ el.title = `model: ${msg.model || '-'}`;
2944
+ }
2945
+
2946
+ function renderMemoryHits(msg) {
2947
+ const el = document.getElementById('memory-hits');
2948
+ if (!el) return;
2949
+ if (msg.indexMissing) {
2950
+ el.innerHTML = '<div class="text-xs opacity-60 text-center py-4">Chat index is empty — click ⟳ Rebuild first.</div>';
2951
+ return;
2952
+ }
2953
+ if (!msg.hits || msg.hits.length === 0) {
2954
+ el.innerHTML = `<div class="text-xs opacity-60 text-center py-4">No memories matched "${escapeHtml(msg.query)}".<br>Try a broader phrase or lower minScore.</div>`;
2955
+ return;
2956
+ }
2957
+ // Cache hit texts on the container so inject buttons can find them by id
2958
+ // without us having to embed full-text in data-* attrs (would bloat DOM).
2959
+ memoryHitsCache = new Map();
2960
+ for (const h of msg.hits) memoryHitsCache.set(h.id, h);
2961
+
2962
+ // Top toolbar: inject top-3 / clear results.
2963
+ const header = `
2964
+ <div class="flex items-center gap-1 text-xs pb-1 mb-1 border-b border-base-content/10">
2965
+ <span class="opacity-60 flex-1">${msg.hits.length} hit(s) for "${escapeHtml(msg.query)}"</span>
2966
+ <button class="btn btn-xs btn-primary btn-outline" id="btn-memory-inject-top3" title="Inject top 3 hits into chat input as a quoted context block">➕ Inject top 3</button>
2967
+ </div>
2968
+ `;
2969
+
2970
+ const items = msg.hits.map((h) => {
2971
+ const ts = (h.timestamp || '').replace('T', ' ').slice(0, 16);
2972
+ const title = h.sessionTitle || h.sessionId.slice(0, 8);
2973
+ const score = typeof h.score === 'number' ? h.score.toFixed(3) : '-';
2974
+ const preview = h.text.length > 500 ? h.text.slice(0, 500) + '…' : h.text;
2975
+ return `
2976
+ <div class="memory-hit card bg-base-100 border border-base-content/10 p-2 hover:border-primary/40"
2977
+ data-hit-id="${escapeHtml(h.id)}">
2978
+ <div class="flex items-center gap-1 text-xs opacity-70 mb-1">
2979
+ <span class="font-semibold truncate flex-1">${escapeHtml(title)}</span>
2980
+ <span class="badge badge-xs">${score}</span>
2981
+ </div>
2982
+ <div class="text-xs opacity-50 mb-1 flex items-center gap-2">
2983
+ <span class="flex-1">${ts} · session ${escapeHtml(h.sessionId.slice(0, 8))}</span>
2984
+ <button class="btn btn-xs btn-ghost memory-hit-inject" title="Inject this hit into chat input">➕</button>
2985
+ <button class="btn btn-xs btn-ghost memory-hit-load" title="Load this session in current tab">↗</button>
2986
+ </div>
2987
+ <div class="text-xs whitespace-pre-wrap leading-snug opacity-90" style="max-height: 8em; overflow: hidden;">${escapeHtml(preview)}</div>
2988
+ </div>
2989
+ `;
2990
+ }).join('');
2991
+ el.innerHTML = header + items;
2992
+
2993
+ document.getElementById('btn-memory-inject-top3')?.addEventListener('click', (e) => {
2994
+ e.stopPropagation();
2995
+ injectMemoryHits(msg.hits.slice(0, 3));
2996
+ });
2997
+ el.querySelectorAll('.memory-hit').forEach((card) => {
2998
+ const hitId = card.getAttribute('data-hit-id');
2999
+ const h = hitId ? memoryHitsCache.get(hitId) : null;
3000
+ if (!h) return;
3001
+ card.querySelector('.memory-hit-inject')?.addEventListener('click', (e) => {
3002
+ e.stopPropagation();
3003
+ injectMemoryHits([h]);
3004
+ });
3005
+ card.querySelector('.memory-hit-load')?.addEventListener('click', (e) => {
3006
+ e.stopPropagation();
3007
+ loadSessionInTab(h.sessionId, h.sessionTitle || '');
3008
+ });
3009
+ });
3010
+ }
3011
+
3012
+ /**
3013
+ * Inject one or more memory hits into the chat input as a quoted context
3014
+ * block. User can then edit or add their question before sending, giving
3015
+ * them full control over what lands in the AI's context (vs automatic
3016
+ * injection which is surprising).
3017
+ */
3018
+ function injectMemoryHits(hits) {
3019
+ if (!hits || hits.length === 0) return;
3020
+ const lines = ['> 🧠 Recalled from past sessions:', '>'];
3021
+ for (const h of hits) {
3022
+ const ts = (h.timestamp || '').replace('T', ' ').slice(0, 16);
3023
+ const title = h.sessionTitle || h.sessionId.slice(0, 8);
3024
+ const body = h.text.length > 800 ? h.text.slice(0, 800) + '…' : h.text;
3025
+ lines.push(`> **${title}** · ${ts}`);
3026
+ for (const ln of body.split('\n')) lines.push(`> ${ln}`);
3027
+ lines.push('>');
3028
+ }
3029
+ lines.push('');
3030
+ const block = lines.join('\n');
3031
+ const existing = userInput?.value || '';
3032
+ if (userInput) {
3033
+ // Prepend — user's in-progress question stays after the quoted block.
3034
+ userInput.value = block + existing;
3035
+ // Fire input event so the auto-resize textarea adapts its height.
3036
+ userInput.dispatchEvent(new Event('input', { bubbles: true }));
3037
+ userInput.focus();
3038
+ // Move caret to end so user can keep typing.
3039
+ const len = userInput.value.length;
3040
+ try { userInput.setSelectionRange(len, len); } catch { /* ignore */ }
3041
+ }
3042
+ addInfoMessage(`🧠 Injected ${hits.length} memory hit(s) into input. Edit and send when ready.`);
3043
+ }
3044
+
3045
+ let memoryHitsCache = new Map();
3046
+
3047
+ function renderMemoryRebuildProgress(msg) {
3048
+ const el = document.getElementById('memory-status');
3049
+ if (!el) return;
3050
+ if (msg.stage === 'embedding' && msg.total) {
3051
+ el.textContent = `Embedding ${msg.processed || 0} / ${msg.total} chunks…`;
3052
+ } else if (msg.stage !== 'done') {
3053
+ el.textContent = `${msg.stage}…`;
3054
+ }
3055
+ el.className = 'text-xs opacity-70 truncate';
3056
+ }
3057
+
3058
+ function renderMemoryRebuildDone(msg) {
3059
+ const el = document.getElementById('memory-status');
3060
+ if (el) {
3061
+ el.textContent = `✓ ${msg.chunksTotal} chunks (+${msg.chunksAdded} / −${msg.chunksRemoved}) · ${msg.sessionsIndexed} re-indexed · ${(msg.durationMs / 1000).toFixed(1)}s`;
3062
+ el.className = 'text-xs opacity-70 truncate';
3063
+ }
3064
+ }
3065
+
3066
+ // Lazy-init memory panel on first click of the 🧠 Memory sidebar tab.
3067
+ // switchSidebarTab already toggles visibility — we just need to bootstrap.
3068
+ document.querySelectorAll('.sidebar-tab').forEach(btn => {
3069
+ if (btn.dataset.tab === 'memory') {
3070
+ btn.addEventListener('click', () => initMemoryPanel());
3071
+ }
3072
+ });
@@ -108,6 +108,7 @@
108
108
  <button class="sidebar-tab flex-1 text-xs font-semibold py-2 px-1 text-center" data-tab="branches" title="Conversation branches (B2)">🌿 Branches</button>
109
109
  <button class="sidebar-tab flex-1 text-xs font-semibold py-2 px-1 text-center" data-tab="tools">🔧 Tools</button>
110
110
  <button class="sidebar-tab flex-1 text-xs font-semibold py-2 px-1 text-center" data-tab="files">📁 Files</button>
111
+ <button class="sidebar-tab flex-1 text-xs font-semibold py-2 px-1 text-center" data-tab="memory" title="Chat memory semantic recall (B4, v0.4.90)">🧠 Memory</button>
111
112
  </div>
112
113
  <!-- Sessions tab -->
113
114
  <div id="tab-sessions" class="sidebar-tab-content flex flex-col flex-1 overflow-hidden">
@@ -158,6 +159,29 @@
158
159
  <div class="text-xs opacity-40 text-center py-4">Click tab to load</div>
159
160
  </div>
160
161
  </div>
162
+
163
+ <!-- Memory tab (B4, v0.4.90+) -->
164
+ <div id="tab-memory" class="sidebar-tab-content flex flex-col flex-1 overflow-hidden hidden">
165
+ <div class="p-2 border-b border-base-content/10 flex flex-col gap-1">
166
+ <div class="flex items-center gap-1">
167
+ <input id="memory-search-input" type="text" class="input input-xs input-bordered flex-1 min-w-0" placeholder="Search past chats (semantic)…">
168
+ <button id="btn-memory-search" class="btn btn-xs btn-primary btn-outline flex-shrink-0" title="Search">🔍</button>
169
+ </div>
170
+ <div class="flex items-center gap-1 text-xs">
171
+ <label class="flex items-center gap-1 opacity-70 cursor-pointer">
172
+ <input id="memory-exclude-current" type="checkbox" class="checkbox checkbox-xs">
173
+ <span>exclude current</span>
174
+ </label>
175
+ <span class="flex-1"></span>
176
+ <button id="btn-memory-refresh" class="btn btn-xs btn-ghost" title="Incremental refresh">↻</button>
177
+ <button id="btn-memory-rebuild" class="btn btn-xs btn-ghost" title="Full rebuild (slow first run)">⟳ Rebuild</button>
178
+ </div>
179
+ <div id="memory-status" class="text-xs opacity-50 truncate">Loading index status…</div>
180
+ </div>
181
+ <div id="memory-hits" class="flex-1 overflow-y-auto p-2 flex flex-col gap-2 text-sm">
182
+ <div class="text-xs opacity-40 text-center py-4">Type a query above to recall past chats.</div>
183
+ </div>
184
+ </div>
161
185
  </aside>
162
186
  <!-- Sidebar resize handle -->
163
187
  <div id="sidebar-resize" class="sidebar-resize-handle" title="Drag to resize sidebar"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jinzd-ai-cli",
3
- "version": "0.4.89",
3
+ "version": "0.4.91",
4
4
  "description": "Cross-platform REPL-style AI CLI with multi-provider support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",