jinzd-ai-cli 0.4.241 → 0.4.243

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 (42) hide show
  1. package/README.md +2 -2
  2. package/dist/{batch-K66BLTLJ.js → batch-CUVZUPRK.js} +2 -2
  3. package/dist/{chat-index-7HXBWQFH.js → chat-index-ALUHFJKI.js} +3 -2
  4. package/dist/{chat-index-WYXYD7YP.js → chat-index-ML5FF5L6.js} +2 -1
  5. package/dist/{chunk-SMQHEJSH.js → chunk-66CE3E6X.js} +1 -1
  6. package/dist/chunk-B5TYJO7V.js +123 -0
  7. package/dist/{chunk-PGHTAKIA.js → chunk-DJGCG7SF.js} +1 -1
  8. package/dist/{chunk-2QQKS56X.js → chunk-DT77O33T.js} +1 -1
  9. package/dist/{chunk-2LLQOMMW.js → chunk-EMFCHE2B.js} +1 -1
  10. package/dist/chunk-GEWPLNKA.js +164 -0
  11. package/dist/{chunk-LXSUPLCK.js → chunk-H3ARWBYR.js} +2 -2
  12. package/dist/{chunk-3AIR7N5M.js → chunk-HNJX3N2V.js} +5 -5
  13. package/dist/{chunk-5MYPIQ3Y.js → chunk-IGPFYNZA.js} +4 -2
  14. package/dist/{chunk-GORPFFV4.js → chunk-KILKF2VN.js} +97 -254
  15. package/dist/{chunk-JMP3LJC2.js → chunk-KWQKQVPH.js} +4 -2
  16. package/dist/{chunk-NAQPP3GZ.js → chunk-L5IRDRWG.js} +1 -1
  17. package/dist/{chunk-UO3XWZFE.js → chunk-NV63EYFL.js} +3 -119
  18. package/dist/{chunk-KCEO2XJ4.js → chunk-PJ3ONBWZ.js} +0 -119
  19. package/dist/{chunk-ZPXMT2B6.js → chunk-PLEXBFCR.js} +4 -163
  20. package/dist/chunk-PNNZRJ67.js +523 -0
  21. package/dist/{chunk-CHFFEVVT.js → chunk-QNK7GUUB.js} +3 -3
  22. package/dist/chunk-TY4FSEZS.js +432 -0
  23. package/dist/{chunk-IV6GCDVR.js → chunk-X73Y4JZI.js} +7 -1
  24. package/dist/chunk-YVTASHS5.js +121 -0
  25. package/dist/{ci-T2ABDMMQ.js → ci-3O3JR6FX.js} +4 -4
  26. package/dist/{ci-format-S2PPU27O.js → ci-format-KGWF6KOU.js} +2 -2
  27. package/dist/{constants-RS7OUU7X.js → constants-NMFRZ5LW.js} +1 -1
  28. package/dist/{doctor-cli-TGAW6RAP.js → doctor-cli-4QUHTJZ4.js} +4 -4
  29. package/dist/electron-server.js +446 -579
  30. package/dist/{hub-GL5ZU6WS.js → hub-2GXFPWOR.js} +2 -2
  31. package/dist/index.js +273 -62
  32. package/dist/{persist-AMH5SQ4W.js → persist-WGIJ6RXY.js} +4 -3
  33. package/dist/persistent-memory-VFBRMKGG.js +65 -0
  34. package/dist/persistent-memory-ZCQKLIC5.js +63 -0
  35. package/dist/{pr-TJFKUZ4B.js → pr-GIDFO4KP.js} +5 -5
  36. package/dist/{run-tests-KEI7IXTB.js → run-tests-BMH6QJN2.js} +2 -2
  37. package/dist/{run-tests-EWMU34A4.js → run-tests-WCZBWQFS.js} +2 -1
  38. package/dist/{server-UVS2D4M6.js → server-A5NGGDMP.js} +130 -47
  39. package/dist/{server-E545UIX7.js → server-FEFIFJDA.js} +8 -6
  40. package/dist/{task-orchestrator-PWXV2EBN.js → task-orchestrator-J2H6OV6R.js} +8 -6
  41. package/dist/{usage-EDJ6QOWB.js → usage-7WJC234G.js} +2 -2
  42. package/package.json +1 -1
@@ -1,4 +1,13 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ addMemoryEntry,
4
+ findSimilarMemory,
5
+ loadMemoryEntries,
6
+ parseWritableScope,
7
+ searchPersistentMemories,
8
+ touchMemoryReferences,
9
+ updateMemoryEntry
10
+ } from "./chunk-TY4FSEZS.js";
2
11
  import {
3
12
  indexProject
4
13
  } from "./chunk-K3CF65QH.js";
