jinzd-ai-cli 0.4.216 → 0.4.217

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 (31) hide show
  1. package/README.md +3 -2
  2. package/dist/{batch-C6HCXZIG.js → batch-YCOVKTXD.js} +2 -2
  3. package/dist/{chat-index-R2E27VXN.js → chat-index-PS274XM7.js} +1 -1
  4. package/dist/{chunk-6HLKCSB3.js → chunk-524WZOKS.js} +1 -1
  5. package/dist/{chunk-GY3C3C2Y.js → chunk-KOPUCJXM.js} +3 -3
  6. package/dist/{chunk-TCOC4AUI.js → chunk-MGBMNCHG.js} +279 -54
  7. package/dist/{chunk-PNTLA3MN.js → chunk-OUAZOE5U.js} +2 -2
  8. package/dist/{chunk-OLN7VUZA.js → chunk-QAYOI57M.js} +3 -1
  9. package/dist/{chunk-77HDCGTN.js → chunk-SNJAOXFT.js} +1 -1
  10. package/dist/{chunk-OQGVGPEK.js → chunk-VHY6NVMQ.js} +2 -0
  11. package/dist/{chunk-VW7Y27WW.js → chunk-VTH7BLXK.js} +1 -1
  12. package/dist/{chunk-WAI3WPV2.js → chunk-WKOQ5CYC.js} +1 -1
  13. package/dist/{chunk-TJ6GGN4B.js → chunk-WZ3VKLF3.js} +1 -1
  14. package/dist/{chunk-7YWS3JLU.js → chunk-XJGEQIYS.js} +3 -1
  15. package/dist/{ci-6ZTFO3LX.js → ci-52RZIYWB.js} +4 -4
  16. package/dist/{ci-format-CLZ6QJRL.js → ci-format-73UXKE65.js} +2 -2
  17. package/dist/{constants-APSORFOH.js → constants-DIXAD35W.js} +3 -1
  18. package/dist/{doctor-cli-GUIX4X5F.js → doctor-cli-7GOQPULZ.js} +4 -4
  19. package/dist/electron-server.js +562 -318
  20. package/dist/{hub-ZGHQWNWE.js → hub-GIGBITZN.js} +1 -1
  21. package/dist/index.js +129 -69
  22. package/dist/{pr-JOL3IAGV.js → pr-KPQ5RPKB.js} +4 -4
  23. package/dist/{run-tests-4PKSIVK5.js → run-tests-K7QR5QN4.js} +1 -1
  24. package/dist/{run-tests-ZRK4TQUN.js → run-tests-ZDSA3QES.js} +2 -2
  25. package/dist/{server-O3XHT56X.js → server-22YF3U34.js} +5 -4
  26. package/dist/{server-FXUF5P64.js → server-QT3SC2KI.js} +129 -86
  27. package/dist/{task-orchestrator-TUMDTOAX.js → task-orchestrator-BHQQCVTY.js} +5 -4
  28. package/dist/{usage-X7MJX4YD.js → usage-WZZFSFLM.js} +2 -2
  29. package/dist/web/client/app.js +53 -2
  30. package/dist/web/client/index.html +11 -1
  31. package/package.json +1 -1
@@ -27,6 +27,7 @@ import {
27
27
  MCP_TOOL_PREFIX,
28
28
  MEMORY_FILE_NAME,
29
29
  MEMORY_MAX_CHARS,
30
+ MEMORY_STORE_FILE_NAME,
30
31
  PLAN_MODE_READONLY_TOOLS,
31
32
  PLAN_MODE_SYSTEM_ADDON,
32
33
  PLUGINS_DIR_NAME,
@@ -37,7 +38,7 @@ import {
37
38
  VERSION,
38
39
  buildUserIdentityPrompt,
39
40
  runTestsTool
40
- } from "./chunk-OLN7VUZA.js";
41
+ } from "./chunk-QAYOI57M.js";
41
42
  import {
42
43
  hasSemanticIndex,
43
44
  semanticSearch
@@ -47,11 +48,13 @@ import {
47
48
  } from "./chunk-RWM2GFRC.js";
48
49
  import "./chunk-IEQAE3QG.js";
49
50
  import {
51
+ DEFAULT_PATTERNS,
50
52
  loadChatIndex,
51
53
  redactJson,
54
+ redactString,
52
55
  scanString,
53
56
  searchChatMemory
54
- } from "./chunk-OQGVGPEK.js";
57
+ } from "./chunk-VHY6NVMQ.js";
55
58
  import "./chunk-JV5N65KN.js";
