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,9 +1,47 @@
1
+ import {
2
+ listFromIndex,
3
+ loadChatIndex,
4
+ readSessionMeta,
5
+ removeSessionFromIndex,
6
+ safeDate,
7
+ searchCandidates,
8
+ searchChatMemory,
9
+ updateSessionInIndex
10
+ } from "./chunk-NV63EYFL.js";
1
11
  import {
2
12
  fileCheckpoints
3
13
  } from "./chunk-SKET65WZ.js";
4
14
  import {
5
15
  indexProject
6
16
  } from "./chunk-BNERFXVO.js";
17
+ import {
18
+ addMemoryEntry,
19
+ clearMemoryEntries,
20
+ deleteMemoryEntry,
21
+ expireMemoryEntry,
22
+ exportMemoryEntries,
23
+ findSimilarMemory,
24
+ formatGitContextForPrompt,
25
+ formatMemoryForPrompt,
26
+ getGitContext,
27
+ getGitRoot,
28
+ isMemoryExpired,
29
+ listMemoryEntries,
30
+ loadMemoryEntries,
31
+ memoryStorePath,
32
+ parseWritableScope,
33
+ searchPersistentMemories,
34
+ touchMemoryReferences,
35
+ updateMemoryApproval,
36
+ updateMemoryEntry
37
+ } from "./chunk-PNNZRJ67.js";
38
+ import {
39
+ redactJson,
40
+ scanString
41
+ } from "./chunk-YVTASHS5.js";
42
+ import {
43
+ runTestsTool
44
+ } from "./chunk-PLEXBFCR.js";
7
45
  import {
8
46
  AGENTIC_BEHAVIOR_GUIDELINE,
9
47
  APP_NAME,
@@ -26,8 +64,6 @@ import {
26
64
  MCP_PROTOCOL_VERSION,
27
65
  MCP_TOOL_PREFIX,
28
66
  MEMORY_FILE_NAME,
29
- MEMORY_MAX_CHARS,
30
- MEMORY_STORE_FILE_NAME,
31
67
  PLAN_MODE_READONLY_TOOLS,
32
68
  PLAN_MODE_SYSTEM_ADDON,
33
69
  PLUGINS_DIR_NAME,
@@ -36,9 +72,8 @@ import {
36
72
  SUBAGENT_DEFAULT_MAX_ROUNDS,
37
73
  SUBAGENT_MAX_ROUNDS_LIMIT,
38
74
  VERSION,
39
- buildUserIdentityPrompt,
40
- runTestsTool
41
- } from "./chunk-ZPXMT2B6.js";
75
+ buildUserIdentityPrompt
76
+ } from "./chunk-GEWPLNKA.js";
42
77
  import {
43
78
  hasSemanticIndex,
44
79
  semanticSearch
@@ -47,20 +82,6 @@ import {
47
82
  loadIndex
48
83
  } from "./chunk-RWM2GFRC.js";
49
84
  import "./chunk-IEQAE3QG.js";
50
- import {
51
- DEFAULT_PATTERNS,
52
- listFromIndex,
53
- loadChatIndex,
54
- readSessionMeta,
55
- redactJson,
56
- redactString,
57
- removeSessionFromIndex,
58
- safeDate,
59
- scanString,
60
- searchCandidates,
61
- searchChatMemory,
62
- updateSessionInIndex
63
- } from "./chunk-UO3XWZFE.js";
64
85
  import "./chunk-JV5N65KN.js";
65
86
  import {
66
87
  atomicWriteFileSync
@@ -70,8 +91,8 @@ import {
70
91
  import express from "express";
71
92
  import { createServer } from "http";
72
93
  import { WebSocketServer } from "ws";
73
- import { join as join28, dirname as dirname11, resolve as resolve13, relative as relative6, sep as sep4 } from "path";
74
- import { existsSync as existsSync33, readFileSync as readFileSync25, readdirSync as readdirSync16, statSync as statSync14, realpathSync as realpathSync3 } from "fs";
94
+ import { join as join27, dirname as dirname10, resolve as resolve12, relative as relative6, sep as sep4 } from "path";
95
+ import { existsSync as existsSync31, readFileSync as readFileSync24, readdirSync as readdirSync16, statSync as statSync14, realpathSync as realpathSync3 } from "fs";
75
96
  import { networkInterfaces } from "os";
76
97
 
77
98
  // src/config/config-manager.ts
@@ -192,6 +213,12 @@ var ConfigSchema = z.object({
192
213
  projectDocMaxBytes: z.number().int().positive().default(32 * 1024),
193
214
  showLoadedContextSources: z.boolean().default(false)
194
215
  }).default({}),
216
+ // 持久记忆注入配置(MEM-005)
217
+ // maxChars: 注入 system prompt 的字符预算,超出丢最老条目并显式标注截断。
218
+ // 注意 CJK ≈ 1 字符/token——中文记忆的 token 开销约等于字符数。
219
+ memory: z.object({
220
+ maxChars: z.number().int().positive().max(2e5).default(1e4)
221
+ }).default({}),
195
222
  // Google Custom Search API 的 Search Engine ID (cx 参数)
196
223
  // API Key 通过 apiKeys['google-search'] 或 AICLI_API_KEY_GOOGLESEARCH 环境变量配置
197
224
  // CX 也可通过 AICLI_GOOGLE_CX 环境变量覆盖
@@ -4927,7 +4954,7 @@ async function execInPool(sessionKey, command, timeout, cwd, abortSignal) {
4927
4954
  const markerStart = `AICLI_MARKER_START:${id}`;
4928
4955
  const markerEnd = `AICLI_MARKER_END:${id}`;
4929
4956
  const wrappedCmd = IS_WINDOWS2 ? `${WIN_UTF8_PREAMBLE} echo "${markerStart}"; ${command}; echo "${markerEnd}:0"` : `echo "${markerStart}"; ${command}; echo "${markerEnd}:$?"`;
4930
- return new Promise((resolve14) => {
4957
+ return new Promise((resolve13) => {
4931
4958
  let stdout = "";
4932
4959
  let stderr = "";
4933
4960
  let timedOut = false;
@@ -4943,7 +4970,7 @@ async function execInPool(sessionKey, command, timeout, cwd, abortSignal) {
4943
4970
  stdout = stripMarkerOutput(stdout, markerStartPattern, markerEndPattern);
4944
4971
  resolved = true;
4945
4972
  cleanup();
4946
- resolve14({ stdout, stderr, exitCode, timedOut, aborted });
4973
+ resolve13({ stdout, stderr, exitCode, timedOut, aborted });
4947
4974
  }
4948
4975
  };
4949
4976
  const onErr = (chunk) => {
@@ -4958,7 +4985,7 @@ async function execInPool(sessionKey, command, timeout, cwd, abortSignal) {
4958
4985
  resolved = true;
4959
4986
  stdout = stripMarkerOutput(stdout, markerStartPattern, markerEndPattern);
4960
4987
  cleanup();
4961
- resolve14({ stdout, stderr, exitCode: null, timedOut, aborted });
4988
+ resolve13({ stdout, stderr, exitCode: null, timedOut, aborted });
4962
4989
  try {
4963
4990
  proc.kill();
4964
4991
  } catch {
@@ -4971,7 +4998,7 @@ async function execInPool(sessionKey, command, timeout, cwd, abortSignal) {
4971
4998
  resolved = true;
4972
4999
  cleanup();
4973
5000
  stdout = stripMarkerOutput(stdout, markerStartPattern, markerEndPattern);
4974
- resolve14({ stdout, stderr, exitCode: null, timedOut, aborted });
5001
+ resolve13({ stdout, stderr, exitCode: null, timedOut, aborted });
4975
5002
  try {
4976
5003
  proc.kill();
4977
5004
  } catch {
@@ -4989,7 +5016,7 @@ async function execInPool(sessionKey, command, timeout, cwd, abortSignal) {
4989
5016
  stderr += `
4990
5017
  [pool error] ${err.message}`;
4991
5018
  cleanup();
4992
- resolve14({ stdout, stderr, exitCode: null, timedOut, aborted });
5019
+ resolve13({ stdout, stderr, exitCode: null, timedOut, aborted });
4993
5020
  pool.delete(sessionKey);
4994
5021
  };
4995
5022
  proc.on("error", onError);
@@ -7384,7 +7411,7 @@ var ToolExecutor = class {
7384
7411
  rl.resume();
7385
7412
  process.stdout.write(prompt);
7386
7413
  this.confirming = true;
7387
- return new Promise((resolve14) => {
7414
+ return new Promise((resolve13) => {
7388
7415
  let completed = false;
7389
7416
  const cleanup = (result) => {
7390
7417
  if (completed) return;
@@ -7394,7 +7421,7 @@ var ToolExecutor = class {
7394
7421
  rl.pause();
7395
7422
  rlAny.output = savedOutput;
7396
7423
  this.confirming = false;
7397
- resolve14(result);
7424
+ resolve13(result);
7398
7425
  };
7399
7426
  const onLine = (line) => {
7400
7427
  const trimmed = line.trim();
@@ -7568,7 +7595,7 @@ var ToolExecutor = class {
7568
7595
  rl.resume();
7569
7596
  process.stdout.write(color("Proceed? [y/N] (type y + Enter to confirm) "));
7570
7597
  this.confirming = true;
7571
- return new Promise((resolve14) => {
7598
+ return new Promise((resolve13) => {
7572
7599
  let completed = false;
7573
7600
  const cleanup = (answer) => {
7574
7601
  if (completed) return;
@@ -7578,7 +7605,7 @@ var ToolExecutor = class {
7578
7605
  rl.pause();
7579
7606
  rlAny.output = savedOutput;
7580
7607
  this.confirming = false;
7581
- resolve14(answer === "y");
7608
+ resolve13(answer === "y");
7582
7609
  };
7583
7610
  const onLine = (line) => {
7584
7611
  const trimmed = line.trim();
@@ -8827,7 +8854,7 @@ var runInteractiveTool = {
8827
8854
  PYTHONDONTWRITEBYTECODE: "1"
8828
8855
  };
8829
8856
  const prefixWarnings = [argsTypeWarning, stdinTypeWarning].filter(Boolean).join("");
8830
- return new Promise((resolve14) => {
8857
+ return new Promise((resolve13) => {
8831
8858
  const child = spawn3(executable, cmdArgs.map(String), {
8832
8859
  cwd: process.cwd(),
8833
8860
  env,
@@ -8860,22 +8887,22 @@ var runInteractiveTool = {
8860
8887
  setTimeout(writeNextLine, 400);
8861
8888
  const timer = setTimeout(() => {
8862
8889
  child.kill();
8863
- resolve14(`${prefixWarnings}[Timeout after ${timeout}ms]
8890
+ resolve13(`${prefixWarnings}[Timeout after ${timeout}ms]
8864
8891
  ${buildOutput(stdout, stderr)}`);
8865
8892
  }, timeout);
8866
8893
  child.on("close", (code) => {
8867
8894
  clearTimeout(timer);
8868
8895
  const output = buildOutput(stdout, stderr);
8869
8896
  if (code !== 0 && code !== null) {
8870
- resolve14(`${prefixWarnings}Exit code ${code}:
8897
+ resolve13(`${prefixWarnings}Exit code ${code}:
8871
8898
  ${output}`);
8872
8899
  } else {
8873
- resolve14(`${prefixWarnings}${output || "(no output)"}`);
8900
+ resolve13(`${prefixWarnings}${output || "(no output)"}`);
8874
8901
  }
8875
8902
  });
8876
8903
  child.on("error", (err) => {
8877
8904
  clearTimeout(timer);
8878
- resolve14(
8905
+ resolve13(
8879
8906
  `${prefixWarnings}Failed to start process "${executable}": ${err.message}
8880
8907
  Hint: On Windows, use the full path to the executable, e.g.:
8881
8908
  C:\\Users\\Jinzd\\anaconda3\\envs\\python312\\python.exe`
@@ -9587,280 +9614,10 @@ Any of these triggers means use save_last_response, NOT write_file:
9587
9614
  };
9588
9615
 
9589
9616
  // src/tools/builtin/save-memory.ts
9590
- import { join as join12 } from "path";
9591
- import { homedir as homedir5 } from "os";
9592
-
9593
- // src/memory/persistent-memory.ts
9594
- import { existsSync as existsSync17, mkdirSync as mkdirSync8, readFileSync as readFileSync12 } from "fs";
9595
- import { randomUUID, createHash as createHash2 } from "crypto";
9596
- import { dirname as dirname7, join as join11, resolve as resolve6 } from "path";
9597
-
9598
- // src/tools/git-context.ts
9599
- import { execSync as execSync2 } from "child_process";
9600
- import { existsSync as existsSync16 } from "fs";
9601
9617
  import { join as join10 } from "path";
9602
- function runGit(cmd, cwd) {
9603
- try {
9604
- return execSync2(`git ${cmd}`, {
9605
- cwd,
9606
- encoding: "utf-8",
9607
- stdio: ["pipe", "pipe", "pipe"],
9608
- timeout: 5e3
9609
- }).trim();
9610
- } catch {
9611
- return null;
9612
- }
9613
- }
9614
- function getGitRoot(cwd = process.cwd()) {
9615
- return runGit("rev-parse --show-toplevel", cwd);
9616
- }
9617
- function getGitContext(cwd = process.cwd()) {
9618
- if (!existsSync16(join10(cwd, ".git"))) {
9619
- const result = runGit("rev-parse --git-dir", cwd);
9620
- if (!result) return null;
9621
- }
9622
- const branch = runGit("rev-parse --abbrev-ref HEAD", cwd);
9623
- if (!branch) return null;
9624
- const statusOutput = runGit("status --porcelain", cwd) ?? "";
9625
- const statusLines = statusOutput ? statusOutput.split("\n").filter(Boolean) : [];
9626
- const stagedFiles = [];
9627
- const changedFiles = [];
9628
- for (const line of statusLines) {
9629
- const xy = line.slice(0, 2);
9630
- const file = line.slice(3).trim();
9631
- const indexStatus = xy[0];
9632
- const workStatus = xy[1];
9633
- if (indexStatus && indexStatus !== " " && indexStatus !== "?") {
9634
- stagedFiles.push(`${indexStatus} ${file}`);
9635
- }
9636
- if (workStatus && workStatus !== " ") {
9637
- changedFiles.push(`${workStatus} ${file}`);
9638
- }
9639
- }
9640
- const logOutput = runGit("log --oneline -3", cwd) ?? "";
9641
- const recentCommits = logOutput ? logOutput.split("\n").filter(Boolean) : [];
9642
- const unpushedOutput = runGit("log @{u}..HEAD --oneline", cwd);
9643
- const hasUnpushed = unpushedOutput !== null && unpushedOutput.trim().length > 0;
9644
- return {
9645
- branch,
9646
- changedFiles,
9647
- stagedFiles,
9648
- recentCommits,
9649
- hasUnpushed
9650
- };
9651
- }
9652
- function formatGitContextForPrompt(ctx) {
9653
- const lines = ["# Git Repository Status", ""];
9654
- lines.push(`- **Branch**: \`${ctx.branch}\``);
9655
- if (ctx.stagedFiles.length > 0) {
9656
- lines.push(`- **Staged** (${ctx.stagedFiles.length} files):`);
9657
- for (const f of ctx.stagedFiles.slice(0, 10)) {
9658
- lines.push(` - ${f}`);
9659
- }
9660
- if (ctx.stagedFiles.length > 10) {
9661
- lines.push(` - ... and ${ctx.stagedFiles.length - 10} more`);
9662
- }
9663
- }
9664
- if (ctx.changedFiles.length > 0) {
9665
- lines.push(`- **Modified** (${ctx.changedFiles.length} files):`);
9666
- for (const f of ctx.changedFiles.slice(0, 10)) {
9667
- lines.push(` - ${f}`);
9668
- }
9669
- if (ctx.changedFiles.length > 10) {
9670
- lines.push(` - ... and ${ctx.changedFiles.length - 10} more`);
9671
- }
9672
- }
9673
- if (ctx.stagedFiles.length === 0 && ctx.changedFiles.length === 0) {
9674
- lines.push("- **Working tree**: clean");
9675
- }
9676
- if (ctx.recentCommits.length > 0) {
9677
- lines.push("- **Recent commits**:");
9678
- for (const c of ctx.recentCommits) {
9679
- lines.push(` - ${c}`);
9680
- }
9681
- }
9682
- if (ctx.hasUnpushed) {
9683
- lines.push("- \u26A0\uFE0F Has unpushed commits");
9684
- }
9685
- return lines.join("\n");
9686
- }
9687
-
9688
- // src/memory/persistent-memory.ts
9689
- function memoryStorePath(configDir) {
9690
- return join11(configDir, MEMORY_STORE_FILE_NAME);
9691
- }
9692
- function memoryMarkdownPath(configDir) {
9693
- return join11(configDir, MEMORY_FILE_NAME);
9694
- }
9695
- function getProjectKey(cwd = process.cwd()) {
9696
- return resolve6(getGitRoot(cwd) ?? cwd);
9697
- }
9698
- function nowIso() {
9699
- return (/* @__PURE__ */ new Date()).toISOString();
9700
- }
9701
- function isValidScope(value) {
9702
- return value === "personal" || value === "project" || value === "session" || value === "team";
9703
- }
9704
- function isValidSensitivity(value) {
9705
- return value === "low" || value === "medium" || value === "high";
9706
- }
9707
- function normalizeEntry(value) {
9708
- if (!value || typeof value !== "object") return null;
9709
- const raw = value;
9710
- const content = typeof raw.content === "string" ? raw.content.trim() : "";
9711
- if (!content) return null;
9712
- const createdAt = typeof raw.createdAt === "string" ? raw.createdAt : nowIso();
9713
- return {
9714
- id: typeof raw.id === "string" && raw.id ? raw.id : randomUUID(),
9715
- content,
9716
- scope: isValidScope(raw.scope) ? raw.scope : "personal",
9717
- sourceSession: typeof raw.sourceSession === "string" && raw.sourceSession ? raw.sourceSession : void 0,
9718
- sourceProject: typeof raw.sourceProject === "string" && raw.sourceProject ? raw.sourceProject : void 0,
9719
- createdAt,
9720
- updatedAt: typeof raw.updatedAt === "string" ? raw.updatedAt : createdAt,
9721
- expiresAt: typeof raw.expiresAt === "string" && raw.expiresAt ? raw.expiresAt : void 0,
9722
- sensitivity: isValidSensitivity(raw.sensitivity) ? raw.sensitivity : "low",
9723
- approved: raw.approved === true,
9724
- redactedKinds: Array.isArray(raw.redactedKinds) ? raw.redactedKinds.filter((x) => typeof x === "string") : void 0
9725
- };
9726
- }
9727
- function legacyEntriesFromMarkdown(configDir) {
9728
- const file = memoryMarkdownPath(configDir);
9729
- if (!existsSync17(file)) return [];
9730
- const content = readFileSync12(file, "utf-8").trim();
9731
- if (!content) return [];
9732
- const parts = content.split(/\n(?=##\s+\d{4}-\d{2}-\d{2})/g);
9733
- const chunks = parts.length > 1 ? parts : [content];
9734
- return chunks.map((chunk, index) => {
9735
- const match = chunk.match(/^##\s+([^\n]+)\n([\s\S]*)$/);
9736
- const body = (match ? match[2] : chunk).trim();
9737
- const parsed = match ? Date.parse(match[1].replace(" ", "T")) : NaN;
9738
- const createdAt = Number.isFinite(parsed) ? new Date(parsed).toISOString() : nowIso();
9739
- const id = "legacy-" + createHash2("sha1").update(`${index}
9740
- ${chunk}`).digest("hex").slice(0, 12);
9741
- return normalizeEntry({ id, content: body, scope: "personal", createdAt, updatedAt: createdAt, sensitivity: "low", approved: true });
9742
- }).filter((entry) => Boolean(entry));
9743
- }
9744
- function loadMemoryEntries(configDir) {
9745
- const file = memoryStorePath(configDir);
9746
- if (!existsSync17(file)) return legacyEntriesFromMarkdown(configDir);
9747
- const text = readFileSync12(file, "utf-8").trim();
9748
- if (!text) return [];
9749
- const entries = [];
9750
- for (const line of text.split("\n")) {
9751
- const trimmed = line.trim();
9752
- if (!trimmed) continue;
9753
- try {
9754
- const entry = normalizeEntry(JSON.parse(trimmed));
9755
- if (entry) entries.push(entry);
9756
- } catch {
9757
- }
9758
- }
9759
- return entries;
9760
- }
9761
- function saveMemoryEntries(configDir, entries) {
9762
- mkdirSync8(configDir, { recursive: true });
9763
- const jsonl = entries.map((entry) => JSON.stringify(entry)).join("\n");
9764
- atomicWriteFileSync(memoryStorePath(configDir), jsonl ? jsonl + "\n" : "");
9765
- }
9766
- function isMemoryExpired(entry, at = /* @__PURE__ */ new Date()) {
9767
- return Boolean(entry.expiresAt && Date.parse(entry.expiresAt) <= at.getTime());
9768
- }
9769
- function isMemoryVisibleInProject(entry, cwd = process.cwd()) {
9770
- if (entry.scope !== "project") return true;
9771
- return entry.sourceProject === getProjectKey(cwd);
9772
- }
9773
- function listMemoryEntries(configDir, options = {}) {
9774
- const cwd = options.cwd ?? process.cwd();
9775
- return loadMemoryEntries(configDir).filter((entry) => options.includeRejected || entry.approved).filter((entry) => options.includeExpired || !isMemoryExpired(entry)).filter((entry) => isMemoryVisibleInProject(entry, cwd));
9776
- }
9777
- function addMemoryEntry(configDir, content, options = {}) {
9778
- const original = content.trim();
9779
- if (!original) throw new Error("memory content is required");
9780
- const redact = options.redact ?? true;
9781
- const redacted = redact ? redactString(original, { enabled: true, patterns: options.patterns ?? DEFAULT_PATTERNS, customRegexes: options.customPatterns }) : { redacted: original, hits: [] };
9782
- const sensitivity = options.sensitivity ?? (redacted.hits.length > 0 ? "high" : "low");
9783
- const approved = options.approved ?? (sensitivity === "low" && redacted.hits.length === 0);
9784
- const scope = options.scope ?? "personal";
9785
- const timestamp = nowIso();
9786
- const entry = {
9787
- id: randomUUID(),
9788
- content: redacted.redacted.trim(),
9789
- scope,
9790
- sourceSession: options.sourceSession,
9791
- sourceProject: scope === "project" ? getProjectKey(options.cwd) : void 0,
9792
- createdAt: timestamp,
9793
- updatedAt: timestamp,
9794
- expiresAt: options.expiresAt,
9795
- sensitivity,
9796
- approved,
9797
- redactedKinds: [...new Set(redacted.hits.map((hit) => hit.kind))]
9798
- };
9799
- const entries = loadMemoryEntries(configDir);
9800
- entries.push(entry);
9801
- saveMemoryEntries(configDir, entries);
9802
- return entry;
9803
- }
9804
- function updateMemoryApproval(configDir, idPrefix, approved) {
9805
- const entries = loadMemoryEntries(configDir);
9806
- const entry = findUnique(entries, idPrefix);
9807
- entry.approved = approved;
9808
- entry.updatedAt = nowIso();
9809
- saveMemoryEntries(configDir, entries);
9810
- return entry;
9811
- }
9812
- function deleteMemoryEntry(configDir, idPrefix) {
9813
- const entries = loadMemoryEntries(configDir);
9814
- const entry = findUnique(entries, idPrefix);
9815
- saveMemoryEntries(configDir, entries.filter((candidate) => candidate.id !== entry.id));
9816
- return entry;
9817
- }
9818
- function expireMemoryEntry(configDir, idPrefix, expiresAt = nowIso()) {
9819
- const entries = loadMemoryEntries(configDir);
9820
- const entry = findUnique(entries, idPrefix);
9821
- entry.expiresAt = expiresAt;
9822
- entry.updatedAt = nowIso();
9823
- saveMemoryEntries(configDir, entries);
9824
- return entry;
9825
- }
9826
- function exportMemoryEntries(configDir, cwd = process.cwd()) {
9827
- return JSON.stringify(listMemoryEntries(configDir, { cwd, includeExpired: true, includeRejected: true }), null, 2);
9828
- }
9829
- function findUnique(entries, idPrefix) {
9830
- const matches = entries.filter((entry) => entry.id.startsWith(idPrefix));
9831
- if (matches.length === 0) throw new Error(`memory id not found: ${idPrefix}`);
9832
- if (matches.length > 1) throw new Error(`memory id is ambiguous: ${idPrefix}`);
9833
- return matches[0];
9834
- }
9835
- function formatMemoryForPrompt(configDir, cwd = process.cwd()) {
9836
- const entries = listMemoryEntries(configDir, { cwd }).filter((entry) => entry.approved && !isMemoryExpired(entry));
9837
- if (entries.length === 0) return null;
9838
- let content = entries.map((entry) => {
9839
- const source = [
9840
- `id ${entry.id.slice(0, 8)}`,
9841
- `scope ${entry.scope}`,
9842
- entry.sourceSession ? `source session ${entry.sourceSession.slice(0, 8)}` : "source manual/tool",
9843
- entry.sourceProject ? `project ${entry.sourceProject}` : "",
9844
- `sensitivity ${entry.sensitivity}`
9845
- ].filter(Boolean).join(" \xB7 ");
9846
- return `## ${entry.createdAt.replace("T", " ").slice(0, 19)}
9847
- Source: ${source}
9848
- ${entry.content}`;
9849
- }).join("\n\n");
9850
- if (content.length > MEMORY_MAX_CHARS) {
9851
- content = content.slice(-MEMORY_MAX_CHARS);
9852
- const firstEntry = content.indexOf("\n## ");
9853
- if (firstEntry !== -1) content = content.slice(firstEntry + 1);
9854
- }
9855
- return { content, entryCount: entries.length };
9856
- }
9857
-
9858
- // src/tools/builtin/save-memory.ts
9618
+ import { homedir as homedir5 } from "os";
9859
9619
  function getConfigDir() {
9860
- return join12(homedir5(), CONFIG_DIR_NAME);
9861
- }
9862
- function parseScope(value) {
9863
- return value === "personal" || value === "project" || value === "session" || value === "team" ? value : void 0;
9620
+ return join10(homedir5(), CONFIG_DIR_NAME);
9864
9621
  }
9865
9622
  function parseSensitivity(value) {
9866
9623
  return value === "low" || value === "medium" || value === "high" ? value : void 0;
@@ -9868,7 +9625,7 @@ function parseSensitivity(value) {
9868
9625
  var saveMemoryTool = {
9869
9626
  definition: {
9870
9627
  name: "save_memory",
9871
- 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.",
9628
+ 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.",
9872
9629
  parameters: {
9873
9630
  content: {
9874
9631
  type: "string",
@@ -9877,7 +9634,7 @@ var saveMemoryTool = {
9877
9634
  },
9878
9635
  scope: {
9879
9636
  type: "string",
9880
- description: "Memory scope: personal, project, session, or team. Defaults to personal. Project memories are only injected in the same project.",
9637
+ description: "Memory scope: personal (default, injected everywhere) or project (only injected inside the current project).",
9881
9638
  required: false
9882
9639
  },
9883
9640
  sensitivity: {
@@ -9889,6 +9646,11 @@ var saveMemoryTool = {
9889
9646
  type: "string",
9890
9647
  description: "Optional ISO timestamp after which this memory should no longer be injected.",
9891
9648
  required: false
9649
+ },
9650
+ updateId: {
9651
+ type: "string",
9652
+ 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.",
9653
+ required: false
9892
9654
  }
9893
9655
  },
9894
9656
  dangerous: false
@@ -9896,16 +9658,29 @@ var saveMemoryTool = {
9896
9658
  async execute(args) {
9897
9659
  const content = String(args["content"] ?? "").trim();
9898
9660
  if (!content) throw new ToolError("save_memory", "content is required");
9661
+ const updateId = typeof args["updateId"] === "string" ? args["updateId"].trim() : "";
9662
+ if (updateId) {
9663
+ try {
9664
+ const updated = updateMemoryEntry(getConfigDir(), updateId, content, { source: "tool", redact: true });
9665
+ const redactedNote = updated.redactedKinds?.length ? ` Redacted: ${updated.redactedKinds.join(", ")}.` : "";
9666
+ 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}`;
9667
+ } catch (err) {
9668
+ throw new ToolError("save_memory", err instanceof Error ? err.message : String(err));
9669
+ }
9670
+ }
9671
+ const similar = findSimilarMemory(loadMemoryEntries(getConfigDir()), content);
9899
9672
  const entry = addMemoryEntry(getConfigDir(), content, {
9900
- scope: parseScope(args["scope"]) ?? "personal",
9673
+ scope: parseWritableScope(args["scope"]) ?? "personal",
9674
+ source: "tool",
9901
9675
  sensitivity: parseSensitivity(args["sensitivity"]),
9902
9676
  expiresAt: typeof args["expiresAt"] === "string" ? args["expiresAt"] : void 0,
9903
9677
  cwd: process.cwd(),
9904
9678
  redact: true
9905
9679
  });
9906
- const status = entry.approved ? "approved and active" : "pending approval";
9680
+ 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`;
9907
9681
  const redacted = entry.redactedKinds?.length ? ` Redacted: ${entry.redactedKinds.join(", ")}.` : "";
9908
- return `Memory saved (${status}). id=${entry.id.slice(0, 8)} scope=${entry.scope} sensitivity=${entry.sensitivity}.${redacted}`;
9682
+ 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.` : "";
9683
+ return `Memory saved (${status}). id=${entry.id.slice(0, 8)} scope=${entry.scope} sensitivity=${entry.sensitivity}.${redacted}${similarNote}`;
9909
9684
  }
9910
9685
  };
9911
9686
 
@@ -9949,7 +9724,7 @@ function promptUser(rl, question) {
9949
9724
  console.log();
9950
9725
  console.log(chalk4.cyan("\u2753 ") + chalk4.bold(question));
9951
9726
  process.stdout.write(chalk4.cyan("> "));
9952
- return new Promise((resolve14) => {
9727
+ return new Promise((resolve13) => {
9953
9728
  let completed = false;
9954
9729
  const cleanup = (answer) => {
9955
9730
  if (completed) return;
@@ -9959,7 +9734,7 @@ function promptUser(rl, question) {
9959
9734
  rl.pause();
9960
9735
  rlAny.output = savedOutput;
9961
9736
  askUserContext.prompting = false;
9962
- resolve14(answer);
9737
+ resolve13(answer);
9963
9738
  };
9964
9739
  const onLine = (line) => {
9965
9740
  cleanup(line);
@@ -10191,14 +9966,14 @@ function formatResults2(query, data, _requested) {
10191
9966
  }
10192
9967
 
10193
9968
  // src/agents/agent-config.ts
10194
- import { existsSync as existsSync19, readdirSync as readdirSync8, readFileSync as readFileSync14 } from "fs";
10195
- import { join as join14 } from "path";
9969
+ import { existsSync as existsSync17, readdirSync as readdirSync8, readFileSync as readFileSync13 } from "fs";
9970
+ import { join as join12 } from "path";
10196
9971
  import { homedir as homedir6 } from "os";
10197
9972
 
10198
9973
  // src/plugins/plugin-manager.ts
10199
- import { existsSync as existsSync18, mkdirSync as mkdirSync9, readdirSync as readdirSync7, readFileSync as readFileSync13, realpathSync as realpathSync2, cpSync, rmSync, statSync as statSync7 } from "fs";
10200
- import { basename as basename6, dirname as dirname8, join as join13, relative as relative3, resolve as resolve7, sep as sep3 } from "path";
10201
- import { createHash as createHash3 } from "crypto";
9974
+ import { existsSync as existsSync16, mkdirSync as mkdirSync8, readdirSync as readdirSync7, readFileSync as readFileSync12, realpathSync as realpathSync2, cpSync, rmSync, statSync as statSync7 } from "fs";
9975
+ import { basename as basename6, dirname as dirname7, join as join11, relative as relative3, resolve as resolve6, sep as sep3 } from "path";
9976
+ import { createHash as createHash2 } from "crypto";
10202
9977
  import { z as z2 } from "zod";
10203
9978
  var MANIFEST_RELATIVE = ".aicli-plugin/plugin.json";
10204
9979
  var STATE_FILE = "plugin-state.json";
@@ -10257,19 +10032,19 @@ var PluginManifestSchema = z2.object({
10257
10032
  permissionHints: z2.array(z2.string()).default([])
10258
10033
  }).strict();
10259
10034
  function pluginRoot(configDir) {
10260
- return join13(configDir, PLUGINS_DIR_NAME);
10035
+ return join11(configDir, PLUGINS_DIR_NAME);
10261
10036
  }
10262
10037
  function pluginManifestPath(pluginDir) {
10263
- return join13(pluginDir, MANIFEST_RELATIVE);
10038
+ return join11(pluginDir, MANIFEST_RELATIVE);
10264
10039
  }
10265
10040
  function statePath(configDir) {
10266
- return join13(pluginRoot(configDir), STATE_FILE);
10041
+ return join11(pluginRoot(configDir), STATE_FILE);
10267
10042
  }
10268
10043
  function loadState(configDir) {
10269
10044
  const file = statePath(configDir);
10270
- if (!existsSync18(file)) return { version: 2, plugins: [] };
10045
+ if (!existsSync16(file)) return { version: 2, plugins: [] };
10271
10046
  try {
10272
- const parsed = JSON.parse(readFileSync13(file, "utf-8"));
10047
+ const parsed = JSON.parse(readFileSync12(file, "utf-8"));
10273
10048
  const rawPlugins = Array.isArray(parsed.plugins) ? parsed.plugins : [];
10274
10049
  return { version: 2, plugins: rawPlugins.map((p) => migrateEntry(p)) };
10275
10050
  } catch {
@@ -10292,7 +10067,7 @@ function migrateEntry(entry) {
10292
10067
  };
10293
10068
  }
10294
10069
  function saveState(configDir, state2) {
10295
- mkdirSync9(pluginRoot(configDir), { recursive: true });
10070
+ mkdirSync8(pluginRoot(configDir), { recursive: true });
10296
10071
  atomicWriteFileSync(statePath(configDir), JSON.stringify(state2, null, 2));
10297
10072
  }
10298
10073
  function normalizeForHash(buf) {
@@ -10300,7 +10075,7 @@ function normalizeForHash(buf) {
10300
10075
  return Buffer.from(str, "utf-8");
10301
10076
  }
10302
10077
  function extractCommandPaths(command, pluginDir) {
10303
- const resolvedDir = resolve7(pluginDir);
10078
+ const resolvedDir = resolve6(pluginDir);
10304
10079
  const refs = [];
10305
10080
  for (const m of command.matchAll(/\$PLUGIN_DIR[\\/]([^"'\s]+)/g)) {
10306
10081
  refs.push(m[1]);
@@ -10317,9 +10092,9 @@ function extractCommandPaths(command, pluginDir) {
10317
10092
  if (seen.has(ref)) continue;
10318
10093
  seen.add(ref);
10319
10094
  try {
10320
- const abs = resolve7(resolvedDir, ref);
10095
+ const abs = resolve6(resolvedDir, ref);
10321
10096
  if (!abs.startsWith(resolvedDir + sep3)) continue;
10322
- if (existsSync18(abs)) {
10097
+ if (existsSync16(abs)) {
10323
10098
  const real = realpathSync2(abs);
10324
10099
  if (!real.startsWith(resolvedDir + sep3)) continue;
10325
10100
  valid.push(relative3(resolvedDir, real));
@@ -10330,7 +10105,7 @@ function extractCommandPaths(command, pluginDir) {
10330
10105
  return valid;
10331
10106
  }
10332
10107
  function hashPluginContent(pluginDir, manifest) {
10333
- const resolvedDir = resolve7(pluginDir);
10108
+ const resolvedDir = resolve6(pluginDir);
10334
10109
  const assetRels = /* @__PURE__ */ new Set(
10335
10110
  [...manifest.skills, ...manifest.commands, ...manifest.agents]
10336
10111
  );
@@ -10350,11 +10125,11 @@ function hashPluginContent(pluginDir, manifest) {
10350
10125
  }
10351
10126
  }
10352
10127
  const sorted = [...assetRels].sort();
10353
- const hasher = createHash3("sha256");
10354
- const manifestPath = join13(resolvedDir, MANIFEST_RELATIVE);
10355
- hasher.update(normalizeForHash(readFileSync13(manifestPath)));
10128
+ const hasher = createHash2("sha256");
10129
+ const manifestPath = join11(resolvedDir, MANIFEST_RELATIVE);
10130
+ hasher.update(normalizeForHash(readFileSync12(manifestPath)));
10356
10131
  for (const rel of sorted) {
10357
- const abs = resolve7(resolvedDir, rel);
10132
+ const abs = resolve6(resolvedDir, rel);
10358
10133
  if (!abs.startsWith(resolvedDir + sep3)) {
10359
10134
  throw new Error(`plugin asset escapes plugin dir: ${rel}`);
10360
10135
  }
@@ -10372,7 +10147,7 @@ function hashPluginContent(pluginDir, manifest) {
10372
10147
  if (!realPath.startsWith(resolvedDir + sep3)) {
10373
10148
  throw new Error(`plugin asset symlink escapes plugin dir: ${rel} \u2192 ${realPath}`);
10374
10149
  }
10375
- hasher.update(normalizeForHash(readFileSync13(abs)));
10150
+ hasher.update(normalizeForHash(readFileSync12(abs)));
10376
10151
  }
10377
10152
  return hasher.digest("hex");
10378
10153
  }
@@ -10380,22 +10155,22 @@ function effectiveHash(entry) {
10380
10155
  return entry.contentHash ?? entry.hash ?? "";
10381
10156
  }
10382
10157
  function readPluginManifest(manifestPath) {
10383
- return PluginManifestSchema.parse(JSON.parse(readFileSync13(manifestPath, "utf-8")));
10158
+ return PluginManifestSchema.parse(JSON.parse(readFileSync12(manifestPath, "utf-8")));
10384
10159
  }
10385
10160
  function findPluginSourceDir(inputPath) {
10386
- const abs = resolve7(inputPath);
10387
- const direct = statSync7(abs).isDirectory() ? abs : dirname8(abs);
10388
- if (existsSync18(pluginManifestPath(direct))) return direct;
10389
- const nested = join13(direct, ".aicli-plugin");
10390
- if (existsSync18(join13(nested, "plugin.json"))) return direct;
10161
+ const abs = resolve6(inputPath);
10162
+ const direct = statSync7(abs).isDirectory() ? abs : dirname7(abs);
10163
+ if (existsSync16(pluginManifestPath(direct))) return direct;
10164
+ const nested = join11(direct, ".aicli-plugin");
10165
+ if (existsSync16(join11(nested, "plugin.json"))) return direct;
10391
10166
  throw new Error(`plugin manifest not found: ${MANIFEST_RELATIVE}`);
10392
10167
  }
10393
10168
  function installPlugin(configDir, inputPath) {
10394
10169
  const sourceDir = findPluginSourceDir(inputPath);
10395
10170
  const manifest = readPluginManifest(pluginManifestPath(sourceDir));
10396
- const targetDir = join13(pluginRoot(configDir), manifest.name);
10397
- mkdirSync9(pluginRoot(configDir), { recursive: true });
10398
- if (existsSync18(targetDir)) rmSync(targetDir, { recursive: true, force: true });
10171
+ const targetDir = join11(pluginRoot(configDir), manifest.name);
10172
+ mkdirSync8(pluginRoot(configDir), { recursive: true });
10173
+ if (existsSync16(targetDir)) rmSync(targetDir, { recursive: true, force: true });
10399
10174
  cpSync(sourceDir, targetDir, { recursive: true });
10400
10175
  const manifestPath = pluginManifestPath(targetDir);
10401
10176
  const contentHash = hashPluginContent(targetDir, manifest);
@@ -10453,18 +10228,18 @@ function getInstalledPlugin(configDir, name) {
10453
10228
  }
10454
10229
  function listInstalledPlugins(configDir) {
10455
10230
  const root = pluginRoot(configDir);
10456
- if (!existsSync18(root)) return [];
10231
+ if (!existsSync16(root)) return [];
10457
10232
  const state2 = loadState(configDir);
10458
10233
  const out = [];
10459
10234
  for (const entry of readdirSync7(root)) {
10460
- const dir = join13(root, entry);
10235
+ const dir = join11(root, entry);
10461
10236
  try {
10462
10237
  if (!statSync7(dir).isDirectory()) continue;
10463
10238
  } catch {
10464
10239
  continue;
10465
10240
  }
10466
10241
  const manifestPath = pluginManifestPath(dir);
10467
- if (!existsSync18(manifestPath)) continue;
10242
+ if (!existsSync16(manifestPath)) continue;
10468
10243
  try {
10469
10244
  const manifest = readPluginManifest(manifestPath);
10470
10245
  const contentHash = hashPluginContent(dir, manifest);
@@ -10502,9 +10277,9 @@ function activePlugins(configDir) {
10502
10277
  function uniqueDirs(plugin, rels) {
10503
10278
  const dirs = /* @__PURE__ */ new Set();
10504
10279
  for (const rel of rels) {
10505
- const abs = resolve7(plugin.dir, rel);
10506
- if (!abs.startsWith(resolve7(plugin.dir))) continue;
10507
- if (existsSync18(abs)) dirs.add(statSync7(abs).isDirectory() ? abs : dirname8(abs));
10280
+ const abs = resolve6(plugin.dir, rel);
10281
+ if (!abs.startsWith(resolve6(plugin.dir))) continue;
10282
+ if (existsSync16(abs)) dirs.add(statSync7(abs).isDirectory() ? abs : dirname7(abs));
10508
10283
  }
10509
10284
  return [...dirs];
10510
10285
  }
@@ -10667,13 +10442,13 @@ function parseAgentConfig(raw, source, path3) {
10667
10442
  return cfg;
10668
10443
  }
10669
10444
  function loadAgentDir(dir, source) {
10670
- if (!existsSync19(dir)) return [];
10445
+ if (!existsSync17(dir)) return [];
10671
10446
  const out = [];
10672
10447
  for (const file of readdirSync8(dir)) {
10673
10448
  if (!file.endsWith(".json")) continue;
10674
- const path3 = join14(dir, file);
10449
+ const path3 = join12(dir, file);
10675
10450
  try {
10676
- const parsed = JSON.parse(readFileSync14(path3, "utf-8"));
10451
+ const parsed = JSON.parse(readFileSync13(path3, "utf-8"));
10677
10452
  const cfg = parseAgentConfig(parsed, source, path3);
10678
10453
  if (cfg) out.push(cfg);
10679
10454
  } catch {
@@ -10683,8 +10458,8 @@ function loadAgentDir(dir, source) {
10683
10458
  }
10684
10459
  function getAgentDirs(configDir, cwd = process.cwd()) {
10685
10460
  return {
10686
- user: join14(configDir ?? join14(homedir6(), CONFIG_DIR_NAME), "agents"),
10687
- project: join14(cwd, CONFIG_DIR_NAME, "agents")
10461
+ user: join12(configDir ?? join12(homedir6(), CONFIG_DIR_NAME), "agents"),
10462
+ project: join12(cwd, CONFIG_DIR_NAME, "agents")
10688
10463
  };
10689
10464
  }
10690
10465
  function listAgentConfigs(configDir, cwd = process.cwd()) {
@@ -10731,7 +10506,7 @@ var nextId = 1;
10731
10506
  var preferredAgentName = "worker";
10732
10507
  var runs = [];
10733
10508
  var MAX_RUNS = 50;
10734
- function nowIso2() {
10509
+ function nowIso() {
10735
10510
  return (/* @__PURE__ */ new Date()).toISOString();
10736
10511
  }
10737
10512
  function startAgentRun(input) {
@@ -10740,7 +10515,7 @@ function startAgentRun(input) {
10740
10515
  agentName: input.agentName,
10741
10516
  task: input.task,
10742
10517
  status: "running",
10743
- startedAt: nowIso2(),
10518
+ startedAt: nowIso(),
10744
10519
  toolNames: [],
10745
10520
  agentIndex: input.agentIndex,
10746
10521
  stopRequested: false
@@ -10758,7 +10533,7 @@ function finishAgentRun(runId, status, summary, error) {
10758
10533
  const run = runs.find((r) => r.id === runId);
10759
10534
  if (!run) return;
10760
10535
  run.status = status;
10761
- run.finishedAt = nowIso2();
10536
+ run.finishedAt = nowIso();
10762
10537
  if (summary) run.summary = summary;
10763
10538
  if (error) run.error = error;
10764
10539
  }
@@ -11184,7 +10959,7 @@ var spawnAgentTool = {
11184
10959
 
11185
10960
  // src/tools/builtin/task-manager.ts
11186
10961
  import { spawn as spawn4 } from "child_process";
11187
- import { randomUUID as randomUUID2 } from "crypto";
10962
+ import { randomUUID } from "crypto";
11188
10963
  import { platform as platform5 } from "os";
11189
10964
  var MAX_OUTPUT_CHARS = 1e4;
11190
10965
  var MAX_OUTPUT_BYTES = 64 * 1024;
@@ -11217,7 +10992,7 @@ function createTask(command, description) {
11217
10992
  }
11218
10993
  }
11219
10994
  }
11220
- const id = randomUUID2().slice(0, 8);
10995
+ const id = randomUUID().slice(0, 8);
11221
10996
  const { shell, args } = shellInvocation(command);
11222
10997
  const proc = spawn4(shell, args, {
11223
10998
  stdio: ["ignore", "pipe", "pipe"],
@@ -11399,20 +11174,20 @@ var taskStopTool = {
11399
11174
 
11400
11175
  // src/tools/builtin/git-tools.ts
11401
11176
  import { execFileSync as execFileSync2 } from "child_process";
11402
- import { existsSync as existsSync20 } from "fs";
11403
- import { join as join15 } from "path";
11177
+ import { existsSync as existsSync18 } from "fs";
11178
+ import { join as join13 } from "path";
11404
11179
  function assertGitRepo(cwd) {
11405
11180
  let dir = cwd;
11406
11181
  const root = dir.split(/[\\/]/)[0] + (dir.includes("\\") ? "\\" : "/");
11407
11182
  while (dir && dir !== root) {
11408
- if (existsSync20(join15(dir, ".git"))) return;
11409
- const parent = join15(dir, "..");
11183
+ if (existsSync18(join13(dir, ".git"))) return;
11184
+ const parent = join13(dir, "..");
11410
11185
  if (parent === dir) break;
11411
11186
  dir = parent;
11412
11187
  }
11413
11188
  throw new ToolError("git", "Not inside a git repository. Run this command from a directory under a git-tracked project.");
11414
11189
  }
11415
- function runGit2(args, cwd, maxBuffer = 10 * 1024 * 1024) {
11190
+ function runGit(args, cwd, maxBuffer = 10 * 1024 * 1024) {
11416
11191
  try {
11417
11192
  const output = execFileSync2("git", args, {
11418
11193
  cwd,
@@ -11447,11 +11222,11 @@ var gitStatusTool = {
11447
11222
  assertGitRepo(cwd);
11448
11223
  let branch = "(unknown)";
11449
11224
  try {
11450
- branch = runGit2(["symbolic-ref", "--short", "HEAD"], cwd).trim();
11225
+ branch = runGit(["symbolic-ref", "--short", "HEAD"], cwd).trim();
11451
11226
  } catch {
11452
11227
  branch = "(detached)";
11453
11228
  }
11454
- const porcelain = runGit2(["status", "--porcelain=v1", "-b"], cwd);
11229
+ const porcelain = runGit(["status", "--porcelain=v1", "-b"], cwd);
11455
11230
  const lines = porcelain.split("\n").filter((l) => l.length > 0);
11456
11231
  const branchLine = lines[0]?.startsWith("##") ? lines[0].slice(3) : `${branch}`;
11457
11232
  const fileLines = lines.slice(lines[0]?.startsWith("##") ? 1 : 0);
@@ -11538,7 +11313,7 @@ var gitDiffTool = {
11538
11313
  if (args["file"]) {
11539
11314
  gitArgs.push("--", String(args["file"]));
11540
11315
  }
11541
- const output = runGit2(gitArgs, cwd);
11316
+ const output = runGit(gitArgs, cwd);
11542
11317
  if (output.trim().length === 0) {
11543
11318
  const label = args["staged"] ? "staged" : args["commit_range"] ? String(args["commit_range"]) : "unstaged";
11544
11319
  return `No ${label} changes.`;
@@ -11600,7 +11375,7 @@ var gitLogTool = {
11600
11375
  if (args["file"]) {
11601
11376
  gitArgs.push("--", String(args["file"]));
11602
11377
  }
11603
- const output = runGit2(gitArgs, cwd);
11378
+ const output = runGit(gitArgs, cwd);
11604
11379
  if (output.trim().length === 0) {
11605
11380
  return "No commits found matching the filter.";
11606
11381
  }
@@ -11647,9 +11422,9 @@ var gitCommitTool = {
11647
11422
  throw new ToolError("git_commit", "Cannot use both stage_all and files at the same time.");
11648
11423
  }
11649
11424
  if (files.length > 0) {
11650
- runGit2(["add", "--", ...files], cwd);
11425
+ runGit(["add", "--", ...files], cwd);
11651
11426
  }
11652
- const statusBefore = runGit2(["status", "--porcelain"], cwd);
11427
+ const statusBefore = runGit(["status", "--porcelain"], cwd);
11653
11428
  const hasStaged = statusBefore.split("\n").some((l) => l.length > 0 && l[0] !== " " && l[0] !== "?");
11654
11429
  const hasUnstaged = statusBefore.split("\n").some((l) => l.length > 1 && l[1] !== " " && l[1] !== "?");
11655
11430
  if (!hasStaged && !stageAll) {
@@ -11660,9 +11435,9 @@ var gitCommitTool = {
11660
11435
  }
11661
11436
  const commitArgs = ["commit", "-m", message];
11662
11437
  if (stageAll) commitArgs.splice(1, 0, "-a");
11663
- const commitOutput = runGit2(commitArgs, cwd);
11664
- const hash = runGit2(["rev-parse", "--short", "HEAD"], cwd).trim();
11665
- const summary = runGit2(["log", "-1", "--pretty=format:%h %s"], cwd).trim();
11438
+ const commitOutput = runGit(commitArgs, cwd);
11439
+ const hash = runGit(["rev-parse", "--short", "HEAD"], cwd).trim();
11440
+ const summary = runGit(["log", "-1", "--pretty=format:%h %s"], cwd).trim();
11666
11441
  return `\u2713 Committed ${hash}
11667
11442
  ${summary}
11668
11443
 
@@ -11671,9 +11446,9 @@ ${commitOutput.trim()}`;
11671
11446
  };
11672
11447
 
11673
11448
  // src/tools/builtin/notebook-edit.ts
11674
- import { readFileSync as readFileSync15, existsSync as existsSync21 } from "fs";
11449
+ import { readFileSync as readFileSync14, existsSync as existsSync19 } from "fs";
11675
11450
  import { writeFile } from "fs/promises";
11676
- import { resolve as resolve8, extname as extname2 } from "path";
11451
+ import { resolve as resolve7, extname as extname2 } from "path";
11677
11452
  var notebookEditTool = {
11678
11453
  definition: {
11679
11454
  name: "notebook_edit",
@@ -11722,14 +11497,14 @@ var notebookEditTool = {
11722
11497
  if (!Number.isInteger(cellIndexRaw) || cellIndexRaw < 1) {
11723
11498
  throw new ToolError("notebook_edit", "cell_index must be a positive integer (1-based)");
11724
11499
  }
11725
- const absPath = resolve8(filePath);
11500
+ const absPath = resolve7(filePath);
11726
11501
  if (extname2(absPath).toLowerCase() !== ".ipynb") {
11727
11502
  throw new ToolError("notebook_edit", "path must point to a .ipynb file");
11728
11503
  }
11729
- if (!existsSync21(absPath)) {
11504
+ if (!existsSync19(absPath)) {
11730
11505
  throw new ToolError("notebook_edit", `Notebook not found: ${filePath}`);
11731
11506
  }
11732
- const raw = readFileSync15(absPath, "utf-8");
11507
+ const raw = readFileSync14(absPath, "utf-8");
11733
11508
  const nb = parseNotebook(raw);
11734
11509
  const cellIdx0 = cellIndexRaw - 1;
11735
11510
  undoStack.push(absPath, `notebook_edit (${action}): ${filePath}`);
@@ -12055,6 +11830,8 @@ ${lines.join("\n")}`;
12055
11830
  };
12056
11831
 
12057
11832
  // src/tools/builtin/recall-memory.ts
11833
+ import { join as join14 } from "path";
11834
+ import { homedir as homedir7 } from "os";
12058
11835
  function formatHit(h, i) {
12059
11836
  const ts = h.chunk.timestamp.slice(0, 16).replace("T", " ");
12060
11837
  const title = h.chunk.sessionTitle ? ` \xB7 ${h.chunk.sessionTitle}` : "";
@@ -12067,7 +11844,7 @@ function formatHit(h, i) {
12067
11844
  var recallMemoryTool = {
12068
11845
  definition: {
12069
11846
  name: "recall_memory",
12070
- 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".',
11847
+ 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".',
12071
11848
  parameters: {
12072
11849
  query: {
12073
11850
  type: "string",
@@ -12104,9 +11881,21 @@ var recallMemoryTool = {
12104
11881
  const excludeCurrent = Boolean(args["excludeCurrentSession"]);
12105
11882
  const currentId = args["currentSessionId"] ? String(args["currentSessionId"]) : void 0;
12106
11883
  const minScore = args["minScore"] !== void 0 ? Number(args["minScore"]) : 0.25;
11884
+ const configDir = join14(homedir7(), CONFIG_DIR_NAME);
11885
+ let persistentSection = "";
11886
+ try {
11887
+ const persistentHits = searchPersistentMemories(configDir, query, process.cwd(), 3);
11888
+ if (persistentHits.length > 0) {
11889
+ touchMemoryReferences(configDir, persistentHits.map((h) => h.entry.id));
11890
+ persistentSection = `\u2500\u2500 Persistent memory (${persistentHits.length} hit(s)) \u2500\u2500
11891
+ ` + persistentHits.map((h) => `[id ${h.entry.id.slice(0, 8)} \xB7 ${h.entry.scope} \xB7 coverage ${h.score.toFixed(2)}]
11892
+ ${h.entry.content}`).join("\n\n") + "\n\n";
11893
+ }
11894
+ } catch {
11895
+ }
12107
11896
  const status = loadChatIndex();
12108
11897
  if (!status) {
12109
- 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.";
11898
+ 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.";
12110
11899
  }
12111
11900
  const hits = await searchChatMemory(query, {
12112
11901
  topK,
@@ -12114,11 +11903,11 @@ var recallMemoryTool = {
12114
11903
  excludeSessionId: excludeCurrent ? currentId : void 0
12115
11904
  });
12116
11905
  if (hits.length === 0) {
12117
- 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.`;
11906
+ 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.`;
12118
11907
  }
12119
- const header = `Found ${hits.length} memory hit(s) for "${query}" (min-score ${minScore}):
11908
+ const header = `Found ${hits.length} chat-history hit(s) for "${query}" (min-score ${minScore}):
12120
11909
  `;
12121
- return header + "\n" + hits.map(formatHit).join("\n\n");
11910
+ return persistentSection + header + "\n" + hits.map(formatHit).join("\n\n");
12122
11911
  }
12123
11912
  };
12124
11913
 
@@ -12146,8 +11935,8 @@ function estimateToolDefinitionTokens(def) {
12146
11935
 
12147
11936
  // src/tools/registry.ts
12148
11937
  import { pathToFileURL } from "url";
12149
- import { existsSync as existsSync22, mkdirSync as mkdirSync10, readdirSync as readdirSync9 } from "fs";
12150
- import { join as join16 } from "path";
11938
+ import { existsSync as existsSync20, mkdirSync as mkdirSync9, readdirSync as readdirSync9 } from "fs";
11939
+ import { join as join15 } from "path";
12151
11940
  var ToolRegistry = class {
12152
11941
  tools = /* @__PURE__ */ new Map();
12153
11942
  pluginToolNames = /* @__PURE__ */ new Set();
@@ -12308,9 +12097,9 @@ var ToolRegistry = class {
12308
12097
  * Returns the number of successfully loaded plugins.
12309
12098
  */
12310
12099
  async loadPlugins(pluginsDir, allowPlugins = false) {
12311
- if (!existsSync22(pluginsDir)) {
12100
+ if (!existsSync20(pluginsDir)) {
12312
12101
  try {
12313
- mkdirSync10(pluginsDir, { recursive: true });
12102
+ mkdirSync9(pluginsDir, { recursive: true });
12314
12103
  } catch {
12315
12104
  }
12316
12105
  return 0;
@@ -12334,12 +12123,12 @@ var ToolRegistry = class {
12334
12123
  process.stderr.write(
12335
12124
  `
12336
12125
  [plugins] \u26A0 Loading ${files.length} plugin(s) with FULL system privileges:
12337
- ` + files.map((f) => ` + ${join16(pluginsDir, f)}`).join("\n") + "\n\n"
12126
+ ` + files.map((f) => ` + ${join15(pluginsDir, f)}`).join("\n") + "\n\n"
12338
12127
  );
12339
12128
  let loaded = 0;
12340
12129
  for (const file of files) {
12341
12130
  try {
12342
- const fileUrl = pathToFileURL(join16(pluginsDir, file)).href;
12131
+ const fileUrl = pathToFileURL(join15(pluginsDir, file)).href;
12343
12132
  const mod = await import(fileUrl);
12344
12133
  const tool = mod.tool ?? mod.default?.tool ?? mod.default;
12345
12134
  if (!tool || typeof tool.execute !== "function" || !tool.definition?.name) {
@@ -12501,7 +12290,7 @@ var McpClient = class {
12501
12290
  // 内部方法:JSON-RPC 通信
12502
12291
  // ══════════════════════════════════════════════════════════════════
12503
12292
  sendRequest(method, params) {
12504
- return new Promise((resolve14, reject) => {
12293
+ return new Promise((resolve13, reject) => {
12505
12294
  if (!this.process?.stdin?.writable) {
12506
12295
  return reject(new Error(`MCP server [${this.serverId}] stdin not writable`));
12507
12296
  }
@@ -12525,7 +12314,7 @@ var McpClient = class {
12525
12314
  this.pendingRequests.set(id, {
12526
12315
  resolve: (result) => {
12527
12316
  cleanup();
12528
- resolve14(result);
12317
+ resolve13(result);
12529
12318
  },
12530
12319
  reject: (error) => {
12531
12320
  cleanup();
@@ -12602,13 +12391,13 @@ var McpClient = class {
12602
12391
  }
12603
12392
  /** Promise 超时包装 */
12604
12393
  withTimeout(promise, ms, label) {
12605
- return new Promise((resolve14, reject) => {
12394
+ return new Promise((resolve13, reject) => {
12606
12395
  const timer = setTimeout(() => {
12607
12396
  reject(new Error(`MCP [${this.serverId}] ${label} timed out after ${ms}ms`));
12608
12397
  }, ms);
12609
12398
  promise.then((val) => {
12610
12399
  clearTimeout(timer);
12611
- resolve14(val);
12400
+ resolve13(val);
12612
12401
  }).catch((err) => {
12613
12402
  clearTimeout(timer);
12614
12403
  reject(err);
@@ -12885,11 +12674,11 @@ var McpManager = class {
12885
12674
  };
12886
12675
 
12887
12676
  // src/skills/manager.ts
12888
- import { existsSync as existsSync23, readdirSync as readdirSync10, mkdirSync as mkdirSync11, statSync as statSync8 } from "fs";
12889
- import { join as join17 } from "path";
12677
+ import { existsSync as existsSync21, readdirSync as readdirSync10, mkdirSync as mkdirSync10, statSync as statSync8 } from "fs";
12678
+ import { join as join16 } from "path";
12890
12679
 
12891
12680
  // src/skills/types.ts
12892
- import { readFileSync as readFileSync16 } from "fs";
12681
+ import { readFileSync as readFileSync15 } from "fs";
12893
12682
  import { basename as basename7 } from "path";
12894
12683
  function parseSimpleYaml(yaml) {
12895
12684
  const result = {};
@@ -12911,7 +12700,7 @@ function parseYamlArray(value) {
12911
12700
  function parseSkillFile(filePath) {
12912
12701
  let raw;
12913
12702
  try {
12914
- raw = readFileSync16(filePath, "utf-8");
12703
+ raw = readFileSync15(filePath, "utf-8");
12915
12704
  } catch {
12916
12705
  return null;
12917
12706
  }
@@ -12959,10 +12748,10 @@ var SkillManager = class {
12959
12748
  return this.skills.size;
12960
12749
  }
12961
12750
  loadSkillDir(dir, createIfMissing) {
12962
- if (!existsSync23(dir)) {
12751
+ if (!existsSync21(dir)) {
12963
12752
  if (createIfMissing) {
12964
12753
  try {
12965
- mkdirSync11(dir, { recursive: true });
12754
+ mkdirSync10(dir, { recursive: true });
12966
12755
  } catch {
12967
12756
  }
12968
12757
  }
@@ -12976,14 +12765,14 @@ var SkillManager = class {
12976
12765
  }
12977
12766
  for (const entry of entries) {
12978
12767
  let filePath;
12979
- const fullPath = join17(dir, entry);
12768
+ const fullPath = join16(dir, entry);
12980
12769
  if (entry.endsWith(".md")) {
12981
12770
  filePath = fullPath;
12982
12771
  } else {
12983
12772
  try {
12984
12773
  if (statSync8(fullPath).isDirectory()) {
12985
- const skillMd = join17(fullPath, "SKILL.md");
12986
- if (existsSync23(skillMd)) filePath = skillMd;
12774
+ const skillMd = join16(fullPath, "SKILL.md");
12775
+ if (existsSync21(skillMd)) filePath = skillMd;
12987
12776
  else continue;
12988
12777
  } else {
12989
12778
  continue;
@@ -13040,9 +12829,9 @@ var SkillManager = class {
13040
12829
  };
13041
12830
 
13042
12831
  // src/web/tool-executor-web.ts
13043
- import { randomUUID as randomUUID3 } from "crypto";
12832
+ import { randomUUID as randomUUID2 } from "crypto";
13044
12833
  import { tmpdir as tmpdir2 } from "os";
13045
- import { existsSync as existsSync24, readFileSync as readFileSync17 } from "fs";
12834
+ import { existsSync as existsSync22, readFileSync as readFileSync16 } from "fs";
13046
12835
  var ToolExecutorWeb = class _ToolExecutorWeb {
13047
12836
  constructor(registry, ws) {
13048
12837
  this.registry = registry;
@@ -13112,33 +12901,33 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
13112
12901
  }
13113
12902
  /** Resolve a pending confirm from client response */
13114
12903
  resolveConfirm(requestId, approved) {
13115
- const resolve14 = this.pendingConfirms.get(requestId);
13116
- if (resolve14) {
12904
+ const resolve13 = this.pendingConfirms.get(requestId);
12905
+ if (resolve13) {
13117
12906
  this.clearPendingTimer(requestId);
13118
12907
  this.pendingConfirms.delete(requestId);
13119
12908
  this.confirming = false;
13120
- resolve14(approved);
12909
+ resolve13(approved);
13121
12910
  }
13122
12911
  }
13123
12912
  /** Resolve a pending batch confirm from client response */
13124
12913
  resolveBatchConfirm(requestId, decision) {
13125
- const resolve14 = this.pendingBatchConfirms.get(requestId);
13126
- if (resolve14) {
12914
+ const resolve13 = this.pendingBatchConfirms.get(requestId);
12915
+ if (resolve13) {
13127
12916
  this.clearPendingTimer(requestId);
13128
12917
  this.pendingBatchConfirms.delete(requestId);
13129
12918
  this.confirming = false;
13130
12919
  if (decision === "all" || decision === "none") {
13131
- resolve14(decision);
12920
+ resolve13(decision);
13132
12921
  } else {
13133
- resolve14(new Set(decision));
12922
+ resolve13(new Set(decision));
13134
12923
  }
13135
12924
  }
13136
12925
  }
13137
12926
  /** Cancel all pending confirms (e.g., on disconnect) */
13138
12927
  cancelAll() {
13139
- for (const resolve14 of this.pendingConfirms.values()) resolve14(false);
12928
+ for (const resolve13 of this.pendingConfirms.values()) resolve13(false);
13140
12929
  this.pendingConfirms.clear();
13141
- for (const resolve14 of this.pendingBatchConfirms.values()) resolve14("none");
12930
+ for (const resolve13 of this.pendingBatchConfirms.values()) resolve13("none");
13142
12931
  this.pendingBatchConfirms.clear();
13143
12932
  this.confirming = false;
13144
12933
  }
@@ -13181,9 +12970,9 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
13181
12970
  if (call.name === "write_file") {
13182
12971
  const filePath = String(call.arguments["path"] ?? "");
13183
12972
  const newContent = String(call.arguments["content"] ?? "");
13184
- if (filePath && existsSync24(filePath)) {
12973
+ if (filePath && existsSync22(filePath)) {
13185
12974
  try {
13186
- const old = readFileSync17(filePath, "utf-8");
12975
+ const old = readFileSync16(filePath, "utf-8");
13187
12976
  return renderDiff(old, newContent, { filePath });
13188
12977
  } catch {
13189
12978
  }
@@ -13203,7 +12992,7 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
13203
12992
  }
13204
12993
  /** WebSocket-based confirm — sends request, returns Promise resolved by client */
13205
12994
  confirm(call, level) {
13206
- const requestId = randomUUID3();
12995
+ const requestId = randomUUID2();
13207
12996
  this.confirming = true;
13208
12997
  const msg = {
13209
12998
  type: "confirm_request",
@@ -13214,8 +13003,8 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
13214
13003
  diff: this.getDiffPreview(call)
13215
13004
  };
13216
13005
  this.send(msg);
13217
- return new Promise((resolve14) => {
13218
- this.pendingConfirms.set(requestId, resolve14);
13006
+ return new Promise((resolve13) => {
13007
+ this.pendingConfirms.set(requestId, resolve13);
13219
13008
  this.pendingTimers.set(requestId, setTimeout(() => {
13220
13009
  if (this.pendingConfirms.has(requestId)) {
13221
13010
  this.resolveConfirm(requestId, false);
@@ -13225,7 +13014,7 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
13225
13014
  }
13226
13015
  /** WebSocket-based batch confirm */
13227
13016
  batchConfirm(calls) {
13228
- const requestId = randomUUID3();
13017
+ const requestId = randomUUID2();
13229
13018
  this.confirming = true;
13230
13019
  const files = calls.map((call, i) => ({
13231
13020
  index: i + 1,
@@ -13239,8 +13028,8 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
13239
13028
  files
13240
13029
  };
13241
13030
  this.send(msg);
13242
- return new Promise((resolve14) => {
13243
- this.pendingBatchConfirms.set(requestId, resolve14);
13031
+ return new Promise((resolve13) => {
13032
+ this.pendingBatchConfirms.set(requestId, resolve13);
13244
13033
  this.pendingTimers.set(requestId, setTimeout(() => {
13245
13034
  if (this.pendingBatchConfirms.has(requestId)) {
13246
13035
  this.resolveBatchConfirm(requestId, "none");
@@ -13487,16 +13276,16 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
13487
13276
  };
13488
13277
 
13489
13278
  // src/repl/dev-state.ts
13490
- import { existsSync as existsSync25, readFileSync as readFileSync18, unlinkSync as unlinkSync5, mkdirSync as mkdirSync12 } from "fs";
13491
- import { join as join18 } from "path";
13492
- import { homedir as homedir7 } from "os";
13279
+ import { existsSync as existsSync23, readFileSync as readFileSync17, unlinkSync as unlinkSync5, mkdirSync as mkdirSync11 } from "fs";
13280
+ import { join as join17 } from "path";
13281
+ import { homedir as homedir8 } from "os";
13493
13282
  function getDevStatePath() {
13494
- return join18(homedir7(), CONFIG_DIR_NAME, DEV_STATE_FILE_NAME);
13283
+ return join17(homedir8(), CONFIG_DIR_NAME, DEV_STATE_FILE_NAME);
13495
13284
  }
13496
13285
  function loadDevState() {
13497
13286
  const path3 = getDevStatePath();
13498
- if (!existsSync25(path3)) return null;
13499
- const content = readFileSync18(path3, "utf-8").trim();
13287
+ if (!existsSync23(path3)) return null;
13288
+ const content = readFileSync17(path3, "utf-8").trim();
13500
13289
  return content || null;
13501
13290
  }
13502
13291
 
@@ -13521,7 +13310,7 @@ Working directory: ${process.cwd()}`;
13521
13310
  }
13522
13311
  stableParts.push(AGENTIC_BEHAVIOR_GUIDELINE);
13523
13312
  if (ctx.configDir) {
13524
- const memory = loadMemoryContent(ctx.configDir);
13313
+ const memory = loadMemoryContent(ctx.configDir, ctx.memoryMaxChars);
13525
13314
  if (memory) stableParts.push(`# Persistent Memory
13526
13315
 
13527
13316
  ${memory.content}`);
@@ -13553,8 +13342,8 @@ ${ctx.activeSkill.content}`);
13553
13342
  if (ctx.planMode) stableParts.push(PLAN_MODE_SYSTEM_ADDON);
13554
13343
  return { stable: stableParts.join("\n\n---\n\n"), volatile };
13555
13344
  }
13556
- function loadMemoryContent(configDir) {
13557
- return formatMemoryForPrompt(configDir, process.cwd());
13345
+ function loadMemoryContent(configDir, maxChars) {
13346
+ return formatMemoryForPrompt(configDir, process.cwd(), maxChars);
13558
13347
  }
13559
13348
 
13560
13349
  // src/core/pricing.ts
@@ -13753,8 +13542,8 @@ function autoTrimSessionIfNeeded(session, sizeLimit = SESSION_SIZE_LIMIT) {
13753
13542
  }
13754
13543
 
13755
13544
  // src/core/context-files.ts
13756
- import { existsSync as existsSync26, readFileSync as readFileSync19 } from "fs";
13757
- import { join as join19, relative as relative4, resolve as resolve9 } from "path";
13545
+ import { existsSync as existsSync24, readFileSync as readFileSync18 } from "fs";
13546
+ import { join as join18, relative as relative4, resolve as resolve8 } from "path";
13758
13547
  function uniqueNonEmpty(values) {
13759
13548
  const seen = /* @__PURE__ */ new Set();
13760
13549
  const out = [];
@@ -13777,14 +13566,14 @@ function displayPath(filePath, cwd) {
13777
13566
  return filePath;
13778
13567
  }
13779
13568
  function isInsideOrEqual(parent, child) {
13780
- const rel = relative4(resolve9(parent), resolve9(child));
13569
+ const rel = relative4(resolve8(parent), resolve8(child));
13781
13570
  return rel === "" || !!rel && !rel.startsWith("..") && !rel.startsWith("/") && !rel.startsWith("\\");
13782
13571
  }
13783
13572
  function readContextFile(level, filePath, cwd, maxBytes) {
13784
13573
  const name = filePath.split(/[\\/]/).pop() ?? filePath;
13785
13574
  const shown = displayPath(filePath, cwd);
13786
13575
  try {
13787
- const raw = readFileSync19(filePath);
13576
+ const raw = readFileSync18(filePath);
13788
13577
  const byteCount = raw.byteLength;
13789
13578
  if (byteCount === 0) {
13790
13579
  return {
@@ -13831,8 +13620,8 @@ function readContextFile(level, filePath, cwd, maxBytes) {
13831
13620
  function findFirstContextFile(level, dir, cwd, candidates, maxBytes) {
13832
13621
  const skipped = [];
13833
13622
  for (const candidate of candidates) {
13834
- const filePath = join19(dir, candidate);
13835
- if (!existsSync26(filePath)) continue;
13623
+ const filePath = join18(dir, candidate);
13624
+ if (!existsSync24(filePath)) continue;
13836
13625
  const result = readContextFile(level, filePath, cwd, maxBytes);
13837
13626
  if (result.skipped) skipped.push(result.skipped);
13838
13627
  if (result.layer) return { layer: result.layer, skipped };
@@ -13840,7 +13629,7 @@ function findFirstContextFile(level, dir, cwd, candidates, maxBytes) {
13840
13629
  return { layer: null, skipped };
13841
13630
  }
13842
13631
  function loadContextFiles(options) {
13843
- const cwd = resolve9(options.cwd);
13632
+ const cwd = resolve8(options.cwd);
13844
13633
  const maxBytes = options.maxBytes ?? CONTEXT_FILE_MAX_BYTES;
13845
13634
  const candidates = options.candidates ?? buildContextFileCandidates(options.fallbackFilenames);
13846
13635
  const skipped = [];
@@ -13848,7 +13637,7 @@ function loadContextFiles(options) {
13848
13637
  return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
13849
13638
  }
13850
13639
  if (options.setting && options.setting !== "auto") {
13851
- const filePath = resolve9(cwd, String(options.setting));
13640
+ const filePath = resolve8(cwd, String(options.setting));
13852
13641
  if (!isInsideOrEqual(cwd, filePath)) {
13853
13642
  skipped.push({
13854
13643
  level: "single",
@@ -13859,7 +13648,7 @@ function loadContextFiles(options) {
13859
13648
  });
13860
13649
  return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
13861
13650
  }
13862
- if (!existsSync26(filePath)) {
13651
+ if (!existsSync24(filePath)) {
13863
13652
  skipped.push({
13864
13653
  level: "single",
13865
13654
  filePath,
@@ -13892,7 +13681,7 @@ function loadContextFiles(options) {
13892
13681
  skipped.push(...result.skipped);
13893
13682
  if (result.layer) layers.push(result.layer);
13894
13683
  }
13895
- if (resolve9(options.cwd) !== resolve9(options.projectRoot)) {
13684
+ if (resolve8(options.cwd) !== resolve8(options.projectRoot)) {
13896
13685
  const result = findFirstContextFile("local", options.cwd, cwd, candidates, maxBytes);
13897
13686
  skipped.push(...result.skipped);
13898
13687
  if (result.layer) layers.push(result.layer);
@@ -13910,12 +13699,12 @@ function loadContextFiles(options) {
13910
13699
  }
13911
13700
 
13912
13701
  // src/web/session-handler.ts
13913
- import { mkdirSync as mkdirSync15, createWriteStream as createWriteStream2 } from "fs";
13914
- import { join as join26, dirname as dirname10 } from "path";
13702
+ import { mkdirSync as mkdirSync14, createWriteStream as createWriteStream2 } from "fs";
13703
+ import { dirname as dirname9 } from "path";
13915
13704
 
13916
13705
  // src/repl/commands/project-init.ts
13917
- import { existsSync as existsSync27, readFileSync as readFileSync20, readdirSync as readdirSync11, statSync as statSync9 } from "fs";
13918
- import { join as join20 } from "path";
13706
+ import { existsSync as existsSync25, readFileSync as readFileSync19, readdirSync as readdirSync11, statSync as statSync9 } from "fs";
13707
+ import { join as join19 } from "path";
13919
13708
  var SCAN_SKIP_DIRS = /* @__PURE__ */ new Set([
13920
13709
  "node_modules",
13921
13710
  ".git",
@@ -13959,11 +13748,11 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
13959
13748
  const sorted = filtered.sort((a, b) => {
13960
13749
  let aIsDir = false, bIsDir = false;
13961
13750
  try {
13962
- aIsDir = statSync9(join20(d, a)).isDirectory();
13751
+ aIsDir = statSync9(join19(d, a)).isDirectory();
13963
13752
  } catch {
13964
13753
  }
13965
13754
  try {
13966
- bIsDir = statSync9(join20(d, b)).isDirectory();
13755
+ bIsDir = statSync9(join19(d, b)).isDirectory();
13967
13756
  } catch {
13968
13757
  }
13969
13758
  if (aIsDir !== bIsDir) return aIsDir ? -1 : 1;
@@ -13971,7 +13760,7 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
13971
13760
  });
13972
13761
  for (let i = 0; i < sorted.length && count < maxEntries; i++) {
13973
13762
  const name = sorted[i];
13974
- const fullPath = join20(d, name);
13763
+ const fullPath = join19(d, name);
13975
13764
  const isLast = i === sorted.length - 1;
13976
13765
  const connector = isLast ? "+-- " : "|-- ";
13977
13766
  let isDir;
@@ -13998,7 +13787,7 @@ function scanProject(cwd) {
13998
13787
  configFiles: [],
13999
13788
  directoryStructure: ""
14000
13789
  };
14001
- const check = (file) => existsSync27(join20(cwd, file));
13790
+ const check = (file) => existsSync25(join19(cwd, file));
14002
13791
  const configCandidates = [
14003
13792
  "package.json",
14004
13793
  "tsconfig.json",
@@ -14025,7 +13814,7 @@ function scanProject(cwd) {
14025
13814
  info.type = "node";
14026
13815
  info.language = check("tsconfig.json") ? "TypeScript" : "JavaScript";
14027
13816
  try {
14028
- const pkg = JSON.parse(readFileSync20(join20(cwd, "package.json"), "utf-8"));
13817
+ const pkg = JSON.parse(readFileSync19(join19(cwd, "package.json"), "utf-8"));
14029
13818
  const scripts = pkg.scripts ?? {};
14030
13819
  info.buildCommand = scripts.build ? "npm run build" : void 0;
14031
13820
  info.testCommand = scripts.test ? "npm test" : void 0;
@@ -14674,7 +14463,7 @@ function resolveRoleProviders(roles, lookup, defaultProvider, defaultModel, avai
14674
14463
  }
14675
14464
 
14676
14465
  // src/hub/persist.ts
14677
- import { join as join21 } from "path";
14466
+ import { join as join20 } from "path";
14678
14467
  function discussionToMessages(state2) {
14679
14468
  const out = [];
14680
14469
  const t0 = state2.messages[0]?.timestamp ?? /* @__PURE__ */ new Date();
@@ -14712,7 +14501,7 @@ async function persistDiscussion(state2, config, defaultProvider, defaultModel)
14712
14501
  session.title = `[Hub] ${state2.topic.slice(0, 48)}`.replace(/\n/g, " ");
14713
14502
  session.titleAiGenerated = true;
14714
14503
  await sm.save();
14715
- return { id: session.id, path: join21(config.getHistoryDir(), `${session.id}.json`) };
14504
+ return { id: session.id, path: join20(config.getHistoryDir(), `${session.id}.json`) };
14716
14505
  }
14717
14506
 
14718
14507
  // src/web/commands/provider-commands.ts
@@ -14800,8 +14589,8 @@ async function handlePlan(args, ctx) {
14800
14589
  }
14801
14590
 
14802
14591
  // src/web/commands/_shared.ts
14803
- import { existsSync as existsSync30, readFileSync as readFileSync22, writeFileSync as writeFileSync3, mkdirSync as mkdirSync14, readdirSync as readdirSync13, statSync as statSync12, createWriteStream } from "fs";
14804
- import { join as join24, resolve as resolve10, dirname as dirname9 } from "path";
14592
+ import { existsSync as existsSync28, readFileSync as readFileSync21, writeFileSync as writeFileSync3, mkdirSync as mkdirSync13, readdirSync as readdirSync13, statSync as statSync12, createWriteStream } from "fs";
14593
+ import { join as join23, resolve as resolve9, dirname as dirname8 } from "path";
14805
14594
 
14806
14595
  // src/cli/review-prompts.ts
14807
14596
  function buildReviewPrompt(diff, gitContextStr, detailed) {
@@ -14873,34 +14662,34 @@ function readGitDiff(options = {}) {
14873
14662
  }
14874
14663
 
14875
14664
  // src/diagnostics/doctor-report.ts
14876
- import { existsSync as existsSync29, statSync as statSync11 } from "fs";
14877
- import { join as join23 } from "path";
14665
+ import { existsSync as existsSync27, statSync as statSync11 } from "fs";
14666
+ import { join as join22 } from "path";
14878
14667
  import { arch as arch2, platform as platform7, release as release2 } from "os";
14879
14668
 
14880
14669
  // src/diagnostics/crash-log.ts
14881
14670
  import {
14882
- existsSync as existsSync28,
14883
- mkdirSync as mkdirSync13,
14671
+ existsSync as existsSync26,
14672
+ mkdirSync as mkdirSync12,
14884
14673
  readdirSync as readdirSync12,
14885
- readFileSync as readFileSync21,
14674
+ readFileSync as readFileSync20,
14886
14675
  statSync as statSync10,
14887
14676
  unlinkSync as unlinkSync6,
14888
14677
  writeFileSync as writeFileSync2
14889
14678
  } from "fs";
14890
- import { join as join22 } from "path";
14891
- import { homedir as homedir8, platform as platform6, release, arch } from "os";
14679
+ import { join as join21 } from "path";
14680
+ import { homedir as homedir9, platform as platform6, release, arch } from "os";
14892
14681
  var LOGS_DIR_NAME = "logs";
14893
14682
  var CRASH_LOG_PREFIX = "crash-";
14894
14683
  function getLogsDir(configDir) {
14895
- const base = configDir ?? join22(homedir8(), CONFIG_DIR_NAME);
14896
- return join22(base, LOGS_DIR_NAME);
14684
+ const base = configDir ?? join21(homedir9(), CONFIG_DIR_NAME);
14685
+ return join21(base, LOGS_DIR_NAME);
14897
14686
  }
14898
14687
  function truncate(s, n) {
14899
14688
  return s.length <= n ? s : s.slice(0, n) + "\u2026";
14900
14689
  }
14901
14690
  function listRecentCrashes(limit = 10, configDir) {
14902
14691
  const dir = getLogsDir(configDir);
14903
- if (!existsSync28(dir)) return [];
14692
+ if (!existsSync26(dir)) return [];
14904
14693
  const files = [];
14905
14694
  let names;
14906
14695
  try {
@@ -14910,10 +14699,10 @@ function listRecentCrashes(limit = 10, configDir) {
14910
14699
  }
14911
14700
  for (const name of names) {
14912
14701
  if (!name.startsWith(CRASH_LOG_PREFIX) || !name.endsWith(".log")) continue;
14913
- const full = join22(dir, name);
14702
+ const full = join21(dir, name);
14914
14703
  try {
14915
14704
  const st = statSync10(full);
14916
- const head = readFileSync21(full, "utf-8").split("\n").slice(0, 20);
14705
+ const head = readFileSync20(full, "utf-8").split("\n").slice(0, 20);
14917
14706
  const tsLine = head.find((l) => l.startsWith("timestamp:"));
14918
14707
  const kindLine = head.find((l) => l.startsWith("kind:"));
14919
14708
  const msgIdx = head.findIndex((l) => l.trim() === "## message");
@@ -14932,8 +14721,8 @@ function listRecentCrashes(limit = 10, configDir) {
14932
14721
  return files.slice(0, limit);
14933
14722
  }
14934
14723
  function getConfigDirUsage(configDir) {
14935
- const base = configDir ?? join22(homedir8(), CONFIG_DIR_NAME);
14936
- if (!existsSync28(base)) return { totalBytes: 0, entries: [] };
14724
+ const base = configDir ?? join21(homedir9(), CONFIG_DIR_NAME);
14725
+ if (!existsSync26(base)) return { totalBytes: 0, entries: [] };
14937
14726
  const entries = [];
14938
14727
  let total = 0;
14939
14728
  let names;
@@ -14943,7 +14732,7 @@ function getConfigDirUsage(configDir) {
14943
14732
  return { totalBytes: 0, entries: [] };
14944
14733
  }
14945
14734
  for (const name of names) {
14946
- const full = join22(base, name);
14735
+ const full = join21(base, name);
14947
14736
  const bytes = dirSize(full);
14948
14737
  entries.push({ name, bytes });
14949
14738
  total += bytes;
@@ -14958,7 +14747,7 @@ function dirSize(path3) {
14958
14747
  if (!st.isDirectory()) return 0;
14959
14748
  let sum = 0;
14960
14749
  for (const name of readdirSync12(path3)) {
14961
- sum += dirSize(join22(path3, name));
14750
+ sum += dirSize(join21(path3, name));
14962
14751
  }
14963
14752
  return sum;
14964
14753
  } catch {
@@ -14968,7 +14757,7 @@ function dirSize(path3) {
14968
14757
 
14969
14758
  // src/diagnostics/doctor-report.ts
14970
14759
  function checkFile(label, path3) {
14971
- const exists = existsSync29(path3);
14760
+ const exists = existsSync27(path3);
14972
14761
  let sizeBytes = null;
14973
14762
  if (exists) {
14974
14763
  try {
@@ -15011,12 +14800,12 @@ function buildDoctorReport(options) {
15011
14800
  configDir,
15012
14801
  providers: options.providers.listAll().map((p) => ({ id: p.id, displayName: p.displayName, configured: p.configured })),
15013
14802
  files: [
15014
- checkFile("config.json", join23(configDir, "config.json")),
15015
- checkFile("memory.md", join23(configDir, MEMORY_FILE_NAME)),
15016
- checkFile("memory.jsonl", join23(configDir, "memory.jsonl")),
15017
- checkFile("dev-state.md", join23(configDir, DEV_STATE_FILE_NAME)),
15018
- checkFile("hooks-trust.json", join23(configDir, "hooks-trust.json")),
15019
- checkFile("plugin-state.json", join23(configDir, "plugins", "plugin-state.json"))
14803
+ checkFile("config.json", join22(configDir, "config.json")),
14804
+ checkFile("memory.md", join22(configDir, MEMORY_FILE_NAME)),
14805
+ checkFile("memory.jsonl", join22(configDir, "memory.jsonl")),
14806
+ checkFile("dev-state.md", join22(configDir, DEV_STATE_FILE_NAME)),
14807
+ checkFile("hooks-trust.json", join22(configDir, "hooks-trust.json")),
14808
+ checkFile("plugin-state.json", join22(configDir, "plugins", "plugin-state.json"))
15020
14809
  ],
15021
14810
  context: {
15022
14811
  enabled: contextSetting !== false,
@@ -15051,7 +14840,7 @@ function buildDoctorReport(options) {
15051
14840
  disabled: hooks.filter((h) => h.disabled).length
15052
14841
  },
15053
14842
  plugins: {
15054
- root: join23(configDir, "plugins"),
14843
+ root: join22(configDir, "plugins"),
15055
14844
  installed: plugins.length,
15056
14845
  active: activeAssets.plugins.length,
15057
14846
  invalid: plugins.filter((p) => !p.valid).length,
@@ -15066,7 +14855,7 @@ function buildDoctorReport(options) {
15066
14855
  mcp: {
15067
14856
  enabled: config.get("mcpEnabled"),
15068
14857
  configured: Object.keys(mcpServers).length,
15069
- projectConfigExists: existsSync29(join23(projectRoot, MCP_PROJECT_CONFIG_NAME)),
14858
+ projectConfigExists: existsSync27(join22(projectRoot, MCP_PROJECT_CONFIG_NAME)),
15070
14859
  statuses: options.mcpStatuses ?? []
15071
14860
  },
15072
14861
  recentCrashes: listRecentCrashes(5, configDir),
@@ -15292,12 +15081,20 @@ async function handleStatus(_args, ctx) {
15292
15081
  Cache: write=${cacheCreate} read=${cacheRead}` : "";
15293
15082
  const costLine = cost != null ? `
15294
15083
  Cost: ${formatCost(cost)}` : "";
15084
+ let memoryLine = "";
15085
+ try {
15086
+ const { countPendingMemories } = await import("./persistent-memory-ZCQKLIC5.js");
15087
+ const pending = countPendingMemories(ctx.config.getConfigDir());
15088
+ if (pending > 0) memoryLine = `
15089
+ Memory: \u26A0 ${pending} pending approval \u2014 see the Memory panel or /memory`;
15090
+ } catch {
15091
+ }
15295
15092
  ctx.send({
15296
15093
  type: "info",
15297
15094
  message: `Provider: ${ctx.currentProvider}
15298
15095
  Model: ${ctx.currentModel}
15299
15096
  Session: ${session?.id ?? "none"} (${session?.messages.length ?? 0} messages)
15300
- Tokens: in=${ctx.sessionTokenUsage.inputTokens} out=${ctx.sessionTokenUsage.outputTokens}${cacheLine}${costLine}`
15097
+ Tokens: in=${ctx.sessionTokenUsage.inputTokens} out=${ctx.sessionTokenUsage.outputTokens}${cacheLine}${costLine}${memoryLine}`
15301
15098
  });
15302
15099
  }
15303
15100
  async function handleSession(args, ctx) {
@@ -15593,7 +15390,7 @@ async function handleBranch(args, ctx) {
15593
15390
  return;
15594
15391
  }
15595
15392
  const sub = args[0]?.toLowerCase();
15596
- const resolve14 = (ref) => {
15393
+ const resolve13 = (ref) => {
15597
15394
  const r = session.resolveBranchRef(ref);
15598
15395
  if (r.ok) return r.id;
15599
15396
  if (r.reason === "ambiguous") {
@@ -15647,7 +15444,7 @@ async function handleBranch(args, ctx) {
15647
15444
  ctx.send({ type: "error", message: "Usage: /branch switch <id|title>" });
15648
15445
  return;
15649
15446
  }
15650
- const id = resolve14(ref);
15447
+ const id = resolve13(ref);
15651
15448
  if (!id) return;
15652
15449
  const ok = session.switchBranch(id);
15653
15450
  if (ok) {
@@ -15667,7 +15464,7 @@ async function handleBranch(args, ctx) {
15667
15464
  ctx.send({ type: "error", message: "Usage: /branch delete <id|title>" });
15668
15465
  return;
15669
15466
  }
15670
- const id = resolve14(ref);
15467
+ const id = resolve13(ref);
15671
15468
  if (!id) return;
15672
15469
  const ok = session.deleteBranch(id);
15673
15470
  if (ok) {
@@ -15686,7 +15483,7 @@ async function handleBranch(args, ctx) {
15686
15483
  ctx.send({ type: "error", message: "Usage: /branch rename <id|title> <new title>" });
15687
15484
  return;
15688
15485
  }
15689
- const id = resolve14(ref);
15486
+ const id = resolve13(ref);
15690
15487
  if (!id) return;
15691
15488
  const ok = session.renameBranch(id, title);
15692
15489
  if (ok) {
@@ -15704,7 +15501,7 @@ async function handleBranch(args, ctx) {
15704
15501
  ctx.send({ type: "error", message: "Usage: /branch diff <id|title>" });
15705
15502
  return;
15706
15503
  }
15707
- const id = resolve14(ref);
15504
+ const id = resolve13(ref);
15708
15505
  if (!id) return;
15709
15506
  const d = session.diffBranches(id);
15710
15507
  if (!d) {
@@ -15746,7 +15543,7 @@ async function handleBranch(args, ctx) {
15746
15543
  ctx.send({ type: "error", message: "Usage: /branch cherry-pick <source-id|title> <msg-index>" });
15747
15544
  return;
15748
15545
  }
15749
- const id = resolve14(ref);
15546
+ const id = resolve13(ref);
15750
15547
  if (!id) return;
15751
15548
  const idx = parseInt(idxStr, 10);
15752
15549
  if (Number.isNaN(idx)) {
@@ -15870,7 +15667,7 @@ async function handlePlugin(args, ctx) {
15870
15667
  lines.push("");
15871
15668
  }
15872
15669
  if (pluginTools.length > 0) {
15873
- const legacyDir = join24(configDir, PLUGINS_DIR_NAME);
15670
+ const legacyDir = join23(configDir, PLUGINS_DIR_NAME);
15874
15671
  lines.push(`**Legacy JS tools** (${pluginTools.length}):`);
15875
15672
  lines.push(`Dir: ${legacyDir}`);
15876
15673
  for (const t of pluginTools) lines.push(` \u2022 ${t.definition.name} \u2014 ${t.definition.description}`);
@@ -15994,8 +15791,8 @@ ${activated.meta.description || ""}` });
15994
15791
  }
15995
15792
  async function handleCommands(_args, ctx) {
15996
15793
  const configDir = ctx.config.getConfigDir();
15997
- const commandsDir = join24(configDir, CUSTOM_COMMANDS_DIR_NAME);
15998
- if (!existsSync30(commandsDir)) {
15794
+ const commandsDir = join23(configDir, CUSTOM_COMMANDS_DIR_NAME);
15795
+ if (!existsSync28(commandsDir)) {
15999
15796
  ctx.send({ type: "info", message: `No custom commands directory.
16000
15797
  Create: ${commandsDir}/ with .md files.` });
16001
15798
  return;
@@ -16095,7 +15892,7 @@ async function handleSecurityReview(args, ctx) {
16095
15892
  async function handleTest(args, ctx) {
16096
15893
  ctx.send({ type: "info", message: "\u{1F9EA} Running tests..." });
16097
15894
  try {
16098
- const { executeTests } = await import("./run-tests-EWMU34A4.js");
15895
+ const { executeTests } = await import("./run-tests-WCZBWQFS.js");
16099
15896
  const argStr = args.join(" ").trim();
16100
15897
  let testArgs = {};
16101
15898
  if (argStr) {
@@ -16125,9 +15922,9 @@ async function handleDiff(args, ctx) {
16125
15922
  let modifiedFiles = 0;
16126
15923
  const diffLines2 = [];
16127
15924
  for (const [filePath, { earliest }] of fileMap) {
16128
- const currentContent = existsSync30(filePath) ? (() => {
15925
+ const currentContent = existsSync28(filePath) ? (() => {
16129
15926
  try {
16130
- return readFileSync22(filePath, "utf-8");
15927
+ return readFileSync21(filePath, "utf-8");
16131
15928
  } catch {
16132
15929
  return null;
16133
15930
  }
@@ -16162,9 +15959,9 @@ async function handleDiff(args, ctx) {
16162
15959
  }
16163
15960
  async function handleInit(args, ctx) {
16164
15961
  const cwd = process.cwd();
16165
- const targetPath = join24(cwd, "AICLI.md");
15962
+ const targetPath = join23(cwd, "AICLI.md");
16166
15963
  const force = args.includes("--force");
16167
- if (existsSync30(targetPath) && !force) {
15964
+ if (existsSync28(targetPath) && !force) {
16168
15965
  ctx.send({ type: "info", message: `AICLI.md already exists at ${targetPath}
16169
15966
  Use /init --force to overwrite.` });
16170
15967
  return;
@@ -16326,9 +16123,58 @@ async function handleIndex(args, ctx) {
16326
16123
  async function handleMemory(args, ctx) {
16327
16124
  const sub = args[0];
16328
16125
  if (sub === "add" && args.length > 1) {
16329
- ctx.memoryAdd(args.slice(1).join(" "));
16126
+ const rest = args.slice(1);
16127
+ const opts = {};
16128
+ const text = [];
16129
+ for (let i = 0; i < rest.length; i++) {
16130
+ if (rest[i] === "--scope" && rest[i + 1]) {
16131
+ opts.scope = rest[++i];
16132
+ } else if (rest[i] === "--sensitivity" && rest[i + 1]) {
16133
+ opts.sensitivity = rest[++i];
16134
+ } else if (rest[i] === "--expires-at" && rest[i + 1]) {
16135
+ opts.expiresAt = rest[++i];
16136
+ } else text.push(rest[i]);
16137
+ }
16138
+ if (text.length === 0) {
16139
+ ctx.send({ type: "info", message: "Usage: /memory add [--scope personal|project] [--sensitivity low|medium|high] [--expires-at ISO] <text>" });
16140
+ return;
16141
+ }
16142
+ ctx.memoryAdd(text.join(" "), opts);
16143
+ } else if (sub === "edit") {
16144
+ const id = args[1];
16145
+ const text = args.slice(2).join(" ").trim();
16146
+ if (!id || !text) {
16147
+ ctx.send({ type: "info", message: "Usage: /memory edit <id> <new text>" });
16148
+ return;
16149
+ }
16150
+ ctx.memoryEdit(id, text);
16151
+ } else if (sub === "approve" || sub === "reject" || sub === "delete" || sub === "expire") {
16152
+ if (!args[1]) {
16153
+ ctx.send({ type: "info", message: `Usage: /memory ${sub} <id>` });
16154
+ return;
16155
+ }
16156
+ ctx.handleMemoryManage(sub, args[1], sub === "expire" ? args[2] : void 0);
16157
+ } else if (sub === "export") {
16158
+ if (args[1] === "md") {
16159
+ const { exportMemoryEntries: exportMemoryEntries2 } = await import("./persistent-memory-ZCQKLIC5.js");
16160
+ ctx.send({
16161
+ type: "export_data",
16162
+ format: "md",
16163
+ filename: `aicli-memory-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.md`,
16164
+ content: exportMemoryEntries2(ctx.config.getConfigDir(), process.cwd(), "md")
16165
+ });
16166
+ } else {
16167
+ ctx.handleMemoryManage("export");
16168
+ }
16330
16169
  } else if (sub === "clear") {
16331
- ctx.memoryClear();
16170
+ if (args[1] === "confirm") {
16171
+ ctx.memoryClear();
16172
+ } else {
16173
+ ctx.send({
16174
+ type: "info",
16175
+ message: "\u26A0 This permanently clears ALL persistent memory (a timestamped .bak backup is kept next to memory.jsonl).\nRun `/memory clear confirm` to proceed, or manage individual entries in the Memory panel."
16176
+ });
16177
+ }
16332
16178
  } else {
16333
16179
  ctx.memoryShow();
16334
16180
  }
@@ -16347,8 +16193,8 @@ Use /add-dir remove to clear.` : "No directories added.\nUsage: /add-dir <path>
16347
16193
  ctx.send({ type: "info", message: "\u2713 All added directories removed from context." });
16348
16194
  return;
16349
16195
  }
16350
- const dirPath = resolve10(sub);
16351
- if (!existsSync30(dirPath)) {
16196
+ const dirPath = resolve9(sub);
16197
+ if (!existsSync28(dirPath)) {
16352
16198
  ctx.send({ type: "error", message: `Directory not found: ${dirPath}` });
16353
16199
  return;
16354
16200
  }
@@ -16913,10 +16759,10 @@ var SessionHandler = class {
16913
16759
  return;
16914
16760
  }
16915
16761
  case "ask_user_response": {
16916
- const resolve14 = this.pendingAskUser.get(msg.requestId);
16917
- if (resolve14) {
16762
+ const resolve13 = this.pendingAskUser.get(msg.requestId);
16763
+ if (resolve13) {
16918
16764
  this.pendingAskUser.delete(msg.requestId);
16919
- resolve14(msg.answer);
16765
+ resolve13(msg.answer);
16920
16766
  }
16921
16767
  return;
16922
16768
  }
@@ -16929,10 +16775,10 @@ var SessionHandler = class {
16929
16775
  case "memory_manage":
16930
16776
  return this.handleMemoryManage(msg.action, msg.id, msg.expiresAt);
16931
16777
  case "auto_pause_response": {
16932
- const resolve14 = this.pendingAutoPause.get(msg.requestId);
16933
- if (resolve14) {
16778
+ const resolve13 = this.pendingAutoPause.get(msg.requestId);
16779
+ if (resolve13) {
16934
16780
  this.pendingAutoPause.delete(msg.requestId);
16935
- resolve14({ action: msg.action, message: msg.message });
16781
+ resolve13({ action: msg.action, message: msg.message });
16936
16782
  }
16937
16783
  return;
16938
16784
  }
@@ -16947,10 +16793,10 @@ var SessionHandler = class {
16947
16793
  this.hubOrchestrator?.abort();
16948
16794
  return;
16949
16795
  case "hub_steer": {
16950
- const resolve14 = this.pendingHubReview.get(msg.requestId);
16951
- if (resolve14) {
16796
+ const resolve13 = this.pendingHubReview.get(msg.requestId);
16797
+ if (resolve13) {
16952
16798
  this.pendingHubReview.delete(msg.requestId);
16953
- resolve14({ action: msg.action, message: msg.message });
16799
+ resolve13({ action: msg.action, message: msg.message });
16954
16800
  }
16955
16801
  return;
16956
16802
  }
@@ -16963,9 +16809,9 @@ var SessionHandler = class {
16963
16809
  runLifecycleHooks(mergePluginHooks(this.config.get("hooks") ?? void 0, this.config.getConfigDir()), "Stop", { sessionId: this.sessions.current?.id }, { configDir: this.config.getConfigDir() });
16964
16810
  this.toolExecutor.cancelAll();
16965
16811
  if (this.abortController) this.abortController.abort();
16966
- for (const resolve14 of this.pendingAskUser.values()) resolve14(null);
16812
+ for (const resolve13 of this.pendingAskUser.values()) resolve13(null);
16967
16813
  this.pendingAskUser.clear();
16968
- for (const resolve14 of this.pendingAutoPause.values()) resolve14({ action: "stop" });
16814
+ for (const resolve13 of this.pendingAutoPause.values()) resolve13({ action: "stop" });
16969
16815
  this.pendingAutoPause.clear();
16970
16816
  this.saveIfNeeded();
16971
16817
  if (this._bashSessionKey) clearBashSession(this._bashSessionKey);
@@ -17100,9 +16946,9 @@ var SessionHandler = class {
17100
16946
  this.hubOrchestrator = orchestrator;
17101
16947
  orchestrator.onEvent = (event) => this.send({ type: "hub_event", event });
17102
16948
  if (config.humanSteer) {
17103
- orchestrator.onRoundReview = ({ round, maxRounds }) => new Promise((resolve14) => {
16949
+ orchestrator.onRoundReview = ({ round, maxRounds }) => new Promise((resolve13) => {
17104
16950
  const requestId = `hubrev_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
17105
- this.pendingHubReview.set(requestId, resolve14);
16951
+ this.pendingHubReview.set(requestId, resolve13);
17106
16952
  this.send({ type: "hub_review", requestId, round, maxRounds });
17107
16953
  });
17108
16954
  }
@@ -17435,7 +17281,7 @@ Try: /compact to reduce context, /clear to reset, or switch to a larger-context
17435
17281
  this.send({ type: "response_done", content, usage });
17436
17282
  if (pendingTeeSave && isCleanDocumentBody(content)) {
17437
17283
  try {
17438
- mkdirSync15(dirname10(pendingTeeSave), { recursive: true });
17284
+ mkdirSync14(dirname9(pendingTeeSave), { recursive: true });
17439
17285
  const bodyToSave = stripOuterCodeFence(content);
17440
17286
  atomicWriteFileSync(pendingTeeSave, bodyToSave);
17441
17287
  undoStack.push(pendingTeeSave, `save_last_response (deferred): ${pendingTeeSave}`);
@@ -17460,8 +17306,8 @@ Try: /compact to reduce context, /clear to reset, or switch to a larger-context
17460
17306
  onMcpToolUsed: (name) => this.usedMcpToolNames.add(name),
17461
17307
  requestAutoPause: async ({ effectiveRound, maxToolRounds: totalRounds, toolSummary }) => {
17462
17308
  const requestId = `pause_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
17463
- const pauseResp = await new Promise((resolve14) => {
17464
- this.pendingAutoPause.set(requestId, resolve14);
17309
+ const pauseResp = await new Promise((resolve13) => {
17310
+ this.pendingAutoPause.set(requestId, resolve13);
17465
17311
  this.send({
17466
17312
  type: "auto_pause_request",
17467
17313
  requestId,
@@ -17611,7 +17457,7 @@ ${summaryContent}`,
17611
17457
  let isError = false;
17612
17458
  let summary;
17613
17459
  try {
17614
- mkdirSync15(dirname10(saveToFile), { recursive: true });
17460
+ mkdirSync14(dirname9(saveToFile), { recursive: true });
17615
17461
  fileStream = createWriteStream2(saveToFile);
17616
17462
  const teeSystemPrompt = stripToolCallReminder(systemPrompt ?? "") + CONTENT_ONLY_STREAM_REMINDER;
17617
17463
  const teeExtraMessages = extraMessages.length > 0 ? [...extraMessages, { role: "user", content: TEE_FINAL_USER_NUDGE }] : [{ role: "user", content: TEE_FINAL_USER_NUDGE }];
@@ -17640,8 +17486,8 @@ ${summaryContent}`,
17640
17486
  }
17641
17487
  if (chunk.done) break;
17642
17488
  }
17643
- await new Promise((resolve14, reject) => {
17644
- fileStream.end((err) => err ? reject(err) : resolve14());
17489
+ await new Promise((resolve13, reject) => {
17490
+ fileStream.end((err) => err ? reject(err) : resolve13());
17645
17491
  });
17646
17492
  const verdict = evaluateTeeContent(fullContent, saveToFile, priorContent);
17647
17493
  if (verdict.kind === "reject") {
@@ -17667,7 +17513,7 @@ ${summaryContent}`,
17667
17513
  } catch (err) {
17668
17514
  if (fileStream) {
17669
17515
  try {
17670
- await new Promise((resolve14) => fileStream.end(() => resolve14()));
17516
+ await new Promise((resolve13) => fileStream.end(() => resolve13()));
17671
17517
  } catch {
17672
17518
  }
17673
17519
  }
@@ -17874,7 +17720,11 @@ ${summaryContent}`,
17874
17720
  const entries = listMemoryEntries(configDir, { includeExpired: true, includeRejected: true });
17875
17721
  const content = entries.length === 0 ? "(empty \u2014 no persistent memory entries yet)" : entries.map((entry) => {
17876
17722
  const status = isMemoryExpired(entry) ? "expired" : entry.approved ? "approved" : "pending";
17877
- const source = entry.sourceSession ? `session ${entry.sourceSession.slice(0, 8)}` : entry.sourceProject ? `project ${entry.sourceProject}` : "manual/tool";
17723
+ const source = [
17724
+ entry.source ?? "unknown",
17725
+ entry.sourceSession ? `session ${entry.sourceSession.slice(0, 8)}` : "",
17726
+ entry.sourceProject ? `project ${entry.sourceProject}` : ""
17727
+ ].filter(Boolean).join(" \xB7 ");
17878
17728
  return `## ${entry.id.slice(0, 8)} \xB7 ${status} \xB7 scope:${entry.scope} \xB7 sensitivity:${entry.sensitivity}
17879
17729
  Source: ${source}
17880
17730
  ${entry.content}`;
@@ -17886,26 +17736,41 @@ ${entry.content}`;
17886
17736
  });
17887
17737
  this.sendMemoryEntries();
17888
17738
  }
17889
- memoryAdd(text) {
17739
+ memoryAdd(text, opts) {
17740
+ const asSensitivity = (v) => v === "low" || v === "medium" || v === "high" ? v : void 0;
17890
17741
  try {
17891
17742
  const entry = addMemoryEntry(this.config.getConfigDir(), text, {
17892
- scope: "personal",
17743
+ scope: parseWritableScope(opts?.scope) ?? "personal",
17744
+ source: "manual",
17745
+ sensitivity: asSensitivity(opts?.sensitivity),
17746
+ expiresAt: opts?.expiresAt,
17893
17747
  cwd: process.cwd(),
17894
17748
  redact: true
17895
17749
  });
17896
17750
  const status = entry.approved ? "approved" : "pending approval";
17897
- this.send({ type: "info", message: `\u{1F4DD} Memory entry ${entry.id.slice(0, 8)} added (${status}).` });
17751
+ this.send({ type: "info", message: `\u{1F4DD} Memory entry ${entry.id.slice(0, 8)} added (${status}, scope:${entry.scope}).` });
17898
17752
  this.sendMemoryEntries();
17899
17753
  } catch (err) {
17900
17754
  this.send({ type: "error", message: `Failed to write memory: ${err.message}` });
17901
17755
  }
17902
17756
  }
17757
+ memoryEdit(id, text) {
17758
+ try {
17759
+ const entry = updateMemoryEntry(this.config.getConfigDir(), id, text, { source: "manual", redact: true });
17760
+ const status = entry.approved ? "approved" : `pending approval \u2014 approve it in the Memory panel or /memory approve ${entry.id.slice(0, 8)}`;
17761
+ this.send({ type: "info", message: `\u{1F4DD} Memory ${entry.id.slice(0, 8)} updated (${status}).` });
17762
+ this.sendMemoryEntries();
17763
+ } catch (err) {
17764
+ this.send({ type: "error", message: `Failed to update memory: ${err.message}` });
17765
+ }
17766
+ }
17903
17767
  memoryClear() {
17904
17768
  const configDir = this.config.getConfigDir();
17905
17769
  try {
17906
- atomicWriteFileSync(memoryStorePath(configDir), "");
17907
- atomicWriteFileSync(join26(configDir, MEMORY_FILE_NAME), "");
17908
- this.send({ type: "info", message: "\u{1F5D1}\uFE0F Persistent memory cleared." });
17770
+ const result = clearMemoryEntries(configDir);
17771
+ const backupNote = result.backupPath ? `
17772
+ Backup saved: ${result.backupPath}` : "";
17773
+ this.send({ type: "info", message: `\u{1F5D1}\uFE0F Persistent memory cleared (${result.cleared} entries).${backupNote}` });
17909
17774
  this.sendMemoryEntries();
17910
17775
  } catch (err) {
17911
17776
  this.send({ type: "error", message: `Failed to clear memory: ${err.message}` });
@@ -17921,6 +17786,7 @@ ${entry.content}`;
17921
17786
  id: entry.id,
17922
17787
  content: entry.content,
17923
17788
  scope: entry.scope,
17789
+ source: entry.source,
17924
17790
  sourceSession: entry.sourceSession,
17925
17791
  sourceProject: entry.sourceProject,
17926
17792
  createdAt: entry.createdAt,
@@ -17970,7 +17836,7 @@ ${entry.content}`;
17970
17836
  return;
17971
17837
  }
17972
17838
  try {
17973
- const { searchChatMemory: searchChatMemory2, loadChatIndex: loadChatIndex2 } = await import("./chat-index-WYXYD7YP.js");
17839
+ const { searchChatMemory: searchChatMemory2, loadChatIndex: loadChatIndex2 } = await import("./chat-index-ML5FF5L6.js");
17974
17840
  const loaded = loadChatIndex2();
17975
17841
  if (!loaded || loaded.idx.chunks.length === 0) {
17976
17842
  this.send({ type: "memory_hits", query: q, hits: [], indexMissing: true });
@@ -18006,7 +17872,7 @@ ${entry.content}`;
18006
17872
  }
18007
17873
  async handleMemoryStatus() {
18008
17874
  try {
18009
- const { getChatIndexStatus } = await import("./chat-index-WYXYD7YP.js");
17875
+ const { getChatIndexStatus } = await import("./chat-index-ML5FF5L6.js");
18010
17876
  const s = getChatIndexStatus();
18011
17877
  this.send({
18012
17878
  type: "memory_status",
@@ -18031,7 +17897,7 @@ ${entry.content}`;
18031
17897
  type: "info",
18032
17898
  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"
18033
17899
  });
18034
- const { buildChatIndex } = await import("./chat-index-WYXYD7YP.js");
17900
+ const { buildChatIndex } = await import("./chat-index-ML5FF5L6.js");
18035
17901
  const stats = await buildChatIndex({
18036
17902
  full,
18037
17903
  onProgress: (p) => {
@@ -18105,6 +17971,7 @@ ${entry.content}`;
18105
17971
  activeSkill,
18106
17972
  planMode: this.planMode,
18107
17973
  configDir: this.config.getConfigDir(),
17974
+ memoryMaxChars: this.config.get("memory")?.maxChars,
18108
17975
  userProfile: this.config.get("userProfile")
18109
17976
  });
18110
17977
  let stable = baseStable;
@@ -18186,8 +18053,8 @@ async function setupProxy(configProxy) {
18186
18053
  }
18187
18054
 
18188
18055
  // src/web/auth.ts
18189
- import { existsSync as existsSync32, readFileSync as readFileSync24, writeFileSync as writeFileSync5, mkdirSync as mkdirSync16, readdirSync as readdirSync15, copyFileSync } from "fs";
18190
- import { isAbsolute as isAbsolute3, join as join27, relative as relative5 } from "path";
18056
+ import { existsSync as existsSync30, readFileSync as readFileSync23, writeFileSync as writeFileSync5, mkdirSync as mkdirSync15, readdirSync as readdirSync15, copyFileSync } from "fs";
18057
+ import { isAbsolute as isAbsolute3, join as join26, relative as relative5 } from "path";
18191
18058
  import { createHmac, randomBytes, timingSafeEqual, pbkdf2Sync } from "crypto";
18192
18059
  var USERS_FILE = "users.json";
18193
18060
  var TOKEN_EXPIRY_HOURS = 24;
@@ -18202,7 +18069,7 @@ var AuthManager = class {
18202
18069
  db;
18203
18070
  constructor(baseDir) {
18204
18071
  this.baseDir = baseDir;
18205
- this.usersFile = join27(baseDir, USERS_FILE);
18072
+ this.usersFile = join26(baseDir, USERS_FILE);
18206
18073
  this.db = this.loadOrCreate();
18207
18074
  }
18208
18075
  // ── Public API ─────────────────────────────────────────────────
@@ -18231,9 +18098,9 @@ var AuthManager = class {
18231
18098
  }
18232
18099
  const salt = randomBytes(16).toString("hex");
18233
18100
  const passwordHash = this.hashPassword(password, salt);
18234
- const dataDir = join27(USERS_DIR, username);
18235
- const fullDataDir = join27(this.baseDir, dataDir);
18236
- mkdirSync16(join27(fullDataDir, "history"), { recursive: true });
18101
+ const dataDir = join26(USERS_DIR, username);
18102
+ const fullDataDir = join26(this.baseDir, dataDir);
18103
+ mkdirSync15(join26(fullDataDir, "history"), { recursive: true });
18237
18104
  const user = {
18238
18105
  username,
18239
18106
  passwordHash,
@@ -18348,7 +18215,7 @@ var AuthManager = class {
18348
18215
  getUserDataDir(username) {
18349
18216
  const user = this.db.users.find((u) => u.username === username);
18350
18217
  if (!user) throw new Error(`User not found: ${username}`);
18351
- return join27(this.baseDir, user.dataDir);
18218
+ return join26(this.baseDir, user.dataDir);
18352
18219
  }
18353
18220
  /** List all usernames */
18354
18221
  listUsers() {
@@ -18384,30 +18251,30 @@ var AuthManager = class {
18384
18251
  const err = this.register(username, password);
18385
18252
  if (err) return err;
18386
18253
  const userDir = this.getUserDataDir(username);
18387
- const globalConfig = join27(this.baseDir, "config.json");
18388
- if (existsSync32(globalConfig)) {
18254
+ const globalConfig = join26(this.baseDir, "config.json");
18255
+ if (existsSync30(globalConfig)) {
18389
18256
  try {
18390
- const content = readFileSync24(globalConfig, "utf-8");
18391
- writeFileSync5(join27(userDir, "config.json"), content, "utf-8");
18257
+ const content = readFileSync23(globalConfig, "utf-8");
18258
+ writeFileSync5(join26(userDir, "config.json"), content, "utf-8");
18392
18259
  } catch {
18393
18260
  }
18394
18261
  }
18395
- const globalMemory = join27(this.baseDir, "memory.md");
18396
- if (existsSync32(globalMemory)) {
18262
+ const globalMemory = join26(this.baseDir, "memory.md");
18263
+ if (existsSync30(globalMemory)) {
18397
18264
  try {
18398
- const content = readFileSync24(globalMemory, "utf-8");
18399
- writeFileSync5(join27(userDir, "memory.md"), content, "utf-8");
18265
+ const content = readFileSync23(globalMemory, "utf-8");
18266
+ writeFileSync5(join26(userDir, "memory.md"), content, "utf-8");
18400
18267
  } catch {
18401
18268
  }
18402
18269
  }
18403
- const globalHistory = join27(this.baseDir, "history");
18404
- if (existsSync32(globalHistory)) {
18270
+ const globalHistory = join26(this.baseDir, "history");
18271
+ if (existsSync30(globalHistory)) {
18405
18272
  try {
18406
18273
  const files = readdirSync15(globalHistory).filter((f) => f.endsWith(".json"));
18407
- const userHistory = join27(userDir, "history");
18274
+ const userHistory = join26(userDir, "history");
18408
18275
  for (const f of files) {
18409
18276
  try {
18410
- copyFileSync(join27(globalHistory, f), join27(userHistory, f));
18277
+ copyFileSync(join26(globalHistory, f), join26(userHistory, f));
18411
18278
  } catch {
18412
18279
  }
18413
18280
  }
@@ -18418,9 +18285,9 @@ var AuthManager = class {
18418
18285
  }
18419
18286
  // ── Private methods ────────────────────────────────────────────
18420
18287
  loadOrCreate() {
18421
- if (existsSync32(this.usersFile)) {
18288
+ if (existsSync30(this.usersFile)) {
18422
18289
  try {
18423
- const parsed = JSON.parse(readFileSync24(this.usersFile, "utf-8"));
18290
+ const parsed = JSON.parse(readFileSync23(this.usersFile, "utf-8"));
18424
18291
  if (!this.isValidUsersDB(parsed)) {
18425
18292
  throw new Error("database structure is invalid");
18426
18293
  }
@@ -18454,7 +18321,7 @@ var AuthManager = class {
18454
18321
  if (typeof record.username !== "string" || !/^[a-z0-9_-]{2,32}$/.test(record.username) || usernames.has(record.username) || typeof record.passwordHash !== "string" || record.passwordHash.length === 0 || typeof record.salt !== "string" || record.salt.length === 0 || typeof record.createdAt !== "string" || typeof record.dataDir !== "string" || record.dataDir.length === 0 || isAbsolute3(record.dataDir)) {
18455
18322
  return false;
18456
18323
  }
18457
- const dataDirFromBase = relative5(this.baseDir, join27(this.baseDir, record.dataDir));
18324
+ const dataDirFromBase = relative5(this.baseDir, join26(this.baseDir, record.dataDir));
18458
18325
  if (dataDirFromBase.startsWith("..") || isAbsolute3(dataDirFromBase)) return false;
18459
18326
  if (record.hashVersion !== void 0 && record.hashVersion !== 1 && record.hashVersion !== 2) return false;
18460
18327
  if (record.tokensRevokedBefore !== void 0 && !Number.isFinite(record.tokensRevokedBefore)) return false;
@@ -18466,7 +18333,7 @@ var AuthManager = class {
18466
18333
  this.saveDB(this.db);
18467
18334
  }
18468
18335
  saveDB(db) {
18469
- mkdirSync16(this.baseDir, { recursive: true });
18336
+ mkdirSync15(this.baseDir, { recursive: true });
18470
18337
  atomicWriteFileSync(this.usersFile, JSON.stringify(db, null, 2));
18471
18338
  }
18472
18339
  /** Legacy hash — kept only for migrating old users (v0.2.x) */
@@ -18498,11 +18365,11 @@ function getModuleDir() {
18498
18365
  if (typeof import.meta?.url === "string") {
18499
18366
  const url = new URL(import.meta.url);
18500
18367
  const filePath = url.pathname.replace(/^\/([A-Z]:)/i, "$1");
18501
- return dirname11(filePath);
18368
+ return dirname10(filePath);
18502
18369
  }
18503
18370
  } catch {
18504
18371
  }
18505
- return resolve13(".");
18372
+ return resolve12(".");
18506
18373
  }
18507
18374
  async function startWebServer(options = {}) {
18508
18375
  const port = options.port ?? 3e3;
@@ -18570,8 +18437,8 @@ async function startWebServer(options = {}) {
18570
18437
  }
18571
18438
  }
18572
18439
  let skillManager = null;
18573
- const skillsDir = join28(config.getConfigDir(), SKILLS_DIR_NAME);
18574
- if (existsSync33(skillsDir)) {
18440
+ const skillsDir = join27(config.getConfigDir(), SKILLS_DIR_NAME);
18441
+ if (existsSync31(skillsDir)) {
18575
18442
  skillManager = new SkillManager(skillsDir, config.get("ui").skillSizeWarn);
18576
18443
  skillManager.loadSkills(pluginAssets.skillDirs);
18577
18444
  const count = skillManager.listSkills().length;
@@ -18642,18 +18509,18 @@ async function startWebServer(options = {}) {
18642
18509
  next();
18643
18510
  };
18644
18511
  const moduleDir = getModuleDir();
18645
- let clientDir = join28(moduleDir, "web", "client");
18646
- if (!existsSync33(clientDir)) {
18647
- clientDir = join28(moduleDir, "client");
18512
+ let clientDir = join27(moduleDir, "web", "client");
18513
+ if (!existsSync31(clientDir)) {
18514
+ clientDir = join27(moduleDir, "client");
18648
18515
  }
18649
- if (!existsSync33(clientDir)) {
18650
- clientDir = join28(moduleDir, "..", "..", "src", "web", "client");
18516
+ if (!existsSync31(clientDir)) {
18517
+ clientDir = join27(moduleDir, "..", "..", "src", "web", "client");
18651
18518
  }
18652
- if (!existsSync33(clientDir)) {
18653
- clientDir = join28(process.cwd(), "src", "web", "client");
18519
+ if (!existsSync31(clientDir)) {
18520
+ clientDir = join27(process.cwd(), "src", "web", "client");
18654
18521
  }
18655
18522
  console.log(` Static files: ${clientDir}`);
18656
- app.use("/vendor", express.static(join28(clientDir, "vendor"), { maxAge: "1y", immutable: true }));
18523
+ app.use("/vendor", express.static(join27(clientDir, "vendor"), { maxAge: "1y", immutable: true }));
18657
18524
  app.use(express.static(clientDir));
18658
18525
  app.get("/api/status", (_req, res) => {
18659
18526
  res.json({
@@ -18720,10 +18587,10 @@ async function startWebServer(options = {}) {
18720
18587
  app.get("/api/files", requireAuth, (req, res) => {
18721
18588
  const cwd = process.cwd();
18722
18589
  const prefix = req.query.prefix || "";
18723
- const targetDir = join28(cwd, prefix);
18590
+ const targetDir = join27(cwd, prefix);
18724
18591
  try {
18725
- const canonicalTarget = realpathSync3(resolve13(targetDir));
18726
- const canonicalCwd = realpathSync3(resolve13(cwd));
18592
+ const canonicalTarget = realpathSync3(resolve12(targetDir));
18593
+ const canonicalCwd = realpathSync3(resolve12(cwd));
18727
18594
  if (!canonicalTarget.startsWith(canonicalCwd + sep4) && canonicalTarget !== canonicalCwd) {
18728
18595
  res.json({ files: [] });
18729
18596
  return;
@@ -18737,7 +18604,7 @@ async function startWebServer(options = {}) {
18737
18604
  const entries = readdirSync16(targetDir, { withFileTypes: true });
18738
18605
  const files = entries.filter((e) => !SKIP.has(e.name) && !e.name.startsWith(".")).slice(0, 50).map((e) => ({
18739
18606
  name: e.name,
18740
- path: relative6(cwd, join28(targetDir, e.name)).replace(/\\/g, "/"),
18607
+ path: relative6(cwd, join27(targetDir, e.name)).replace(/\\/g, "/"),
18741
18608
  isDir: e.isDirectory()
18742
18609
  }));
18743
18610
  res.json({ files });
@@ -18777,8 +18644,8 @@ async function startWebServer(options = {}) {
18777
18644
  try {
18778
18645
  const authUser = req._authUser;
18779
18646
  const histDir = authUser ? getUserShared(authUser).config.getHistoryDir() : config.getHistoryDir();
18780
- const filePath = join28(histDir, `${id}.json`);
18781
- if (!existsSync33(filePath)) {
18647
+ const filePath = join27(histDir, `${id}.json`);
18648
+ if (!existsSync31(filePath)) {
18782
18649
  res.status(404).json({ error: "Session not found" });
18783
18650
  return;
18784
18651
  }
@@ -18793,7 +18660,7 @@ async function startWebServer(options = {}) {
18793
18660
  res.status(404).json({ error: "Session not found" });
18794
18661
  return;
18795
18662
  }
18796
- const data = JSON.parse(readFileSync25(filePath, "utf-8"));
18663
+ const data = JSON.parse(readFileSync24(filePath, "utf-8"));
18797
18664
  res.json({ session: data });
18798
18665
  } catch (err) {
18799
18666
  res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
@@ -18806,10 +18673,10 @@ async function startWebServer(options = {}) {
18806
18673
  return;
18807
18674
  }
18808
18675
  const cwd = process.cwd();
18809
- const fullPath = resolve13(join28(cwd, filePath));
18676
+ const fullPath = resolve12(join27(cwd, filePath));
18810
18677
  try {
18811
18678
  const canonicalFull = realpathSync3(fullPath);
18812
- const canonicalCwd = realpathSync3(resolve13(cwd));
18679
+ const canonicalCwd = realpathSync3(resolve12(cwd));
18813
18680
  if (!canonicalFull.startsWith(canonicalCwd + sep4) && canonicalFull !== canonicalCwd) {
18814
18681
  res.json({ error: "Access denied" });
18815
18682
  return;
@@ -18824,7 +18691,7 @@ async function startWebServer(options = {}) {
18824
18691
  res.json({ error: `File too large (${(stat.size / 1024).toFixed(0)} KB, max 512 KB)` });
18825
18692
  return;
18826
18693
  }
18827
- const content = readFileSync25(fullPath, "utf-8");
18694
+ const content = readFileSync24(fullPath, "utf-8");
18828
18695
  res.json({ content, size: stat.size });
18829
18696
  } catch {
18830
18697
  res.json({ error: "Cannot read file" });
@@ -19028,7 +18895,7 @@ async function startWebServer(options = {}) {
19028
18895
  });
19029
18896
  const MAX_PORT_ATTEMPTS = 10;
19030
18897
  let actualPort = port;
19031
- const result = await new Promise((resolve14, reject) => {
18898
+ const result = await new Promise((resolve13, reject) => {
19032
18899
  const tryListen = (attempt) => {
19033
18900
  server.once("error", (err) => {
19034
18901
  if (err.code === "EADDRINUSE" && attempt < MAX_PORT_ATTEMPTS) {
@@ -19059,7 +18926,7 @@ async function startWebServer(options = {}) {
19059
18926
  }
19060
18927
  console.log(` Press Ctrl+C to stop
19061
18928
  `);
19062
- resolve14({ port: actualPort, host, url });
18929
+ resolve13({ port: actualPort, host, url });
19063
18930
  });
19064
18931
  };
19065
18932
  tryListen(1);
@@ -19079,17 +18946,17 @@ function resolveProjectMcpPath() {
19079
18946
  const cwd = process.cwd();
19080
18947
  const gitRoot = getGitRoot(cwd);
19081
18948
  const projectRoot = gitRoot ?? cwd;
19082
- const configPath = join28(projectRoot, MCP_PROJECT_CONFIG_NAME);
19083
- return existsSync33(configPath) ? configPath : null;
18949
+ const configPath = join27(projectRoot, MCP_PROJECT_CONFIG_NAME);
18950
+ return existsSync31(configPath) ? configPath : null;
19084
18951
  }
19085
18952
  function loadProjectMcpConfig() {
19086
18953
  const cwd = process.cwd();
19087
18954
  const gitRoot = getGitRoot(cwd);
19088
18955
  const projectRoot = gitRoot ?? cwd;
19089
- const configPath = join28(projectRoot, MCP_PROJECT_CONFIG_NAME);
19090
- if (!existsSync33(configPath)) return null;
18956
+ const configPath = join27(projectRoot, MCP_PROJECT_CONFIG_NAME);
18957
+ if (!existsSync31(configPath)) return null;
19091
18958
  try {
19092
- const raw = JSON.parse(readFileSync25(configPath, "utf-8"));
18959
+ const raw = JSON.parse(readFileSync24(configPath, "utf-8"));
19093
18960
  return raw.mcpServers ?? raw;
19094
18961
  } catch {
19095
18962
  return null;