@@ -14,7 +23,7 @@ import {
14
23
  runHook,
15
24
  runLifecycleHooks,
16
25
  runTool
17
- } from "./chunk-2LLQOMMW.js";
26
+ } from "./chunk-EMFCHE2B.js";
18
27
  import {
19
28
  getDangerLevel,
20
29
  isFileWriteTool,
@@ -25,33 +34,23 @@ import {
25
34
  NetworkError,
26
35
  ToolError
27
36
  } from "./chunk-5ULLIOVC.js";
28
- import {
29
- getGitRoot
30
- } from "./chunk-HOSJZMQS.js";
31
37
  import {
32
38
  fileCheckpoints
33
39
  } from "./chunk-4BKXL7SM.js";
34
40
  import {
35
41
  runTestsTool
36
- } from "./chunk-NAQPP3GZ.js";
42
+ } from "./chunk-L5IRDRWG.js";
37
43
  import {
38
44
  CONFIG_DIR_NAME,
39
45
  DEFAULT_MAX_TOOL_OUTPUT_CHARS_CAP,
40
- MEMORY_FILE_NAME,
41
- MEMORY_MAX_CHARS,
42
- MEMORY_STORE_FILE_NAME,
43
46
  SUBAGENT_ALLOWED_TOOLS,
44
47
  SUBAGENT_DEFAULT_MAX_ROUNDS,
45
48
  SUBAGENT_MAX_ROUNDS_LIMIT
46
- } from "./chunk-SMQHEJSH.js";
49
+ } from "./chunk-66CE3E6X.js";
47
50
  import {
48
51
  loadChatIndex,
49
52
  searchChatMemory
50
- } from "./chunk-5MYPIQ3Y.js";
51
- import {
52
- DEFAULT_PATTERNS,
53
- redactString
54
- } from "./chunk-KCEO2XJ4.js";
53
+ } from "./chunk-IGPFYNZA.js";
55
54
  import {
56
55
  atomicWriteFileSync
57
56
  } from "./chunk-IW3Q7AE5.js";