56
59
  import {
57
60
  atomicWriteFileSync
@@ -61,8 +64,8 @@ import {
61
64
  import express from "express";
62
65
  import { createServer } from "http";
63
66
  import { WebSocketServer } from "ws";
64
- import { join as join22, dirname as dirname6, resolve as resolve9, relative as relative4, sep as sep3 } from "path";
65
- import { existsSync as existsSync28, readFileSync as readFileSync22, readdirSync as readdirSync13, statSync as statSync10, realpathSync } from "fs";
67
+ import { join as join22, dirname as dirname7, resolve as resolve10, relative as relative4, sep as sep3 } from "path";
68
+ import { existsSync as existsSync27, readFileSync as readFileSync21, readdirSync as readdirSync13, statSync as statSync9, realpathSync } from "fs";
66
69
  import { networkInterfaces } from "os";
67
70
 
68
71
  // src/config/config-manager.ts
@@ -7179,7 +7182,7 @@ var ToolExecutor = class {
7179
7182
  rl.resume();
7180
7183
  process.stdout.write(prompt);
7181
7184
  this.confirming = true;
7182
- return new Promise((resolve10) => {
7185
+ return new Promise((resolve11) => {
7183
7186
  let completed = false;
7184
7187
  const cleanup = (result) => {
7185
7188
  if (completed) return;
@@ -7189,7 +7192,7 @@ var ToolExecutor = class {
7189
7192
  rl.pause();
7190
7193
  rlAny.output = savedOutput;
7191
7194
  this.confirming = false;
7192
- resolve10(result);
7195
+ resolve11(result);
7193
7196
  };
7194
7197
  const onLine = (line) => {
7195
7198
  const trimmed = line.trim();
@@ -7363,7 +7366,7 @@ var ToolExecutor = class {
7363
7366
  rl.resume();
7364
7367
  process.stdout.write(color("Proceed? [y/N] (type y + Enter to confirm) "));
7365
7368
  this.confirming = true;
7366
- return new Promise((resolve10) => {
7369
+ return new Promise((resolve11) => {
7367
7370
  let completed = false;
7368
7371
  const cleanup = (answer) => {
7369
7372
  if (completed) return;
@@ -7373,7 +7376,7 @@ var ToolExecutor = class {
7373
7376
  rl.pause();
7374
7377
  rlAny.output = savedOutput;
7375
7378
  this.confirming = false;
7376
- resolve10(answer === "y");
7379
+ resolve11(answer === "y");
7377
7380
  };
7378
7381
  const onLine = (line) => {
7379
7382
  const trimmed = line.trim();
@@ -8622,7 +8625,7 @@ var runInteractiveTool = {
8622
8625
  PYTHONDONTWRITEBYTECODE: "1"
8623
8626
  };
8624
8627
  const prefixWarnings = [argsTypeWarning, stdinTypeWarning].filter(Boolean).join("");
8625
- return new Promise((resolve10) => {
8628
+ return new Promise((resolve11) => {
8626
8629
  const child = spawn2(executable, cmdArgs.map(String), {
8627
8630
  cwd: process.cwd(),
8628
8631
  env,
@@ -8655,22 +8658,22 @@ var runInteractiveTool = {
8655
8658
  setTimeout(writeNextLine, 400);
8656
8659
  const timer = setTimeout(() => {
8657
8660
  child.kill();
8658
- resolve10(`${prefixWarnings}[Timeout after ${timeout}ms]
8661
+ resolve11(`${prefixWarnings}[Timeout after ${timeout}ms]
8659
8662
  ${buildOutput(stdout, stderr)}`);
8660
8663
  }, timeout);
8661
8664
  child.on("close", (code) => {
8662
8665
  clearTimeout(timer);
8663
8666
  const output = buildOutput(stdout, stderr);
8664
8667
  if (code !== 0 && code !== null) {
8665
- resolve10(`${prefixWarnings}Exit code ${code}:
8668
+ resolve11(`${prefixWarnings}Exit code ${code}:
8666
8669
  ${output}`);
8667
8670
  } else {
8668
- resolve10(`${prefixWarnings}${output || "(no output)"}`);
8671
+ resolve11(`${prefixWarnings}${output || "(no output)"}`);
8669
8672
  }
8670
8673
  });
8671
8674
  child.on("error", (err) => {
8672
8675
  clearTimeout(timer);
8673
- resolve10(
8676
+ resolve11(
8674
8677
  `${prefixWarnings}Failed to start process "${executable}": ${err.message}
8675
8678
  Hint: On Windows, use the full path to the executable, e.g.:
8676
8679
  C:\\Users\\Jinzd\\anaconda3\\envs\\python312\\python.exe`
@@ -9338,26 +9341,328 @@ Any of these triggers means use save_last_response, NOT write_file:
9338
9341
  };
9339
9342
 
9340
9343
  // src/tools/builtin/save-memory.ts
9341
- import { existsSync as existsSync14, readFileSync as readFileSync11, statSync as statSync6, mkdirSync as mkdirSync7 } from "fs";
9342
- import { join as join9 } from "path";
9344
+ import { join as join11 } from "path";
9343
9345
  import { homedir as homedir5 } from "os";
9344
- function getMemoryFilePath() {
9345
- return join9(homedir5(), CONFIG_DIR_NAME, MEMORY_FILE_NAME);
9346
+
9347
+ // src/memory/persistent-memory.ts
9348
+ import { existsSync as existsSync15, mkdirSync as mkdirSync7, readFileSync as readFileSync11 } from "fs";
9349
+ import { randomUUID, createHash as createHash2 } from "crypto";
9350
+ import { dirname as dirname5, join as join10, resolve as resolve6 } from "path";
9351
+
9352
+ // src/tools/git-context.ts
9353
+ import { execSync as execSync2 } from "child_process";
9354
+ import { existsSync as existsSync14 } from "fs";
9355
+ import { join as join9 } from "path";
9356
+ function runGit(cmd, cwd) {
9357
+ try {
9358
+ return execSync2(`git ${cmd}`, {
9359
+ cwd,
9360
+ encoding: "utf-8",
9361
+ stdio: ["pipe", "pipe", "pipe"],
9362
+ timeout: 5e3
9363
+ }).trim();
9364
+ } catch {
9365
+ return null;
9366
+ }
9346
9367
  }
9347
- function formatTimestamp() {
9348
- const now = /* @__PURE__ */ new Date();
9349
- const pad = (n) => String(n).padStart(2, "0");
9350
- return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;
9368
+ function getGitRoot(cwd = process.cwd()) {
9369
+ return runGit("rev-parse --show-toplevel", cwd);
9370
+ }
9371
+ function getGitContext(cwd = process.cwd()) {
9372
+ if (!existsSync14(join9(cwd, ".git"))) {
9373
+ const result = runGit("rev-parse --git-dir", cwd);
9374
+ if (!result) return null;
9375
+ }
9376
+ const branch = runGit("rev-parse --abbrev-ref HEAD", cwd);
9377
+ if (!branch) return null;
9378
+ const statusOutput = runGit("status --porcelain", cwd) ?? "";
9379
+ const statusLines = statusOutput ? statusOutput.split("\n").filter(Boolean) : [];
9380
+ const stagedFiles = [];
9381
+ const changedFiles = [];
9382
+ for (const line of statusLines) {
9383
+ const xy = line.slice(0, 2);
9384
+ const file = line.slice(3).trim();
9385
+ const indexStatus = xy[0];
9386
+ const workStatus = xy[1];
9387
+ if (indexStatus && indexStatus !== " " && indexStatus !== "?") {
9388
+ stagedFiles.push(`${indexStatus} ${file}`);
9389
+ }
9390
+ if (workStatus && workStatus !== " ") {
9391
+ changedFiles.push(`${workStatus} ${file}`);
9392
+ }
9393
+ }
9394
+ const logOutput = runGit("log --oneline -3", cwd) ?? "";
9395
+ const recentCommits = logOutput ? logOutput.split("\n").filter(Boolean) : [];
9396
+ const unpushedOutput = runGit("log @{u}..HEAD --oneline", cwd);
9397
+ const hasUnpushed = unpushedOutput !== null && unpushedOutput.trim().length > 0;
9398
+ return {
9399
+ branch,
9400
+ changedFiles,
9401
+ stagedFiles,
9402
+ recentCommits,
9403
+ hasUnpushed
9404
+ };
9405
+ }
9406
+ function formatGitContextForPrompt(ctx) {
9407
+ const lines = ["# Git Repository Status", ""];
9408
+ lines.push(`- **Branch**: \`${ctx.branch}\``);
9409
+ if (ctx.stagedFiles.length > 0) {
9410
+ lines.push(`- **Staged** (${ctx.stagedFiles.length} files):`);
9411
+ for (const f of ctx.stagedFiles.slice(0, 10)) {
9412
+ lines.push(` - ${f}`);
9413
+ }
9414
+ if (ctx.stagedFiles.length > 10) {
9415
+ lines.push(` - ... and ${ctx.stagedFiles.length - 10} more`);
9416
+ }
9417
+ }
9418
+ if (ctx.changedFiles.length > 0) {
9419
+ lines.push(`- **Modified** (${ctx.changedFiles.length} files):`);
9420
+ for (const f of ctx.changedFiles.slice(0, 10)) {
9421
+ lines.push(` - ${f}`);
9422
+ }
9423
+ if (ctx.changedFiles.length > 10) {
9424
+ lines.push(` - ... and ${ctx.changedFiles.length - 10} more`);
9425
+ }
9426
+ }
9427
+ if (ctx.stagedFiles.length === 0 && ctx.changedFiles.length === 0) {
9428
+ lines.push("- **Working tree**: clean");
9429
+ }
9430
+ if (ctx.recentCommits.length > 0) {
9431
+ lines.push("- **Recent commits**:");
9432
+ for (const c of ctx.recentCommits) {
9433
+ lines.push(` - ${c}`);
9434
+ }
9435
+ }
9436
+ if (ctx.hasUnpushed) {
9437
+ lines.push("- \u26A0\uFE0F Has unpushed commits");
9438
+ }
9439
+ return lines.join("\n");
9440
+ }
9441
+
9442
+ // src/memory/persistent-memory.ts
9443
+ function memoryStorePath(configDir) {
9444
+ return join10(configDir, MEMORY_STORE_FILE_NAME);
9445
+ }
9446
+ function memoryMarkdownPath(configDir) {
9447
+ return join10(configDir, MEMORY_FILE_NAME);
9448
+ }
9449
+ function getProjectKey(cwd = process.cwd()) {
9450
+ return resolve6(getGitRoot(cwd) ?? cwd);
9451
+ }
9452
+ function nowIso() {
9453
+ return (/* @__PURE__ */ new Date()).toISOString();
9454
+ }
9455
+ function isValidScope(value) {
9456
+ return value === "personal" || value === "project" || value === "session" || value === "team";
9457
+ }
9458
+ function isValidSensitivity(value) {
9459
+ return value === "low" || value === "medium" || value === "high";
9460
+ }
9461
+ function normalizeEntry(value) {
9462
+ if (!value || typeof value !== "object") return null;
9463
+ const raw = value;
9464
+ const content = typeof raw.content === "string" ? raw.content.trim() : "";
9465
+ if (!content) return null;
9466
+ const createdAt = typeof raw.createdAt === "string" ? raw.createdAt : nowIso();
9467
+ return {
9468
+ id: typeof raw.id === "string" && raw.id ? raw.id : randomUUID(),
9469
+ content,
9470
+ scope: isValidScope(raw.scope) ? raw.scope : "personal",
9471
+ sourceSession: typeof raw.sourceSession === "string" && raw.sourceSession ? raw.sourceSession : void 0,
9472
+ sourceProject: typeof raw.sourceProject === "string" && raw.sourceProject ? raw.sourceProject : void 0,
9473
+ createdAt,
9474
+ updatedAt: typeof raw.updatedAt === "string" ? raw.updatedAt : createdAt,
9475
+ expiresAt: typeof raw.expiresAt === "string" && raw.expiresAt ? raw.expiresAt : void 0,
9476
+ sensitivity: isValidSensitivity(raw.sensitivity) ? raw.sensitivity : "low",
9477
+ approved: raw.approved === true,
9478
+ redactedKinds: Array.isArray(raw.redactedKinds) ? raw.redactedKinds.filter((x) => typeof x === "string") : void 0
9479
+ };
9480
+ }
9481
+ function legacyEntriesFromMarkdown(configDir) {
9482
+ const file = memoryMarkdownPath(configDir);
9483
+ if (!existsSync15(file)) return [];
9484
+ const content = readFileSync11(file, "utf-8").trim();
9485
+ if (!content) return [];
9486
+ const parts = content.split(/\n(?=##\s+\d{4}-\d{2}-\d{2})/g);
9487
+ const chunks = parts.length > 1 ? parts : [content];
9488
+ return chunks.map((chunk, index) => {
9489
+ const match = chunk.match(/^##\s+([^\n]+)\n([\s\S]*)$/);
9490
+ const body = (match ? match[2] : chunk).trim();
9491
+ const parsed = match ? Date.parse(match[1].replace(" ", "T")) : NaN;
9492
+ const createdAt = Number.isFinite(parsed) ? new Date(parsed).toISOString() : nowIso();
9493
+ const id = "legacy-" + createHash2("sha1").update(`${index}
9494
+ ${chunk}`).digest("hex").slice(0, 12);
9495
+ return normalizeEntry({ id, content: body, scope: "personal", createdAt, updatedAt: createdAt, sensitivity: "low", approved: true });
9496
+ }).filter((entry) => Boolean(entry));
9497
+ }
9498
+ function loadMemoryEntries(configDir) {
9499
+ const file = memoryStorePath(configDir);
9500
+ if (!existsSync15(file)) return legacyEntriesFromMarkdown(configDir);
9501
+ const text = readFileSync11(file, "utf-8").trim();
9502
+ if (!text) return [];
9503
+ const entries = [];
9504
+ for (const line of text.split("\n")) {
9505
+ const trimmed = line.trim();
9506
+ if (!trimmed) continue;
9507
+ try {
9508
+ const entry = normalizeEntry(JSON.parse(trimmed));
9509
+ if (entry) entries.push(entry);
9510
+ } catch {
9511
+ }
9512
+ }
9513
+ return entries;
9514
+ }
9515
+ function saveMemoryEntries(configDir, entries) {
9516
+ mkdirSync7(configDir, { recursive: true });
9517
+ const jsonl = entries.map((entry) => JSON.stringify(entry)).join("\n");
9518
+ atomicWriteFileSync(memoryStorePath(configDir), jsonl ? jsonl + "\n" : "");
9519
+ syncLegacyMarkdown(configDir, entries);
9520
+ }
9521
+ function isMemoryExpired(entry, at = /* @__PURE__ */ new Date()) {
9522
+ return Boolean(entry.expiresAt && Date.parse(entry.expiresAt) <= at.getTime());
9523
+ }
9524
+ function isMemoryVisibleInProject(entry, cwd = process.cwd()) {
9525
+ if (entry.scope !== "project") return true;
9526
+ return entry.sourceProject === getProjectKey(cwd);
9527
+ }
9528
+ function listMemoryEntries(configDir, options = {}) {
9529
+ const cwd = options.cwd ?? process.cwd();
9530
+ return loadMemoryEntries(configDir).filter((entry) => options.includeRejected || entry.approved).filter((entry) => options.includeExpired || !isMemoryExpired(entry)).filter((entry) => isMemoryVisibleInProject(entry, cwd));
9531
+ }
9532
+ function addMemoryEntry(configDir, content, options = {}) {
9533
+ const original = content.trim();
9534
+ if (!original) throw new Error("memory content is required");
9535
+ const redact = options.redact ?? true;
9536
+ const redacted = redact ? redactString(original, { enabled: true, patterns: options.patterns ?? DEFAULT_PATTERNS, customRegexes: options.customPatterns }) : { redacted: original, hits: [] };
9537
+ const sensitivity = options.sensitivity ?? (redacted.hits.length > 0 ? "high" : "low");
9538
+ const approved = options.approved ?? (sensitivity === "low" && redacted.hits.length === 0);
9539
+ const scope = options.scope ?? "personal";
9540
+ const timestamp = nowIso();
9541
+ const entry = {
9542
+ id: randomUUID(),
9543
+ content: redacted.redacted.trim(),
9544
+ scope,
9545
+ sourceSession: options.sourceSession,
9546
+ sourceProject: scope === "project" ? getProjectKey(options.cwd) : void 0,
9547
+ createdAt: timestamp,
9548
+ updatedAt: timestamp,
9549
+ expiresAt: options.expiresAt,
9550
+ sensitivity,
9551
+ approved,
9552
+ redactedKinds: [...new Set(redacted.hits.map((hit) => hit.kind))]
9553
+ };
9554
+ const entries = loadMemoryEntries(configDir);
9555
+ entries.push(entry);
9556
+ saveMemoryEntries(configDir, entries);
9557
+ return entry;
9558
+ }
9559
+ function updateMemoryApproval(configDir, idPrefix, approved) {
9560
+ const entries = loadMemoryEntries(configDir);
9561
+ const entry = findUnique(entries, idPrefix);
9562
+ entry.approved = approved;
9563
+ entry.updatedAt = nowIso();
9564
+ saveMemoryEntries(configDir, entries);
9565
+ return entry;
9566
+ }
9567
+ function deleteMemoryEntry(configDir, idPrefix) {
9568
+ const entries = loadMemoryEntries(configDir);
9569
+ const entry = findUnique(entries, idPrefix);
9570
+ saveMemoryEntries(configDir, entries.filter((candidate) => candidate.id !== entry.id));
9571
+ return entry;
9572
+ }
9573
+ function expireMemoryEntry(configDir, idPrefix, expiresAt = nowIso()) {
9574
+ const entries = loadMemoryEntries(configDir);
9575
+ const entry = findUnique(entries, idPrefix);
9576
+ entry.expiresAt = expiresAt;
9577
+ entry.updatedAt = nowIso();
9578
+ saveMemoryEntries(configDir, entries);
9579
+ return entry;
9580
+ }
9581
+ function exportMemoryEntries(configDir, cwd = process.cwd()) {
9582
+ return JSON.stringify(listMemoryEntries(configDir, { cwd, includeExpired: true, includeRejected: true }), null, 2);
9583
+ }
9584
+ function findUnique(entries, idPrefix) {
9585
+ const matches = entries.filter((entry) => entry.id.startsWith(idPrefix));
9586
+ if (matches.length === 0) throw new Error(`memory id not found: ${idPrefix}`);
9587
+ if (matches.length > 1) throw new Error(`memory id is ambiguous: ${idPrefix}`);
9588
+ return matches[0];
9589
+ }
9590
+ function syncLegacyMarkdown(configDir, entries = loadMemoryEntries(configDir)) {
9591
+ mkdirSync7(dirname5(memoryMarkdownPath(configDir)), { recursive: true });
9592
+ const active = entries.filter((entry) => entry.approved && !isMemoryExpired(entry));
9593
+ const markdown = active.map((entry) => {
9594
+ const date = entry.createdAt.replace("T", " ").slice(0, 19);
9595
+ const source = [
9596
+ `id:${entry.id.slice(0, 8)}`,
9597
+ `scope:${entry.scope}`,
9598
+ entry.sourceSession ? `session:${entry.sourceSession.slice(0, 8)}` : "",
9599
+ entry.sourceProject ? `project:${entry.sourceProject}` : "",
9600
+ `sensitivity:${entry.sensitivity}`
9601
+ ].filter(Boolean).join(" \xB7 ");
9602
+ return `## ${date}
9603
+ <!-- ${source} -->
9604
+ ${entry.content}
9605
+ `;
9606
+ }).join("\n");
9607
+ atomicWriteFileSync(memoryMarkdownPath(configDir), markdown);
9608
+ }
9609
+ function formatMemoryForPrompt(configDir, cwd = process.cwd()) {
9610
+ const entries = listMemoryEntries(configDir, { cwd }).filter((entry) => entry.approved && !isMemoryExpired(entry));
9611
+ if (entries.length === 0) return null;
9612
+ let content = entries.map((entry) => {
9613
+ const source = [
9614
+ `id ${entry.id.slice(0, 8)}`,
9615
+ `scope ${entry.scope}`,
9616
+ entry.sourceSession ? `source session ${entry.sourceSession.slice(0, 8)}` : "source manual/tool",
9617
+ entry.sourceProject ? `project ${entry.sourceProject}` : "",
9618
+ `sensitivity ${entry.sensitivity}`
9619
+ ].filter(Boolean).join(" \xB7 ");
9620
+ return `## ${entry.createdAt.replace("T", " ").slice(0, 19)}
9621
+ Source: ${source}
9622
+ ${entry.content}`;
9623
+ }).join("\n\n");
9624
+ if (content.length > MEMORY_MAX_CHARS) {
9625
+ content = content.slice(-MEMORY_MAX_CHARS);
9626
+ const firstEntry = content.indexOf("\n## ");
9627
+ if (firstEntry !== -1) content = content.slice(firstEntry + 1);
9628
+ }
9629
+ return { content, entryCount: entries.length };
9630
+ }
9631
+
9632
+ // src/tools/builtin/save-memory.ts
9633
+ function getConfigDir() {
9634
+ return join11(homedir5(), CONFIG_DIR_NAME);
9635
+ }
9636
+ function parseScope(value) {
9637
+ return value === "personal" || value === "project" || value === "session" || value === "team" ? value : void 0;
9638
+ }
9639
+ function parseSensitivity(value) {
9640
+ return value === "low" || value === "medium" || value === "high" ? value : void 0;
9351
9641
  }
9352
9642
  var saveMemoryTool = {
9353
9643
  definition: {
9354
9644
  name: "save_memory",
9355
- description: "Save important information to persistent memory that survives across sessions. Use this to remember: user preferences, coding style, project architecture decisions, recurring patterns, key findings, or any knowledge worth preserving. Keep each memory entry concise (1-3 sentences). The content will be automatically timestamped.",
9645
+ 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.",
9356
9646
  parameters: {
9357
9647
  content: {
9358
9648
  type: "string",
9359
9649
  description: "The information to save to persistent memory. Keep it concise and actionable.",
9360
9650
  required: true
9651
+ },
9652
+ scope: {
9653
+ type: "string",
9654
+ description: "Memory scope: personal, project, session, or team. Defaults to personal. Project memories are only injected in the same project.",
9655
+ required: false
9656
+ },
9657
+ sensitivity: {
9658
+ type: "string",
9659
+ description: "Sensitivity: low, medium, or high. Low-risk entries may be auto-approved; medium/high remain pending unless explicitly approved.",
9660
+ required: false
9661
+ },
9662
+ expiresAt: {
9663
+ type: "string",
9664
+ description: "Optional ISO timestamp after which this memory should no longer be injected.",
9665
+ required: false
9361
9666
  }
9362
9667
  },
9363
9668
  dangerous: false
@@ -9365,20 +9670,16 @@ var saveMemoryTool = {
9365
9670
  async execute(args) {
9366
9671
  const content = String(args["content"] ?? "").trim();
9367
9672
  if (!content) throw new ToolError("save_memory", "content is required");
9368
- const memoryPath = getMemoryFilePath();
9369
- const configDir = join9(homedir5(), CONFIG_DIR_NAME);
9370
- if (!existsSync14(configDir)) {
9371
- mkdirSync7(configDir, { recursive: true });
9372
- }
9373
- const timestamp = formatTimestamp();
9374
- const entry = `
9375
- ## ${timestamp}
9376
- ${content}
9377
- `;
9378
- const previous = existsSync14(memoryPath) ? readFileSync11(memoryPath, "utf-8") : "";
9379
- atomicWriteFileSync(memoryPath, previous + entry);
9380
- const byteSize = statSync6(memoryPath).size;
9381
- return `Memory saved successfully. File size: ${byteSize} bytes in ${MEMORY_FILE_NAME}`;
9673
+ const entry = addMemoryEntry(getConfigDir(), content, {
9674
+ scope: parseScope(args["scope"]) ?? "personal",
9675
+ sensitivity: parseSensitivity(args["sensitivity"]),
9676
+ expiresAt: typeof args["expiresAt"] === "string" ? args["expiresAt"] : void 0,
9677
+ cwd: process.cwd(),
9678
+ redact: true
9679
+ });
9680
+ const status = entry.approved ? "approved and active" : "pending approval";
9681
+ const redacted = entry.redactedKinds?.length ? ` Redacted: ${entry.redactedKinds.join(", ")}.` : "";
9682
+ return `Memory saved (${status}). id=${entry.id.slice(0, 8)} scope=${entry.scope} sensitivity=${entry.sensitivity}.${redacted}`;
9382
9683
  }
9383
9684
  };
9384
9685
 
@@ -9422,7 +9723,7 @@ function promptUser(rl, question) {
9422
9723
  console.log();
9423
9724
  console.log(chalk4.cyan("\u2753 ") + chalk4.bold(question));
9424
9725
  process.stdout.write(chalk4.cyan("> "));
9425
- return new Promise((resolve10) => {
9726
+ return new Promise((resolve11) => {
9426
9727
  let completed = false;
9427
9728
  const cleanup = (answer) => {
9428
9729
  if (completed) return;
@@ -9432,7 +9733,7 @@ function promptUser(rl, question) {
9432
9733
  rl.pause();
9433
9734
  rlAny.output = savedOutput;
9434
9735
  askUserContext.prompting = false;
9435
- resolve10(answer);
9736
+ resolve11(answer);
9436
9737
  };
9437
9738
  const onLine = (line) => {
9438
9739
  cleanup(line);
@@ -9664,8 +9965,8 @@ function formatResults2(query, data, _requested) {
9664
9965
  }
9665
9966
 
9666
9967
  // src/agents/agent-config.ts
9667
- import { existsSync as existsSync15, readdirSync as readdirSync7, readFileSync as readFileSync12 } from "fs";
9668
- import { join as join10 } from "path";
9968
+ import { existsSync as existsSync16, readdirSync as readdirSync7, readFileSync as readFileSync12 } from "fs";
9969
+ import { join as join12 } from "path";
9669
9970
  import { homedir as homedir6 } from "os";
9670
9971
  var READ_ONLY_TOOLS2 = /* @__PURE__ */ new Set([
9671
9972
  "read_file",
@@ -9760,11 +10061,11 @@ function parseAgentConfig(raw, source, path3) {
9760
10061
  return cfg;
9761
10062
  }
9762
10063
  function loadAgentDir(dir, source) {
9763
- if (!existsSync15(dir)) return [];
10064
+ if (!existsSync16(dir)) return [];
9764
10065
  const out = [];
9765
10066
  for (const file of readdirSync7(dir)) {
9766
10067
  if (!file.endsWith(".json")) continue;
9767
- const path3 = join10(dir, file);
10068
+ const path3 = join12(dir, file);
9768
10069
  try {
9769
10070
  const parsed = JSON.parse(readFileSync12(path3, "utf-8"));
9770
10071
  const cfg = parseAgentConfig(parsed, source, path3);
@@ -9776,8 +10077,8 @@ function loadAgentDir(dir, source) {
9776
10077
  }
9777
10078
  function getAgentDirs(configDir, cwd = process.cwd()) {
9778
10079
  return {
9779
- user: join10(configDir ?? join10(homedir6(), CONFIG_DIR_NAME), "agents"),
9780
- project: join10(cwd, CONFIG_DIR_NAME, "agents")
10080
+ user: join12(configDir ?? join12(homedir6(), CONFIG_DIR_NAME), "agents"),
10081
+ project: join12(cwd, CONFIG_DIR_NAME, "agents")
9781
10082
  };
9782
10083
  }
9783
10084
  function listAgentConfigs(configDir, cwd = process.cwd()) {
@@ -9819,7 +10120,7 @@ var nextId = 1;
9819
10120
  var preferredAgentName = "worker";
9820
10121
  var runs = [];
9821
10122
  var MAX_RUNS = 50;
9822
- function nowIso() {
10123
+ function nowIso2() {
9823
10124
  return (/* @__PURE__ */ new Date()).toISOString();
9824
10125
  }
9825
10126
  function startAgentRun(input) {
@@ -9828,7 +10129,7 @@ function startAgentRun(input) {
9828
10129
  agentName: input.agentName,
9829
10130
  task: input.task,
9830
10131
  status: "running",
9831
- startedAt: nowIso(),
10132
+ startedAt: nowIso2(),
9832
10133
  toolNames: [],
9833
10134
  agentIndex: input.agentIndex,
9834
10135
  stopRequested: false
@@ -9846,7 +10147,7 @@ function finishAgentRun(runId, status, summary, error) {
9846
10147
  const run = runs.find((r) => r.id === runId);
9847
10148
  if (!run) return;
9848
10149
  run.status = status;
9849
- run.finishedAt = nowIso();
10150
+ run.finishedAt = nowIso2();
9850
10151
  if (summary) run.summary = summary;
9851
10152
  if (error) run.error = error;
9852
10153
  }
@@ -10272,7 +10573,7 @@ var spawnAgentTool = {
10272
10573
 
10273
10574
  // src/tools/builtin/task-manager.ts
10274
10575
  import { spawn as spawn3 } from "child_process";
10275
- import { randomUUID } from "crypto";
10576
+ import { randomUUID as randomUUID2 } from "crypto";
10276
10577
  import { platform as platform4 } from "os";
10277
10578
  var MAX_OUTPUT_CHARS = 1e4;
10278
10579
  var MAX_OUTPUT_BYTES = 64 * 1024;
@@ -10305,7 +10606,7 @@ function createTask(command, description) {
10305
10606
  }
10306
10607
  }
10307
10608
  }
10308
- const id = randomUUID().slice(0, 8);
10609
+ const id = randomUUID2().slice(0, 8);
10309
10610
  const { shell, args } = shellInvocation(command);
10310
10611
  const proc = spawn3(shell, args, {
10311
10612
  stdio: ["ignore", "pipe", "pipe"],
@@ -10487,20 +10788,20 @@ var taskStopTool = {
10487
10788
 
10488
10789
  // src/tools/builtin/git-tools.ts
10489
10790
  import { execFileSync as execFileSync2 } from "child_process";
10490
- import { existsSync as existsSync16 } from "fs";
10491
- import { join as join11 } from "path";
10791
+ import { existsSync as existsSync17 } from "fs";
10792
+ import { join as join13 } from "path";
10492
10793
  function assertGitRepo(cwd) {
10493
10794
  let dir = cwd;
10494
10795
  const root = dir.split(/[\\/]/)[0] + (dir.includes("\\") ? "\\" : "/");
10495
10796
  while (dir && dir !== root) {
10496
- if (existsSync16(join11(dir, ".git"))) return;
10497
- const parent = join11(dir, "..");
10797
+ if (existsSync17(join13(dir, ".git"))) return;
10798
+ const parent = join13(dir, "..");
10498
10799
  if (parent === dir) break;
10499
10800
  dir = parent;
10500
10801
  }
10501
10802
  throw new ToolError("git", "Not inside a git repository. Run this command from a directory under a git-tracked project.");
10502
10803
  }
10503
- function runGit(args, cwd, maxBuffer = 10 * 1024 * 1024) {
10804
+ function runGit2(args, cwd, maxBuffer = 10 * 1024 * 1024) {
10504
10805
  try {
10505
10806
  const output = execFileSync2("git", args, {
10506
10807
  cwd,
@@ -10535,11 +10836,11 @@ var gitStatusTool = {
10535
10836
  assertGitRepo(cwd);
10536
10837
  let branch = "(unknown)";
10537
10838
  try {
10538
- branch = runGit(["symbolic-ref", "--short", "HEAD"], cwd).trim();
10839
+ branch = runGit2(["symbolic-ref", "--short", "HEAD"], cwd).trim();
10539
10840
  } catch {
10540
10841
  branch = "(detached)";
10541
10842
  }
10542
- const porcelain = runGit(["status", "--porcelain=v1", "-b"], cwd);
10843
+ const porcelain = runGit2(["status", "--porcelain=v1", "-b"], cwd);
10543
10844
  const lines = porcelain.split("\n").filter((l) => l.length > 0);
10544
10845
  const branchLine = lines[0]?.startsWith("##") ? lines[0].slice(3) : `${branch}`;
10545
10846
  const fileLines = lines.slice(lines[0]?.startsWith("##") ? 1 : 0);
@@ -10626,7 +10927,7 @@ var gitDiffTool = {
10626
10927
  if (args["file"]) {
10627
10928
  gitArgs.push("--", String(args["file"]));
10628
10929
  }
10629
- const output = runGit(gitArgs, cwd);
10930
+ const output = runGit2(gitArgs, cwd);
10630
10931
  if (output.trim().length === 0) {
10631
10932
  const label = args["staged"] ? "staged" : args["commit_range"] ? String(args["commit_range"]) : "unstaged";
10632
10933
  return `No ${label} changes.`;
@@ -10688,7 +10989,7 @@ var gitLogTool = {
10688
10989
  if (args["file"]) {
10689
10990
  gitArgs.push("--", String(args["file"]));
10690
10991
  }
10691
- const output = runGit(gitArgs, cwd);
10992
+ const output = runGit2(gitArgs, cwd);
10692
10993
  if (output.trim().length === 0) {
10693
10994
  return "No commits found matching the filter.";
10694
10995
  }
@@ -10735,9 +11036,9 @@ var gitCommitTool = {
10735
11036
  throw new ToolError("git_commit", "Cannot use both stage_all and files at the same time.");
10736
11037
  }
10737
11038
  if (files.length > 0) {
10738
- runGit(["add", "--", ...files], cwd);
11039
+ runGit2(["add", "--", ...files], cwd);
10739
11040
  }
10740
- const statusBefore = runGit(["status", "--porcelain"], cwd);
11041
+ const statusBefore = runGit2(["status", "--porcelain"], cwd);
10741
11042
  const hasStaged = statusBefore.split("\n").some((l) => l.length > 0 && l[0] !== " " && l[0] !== "?");
10742
11043
  const hasUnstaged = statusBefore.split("\n").some((l) => l.length > 1 && l[1] !== " " && l[1] !== "?");
10743
11044
  if (!hasStaged && !stageAll) {
@@ -10748,9 +11049,9 @@ var gitCommitTool = {
10748
11049
  }
10749
11050
  const commitArgs = ["commit", "-m", message];
10750
11051
  if (stageAll) commitArgs.splice(1, 0, "-a");
10751
- const commitOutput = runGit(commitArgs, cwd);
10752
- const hash = runGit(["rev-parse", "--short", "HEAD"], cwd).trim();
10753
- const summary = runGit(["log", "-1", "--pretty=format:%h %s"], cwd).trim();
11052
+ const commitOutput = runGit2(commitArgs, cwd);
11053
+ const hash = runGit2(["rev-parse", "--short", "HEAD"], cwd).trim();
11054
+ const summary = runGit2(["log", "-1", "--pretty=format:%h %s"], cwd).trim();
10754
11055
  return `\u2713 Committed ${hash}
10755
11056
  ${summary}
10756
11057
 
@@ -10759,9 +11060,9 @@ ${commitOutput.trim()}`;
10759
11060
  };
10760
11061
 
10761
11062
  // src/tools/builtin/notebook-edit.ts
10762
- import { readFileSync as readFileSync13, existsSync as existsSync17 } from "fs";
11063
+ import { readFileSync as readFileSync13, existsSync as existsSync18 } from "fs";
10763
11064
  import { writeFile } from "fs/promises";
10764
- import { resolve as resolve6, extname as extname2 } from "path";
11065
+ import { resolve as resolve7, extname as extname2 } from "path";
10765
11066
  var notebookEditTool = {
10766
11067
  definition: {
10767
11068
  name: "notebook_edit",
@@ -10810,11 +11111,11 @@ var notebookEditTool = {
10810
11111
  if (!Number.isInteger(cellIndexRaw) || cellIndexRaw < 1) {
10811
11112
  throw new ToolError("notebook_edit", "cell_index must be a positive integer (1-based)");
10812
11113
  }
10813
- const absPath = resolve6(filePath);
11114
+ const absPath = resolve7(filePath);
10814
11115
  if (extname2(absPath).toLowerCase() !== ".ipynb") {
10815
11116
  throw new ToolError("notebook_edit", "path must point to a .ipynb file");
10816
11117
  }
10817
- if (!existsSync17(absPath)) {
11118
+ if (!existsSync18(absPath)) {
10818
11119
  throw new ToolError("notebook_edit", `Notebook not found: ${filePath}`);
10819
11120
  }
10820
11121
  const raw = readFileSync13(absPath, "utf-8");
@@ -11234,8 +11535,8 @@ function estimateToolDefinitionTokens(def) {
11234
11535
 
11235
11536
  // src/tools/registry.ts
11236
11537
  import { pathToFileURL } from "url";
11237
- import { existsSync as existsSync18, mkdirSync as mkdirSync8, readdirSync as readdirSync8 } from "fs";
11238
- import { join as join12 } from "path";
11538
+ import { existsSync as existsSync19, mkdirSync as mkdirSync8, readdirSync as readdirSync8 } from "fs";
11539
+ import { join as join14 } from "path";
11239
11540
  var ToolRegistry = class {
11240
11541
  tools = /* @__PURE__ */ new Map();
11241
11542
  pluginToolNames = /* @__PURE__ */ new Set();
@@ -11396,7 +11697,7 @@ var ToolRegistry = class {
11396
11697
  * Returns the number of successfully loaded plugins.
11397
11698
  */
11398
11699
  async loadPlugins(pluginsDir, allowPlugins = false) {
11399
- if (!existsSync18(pluginsDir)) {
11700
+ if (!existsSync19(pluginsDir)) {
11400
11701
  try {
11401
11702
  mkdirSync8(pluginsDir, { recursive: true });
11402
11703
  } catch {
@@ -11422,12 +11723,12 @@ var ToolRegistry = class {
11422
11723
  process.stderr.write(
11423
11724
  `
11424
11725
  [plugins] \u26A0 Loading ${files.length} plugin(s) with FULL system privileges:
11425
- ` + files.map((f) => ` + ${join12(pluginsDir, f)}`).join("\n") + "\n\n"
11726
+ ` + files.map((f) => ` + ${join14(pluginsDir, f)}`).join("\n") + "\n\n"
11426
11727
  );
11427
11728
  let loaded = 0;
11428
11729
  for (const file of files) {
11429
11730
  try {
11430
- const fileUrl = pathToFileURL(join12(pluginsDir, file)).href;
11731
+ const fileUrl = pathToFileURL(join14(pluginsDir, file)).href;
11431
11732
  const mod = await import(fileUrl);
11432
11733
  const tool = mod.tool ?? mod.default?.tool ?? mod.default;
11433
11734
  if (!tool || typeof tool.execute !== "function" || !tool.definition?.name) {
@@ -11589,7 +11890,7 @@ var McpClient = class {
11589
11890
  // 内部方法:JSON-RPC 通信
11590
11891
  // ══════════════════════════════════════════════════════════════════
11591
11892
  sendRequest(method, params) {
11592
- return new Promise((resolve10, reject) => {
11893
+ return new Promise((resolve11, reject) => {
11593
11894
  if (!this.process?.stdin?.writable) {
11594
11895
  return reject(new Error(`MCP server [${this.serverId}] stdin not writable`));
11595
11896
  }
@@ -11613,7 +11914,7 @@ var McpClient = class {
11613
11914
  this.pendingRequests.set(id, {
11614
11915
  resolve: (result) => {
11615
11916
  cleanup();
11616
- resolve10(result);
11917
+ resolve11(result);
11617
11918
  },
11618
11919
  reject: (error) => {
11619
11920
  cleanup();
@@ -11690,13 +11991,13 @@ var McpClient = class {
11690
11991
  }
11691
11992
  /** Promise 超时包装 */
11692
11993
  withTimeout(promise, ms, label) {
11693
- return new Promise((resolve10, reject) => {
11994
+ return new Promise((resolve11, reject) => {
11694
11995
  const timer = setTimeout(() => {
11695
11996
  reject(new Error(`MCP [${this.serverId}] ${label} timed out after ${ms}ms`));
11696
11997
  }, ms);
11697
11998
  promise.then((val) => {
11698
11999
  clearTimeout(timer);
11699
- resolve10(val);
12000
+ resolve11(val);
11700
12001
  }).catch((err) => {
11701
12002
  clearTimeout(timer);
11702
12003
  reject(err);
@@ -11973,8 +12274,8 @@ var McpManager = class {
11973
12274
  };
11974
12275
 
11975
12276
  // src/skills/manager.ts
11976
- import { existsSync as existsSync19, readdirSync as readdirSync9, mkdirSync as mkdirSync9, statSync as statSync7 } from "fs";
11977
- import { join as join13 } from "path";
12277
+ import { existsSync as existsSync20, readdirSync as readdirSync9, mkdirSync as mkdirSync9, statSync as statSync6 } from "fs";
12278
+ import { join as join15 } from "path";
11978
12279
 
11979
12280
  // src/skills/types.ts
11980
12281
  import { readFileSync as readFileSync14 } from "fs";
@@ -12042,7 +12343,7 @@ var SkillManager = class {
12042
12343
  /** 发现并加载 skillsDir 下所有 .md 文件,返回加载数量 */
12043
12344
  loadSkills() {
12044
12345
  this.skills.clear();
12045
- if (!existsSync19(this.skillsDir)) {
12346
+ if (!existsSync20(this.skillsDir)) {
12046
12347
  try {
12047
12348
  mkdirSync9(this.skillsDir, { recursive: true });
12048
12349
  } catch {
@@ -12057,14 +12358,14 @@ var SkillManager = class {
12057
12358
  }
12058
12359
  for (const entry of entries) {
12059
12360
  let filePath;
12060
- const fullPath = join13(this.skillsDir, entry);
12361
+ const fullPath = join15(this.skillsDir, entry);
12061
12362
  if (entry.endsWith(".md")) {
12062
12363
  filePath = fullPath;
12063
12364
  } else {
12064
12365
  try {
12065
- if (statSync7(fullPath).isDirectory()) {
12066
- const skillMd = join13(fullPath, "SKILL.md");
12067
- if (existsSync19(skillMd)) {
12366
+ if (statSync6(fullPath).isDirectory()) {
12367
+ const skillMd = join15(fullPath, "SKILL.md");
12368
+ if (existsSync20(skillMd)) {
12068
12369
  filePath = skillMd;
12069
12370
  } else {
12070
12371
  continue;
@@ -12127,9 +12428,9 @@ var SkillManager = class {
12127
12428
  };
12128
12429
 
12129
12430
  // src/web/tool-executor-web.ts
12130
- import { randomUUID as randomUUID2 } from "crypto";
12431
+ import { randomUUID as randomUUID3 } from "crypto";
12131
12432
  import { tmpdir as tmpdir2 } from "os";
12132
- import { existsSync as existsSync20, readFileSync as readFileSync15 } from "fs";
12433
+ import { existsSync as existsSync21, readFileSync as readFileSync15 } from "fs";
12133
12434
  var ToolExecutorWeb = class _ToolExecutorWeb {
12134
12435
  constructor(registry, ws) {
12135
12436
  this.registry = registry;
@@ -12199,33 +12500,33 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
12199
12500
  }
12200
12501
  /** Resolve a pending confirm from client response */
12201
12502
  resolveConfirm(requestId, approved) {
12202
- const resolve10 = this.pendingConfirms.get(requestId);
12203
- if (resolve10) {
12503
+ const resolve11 = this.pendingConfirms.get(requestId);
12504
+ if (resolve11) {
12204
12505
  this.clearPendingTimer(requestId);
12205
12506
  this.pendingConfirms.delete(requestId);
12206
12507
  this.confirming = false;
12207
- resolve10(approved);
12508
+ resolve11(approved);
12208
12509
  }
12209
12510
  }
12210
12511
  /** Resolve a pending batch confirm from client response */
12211
12512
  resolveBatchConfirm(requestId, decision) {
12212
- const resolve10 = this.pendingBatchConfirms.get(requestId);
12213
- if (resolve10) {
12513
+ const resolve11 = this.pendingBatchConfirms.get(requestId);
12514
+ if (resolve11) {
12214
12515
  this.clearPendingTimer(requestId);
12215
12516
  this.pendingBatchConfirms.delete(requestId);
12216
12517
  this.confirming = false;
12217
12518
  if (decision === "all" || decision === "none") {
12218
- resolve10(decision);
12519
+ resolve11(decision);
12219
12520
  } else {
12220
- resolve10(new Set(decision));
12521
+ resolve11(new Set(decision));
12221
12522
  }
12222
12523
  }
12223
12524
  }
12224
12525
  /** Cancel all pending confirms (e.g., on disconnect) */
12225
12526
  cancelAll() {
12226
- for (const resolve10 of this.pendingConfirms.values()) resolve10(false);
12527
+ for (const resolve11 of this.pendingConfirms.values()) resolve11(false);
12227
12528
  this.pendingConfirms.clear();
12228
- for (const resolve10 of this.pendingBatchConfirms.values()) resolve10("none");
12529
+ for (const resolve11 of this.pendingBatchConfirms.values()) resolve11("none");
12229
12530
  this.pendingBatchConfirms.clear();
12230
12531
  this.confirming = false;
12231
12532
  }
@@ -12268,7 +12569,7 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
12268
12569
  if (call.name === "write_file") {
12269
12570
  const filePath = String(call.arguments["path"] ?? "");
12270
12571
  const newContent = String(call.arguments["content"] ?? "");
12271
- if (filePath && existsSync20(filePath)) {
12572
+ if (filePath && existsSync21(filePath)) {
12272
12573
  try {
12273
12574
  const old = readFileSync15(filePath, "utf-8");
12274
12575
  return renderDiff(old, newContent, { filePath });
@@ -12290,7 +12591,7 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
12290
12591
  }
12291
12592
  /** WebSocket-based confirm — sends request, returns Promise resolved by client */
12292
12593
  confirm(call, level) {
12293
- const requestId = randomUUID2();
12594
+ const requestId = randomUUID3();
12294
12595
  this.confirming = true;
12295
12596
  const msg = {
12296
12597
  type: "confirm_request",
@@ -12301,8 +12602,8 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
12301
12602
  diff: this.getDiffPreview(call)
12302
12603
  };
12303
12604
  this.send(msg);
12304
- return new Promise((resolve10) => {
12305
- this.pendingConfirms.set(requestId, resolve10);
12605
+ return new Promise((resolve11) => {
12606
+ this.pendingConfirms.set(requestId, resolve11);
12306
12607
  this.pendingTimers.set(requestId, setTimeout(() => {
12307
12608
  if (this.pendingConfirms.has(requestId)) {
12308
12609
  this.resolveConfirm(requestId, false);
@@ -12312,7 +12613,7 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
12312
12613
  }
12313
12614
  /** WebSocket-based batch confirm */
12314
12615
  batchConfirm(calls) {
12315
- const requestId = randomUUID2();
12616
+ const requestId = randomUUID3();
12316
12617
  this.confirming = true;
12317
12618
  const files = calls.map((call, i) => ({
12318
12619
  index: i + 1,
@@ -12326,8 +12627,8 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
12326
12627
  files
12327
12628
  };
12328
12629
  this.send(msg);
12329
- return new Promise((resolve10) => {
12330
- this.pendingBatchConfirms.set(requestId, resolve10);
12630
+ return new Promise((resolve11) => {
12631
+ this.pendingBatchConfirms.set(requestId, resolve11);
12331
12632
  this.pendingTimers.set(requestId, setTimeout(() => {
12332
12633
  if (this.pendingBatchConfirms.has(requestId)) {
12333
12634
  this.resolveBatchConfirm(requestId, "none");
@@ -12572,20 +12873,16 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
12572
12873
  }
12573
12874
  };
12574
12875
 
12575
- // src/core/system-prompt-builder.ts
12576
- import { existsSync as existsSync22, readFileSync as readFileSync17 } from "fs";
12577
- import { join as join15 } from "path";
12578
-
12579
12876
  // src/repl/dev-state.ts
12580
- import { existsSync as existsSync21, readFileSync as readFileSync16, unlinkSync as unlinkSync4, mkdirSync as mkdirSync10 } from "fs";
12581
- import { join as join14 } from "path";
12877
+ import { existsSync as existsSync22, readFileSync as readFileSync16, unlinkSync as unlinkSync4, mkdirSync as mkdirSync10 } from "fs";
12878
+ import { join as join16 } from "path";
12582
12879
  import { homedir as homedir7 } from "os";
12583
12880
  function getDevStatePath() {
12584
- return join14(homedir7(), CONFIG_DIR_NAME, DEV_STATE_FILE_NAME);
12881
+ return join16(homedir7(), CONFIG_DIR_NAME, DEV_STATE_FILE_NAME);
12585
12882
  }
12586
12883
  function loadDevState() {
12587
12884
  const path3 = getDevStatePath();
12588
- if (!existsSync21(path3)) return null;
12885
+ if (!existsSync22(path3)) return null;
12589
12886
  const content = readFileSync16(path3, "utf-8").trim();
12590
12887
  return content || null;
12591
12888
  }
@@ -12644,19 +12941,7 @@ ${ctx.activeSkill.content}`);
12644
12941
  return { stable: stableParts.join("\n\n---\n\n"), volatile };
12645
12942
  }
12646
12943
  function loadMemoryContent(configDir) {
12647
- const memoryPath = join15(configDir, MEMORY_FILE_NAME);
12648
- if (!existsSync22(memoryPath)) return null;
12649
- let content = readFileSync17(memoryPath, "utf-8").trim();
12650
- if (!content) return null;
12651
- if (content.length > MEMORY_MAX_CHARS) {
12652
- content = content.slice(-MEMORY_MAX_CHARS);
12653
- const firstEntry = content.indexOf("\n## ");
12654
- if (firstEntry !== -1) {
12655
- content = content.slice(firstEntry + 1);
12656
- }
12657
- }
12658
- const entryCount = (content.match(/^## \d{4}-\d{2}-\d{2}/gm) ?? []).length;
12659
- return { content, entryCount };
12944
+ return formatMemoryForPrompt(configDir, process.cwd());
12660
12945
  }
12661
12946
 
12662
12947
  // src/core/pricing.ts
@@ -12855,8 +13140,8 @@ function autoTrimSessionIfNeeded(session, sizeLimit = SESSION_SIZE_LIMIT) {
12855
13140
  }
12856
13141
 
12857
13142
  // src/core/context-files.ts
12858
- import { existsSync as existsSync23, readFileSync as readFileSync18 } from "fs";
12859
- import { join as join16, relative as relative3, resolve as resolve7 } from "path";
13143
+ import { existsSync as existsSync23, readFileSync as readFileSync17 } from "fs";
13144
+ import { join as join17, relative as relative3, resolve as resolve8 } from "path";
12860
13145
  function uniqueNonEmpty(values) {
12861
13146
  const seen = /* @__PURE__ */ new Set();
12862
13147
  const out = [];
@@ -12879,14 +13164,14 @@ function displayPath(filePath, cwd) {
12879
13164
  return filePath;
12880
13165
  }
12881
13166
  function isInsideOrEqual(parent, child) {
12882
- const rel = relative3(resolve7(parent), resolve7(child));
13167
+ const rel = relative3(resolve8(parent), resolve8(child));
12883
13168
  return rel === "" || !!rel && !rel.startsWith("..") && !rel.startsWith("/") && !rel.startsWith("\\");
12884
13169
  }
12885
13170
  function readContextFile(level, filePath, cwd, maxBytes) {
12886
13171
  const name = filePath.split(/[\\/]/).pop() ?? filePath;
12887
13172
  const shown = displayPath(filePath, cwd);
12888
13173
  try {
12889
- const raw = readFileSync18(filePath);
13174
+ const raw = readFileSync17(filePath);
12890
13175
  const byteCount = raw.byteLength;
12891
13176
  if (byteCount === 0) {
12892
13177
  return {
@@ -12933,7 +13218,7 @@ function readContextFile(level, filePath, cwd, maxBytes) {
12933
13218
  function findFirstContextFile(level, dir, cwd, candidates, maxBytes) {
12934
13219
  const skipped = [];
12935
13220
  for (const candidate of candidates) {
12936
- const filePath = join16(dir, candidate);
13221
+ const filePath = join17(dir, candidate);
12937
13222
  if (!existsSync23(filePath)) continue;
12938
13223
  const result = readContextFile(level, filePath, cwd, maxBytes);
12939
13224
  if (result.skipped) skipped.push(result.skipped);
@@ -12942,7 +13227,7 @@ function findFirstContextFile(level, dir, cwd, candidates, maxBytes) {
12942
13227
  return { layer: null, skipped };
12943
13228
  }
12944
13229
  function loadContextFiles(options) {
12945
- const cwd = resolve7(options.cwd);
13230
+ const cwd = resolve8(options.cwd);
12946
13231
  const maxBytes = options.maxBytes ?? CONTEXT_FILE_MAX_BYTES;
12947
13232
  const candidates = options.candidates ?? buildContextFileCandidates(options.fallbackFilenames);
12948
13233
  const skipped = [];
@@ -12950,7 +13235,7 @@ function loadContextFiles(options) {
12950
13235
  return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
12951
13236
  }
12952
13237
  if (options.setting && options.setting !== "auto") {
12953
- const filePath = resolve7(cwd, String(options.setting));
13238
+ const filePath = resolve8(cwd, String(options.setting));
12954
13239
  if (!isInsideOrEqual(cwd, filePath)) {
12955
13240
  skipped.push({
12956
13241
  level: "single",
@@ -12994,7 +13279,7 @@ function loadContextFiles(options) {
12994
13279
  skipped.push(...result.skipped);
12995
13280
  if (result.layer) layers.push(result.layer);
12996
13281
  }
12997
- if (resolve7(options.cwd) !== resolve7(options.projectRoot)) {
13282
+ if (resolve8(options.cwd) !== resolve8(options.projectRoot)) {
12998
13283
  const result = findFirstContextFile("local", options.cwd, cwd, candidates, maxBytes);
12999
13284
  skipped.push(...result.skipped);
13000
13285
  if (result.layer) layers.push(result.layer);
@@ -13012,98 +13297,8 @@ function loadContextFiles(options) {
13012
13297
  }
13013
13298
 
13014
13299
  // src/web/session-handler.ts
13015
- import { existsSync as existsSync26, readFileSync as readFileSync20, writeFileSync as writeFileSync2, mkdirSync as mkdirSync11, readdirSync as readdirSync11, statSync as statSync9, createWriteStream } from "fs";
13016
- import { join as join20, resolve as resolve8, dirname as dirname5 } from "path";
13017
-
13018
- // src/tools/git-context.ts
13019
- import { execSync as execSync2 } from "child_process";
13020
- import { existsSync as existsSync24 } from "fs";
13021
- import { join as join17 } from "path";
13022
- function runGit2(cmd, cwd) {
13023
- try {
13024
- return execSync2(`git ${cmd}`, {
13025
- cwd,
13026
- encoding: "utf-8",
13027
- stdio: ["pipe", "pipe", "pipe"],
13028
- timeout: 5e3
13029
- }).trim();
13030
- } catch {
13031
- return null;
13032
- }
13033
- }
13034
- function getGitRoot(cwd = process.cwd()) {
13035
- return runGit2("rev-parse --show-toplevel", cwd);
13036
- }
13037
- function getGitContext(cwd = process.cwd()) {
13038
- if (!existsSync24(join17(cwd, ".git"))) {
13039
- const result = runGit2("rev-parse --git-dir", cwd);
13040
- if (!result) return null;
13041
- }
13042
- const branch = runGit2("rev-parse --abbrev-ref HEAD", cwd);
13043
- if (!branch) return null;
13044
- const statusOutput = runGit2("status --porcelain", cwd) ?? "";
13045
- const statusLines = statusOutput ? statusOutput.split("\n").filter(Boolean) : [];
13046
- const stagedFiles = [];
13047
- const changedFiles = [];
13048
- for (const line of statusLines) {
13049
- const xy = line.slice(0, 2);
13050
- const file = line.slice(3).trim();
13051
- const indexStatus = xy[0];
13052
- const workStatus = xy[1];
13053
- if (indexStatus && indexStatus !== " " && indexStatus !== "?") {
13054
- stagedFiles.push(`${indexStatus} ${file}`);
13055
- }
13056
- if (workStatus && workStatus !== " ") {
13057
- changedFiles.push(`${workStatus} ${file}`);
13058
- }
13059
- }
13060
- const logOutput = runGit2("log --oneline -3", cwd) ?? "";
13061
- const recentCommits = logOutput ? logOutput.split("\n").filter(Boolean) : [];
13062
- const unpushedOutput = runGit2("log @{u}..HEAD --oneline", cwd);
13063
- const hasUnpushed = unpushedOutput !== null && unpushedOutput.trim().length > 0;
13064
- return {
13065
- branch,
13066
- changedFiles,
13067
- stagedFiles,
13068
- recentCommits,
13069
- hasUnpushed
13070
- };
13071
- }
13072
- function formatGitContextForPrompt(ctx) {
13073
- const lines = ["# Git Repository Status", ""];
13074
- lines.push(`- **Branch**: \`${ctx.branch}\``);
13075
- if (ctx.stagedFiles.length > 0) {
13076
- lines.push(`- **Staged** (${ctx.stagedFiles.length} files):`);
13077
- for (const f of ctx.stagedFiles.slice(0, 10)) {
13078
- lines.push(` - ${f}`);
13079
- }
13080
- if (ctx.stagedFiles.length > 10) {
13081
- lines.push(` - ... and ${ctx.stagedFiles.length - 10} more`);
13082
- }
13083
- }
13084
- if (ctx.changedFiles.length > 0) {
13085
- lines.push(`- **Modified** (${ctx.changedFiles.length} files):`);
13086
- for (const f of ctx.changedFiles.slice(0, 10)) {
13087
- lines.push(` - ${f}`);
13088
- }
13089
- if (ctx.changedFiles.length > 10) {
13090
- lines.push(` - ... and ${ctx.changedFiles.length - 10} more`);
13091
- }
13092
- }
13093
- if (ctx.stagedFiles.length === 0 && ctx.changedFiles.length === 0) {
13094
- lines.push("- **Working tree**: clean");
13095
- }
13096
- if (ctx.recentCommits.length > 0) {
13097
- lines.push("- **Recent commits**:");
13098
- for (const c of ctx.recentCommits) {
13099
- lines.push(` - ${c}`);
13100
- }
13101
- }
13102
- if (ctx.hasUnpushed) {
13103
- lines.push("- \u26A0\uFE0F Has unpushed commits");
13104
- }
13105
- return lines.join("\n");
13106
- }
13300
+ import { existsSync as existsSync25, readFileSync as readFileSync19, writeFileSync as writeFileSync2, mkdirSync as mkdirSync11, readdirSync as readdirSync11, statSync as statSync8, createWriteStream } from "fs";
13301
+ import { join as join20, resolve as resolve9, dirname as dirname6 } from "path";
13107
13302
 
13108
13303
  // src/cli/review-prompts.ts
13109
13304
  function buildReviewPrompt(diff, gitContextStr, detailed) {
@@ -13164,7 +13359,7 @@ If no security issues found, state "\u2705 No security vulnerabilities detected"
13164
13359
  }
13165
13360
 
13166
13361
  // src/repl/commands/project-init.ts
13167
- import { existsSync as existsSync25, readFileSync as readFileSync19, readdirSync as readdirSync10, statSync as statSync8 } from "fs";
13362
+ import { existsSync as existsSync24, readFileSync as readFileSync18, readdirSync as readdirSync10, statSync as statSync7 } from "fs";
13168
13363
  import { join as join18 } from "path";
13169
13364
  var SCAN_SKIP_DIRS = /* @__PURE__ */ new Set([
13170
13365
  "node_modules",
@@ -13209,11 +13404,11 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
13209
13404
  const sorted = filtered.sort((a, b) => {
13210
13405
  let aIsDir = false, bIsDir = false;
13211
13406
  try {
13212
- aIsDir = statSync8(join18(d, a)).isDirectory();
13407
+ aIsDir = statSync7(join18(d, a)).isDirectory();
13213
13408
  } catch {
13214
13409
  }
13215
13410
  try {
13216
- bIsDir = statSync8(join18(d, b)).isDirectory();
13411
+ bIsDir = statSync7(join18(d, b)).isDirectory();
13217
13412
  } catch {
13218
13413
  }
13219
13414
  if (aIsDir !== bIsDir) return aIsDir ? -1 : 1;
@@ -13226,7 +13421,7 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
13226
13421
  const connector = isLast ? "+-- " : "|-- ";
13227
13422
  let isDir;
13228
13423
  try {
13229
- isDir = statSync8(fullPath).isDirectory();
13424
+ isDir = statSync7(fullPath).isDirectory();
13230
13425
  } catch {
13231
13426
  continue;
13232
13427
  }
@@ -13248,7 +13443,7 @@ function scanProject(cwd) {
13248
13443
  configFiles: [],
13249
13444
  directoryStructure: ""
13250
13445
  };
13251
- const check = (file) => existsSync25(join18(cwd, file));
13446
+ const check = (file) => existsSync24(join18(cwd, file));
13252
13447
  const configCandidates = [
13253
13448
  "package.json",
13254
13449
  "tsconfig.json",
@@ -13275,7 +13470,7 @@ function scanProject(cwd) {
13275
13470
  info.type = "node";
13276
13471
  info.language = check("tsconfig.json") ? "TypeScript" : "JavaScript";
13277
13472
  try {
13278
- const pkg = JSON.parse(readFileSync19(join18(cwd, "package.json"), "utf-8"));
13473
+ const pkg = JSON.parse(readFileSync18(join18(cwd, "package.json"), "utf-8"));
13279
13474
  const scripts = pkg.scripts ?? {};
13280
13475
  info.buildCommand = scripts.build ? "npm run build" : void 0;
13281
13476
  info.testCommand = scripts.test ? "npm test" : void 0;
@@ -14169,10 +14364,10 @@ var SessionHandler = class {
14169
14364
  return;
14170
14365
  }
14171
14366
  case "ask_user_response": {
14172
- const resolve10 = this.pendingAskUser.get(msg.requestId);
14173
- if (resolve10) {
14367
+ const resolve11 = this.pendingAskUser.get(msg.requestId);
14368
+ if (resolve11) {
14174
14369
  this.pendingAskUser.delete(msg.requestId);
14175
- resolve10(msg.answer);
14370
+ resolve11(msg.answer);
14176
14371
  }
14177
14372
  return;
14178
14373
  }
@@ -14182,11 +14377,13 @@ var SessionHandler = class {
14182
14377
  return this.handleMemoryStatus();
14183
14378
  case "memory_rebuild":
14184
14379
  return this.handleMemoryRebuild(Boolean(msg.full));
14380
+ case "memory_manage":
14381
+ return this.handleMemoryManage(msg.action, msg.id, msg.expiresAt);
14185
14382
  case "auto_pause_response": {
14186
- const resolve10 = this.pendingAutoPause.get(msg.requestId);
14187
- if (resolve10) {
14383
+ const resolve11 = this.pendingAutoPause.get(msg.requestId);
14384
+ if (resolve11) {
14188
14385
  this.pendingAutoPause.delete(msg.requestId);
14189
- resolve10({ action: msg.action, message: msg.message });
14386
+ resolve11({ action: msg.action, message: msg.message });
14190
14387
  }
14191
14388
  return;
14192
14389
  }
@@ -14201,10 +14398,10 @@ var SessionHandler = class {
14201
14398
  this.hubOrchestrator?.abort();
14202
14399
  return;
14203
14400
  case "hub_steer": {
14204
- const resolve10 = this.pendingHubReview.get(msg.requestId);
14205
- if (resolve10) {
14401
+ const resolve11 = this.pendingHubReview.get(msg.requestId);
14402
+ if (resolve11) {
14206
14403
  this.pendingHubReview.delete(msg.requestId);
14207
- resolve10({ action: msg.action, message: msg.message });
14404
+ resolve11({ action: msg.action, message: msg.message });
14208
14405
  }
14209
14406
  return;
14210
14407
  }
@@ -14217,9 +14414,9 @@ var SessionHandler = class {
14217
14414
  runLifecycleHooks(this.config.get("hooks") ?? void 0, "Stop", { sessionId: this.sessions.current?.id }, { configDir: this.config.getConfigDir() });
14218
14415
  this.toolExecutor.cancelAll();
14219
14416
  if (this.abortController) this.abortController.abort();
14220
- for (const resolve10 of this.pendingAskUser.values()) resolve10(null);
14417
+ for (const resolve11 of this.pendingAskUser.values()) resolve11(null);
14221
14418
  this.pendingAskUser.clear();
14222
- for (const resolve10 of this.pendingAutoPause.values()) resolve10({ action: "stop" });
14419
+ for (const resolve11 of this.pendingAutoPause.values()) resolve11({ action: "stop" });
14223
14420
  this.pendingAutoPause.clear();
14224
14421
  this.saveIfNeeded();
14225
14422
  }
@@ -14353,9 +14550,9 @@ var SessionHandler = class {
14353
14550
  this.hubOrchestrator = orchestrator;
14354
14551
  orchestrator.onEvent = (event) => this.send({ type: "hub_event", event });
14355
14552
  if (config.humanSteer) {
14356
- orchestrator.onRoundReview = ({ round, maxRounds }) => new Promise((resolve10) => {
14553
+ orchestrator.onRoundReview = ({ round, maxRounds }) => new Promise((resolve11) => {
14357
14554
  const requestId = `hubrev_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
14358
- this.pendingHubReview.set(requestId, resolve10);
14555
+ this.pendingHubReview.set(requestId, resolve11);
14359
14556
  this.send({ type: "hub_review", requestId, round, maxRounds });
14360
14557
  });
14361
14558
  }
@@ -14688,7 +14885,7 @@ Try: /compact to reduce context, /clear to reset, or switch to a larger-context
14688
14885
  this.send({ type: "response_done", content, usage });
14689
14886
  if (pendingTeeSave && isCleanDocumentBody(content)) {
14690
14887
  try {
14691
- mkdirSync11(dirname5(pendingTeeSave), { recursive: true });
14888
+ mkdirSync11(dirname6(pendingTeeSave), { recursive: true });
14692
14889
  const bodyToSave = stripOuterCodeFence(content);
14693
14890
  atomicWriteFileSync(pendingTeeSave, bodyToSave);
14694
14891
  undoStack.push(pendingTeeSave, `save_last_response (deferred): ${pendingTeeSave}`);
@@ -14713,8 +14910,8 @@ Try: /compact to reduce context, /clear to reset, or switch to a larger-context
14713
14910
  onMcpToolUsed: (name) => this.usedMcpToolNames.add(name),
14714
14911
  requestAutoPause: async ({ effectiveRound, maxToolRounds: totalRounds, toolSummary }) => {
14715
14912
  const requestId = `pause_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
14716
- const pauseResp = await new Promise((resolve10) => {
14717
- this.pendingAutoPause.set(requestId, resolve10);
14913
+ const pauseResp = await new Promise((resolve11) => {
14914
+ this.pendingAutoPause.set(requestId, resolve11);
14718
14915
  this.send({
14719
14916
  type: "auto_pause_request",
14720
14917
  requestId,
@@ -14864,7 +15061,7 @@ ${summaryContent}`,
14864
15061
  let isError = false;
14865
15062
  let summary;
14866
15063
  try {
14867
- mkdirSync11(dirname5(saveToFile), { recursive: true });
15064
+ mkdirSync11(dirname6(saveToFile), { recursive: true });
14868
15065
  fileStream = createWriteStream(saveToFile);
14869
15066
  const teeSystemPrompt = stripToolCallReminder(systemPrompt ?? "") + CONTENT_ONLY_STREAM_REMINDER;
14870
15067
  const teeExtraMessages = extraMessages.length > 0 ? [...extraMessages, { role: "user", content: TEE_FINAL_USER_NUDGE }] : [{ role: "user", content: TEE_FINAL_USER_NUDGE }];
@@ -14893,8 +15090,8 @@ ${summaryContent}`,
14893
15090
  }
14894
15091
  if (chunk.done) break;
14895
15092
  }
14896
- await new Promise((resolve10, reject) => {
14897
- fileStream.end((err) => err ? reject(err) : resolve10());
15093
+ await new Promise((resolve11, reject) => {
15094
+ fileStream.end((err) => err ? reject(err) : resolve11());
14898
15095
  });
14899
15096
  const verdict = evaluateTeeContent(fullContent, saveToFile, priorContent);
14900
15097
  if (verdict.kind === "reject") {
@@ -14920,7 +15117,7 @@ ${summaryContent}`,
14920
15117
  } catch (err) {
14921
15118
  if (fileStream) {
14922
15119
  try {
14923
- await new Promise((resolve10) => fileStream.end(() => resolve10()));
15120
+ await new Promise((resolve11) => fileStream.end(() => resolve11()));
14924
15121
  } catch {
14925
15122
  }
14926
15123
  }
@@ -15552,9 +15749,9 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
15552
15749
  let modifiedFiles = 0;
15553
15750
  const diffLines2 = [];
15554
15751
  for (const [filePath, { earliest }] of fileMap) {
15555
- const currentContent = existsSync26(filePath) ? (() => {
15752
+ const currentContent = existsSync25(filePath) ? (() => {
15556
15753
  try {
15557
- return readFileSync20(filePath, "utf-8");
15754
+ return readFileSync19(filePath, "utf-8");
15558
15755
  } catch {
15559
15756
  return null;
15560
15757
  }
@@ -15655,7 +15852,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
15655
15852
  break;
15656
15853
  }
15657
15854
  const sub = args[0]?.toLowerCase();
15658
- const resolve10 = (ref) => {
15855
+ const resolve11 = (ref) => {
15659
15856
  const r = session.resolveBranchRef(ref);
15660
15857
  if (r.ok) return r.id;
15661
15858
  if (r.reason === "ambiguous") {
@@ -15709,7 +15906,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
15709
15906
  this.send({ type: "error", message: "Usage: /branch switch <id|title>" });
15710
15907
  break;
15711
15908
  }
15712
- const id = resolve10(ref);
15909
+ const id = resolve11(ref);
15713
15910
  if (!id) break;
15714
15911
  const ok = session.switchBranch(id);
15715
15912
  if (ok) {
@@ -15729,7 +15926,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
15729
15926
  this.send({ type: "error", message: "Usage: /branch delete <id|title>" });
15730
15927
  break;
15731
15928
  }
15732
- const id = resolve10(ref);
15929
+ const id = resolve11(ref);
15733
15930
  if (!id) break;
15734
15931
  const ok = session.deleteBranch(id);
15735
15932
  if (ok) {
@@ -15748,7 +15945,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
15748
15945
  this.send({ type: "error", message: "Usage: /branch rename <id|title> <new title>" });
15749
15946
  break;
15750
15947
  }
15751
- const id = resolve10(ref);
15948
+ const id = resolve11(ref);
15752
15949
  if (!id) break;
15753
15950
  const ok = session.renameBranch(id, title);
15754
15951
  if (ok) {
@@ -15766,7 +15963,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
15766
15963
  this.send({ type: "error", message: "Usage: /branch diff <id|title>" });
15767
15964
  break;
15768
15965
  }
15769
- const id = resolve10(ref);
15966
+ const id = resolve11(ref);
15770
15967
  if (!id) break;
15771
15968
  const d = session.diffBranches(id);
15772
15969
  if (!d) {
@@ -15808,7 +16005,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
15808
16005
  this.send({ type: "error", message: "Usage: /branch cherry-pick <source-id|title> <msg-index>" });
15809
16006
  break;
15810
16007
  }
15811
- const id = resolve10(ref);
16008
+ const id = resolve11(ref);
15812
16009
  if (!id) break;
15813
16010
  const idx = parseInt(idxStr, 10);
15814
16011
  if (Number.isNaN(idx)) {
@@ -16070,7 +16267,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
16070
16267
  case "test": {
16071
16268
  this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
16072
16269
  try {
16073
- const { executeTests } = await import("./run-tests-4PKSIVK5.js");
16270
+ const { executeTests } = await import("./run-tests-K7QR5QN4.js");
16074
16271
  const argStr = args.join(" ").trim();
16075
16272
  let testArgs = {};
16076
16273
  if (argStr) {
@@ -16089,7 +16286,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
16089
16286
  const cwd = process.cwd();
16090
16287
  const targetPath = join20(cwd, "AICLI.md");
16091
16288
  const force = args.includes("--force");
16092
- if (existsSync26(targetPath) && !force) {
16289
+ if (existsSync25(targetPath) && !force) {
16093
16290
  this.send({ type: "info", message: `AICLI.md already exists at ${targetPath}
16094
16291
  Use /init --force to overwrite.` });
16095
16292
  break;
@@ -16122,11 +16319,11 @@ Use /context reload to load it.` });
16122
16319
  lines.push("**Config Files:**");
16123
16320
  lines.push(` Dir: ${configDir}`);
16124
16321
  const checkFile = (label, filePath) => {
16125
- const exists = existsSync26(filePath);
16322
+ const exists = existsSync25(filePath);
16126
16323
  let extra = "";
16127
16324
  if (exists) {
16128
16325
  try {
16129
- extra = ` (${statSync9(filePath).size} bytes)`;
16326
+ extra = ` (${statSync8(filePath).size} bytes)`;
16130
16327
  } catch {
16131
16328
  }
16132
16329
  }
@@ -16321,12 +16518,12 @@ Use /add-dir remove to clear.` : "No directories added.\nUsage: /add-dir <path>
16321
16518
  this.send({ type: "info", message: "\u2713 All added directories removed from context." });
16322
16519
  break;
16323
16520
  }
16324
- const dirPath = resolve8(sub);
16325
- if (!existsSync26(dirPath)) {
16521
+ const dirPath = resolve9(sub);
16522
+ if (!existsSync25(dirPath)) {
16326
16523
  this.send({ type: "error", message: `Directory not found: ${dirPath}` });
16327
16524
  break;
16328
16525
  }
16329
- if (!statSync9(dirPath).isDirectory()) {
16526
+ if (!statSync8(dirPath).isDirectory()) {
16330
16527
  this.send({ type: "error", message: `Not a directory: ${dirPath}` });
16331
16528
  break;
16332
16529
  }
@@ -16339,7 +16536,7 @@ It will be included in AI context for subsequent messages.` });
16339
16536
  case "commands": {
16340
16537
  const configDir = this.config.getConfigDir();
16341
16538
  const commandsDir = join20(configDir, CUSTOM_COMMANDS_DIR_NAME);
16342
- if (!existsSync26(commandsDir)) {
16539
+ if (!existsSync25(commandsDir)) {
16343
16540
  this.send({ type: "info", message: `No custom commands directory.
16344
16541
  Create: ${commandsDir}/ with .md files.` });
16345
16542
  break;
@@ -16562,48 +16759,95 @@ Add .md files to create commands.` });
16562
16759
  }
16563
16760
  memoryShow() {
16564
16761
  const configDir = this.config.getConfigDir();
16565
- const memPath = join20(configDir, MEMORY_FILE_NAME);
16566
- let content = "";
16567
- try {
16568
- if (existsSync26(memPath)) {
16569
- content = readFileSync20(memPath, "utf-8");
16570
- }
16571
- } catch (err) {
16572
- process.stderr.write(`[web] Failed to read memory file: ${err instanceof Error ? err.message : err}
16573
- `);
16574
- }
16762
+ const entries = listMemoryEntries(configDir, { includeExpired: true, includeRejected: true });
16763
+ const content = entries.length === 0 ? "(empty \u2014 no persistent memory entries yet)" : entries.map((entry) => {
16764
+ const status = isMemoryExpired(entry) ? "expired" : entry.approved ? "approved" : "pending";
16765
+ const source = entry.sourceSession ? `session ${entry.sourceSession.slice(0, 8)}` : entry.sourceProject ? `project ${entry.sourceProject}` : "manual/tool";
16766
+ return `## ${entry.id.slice(0, 8)} \xB7 ${status} \xB7 scope:${entry.scope} \xB7 sensitivity:${entry.sensitivity}
16767
+ Source: ${source}
16768
+ ${entry.content}`;
16769
+ }).join("\n\n");
16575
16770
  this.send({
16576
16771
  type: "memory_content",
16577
- content: content || "(empty \u2014 no persistent memory entries yet)",
16578
- filePath: memPath
16772
+ content,
16773
+ filePath: memoryStorePath(configDir)
16579
16774
  });
16775
+ this.sendMemoryEntries();
16580
16776
  }
16581
16777
  memoryAdd(text) {
16582
- const configDir = this.config.getConfigDir();
16583
- const memPath = join20(configDir, MEMORY_FILE_NAME);
16584
16778
  try {
16585
- mkdirSync11(configDir, { recursive: true });
16586
- const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace("T", " ");
16587
- const previous = existsSync26(memPath) ? readFileSync20(memPath, "utf-8") : "";
16588
- const entry = `
16589
- - [${timestamp}] ${text}
16590
- `;
16591
- atomicWriteFileSync(memPath, previous + entry);
16592
- this.send({ type: "info", message: `\u{1F4DD} Memory entry added: "${text}"` });
16779
+ const entry = addMemoryEntry(this.config.getConfigDir(), text, {
16780
+ scope: "personal",
16781
+ cwd: process.cwd(),
16782
+ redact: true
16783
+ });
16784
+ const status = entry.approved ? "approved" : "pending approval";
16785
+ this.send({ type: "info", message: `\u{1F4DD} Memory entry ${entry.id.slice(0, 8)} added (${status}).` });
16786
+ this.sendMemoryEntries();
16593
16787
  } catch (err) {
16594
16788
  this.send({ type: "error", message: `Failed to write memory: ${err.message}` });
16595
16789
  }
16596
16790
  }
16597
16791
  memoryClear() {
16598
16792
  const configDir = this.config.getConfigDir();
16599
- const memPath = join20(configDir, MEMORY_FILE_NAME);
16600
16793
  try {
16601
- atomicWriteFileSync(memPath, "");
16794
+ atomicWriteFileSync(memoryStorePath(configDir), "");
16795
+ atomicWriteFileSync(join20(configDir, MEMORY_FILE_NAME), "");
16602
16796
  this.send({ type: "info", message: "\u{1F5D1}\uFE0F Persistent memory cleared." });
16797
+ this.sendMemoryEntries();
16603
16798
  } catch (err) {
16604
16799
  this.send({ type: "error", message: `Failed to clear memory: ${err.message}` });
16605
16800
  }
16606
16801
  }
16802
+ sendMemoryEntries() {
16803
+ const configDir = this.config.getConfigDir();
16804
+ const entries = listMemoryEntries(configDir, { includeExpired: true, includeRejected: true });
16805
+ this.send({
16806
+ type: "memory_entries",
16807
+ storePath: memoryStorePath(configDir),
16808
+ entries: entries.map((entry) => ({
16809
+ id: entry.id,
16810
+ content: entry.content,
16811
+ scope: entry.scope,
16812
+ sourceSession: entry.sourceSession,
16813
+ sourceProject: entry.sourceProject,
16814
+ createdAt: entry.createdAt,
16815
+ updatedAt: entry.updatedAt,
16816
+ expiresAt: entry.expiresAt,
16817
+ sensitivity: entry.sensitivity,
16818
+ approved: entry.approved,
16819
+ expired: isMemoryExpired(entry),
16820
+ redactedKinds: entry.redactedKinds
16821
+ }))
16822
+ });
16823
+ }
16824
+ handleMemoryManage(action, id, expiresAt) {
16825
+ const configDir = this.config.getConfigDir();
16826
+ try {
16827
+ if (action === "approve" || action === "reject") {
16828
+ if (!id) throw new Error(`${action} requires id`);
16829
+ updateMemoryApproval(configDir, id, action === "approve");
16830
+ } else if (action === "delete") {
16831
+ if (!id) throw new Error("delete requires id");
16832
+ deleteMemoryEntry(configDir, id);
16833
+ } else if (action === "expire") {
16834
+ if (!id) throw new Error("expire requires id");
16835
+ expireMemoryEntry(configDir, id, expiresAt);
16836
+ } else if (action === "export") {
16837
+ this.send({
16838
+ type: "export_data",
16839
+ format: "json",
16840
+ filename: `aicli-memory-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.json`,
16841
+ content: exportMemoryEntries(configDir, process.cwd())
16842
+ });
16843
+ } else if (action !== "list") {
16844
+ throw new Error(`unknown memory action: ${action}`);
16845
+ }
16846
+ this.sendMemoryEntries();
16847
+ } catch (err) {
16848
+ this.send({ type: "error", message: `Memory management failed: ${err instanceof Error ? err.message : String(err)}` });
16849
+ }
16850
+ }
16607
16851
  // ── B4 chat memory recall (v0.4.90+) ──────────────────────────────
16608
16852
  // Lazy-imported so the 117 MB embedder stays out of the load path for
16609
16853
  // clients that never open the Memory panel.
@@ -16614,7 +16858,7 @@ Add .md files to create commands.` });
16614
16858
  return;
16615
16859
  }
16616
16860
  try {
16617
- const { searchChatMemory: searchChatMemory2, loadChatIndex: loadChatIndex2 } = await import("./chat-index-R2E27VXN.js");
16861
+ const { searchChatMemory: searchChatMemory2, loadChatIndex: loadChatIndex2 } = await import("./chat-index-PS274XM7.js");
16618
16862
  const loaded = loadChatIndex2();
16619
16863
  if (!loaded || loaded.idx.chunks.length === 0) {
16620
16864
  this.send({ type: "memory_hits", query: q, hits: [], indexMissing: true });
@@ -16650,7 +16894,7 @@ Add .md files to create commands.` });
16650
16894
  }
16651
16895
  async handleMemoryStatus() {
16652
16896
  try {
16653
- const { getChatIndexStatus } = await import("./chat-index-R2E27VXN.js");
16897
+ const { getChatIndexStatus } = await import("./chat-index-PS274XM7.js");
16654
16898
  const s = getChatIndexStatus();
16655
16899
  this.send({
16656
16900
  type: "memory_status",
@@ -16675,7 +16919,7 @@ Add .md files to create commands.` });
16675
16919
  type: "info",
16676
16920
  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"
16677
16921
  });
16678
- const { buildChatIndex } = await import("./chat-index-R2E27VXN.js");
16922
+ const { buildChatIndex } = await import("./chat-index-PS274XM7.js");
16679
16923
  const stats = await buildChatIndex({
16680
16924
  full,
16681
16925
  onProgress: (p) => {
@@ -16830,7 +17074,7 @@ async function setupProxy(configProxy) {
16830
17074
  }
16831
17075
 
16832
17076
  // src/web/auth.ts
16833
- import { existsSync as existsSync27, readFileSync as readFileSync21, writeFileSync as writeFileSync3, mkdirSync as mkdirSync12, readdirSync as readdirSync12, copyFileSync } from "fs";
17077
+ import { existsSync as existsSync26, readFileSync as readFileSync20, writeFileSync as writeFileSync3, mkdirSync as mkdirSync12, readdirSync as readdirSync12, copyFileSync } from "fs";
16834
17078
  import { join as join21 } from "path";
16835
17079
  import { createHmac, randomBytes, timingSafeEqual, pbkdf2Sync } from "crypto";
16836
17080
  var USERS_FILE = "users.json";
@@ -17029,23 +17273,23 @@ var AuthManager = class {
17029
17273
  if (err) return err;
17030
17274
  const userDir = this.getUserDataDir(username);
17031
17275
  const globalConfig = join21(this.baseDir, "config.json");
17032
- if (existsSync27(globalConfig)) {
17276
+ if (existsSync26(globalConfig)) {
17033
17277
  try {
17034
- const content = readFileSync21(globalConfig, "utf-8");
17278
+ const content = readFileSync20(globalConfig, "utf-8");
17035
17279
  writeFileSync3(join21(userDir, "config.json"), content, "utf-8");
17036
17280
  } catch {
17037
17281
  }
17038
17282
  }
17039
17283
  const globalMemory = join21(this.baseDir, "memory.md");
17040
- if (existsSync27(globalMemory)) {
17284
+ if (existsSync26(globalMemory)) {
17041
17285
  try {
17042
- const content = readFileSync21(globalMemory, "utf-8");
17286
+ const content = readFileSync20(globalMemory, "utf-8");
17043
17287
  writeFileSync3(join21(userDir, "memory.md"), content, "utf-8");
17044
17288
  } catch {
17045
17289
  }
17046
17290
  }
17047
17291
  const globalHistory = join21(this.baseDir, "history");
17048
- if (existsSync27(globalHistory)) {
17292
+ if (existsSync26(globalHistory)) {
17049
17293
  try {
17050
17294
  const files = readdirSync12(globalHistory).filter((f) => f.endsWith(".json"));
17051
17295
  const userHistory = join21(userDir, "history");
@@ -17062,9 +17306,9 @@ var AuthManager = class {
17062
17306
  }
17063
17307
  // ── Private methods ────────────────────────────────────────────
17064
17308
  loadOrCreate() {
17065
- if (existsSync27(this.usersFile)) {
17309
+ if (existsSync26(this.usersFile)) {
17066
17310
  try {
17067
- return JSON.parse(readFileSync21(this.usersFile, "utf-8"));
17311
+ return JSON.parse(readFileSync20(this.usersFile, "utf-8"));
17068
17312
  } catch {
17069
17313
  }
17070
17314
  }
@@ -17112,11 +17356,11 @@ function getModuleDir() {
17112
17356
  if (typeof import.meta?.url === "string") {
17113
17357
  const url = new URL(import.meta.url);
17114
17358
  const filePath = url.pathname.replace(/^\/([A-Z]:)/i, "$1");
17115
- return dirname6(filePath);
17359
+ return dirname7(filePath);
17116
17360
  }
17117
17361
  } catch {
17118
17362
  }
17119
- return resolve9(".");
17363
+ return resolve10(".");
17120
17364
  }
17121
17365
  async function startWebServer(options = {}) {
17122
17366
  const port = options.port ?? 3e3;
@@ -17182,7 +17426,7 @@ async function startWebServer(options = {}) {
17182
17426
  }
17183
17427
  let skillManager = null;
17184
17428
  const skillsDir = join22(config.getConfigDir(), SKILLS_DIR_NAME);
17185
- if (existsSync28(skillsDir)) {
17429
+ if (existsSync27(skillsDir)) {
17186
17430
  skillManager = new SkillManager(skillsDir, config.get("ui").skillSizeWarn);
17187
17431
  skillManager.loadSkills();
17188
17432
  const count = skillManager.listSkills().length;
@@ -17254,13 +17498,13 @@ async function startWebServer(options = {}) {
17254
17498
  };
17255
17499
  const moduleDir = getModuleDir();
17256
17500
  let clientDir = join22(moduleDir, "web", "client");
17257
- if (!existsSync28(clientDir)) {
17501
+ if (!existsSync27(clientDir)) {
17258
17502
  clientDir = join22(moduleDir, "client");
17259
17503
  }
17260
- if (!existsSync28(clientDir)) {
17504
+ if (!existsSync27(clientDir)) {
17261
17505
  clientDir = join22(moduleDir, "..", "..", "src", "web", "client");
17262
17506
  }
17263
- if (!existsSync28(clientDir)) {
17507
+ if (!existsSync27(clientDir)) {
17264
17508
  clientDir = join22(process.cwd(), "src", "web", "client");
17265
17509
  }
17266
17510
  console.log(` Static files: ${clientDir}`);
@@ -17333,8 +17577,8 @@ async function startWebServer(options = {}) {
17333
17577
  const prefix = req.query.prefix || "";
17334
17578
  const targetDir = join22(cwd, prefix);
17335
17579
  try {
17336
- const canonicalTarget = realpathSync(resolve9(targetDir));
17337
- const canonicalCwd = realpathSync(resolve9(cwd));
17580
+ const canonicalTarget = realpathSync(resolve10(targetDir));
17581
+ const canonicalCwd = realpathSync(resolve10(cwd));
17338
17582
  if (!canonicalTarget.startsWith(canonicalCwd + sep3) && canonicalTarget !== canonicalCwd) {
17339
17583
  res.json({ files: [] });
17340
17584
  return;
@@ -17385,7 +17629,7 @@ async function startWebServer(options = {}) {
17385
17629
  const authUser = req._authUser;
17386
17630
  const histDir = authUser ? getUserShared(authUser).config.getHistoryDir() : config.getHistoryDir();
17387
17631
  const filePath = join22(histDir, `${id}.json`);
17388
- if (!existsSync28(filePath)) {
17632
+ if (!existsSync27(filePath)) {
17389
17633
  res.status(404).json({ error: "Session not found" });
17390
17634
  return;
17391
17635
  }
@@ -17400,7 +17644,7 @@ async function startWebServer(options = {}) {
17400
17644
  res.status(404).json({ error: "Session not found" });
17401
17645
  return;
17402
17646
  }
17403
- const data = JSON.parse(readFileSync22(filePath, "utf-8"));
17647
+ const data = JSON.parse(readFileSync21(filePath, "utf-8"));
17404
17648
  res.json({ session: data });
17405
17649
  } catch (err) {
17406
17650
  res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
@@ -17413,10 +17657,10 @@ async function startWebServer(options = {}) {
17413
17657
  return;
17414
17658
  }
17415
17659
  const cwd = process.cwd();
17416
- const fullPath = resolve9(join22(cwd, filePath));
17660
+ const fullPath = resolve10(join22(cwd, filePath));
17417
17661
  try {
17418
17662
  const canonicalFull = realpathSync(fullPath);
17419
- const canonicalCwd = realpathSync(resolve9(cwd));
17663
+ const canonicalCwd = realpathSync(resolve10(cwd));
17420
17664
  if (!canonicalFull.startsWith(canonicalCwd + sep3) && canonicalFull !== canonicalCwd) {
17421
17665
  res.json({ error: "Access denied" });
17422
17666
  return;
@@ -17426,12 +17670,12 @@ async function startWebServer(options = {}) {
17426
17670
  return;
17427
17671
  }
17428
17672
  try {
17429
- const stat = statSync10(fullPath);
17673
+ const stat = statSync9(fullPath);
17430
17674
  if (stat.size > 512 * 1024) {
17431
17675
  res.json({ error: `File too large (${(stat.size / 1024).toFixed(0)} KB, max 512 KB)` });
17432
17676
  return;
17433
17677
  }
17434
- const content = readFileSync22(fullPath, "utf-8");
17678
+ const content = readFileSync21(fullPath, "utf-8");
17435
17679
  res.json({ content, size: stat.size });
17436
17680
  } catch {
17437
17681
  res.json({ error: "Cannot read file" });
@@ -17635,7 +17879,7 @@ async function startWebServer(options = {}) {
17635
17879
  });
17636
17880
  const MAX_PORT_ATTEMPTS = 10;
17637
17881
  let actualPort = port;
17638
- const result = await new Promise((resolve10, reject) => {
17882
+ const result = await new Promise((resolve11, reject) => {
17639
17883
  const tryListen = (attempt) => {
17640
17884
  server.once("error", (err) => {
17641
17885
  if (err.code === "EADDRINUSE" && attempt < MAX_PORT_ATTEMPTS) {
@@ -17666,7 +17910,7 @@ async function startWebServer(options = {}) {
17666
17910
  }
17667
17911
  console.log(` Press Ctrl+C to stop
17668
17912
  `);
17669
- resolve10({ port: actualPort, host, url });
17913
+ resolve11({ port: actualPort, host, url });
17670
17914
  });
17671
17915
  };
17672
17916
  tryListen(1);
@@ -17687,16 +17931,16 @@ function resolveProjectMcpPath() {
17687
17931
  const gitRoot = getGitRoot(cwd);
17688
17932
  const projectRoot = gitRoot ?? cwd;
17689
17933
  const configPath = join22(projectRoot, MCP_PROJECT_CONFIG_NAME);
17690
- return existsSync28(configPath) ? configPath : null;
17934
+ return existsSync27(configPath) ? configPath : null;
17691
17935
  }
17692
17936
  function loadProjectMcpConfig() {
17693
17937
  const cwd = process.cwd();
17694
17938
  const gitRoot = getGitRoot(cwd);
17695
17939
  const projectRoot = gitRoot ?? cwd;
17696
17940
  const configPath = join22(projectRoot, MCP_PROJECT_CONFIG_NAME);
17697
- if (!existsSync28(configPath)) return null;
17941
+ if (!existsSync27(configPath)) return null;
17698
17942
  try {
17699
- const raw = JSON.parse(readFileSync22(configPath, "utf-8"));
17943
+ const raw = JSON.parse(readFileSync21(configPath, "utf-8"));
17700
17944
  return raw.mcpServers ?? raw;
17701
17945
  } catch {
17702
17946
  return null;