@@ -140,7 +139,7 @@ async function execInPool(sessionKey, command, timeout, cwd, abortSignal) {
140
139
  const markerStart = `AICLI_MARKER_START:${id}`;
141
140
  const markerEnd = `AICLI_MARKER_END:${id}`;
142
141
  const wrappedCmd = IS_WINDOWS2 ? `${WIN_UTF8_PREAMBLE} echo "${markerStart}"; ${command}; echo "${markerEnd}:0"` : `echo "${markerStart}"; ${command}; echo "${markerEnd}:$?"`;
143
- return new Promise((resolve8) => {
142
+ return new Promise((resolve7) => {
144
143
  let stdout = "";
145
144
  let stderr = "";
146
145
  let timedOut = false;
@@ -156,7 +155,7 @@ async function execInPool(sessionKey, command, timeout, cwd, abortSignal) {
156
155
  stdout = stripMarkerOutput(stdout, markerStartPattern, markerEndPattern);
157
156
  resolved = true;
158
157
  cleanup();
159
- resolve8({ stdout, stderr, exitCode, timedOut, aborted });
158
+ resolve7({ stdout, stderr, exitCode, timedOut, aborted });
160
159
  }
161
160
  };
162
161
  const onErr = (chunk) => {
@@ -171,7 +170,7 @@ async function execInPool(sessionKey, command, timeout, cwd, abortSignal) {
171
170
  resolved = true;
172
171
  stdout = stripMarkerOutput(stdout, markerStartPattern, markerEndPattern);
173
172
  cleanup();
174
- resolve8({ stdout, stderr, exitCode: null, timedOut, aborted });
173
+ resolve7({ stdout, stderr, exitCode: null, timedOut, aborted });
175
174
  try {
176
175
  proc.kill();
177
176
  } catch {
@@ -184,7 +183,7 @@ async function execInPool(sessionKey, command, timeout, cwd, abortSignal) {
184
183
  resolved = true;
185
184
  cleanup();
186
185
  stdout = stripMarkerOutput(stdout, markerStartPattern, markerEndPattern);
187
- resolve8({ stdout, stderr, exitCode: null, timedOut, aborted });
186
+ resolve7({ stdout, stderr, exitCode: null, timedOut, aborted });
188
187
  try {
189
188
  proc.kill();
190
189
  } catch {
@@ -202,7 +201,7 @@ async function execInPool(sessionKey, command, timeout, cwd, abortSignal) {
202
201
  stderr += `
203
202
  [pool error] ${err.message}`;
204
203
  cleanup();
205
- resolve8({ stdout, stderr, exitCode: null, timedOut, aborted });
204
+ resolve7({ stdout, stderr, exitCode: null, timedOut, aborted });
206
205
  pool.delete(sessionKey);
207
206
  };
208
207
  proc.on("error", onError);
@@ -2540,7 +2539,7 @@ var ToolExecutor = class {
2540
2539
  rl.resume();
2541
2540
  process.stdout.write(prompt);
2542
2541
  this.confirming = true;
2543
- return new Promise((resolve8) => {
2542
+ return new Promise((resolve7) => {
2544
2543
  let completed = false;
2545
2544
  const cleanup = (result) => {
2546
2545
  if (completed) return;
@@ -2550,7 +2549,7 @@ var ToolExecutor = class {
2550
2549
  rl.pause();
2551
2550
  rlAny.output = savedOutput;
2552
2551
  this.confirming = false;
2553
- resolve8(result);
2552
+ resolve7(result);
2554
2553
  };
2555
2554
  const onLine = (line) => {
2556
2555
  const trimmed = line.trim();
@@ -2724,7 +2723,7 @@ var ToolExecutor = class {
2724
2723
  rl.resume();
2725
2724
  process.stdout.write(color("Proceed? [y/N] (type y + Enter to confirm) "));
2726
2725
  this.confirming = true;
2727
- return new Promise((resolve8) => {
2726
+ return new Promise((resolve7) => {
2728
2727
  let completed = false;
2729
2728
  const cleanup = (answer) => {
2730
2729
  if (completed) return;
@@ -2734,7 +2733,7 @@ var ToolExecutor = class {
2734
2733
  rl.pause();
2735
2734
  rlAny.output = savedOutput;
2736
2735
  this.confirming = false;
2737
- resolve8(answer === "y");
2736
+ resolve7(answer === "y");
2738
2737
  };
2739
2738
  const onLine = (line) => {
2740
2739
  const trimmed = line.trim();
@@ -3983,7 +3982,7 @@ var runInteractiveTool = {
3983
3982
  PYTHONDONTWRITEBYTECODE: "1"
3984
3983
  };
3985
3984
  const prefixWarnings = [argsTypeWarning, stdinTypeWarning].filter(Boolean).join("");
3986
- return new Promise((resolve8) => {
3985
+ return new Promise((resolve7) => {
3987
3986
  const child = spawn3(executable, cmdArgs.map(String), {
3988
3987
  cwd: process.cwd(),
3989
3988
  env,
@@ -4016,22 +4015,22 @@ var runInteractiveTool = {
4016
4015
  setTimeout(writeNextLine, 400);
4017
4016
  const timer = setTimeout(() => {
4018
4017
  child.kill();
4019
- resolve8(`${prefixWarnings}[Timeout after ${timeout}ms]
4018
+ resolve7(`${prefixWarnings}[Timeout after ${timeout}ms]
4020
4019
  ${buildOutput(stdout, stderr)}`);
4021
4020
  }, timeout);
4022
4021
  child.on("close", (code) => {
4023
4022
  clearTimeout(timer);
4024
4023
  const output = buildOutput(stdout, stderr);
4025
4024
  if (code !== 0 && code !== null) {
4026
- resolve8(`${prefixWarnings}Exit code ${code}:
4025
+ resolve7(`${prefixWarnings}Exit code ${code}:
4027
4026
  ${output}`);
4028
4027
  } else {
4029
- resolve8(`${prefixWarnings}${output || "(no output)"}`);
4028
+ resolve7(`${prefixWarnings}${output || "(no output)"}`);
4030
4029
  }
4031
4030
  });
4032
4031
  child.on("error", (err) => {
4033
4032
  clearTimeout(timer);
4034
- resolve8(
4033
+ resolve7(
4035
4034
  `${prefixWarnings}Failed to start process "${executable}": ${err.message}
4036
4035
  Hint: On Windows, use the full path to the executable, e.g.:
4037
4036
  C:\\Users\\Jinzd\\anaconda3\\envs\\python312\\python.exe`
@@ -4743,188 +4742,10 @@ Any of these triggers means use save_last_response, NOT write_file:
4743
4742
  };
4744
4743
 
4745
4744
  // src/tools/builtin/save-memory.ts
4746
- import { join as join6 } from "path";
4745
+ import { join as join5 } from "path";
4747
4746
  import { homedir as homedir3 } from "os";
4748
-
4749
- // src/memory/persistent-memory.ts
4750
- import { existsSync as existsSync11, mkdirSync as mkdirSync3, readFileSync as readFileSync7 } from "fs";
4751
- import { randomUUID, createHash } from "crypto";
4752
- import { dirname as dirname5, join as join5, resolve as resolve6 } from "path";
4753
- function memoryStorePath(configDir) {
4754
- return join5(configDir, MEMORY_STORE_FILE_NAME);
4755
- }
4756
- function memoryMarkdownPath(configDir) {
4757
- return join5(configDir, MEMORY_FILE_NAME);
4758
- }
4759
- function getProjectKey(cwd = process.cwd()) {
4760
- return resolve6(getGitRoot(cwd) ?? cwd);
4761
- }
4762
- function nowIso() {
4763
- return (/* @__PURE__ */ new Date()).toISOString();
4764
- }
4765
- function isValidScope(value) {
4766
- return value === "personal" || value === "project" || value === "session" || value === "team";
4767
- }
4768
- function isValidSensitivity(value) {
4769
- return value === "low" || value === "medium" || value === "high";
4770
- }
4771
- function normalizeEntry(value) {
4772
- if (!value || typeof value !== "object") return null;
4773
- const raw = value;
4774
- const content = typeof raw.content === "string" ? raw.content.trim() : "";
4775
- if (!content) return null;
4776
- const createdAt = typeof raw.createdAt === "string" ? raw.createdAt : nowIso();
4777
- return {
4778
- id: typeof raw.id === "string" && raw.id ? raw.id : randomUUID(),
4779
- content,
4780
- scope: isValidScope(raw.scope) ? raw.scope : "personal",
4781
- sourceSession: typeof raw.sourceSession === "string" && raw.sourceSession ? raw.sourceSession : void 0,
4782
- sourceProject: typeof raw.sourceProject === "string" && raw.sourceProject ? raw.sourceProject : void 0,
4783
- createdAt,
4784
- updatedAt: typeof raw.updatedAt === "string" ? raw.updatedAt : createdAt,
4785
- expiresAt: typeof raw.expiresAt === "string" && raw.expiresAt ? raw.expiresAt : void 0,
4786
- sensitivity: isValidSensitivity(raw.sensitivity) ? raw.sensitivity : "low",
4787
- approved: raw.approved === true,
4788
- redactedKinds: Array.isArray(raw.redactedKinds) ? raw.redactedKinds.filter((x) => typeof x === "string") : void 0
4789
- };
4790
- }
4791
- function legacyEntriesFromMarkdown(configDir) {
4792
- const file = memoryMarkdownPath(configDir);
4793
- if (!existsSync11(file)) return [];
4794
- const content = readFileSync7(file, "utf-8").trim();
4795
- if (!content) return [];
4796
- const parts = content.split(/\n(?=##\s+\d{4}-\d{2}-\d{2})/g);
4797
- const chunks = parts.length > 1 ? parts : [content];
4798
- return chunks.map((chunk, index) => {
4799
- const match = chunk.match(/^##\s+([^\n]+)\n([\s\S]*)$/);
4800
- const body = (match ? match[2] : chunk).trim();
4801
- const parsed = match ? Date.parse(match[1].replace(" ", "T")) : NaN;
4802
- const createdAt = Number.isFinite(parsed) ? new Date(parsed).toISOString() : nowIso();
4803
- const id = "legacy-" + createHash("sha1").update(`${index}
4804
- ${chunk}`).digest("hex").slice(0, 12);
4805
- return normalizeEntry({ id, content: body, scope: "personal", createdAt, updatedAt: createdAt, sensitivity: "low", approved: true });
4806
- }).filter((entry) => Boolean(entry));
4807
- }
4808
- function loadMemoryEntries(configDir) {
4809
- const file = memoryStorePath(configDir);
4810
- if (!existsSync11(file)) return legacyEntriesFromMarkdown(configDir);
4811
- const text = readFileSync7(file, "utf-8").trim();
4812
- if (!text) return [];
4813
- const entries = [];
4814
- for (const line of text.split("\n")) {
4815
- const trimmed = line.trim();
4816
- if (!trimmed) continue;
4817
- try {
4818
- const entry = normalizeEntry(JSON.parse(trimmed));
4819
- if (entry) entries.push(entry);
4820
- } catch {
4821
- }
4822
- }
4823
- return entries;
4824
- }
4825
- function saveMemoryEntries(configDir, entries) {
4826
- mkdirSync3(configDir, { recursive: true });
4827
- const jsonl = entries.map((entry) => JSON.stringify(entry)).join("\n");
4828
- atomicWriteFileSync(memoryStorePath(configDir), jsonl ? jsonl + "\n" : "");
4829
- }
4830
- function isMemoryExpired(entry, at = /* @__PURE__ */ new Date()) {
4831
- return Boolean(entry.expiresAt && Date.parse(entry.expiresAt) <= at.getTime());
4832
- }
4833
- function isMemoryVisibleInProject(entry, cwd = process.cwd()) {
4834
- if (entry.scope !== "project") return true;
4835
- return entry.sourceProject === getProjectKey(cwd);
4836
- }
4837
- function listMemoryEntries(configDir, options = {}) {
4838
- const cwd = options.cwd ?? process.cwd();
4839
- return loadMemoryEntries(configDir).filter((entry) => options.includeRejected || entry.approved).filter((entry) => options.includeExpired || !isMemoryExpired(entry)).filter((entry) => isMemoryVisibleInProject(entry, cwd));
4840
- }
4841
- function addMemoryEntry(configDir, content, options = {}) {
4842
- const original = content.trim();
4843
- if (!original) throw new Error("memory content is required");
4844
- const redact = options.redact ?? true;
4845
- const redacted = redact ? redactString(original, { enabled: true, patterns: options.patterns ?? DEFAULT_PATTERNS, customRegexes: options.customPatterns }) : { redacted: original, hits: [] };
4846
- const sensitivity = options.sensitivity ?? (redacted.hits.length > 0 ? "high" : "low");
4847
- const approved = options.approved ?? (sensitivity === "low" && redacted.hits.length === 0);
4848
- const scope = options.scope ?? "personal";
4849
- const timestamp = nowIso();
4850
- const entry = {
4851
- id: randomUUID(),
4852
- content: redacted.redacted.trim(),
4853
- scope,
4854
- sourceSession: options.sourceSession,
4855
- sourceProject: scope === "project" ? getProjectKey(options.cwd) : void 0,
4856
- createdAt: timestamp,
4857
- updatedAt: timestamp,
4858
- expiresAt: options.expiresAt,
4859
- sensitivity,
4860
- approved,
4861
- redactedKinds: [...new Set(redacted.hits.map((hit) => hit.kind))]
4862
- };
4863
- const entries = loadMemoryEntries(configDir);
4864
- entries.push(entry);
4865
- saveMemoryEntries(configDir, entries);
4866
- return entry;
4867
- }
4868
- function updateMemoryApproval(configDir, idPrefix, approved) {
4869
- const entries = loadMemoryEntries(configDir);
4870
- const entry = findUnique(entries, idPrefix);
4871
- entry.approved = approved;
4872
- entry.updatedAt = nowIso();
4873
- saveMemoryEntries(configDir, entries);
4874
- return entry;
4875
- }
4876
- function deleteMemoryEntry(configDir, idPrefix) {
4877
- const entries = loadMemoryEntries(configDir);
4878
- const entry = findUnique(entries, idPrefix);
4879
- saveMemoryEntries(configDir, entries.filter((candidate) => candidate.id !== entry.id));
4880
- return entry;
4881
- }
4882
- function expireMemoryEntry(configDir, idPrefix, expiresAt = nowIso()) {
4883
- const entries = loadMemoryEntries(configDir);
4884
- const entry = findUnique(entries, idPrefix);
4885
- entry.expiresAt = expiresAt;
4886
- entry.updatedAt = nowIso();
4887
- saveMemoryEntries(configDir, entries);
4888
- return entry;
4889
- }
4890
- function exportMemoryEntries(configDir, cwd = process.cwd()) {
4891
- return JSON.stringify(listMemoryEntries(configDir, { cwd, includeExpired: true, includeRejected: true }), null, 2);
4892
- }
4893
- function findUnique(entries, idPrefix) {
4894
- const matches = entries.filter((entry) => entry.id.startsWith(idPrefix));
4895
- if (matches.length === 0) throw new Error(`memory id not found: ${idPrefix}`);
4896
- if (matches.length > 1) throw new Error(`memory id is ambiguous: ${idPrefix}`);
4897
- return matches[0];
4898
- }
4899
- function formatMemoryForPrompt(configDir, cwd = process.cwd()) {
4900
- const entries = listMemoryEntries(configDir, { cwd }).filter((entry) => entry.approved && !isMemoryExpired(entry));
4901
- if (entries.length === 0) return null;
4902
- let content = entries.map((entry) => {
4903
- const source = [
4904
- `id ${entry.id.slice(0, 8)}`,
4905
- `scope ${entry.scope}`,
4906
- entry.sourceSession ? `source session ${entry.sourceSession.slice(0, 8)}` : "source manual/tool",
4907
- entry.sourceProject ? `project ${entry.sourceProject}` : "",
4908
- `sensitivity ${entry.sensitivity}`
4909
- ].filter(Boolean).join(" \xB7 ");
4910
- return `## ${entry.createdAt.replace("T", " ").slice(0, 19)}
4911
- Source: ${source}
4912
- ${entry.content}`;
4913
- }).join("\n\n");
4914
- if (content.length > MEMORY_MAX_CHARS) {
4915
- content = content.slice(-MEMORY_MAX_CHARS);
4916
- const firstEntry = content.indexOf("\n## ");
4917
- if (firstEntry !== -1) content = content.slice(firstEntry + 1);
4918
- }
4919
- return { content, entryCount: entries.length };
4920
- }
4921
-
4922
- // src/tools/builtin/save-memory.ts
4923
4747
  function getConfigDir() {
4924
- return join6(homedir3(), CONFIG_DIR_NAME);
4925
- }
4926
- function parseScope(value) {
4927
- return value === "personal" || value === "project" || value === "session" || value === "team" ? value : void 0;
4748
+ return join5(homedir3(), CONFIG_DIR_NAME);
4928
4749
  }
4929
4750
  function parseSensitivity(value) {
4930
4751
  return value === "low" || value === "medium" || value === "high" ? value : void 0;
@@ -4932,7 +4753,7 @@ function parseSensitivity(value) {
4932
4753
  var saveMemoryTool = {
4933
4754
  definition: {
4934
4755
  name: "save_memory",
4935
- description: "Save important information to governed persistent memory. Low-risk memories are approved automatically; sensitive entries are stored as pending and require /memory approve <id>. Use concise entries for user preferences, project decisions, recurring patterns, and key findings.",
4756
+ description: "Save important information to governed persistent memory. Entries saved by this tool are stored as PENDING and only take effect after the user runs /memory approve <id> \u2014 tell the user the returned id so they can approve it. Use concise entries for user preferences, project decisions, recurring patterns, and key findings.",
4936
4757
  parameters: {
4937
4758
  content: {
4938
4759
  type: "string",
@@ -4941,7 +4762,7 @@ var saveMemoryTool = {
4941
4762
  },
4942
4763
  scope: {
4943
4764
  type: "string",
4944
- description: "Memory scope: personal, project, session, or team. Defaults to personal. Project memories are only injected in the same project.",
4765
+ description: "Memory scope: personal (default, injected everywhere) or project (only injected inside the current project).",
4945
4766
  required: false
4946
4767
  },
4947
4768
  sensitivity: {
@@ -4953,6 +4774,11 @@ var saveMemoryTool = {
4953
4774
  type: "string",
4954
4775
  description: "Optional ISO timestamp after which this memory should no longer be injected.",
4955
4776
  required: false
4777
+ },
4778
+ updateId: {
4779
+ type: "string",
4780
+ description: "Optional id (or unique id prefix) of an existing memory entry to update in place instead of creating a new one. Use this when a previously saved memory is outdated \u2014 do not save a contradicting duplicate. Updated entries go back to pending and need user re-approval.",
4781
+ required: false
4956
4782
  }
4957
4783
  },
4958
4784
  dangerous: false
@@ -4960,16 +4786,29 @@ var saveMemoryTool = {
4960
4786
  async execute(args) {
4961
4787
  const content = String(args["content"] ?? "").trim();
4962
4788
  if (!content) throw new ToolError("save_memory", "content is required");
4789
+ const updateId = typeof args["updateId"] === "string" ? args["updateId"].trim() : "";
4790
+ if (updateId) {
4791
+ try {
4792
+ const updated = updateMemoryEntry(getConfigDir(), updateId, content, { source: "tool", redact: true });
4793
+ const redactedNote = updated.redactedKinds?.length ? ` Redacted: ${updated.redactedKinds.join(", ")}.` : "";
4794
+ return `Memory ${updated.id.slice(0, 8)} updated (pending approval \u2014 the user must run \`/memory approve ${updated.id.slice(0, 8)}\` before the new content is injected; mention this to the user). scope=${updated.scope} sensitivity=${updated.sensitivity}.${redactedNote}`;
4795
+ } catch (err) {
4796
+ throw new ToolError("save_memory", err instanceof Error ? err.message : String(err));
4797
+ }
4798
+ }
4799
+ const similar = findSimilarMemory(loadMemoryEntries(getConfigDir()), content);
4963
4800
  const entry = addMemoryEntry(getConfigDir(), content, {
4964
- scope: parseScope(args["scope"]) ?? "personal",
4801
+ scope: parseWritableScope(args["scope"]) ?? "personal",
4802
+ source: "tool",
4965
4803
  sensitivity: parseSensitivity(args["sensitivity"]),
4966
4804
  expiresAt: typeof args["expiresAt"] === "string" ? args["expiresAt"] : void 0,
4967
4805
  cwd: process.cwd(),
4968
4806
  redact: true
4969
4807
  });
4970
- const status = entry.approved ? "approved and active" : "pending approval";
4808
+ const status = entry.approved ? "approved and active" : `pending approval \u2014 the user must run \`/memory approve ${entry.id.slice(0, 8)}\` before it is injected; mention this to the user`;
4971
4809
  const redacted = entry.redactedKinds?.length ? ` Redacted: ${entry.redactedKinds.join(", ")}.` : "";
4972
- return `Memory saved (${status}). id=${entry.id.slice(0, 8)} scope=${entry.scope} sensitivity=${entry.sensitivity}.${redacted}`;
4810
+ const similarNote = similar ? ` Warning: a similar entry already exists (id=${similar.id.slice(0, 8)}) \u2014 next time pass updateId to revise it instead of duplicating.` : "";
4811
+ return `Memory saved (${status}). id=${entry.id.slice(0, 8)} scope=${entry.scope} sensitivity=${entry.sensitivity}.${redacted}${similarNote}`;
4973
4812
  }
4974
4813
  };
4975
4814
 
@@ -5013,7 +4852,7 @@ function promptUser(rl, question) {
5013
4852
  console.log();
5014
4853
  console.log(chalk4.cyan("\u2753 ") + chalk4.bold(question));
5015
4854
  process.stdout.write(chalk4.cyan("> "));
5016
- return new Promise((resolve8) => {
4855
+ return new Promise((resolve7) => {
5017
4856
  let completed = false;
5018
4857
  const cleanup = (answer) => {
5019
4858
  if (completed) return;
@@ -5023,7 +4862,7 @@ function promptUser(rl, question) {
5023
4862
  rl.pause();
5024
4863
  rlAny.output = savedOutput;
5025
4864
  askUserContext.prompting = false;
5026
- resolve8(answer);
4865
+ resolve7(answer);
5027
4866
  };
5028
4867
  const onLine = (line) => {
5029
4868
  cleanup(line);
@@ -5255,8 +5094,8 @@ function formatResults2(query, data, _requested) {
5255
5094
  }
5256
5095
 
5257
5096
  // src/agents/agent-config.ts
5258
- import { existsSync as existsSync12, readdirSync as readdirSync6, readFileSync as readFileSync8 } from "fs";
5259
- import { join as join7 } from "path";
5097
+ import { existsSync as existsSync11, readdirSync as readdirSync6, readFileSync as readFileSync7 } from "fs";
5098
+ import { join as join6 } from "path";
5260
5099
  import { homedir as homedir4 } from "os";
5261
5100
  var READ_ONLY_TOOLS2 = /* @__PURE__ */ new Set([
5262
5101
  "read_file",
@@ -5351,13 +5190,13 @@ function parseAgentConfig(raw, source, path3) {
5351
5190
  return cfg;
5352
5191
  }
5353
5192
  function loadAgentDir(dir, source) {
5354
- if (!existsSync12(dir)) return [];
5193
+ if (!existsSync11(dir)) return [];
5355
5194
  const out = [];
5356
5195
  for (const file of readdirSync6(dir)) {
5357
5196
  if (!file.endsWith(".json")) continue;
5358
- const path3 = join7(dir, file);
5197
+ const path3 = join6(dir, file);
5359
5198
  try {
5360
- const parsed = JSON.parse(readFileSync8(path3, "utf-8"));
5199
+ const parsed = JSON.parse(readFileSync7(path3, "utf-8"));
5361
5200
  const cfg = parseAgentConfig(parsed, source, path3);
5362
5201
  if (cfg) out.push(cfg);
5363
5202
  } catch {
@@ -5367,8 +5206,8 @@ function loadAgentDir(dir, source) {
5367
5206
  }
5368
5207
  function getAgentDirs(configDir, cwd = process.cwd()) {
5369
5208
  return {
5370
- user: join7(configDir ?? join7(homedir4(), CONFIG_DIR_NAME), "agents"),
5371
- project: join7(cwd, CONFIG_DIR_NAME, "agents")
5209
+ user: join6(configDir ?? join6(homedir4(), CONFIG_DIR_NAME), "agents"),
5210
+ project: join6(cwd, CONFIG_DIR_NAME, "agents")
5372
5211
  };
5373
5212
  }
5374
5213
  function listAgentConfigs(configDir, cwd = process.cwd()) {
@@ -5415,7 +5254,7 @@ var nextId = 1;
5415
5254
  var preferredAgentName = "worker";
5416
5255
  var runs = [];
5417
5256
  var MAX_RUNS = 50;
5418
- function nowIso2() {
5257
+ function nowIso() {
5419
5258
  return (/* @__PURE__ */ new Date()).toISOString();
5420
5259
  }
5421
5260
  function startAgentRun(input) {
@@ -5424,7 +5263,7 @@ function startAgentRun(input) {
5424
5263
  agentName: input.agentName,
5425
5264
  task: input.task,
5426
5265
  status: "running",
5427
- startedAt: nowIso2(),
5266
+ startedAt: nowIso(),
5428
5267
  toolNames: [],
5429
5268
  agentIndex: input.agentIndex,
5430
5269
  stopRequested: false
@@ -5442,7 +5281,7 @@ function finishAgentRun(runId, status, summary, error) {
5442
5281
  const run = runs.find((r) => r.id === runId);
5443
5282
  if (!run) return;
5444
5283
  run.status = status;
5445
- run.finishedAt = nowIso2();
5284
+ run.finishedAt = nowIso();
5446
5285
  if (summary) run.summary = summary;
5447
5286
  if (error) run.error = error;
5448
5287
  }
@@ -5868,7 +5707,7 @@ var spawnAgentTool = {
5868
5707
 
5869
5708
  // src/tools/builtin/task-manager.ts
5870
5709
  import { spawn as spawn4 } from "child_process";
5871
- import { randomUUID as randomUUID2 } from "crypto";
5710
+ import { randomUUID } from "crypto";
5872
5711
  import { platform as platform5 } from "os";
5873
5712
  var MAX_OUTPUT_CHARS = 1e4;
5874
5713
  var MAX_OUTPUT_BYTES = 64 * 1024;
@@ -5901,7 +5740,7 @@ function createTask(command, description) {
5901
5740
  }
5902
5741
  }
5903
5742
  }
5904
- const id = randomUUID2().slice(0, 8);
5743
+ const id = randomUUID().slice(0, 8);
5905
5744
  const { shell, args } = shellInvocation(command);
5906
5745
  const proc = spawn4(shell, args, {
5907
5746
  stdio: ["ignore", "pipe", "pipe"],
@@ -6083,14 +5922,14 @@ var taskStopTool = {
6083
5922
 
6084
5923
  // src/tools/builtin/git-tools.ts
6085
5924
  import { execFileSync as execFileSync2 } from "child_process";
6086
- import { existsSync as existsSync13 } from "fs";
6087
- import { join as join8 } from "path";
5925
+ import { existsSync as existsSync12 } from "fs";
5926
+ import { join as join7 } from "path";
6088
5927
  function assertGitRepo(cwd) {
6089
5928
  let dir = cwd;
6090
5929
  const root = dir.split(/[\\/]/)[0] + (dir.includes("\\") ? "\\" : "/");
6091
5930
  while (dir && dir !== root) {
6092
- if (existsSync13(join8(dir, ".git"))) return;
6093
- const parent = join8(dir, "..");
5931
+ if (existsSync12(join7(dir, ".git"))) return;
5932
+ const parent = join7(dir, "..");
6094
5933
  if (parent === dir) break;
6095
5934
  dir = parent;
6096
5935
  }
@@ -6355,9 +6194,9 @@ ${commitOutput.trim()}`;
6355
6194
  };
6356
6195
 
6357
6196
  // src/tools/builtin/notebook-edit.ts
6358
- import { readFileSync as readFileSync9, existsSync as existsSync14 } from "fs";
6197
+ import { readFileSync as readFileSync8, existsSync as existsSync13 } from "fs";
6359
6198
  import { writeFile } from "fs/promises";
6360
- import { resolve as resolve7, extname as extname2 } from "path";
6199
+ import { resolve as resolve6, extname as extname2 } from "path";
6361
6200
  var notebookEditTool = {
6362
6201
  definition: {
6363
6202
  name: "notebook_edit",
@@ -6406,14 +6245,14 @@ var notebookEditTool = {
6406
6245
  if (!Number.isInteger(cellIndexRaw) || cellIndexRaw < 1) {
6407
6246
  throw new ToolError("notebook_edit", "cell_index must be a positive integer (1-based)");
6408
6247
  }
6409
- const absPath = resolve7(filePath);
6248
+ const absPath = resolve6(filePath);
6410
6249
  if (extname2(absPath).toLowerCase() !== ".ipynb") {
6411
6250
  throw new ToolError("notebook_edit", "path must point to a .ipynb file");
6412
6251
  }
6413
- if (!existsSync14(absPath)) {
6252
+ if (!existsSync13(absPath)) {
6414
6253
  throw new ToolError("notebook_edit", `Notebook not found: ${filePath}`);
6415
6254
  }
6416
- const raw = readFileSync9(absPath, "utf-8");
6255
+ const raw = readFileSync8(absPath, "utf-8");
6417
6256
  const nb = parseNotebook(raw);
6418
6257
  const cellIdx0 = cellIndexRaw - 1;
6419
6258
  undoStack.push(absPath, `notebook_edit (${action}): ${filePath}`);
@@ -6739,6 +6578,8 @@ ${lines.join("\n")}`;
6739
6578
  };
6740
6579
 
6741
6580
  // src/tools/builtin/recall-memory.ts
6581
+ import { join as join8 } from "path";
6582
+ import { homedir as homedir5 } from "os";
6742
6583
  function formatHit(h, i) {
6743
6584
  const ts = h.chunk.timestamp.slice(0, 16).replace("T", " ");
6744
6585
  const title = h.chunk.sessionTitle ? ` \xB7 ${h.chunk.sessionTitle}` : "";
@@ -6751,7 +6592,7 @@ function formatHit(h, i) {
6751
6592
  var recallMemoryTool = {
6752
6593
  definition: {
6753
6594
  name: "recall_memory",
6754
- 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".',
6595
+ description: 'Search long-term memory: governed persistent memory entries (keyword match) plus 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 \u2014 including memories that were dropped from the injected system prompt by the size budget. Returns persistent-memory hits first, then up to `topK` chat snippets with session id, timestamp, and cosine similarity score. Prefer this over asking the user "can you remind me".',
6755
6596
  parameters: {
6756
6597
  query: {
6757
6598
  type: "string",
@@ -6788,9 +6629,21 @@ var recallMemoryTool = {
6788
6629
  const excludeCurrent = Boolean(args["excludeCurrentSession"]);
6789
6630
  const currentId = args["currentSessionId"] ? String(args["currentSessionId"]) : void 0;
6790
6631
  const minScore = args["minScore"] !== void 0 ? Number(args["minScore"]) : 0.25;
6632
+ const configDir = join8(homedir5(), CONFIG_DIR_NAME);
6633
+ let persistentSection = "";
6634
+ try {
6635
+ const persistentHits = searchPersistentMemories(configDir, query, process.cwd(), 3);
6636
+ if (persistentHits.length > 0) {
6637
+ touchMemoryReferences(configDir, persistentHits.map((h) => h.entry.id));
6638
+ persistentSection = `\u2500\u2500 Persistent memory (${persistentHits.length} hit(s)) \u2500\u2500
6639
+ ` + persistentHits.map((h) => `[id ${h.entry.id.slice(0, 8)} \xB7 ${h.entry.scope} \xB7 coverage ${h.score.toFixed(2)}]
6640
+ ${h.entry.content}`).join("\n\n") + "\n\n";
6641
+ }
6642
+ } catch {
6643
+ }
6791
6644
  const status = loadChatIndex();
6792
6645
  if (!status) {
6793
- 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.";
6646
+ return persistentSection + "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.";
6794
6647
  }
6795
6648
  const hits = await searchChatMemory(query, {
6796
6649
  topK,
@@ -6798,11 +6651,11 @@ var recallMemoryTool = {
6798
6651
  excludeSessionId: excludeCurrent ? currentId : void 0
6799
6652
  });
6800
6653
  if (hits.length === 0) {
6801
- 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.`;
6654
+ return persistentSection + `No chat-history 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.`;
6802
6655
  }
6803
- const header = `Found ${hits.length} memory hit(s) for "${query}" (min-score ${minScore}):
6656
+ const header = `Found ${hits.length} chat-history hit(s) for "${query}" (min-score ${minScore}):
6804
6657
  `;
6805
- return header + "\n" + hits.map(formatHit).join("\n\n");
6658
+ return persistentSection + header + "\n" + hits.map(formatHit).join("\n\n");
6806
6659
  }
6807
6660
  };
6808
6661
 
@@ -6830,7 +6683,7 @@ function estimateToolDefinitionTokens(def) {
6830
6683
 
6831
6684
  // src/tools/registry.ts
6832
6685
  import { pathToFileURL } from "url";
6833
- import { existsSync as existsSync15, mkdirSync as mkdirSync4, readdirSync as readdirSync7 } from "fs";
6686
+ import { existsSync as existsSync14, mkdirSync as mkdirSync3, readdirSync as readdirSync7 } from "fs";
6834
6687
  import { join as join9 } from "path";
6835
6688
  var ToolRegistry = class {
6836
6689
  tools = /* @__PURE__ */ new Map();
@@ -6992,9 +6845,9 @@ var ToolRegistry = class {
6992
6845
  * Returns the number of successfully loaded plugins.
6993
6846
  */
6994
6847
  async loadPlugins(pluginsDir, allowPlugins = false) {
6995
- if (!existsSync15(pluginsDir)) {
6848
+ if (!existsSync14(pluginsDir)) {
6996
6849
  try {
6997
- mkdirSync4(pluginsDir, { recursive: true });
6850
+ mkdirSync3(pluginsDir, { recursive: true });
6998
6851
  } catch {
6999
6852
  }
7000
6853
  return 0;
@@ -7056,16 +6909,6 @@ export {
7056
6909
  theme,
7057
6910
  undoStack,
7058
6911
  renderDiff,
7059
- memoryStorePath,
7060
- memoryMarkdownPath,
7061
- isMemoryExpired,
7062
- listMemoryEntries,
7063
- addMemoryEntry,
7064
- updateMemoryApproval,
7065
- deleteMemoryEntry,
7066
- expireMemoryEntry,
7067
- exportMemoryEntries,
7068
- formatMemoryForPrompt,
7069
6912
  recordRecentlyDeniedAutoAction,
7070
6913
  getRecentlyDeniedAutoActions,
7071
6914
  clearRecentlyDeniedAutoActions,
@@ -2,12 +2,14 @@
2
2
  import {
3
3
  listFromIndex,
4
4
  readSessionMeta,
5
- redactJson,
6
5
  removeSessionFromIndex,
7
6
  safeDate,
8
7
  searchCandidates,
9
8
  updateSessionInIndex
10
- } from "./chunk-KCEO2XJ4.js";
9
+ } from "./chunk-PJ3ONBWZ.js";
10
+ import {
11
+ redactJson
12
+ } from "./chunk-B5TYJO7V.js";
11
13
  import {
12
14
  atomicWriteFileSync
13
15
  } from "./chunk-IW3Q7AE5.js";