jinzd-ai-cli 0.4.216 → 0.4.218

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 +6 -3
  2. package/dist/{batch-C6HCXZIG.js → batch-7V7OTMUP.js} +2 -2
  3. package/dist/{chat-index-R2E27VXN.js → chat-index-PS274XM7.js} +1 -1
  4. package/dist/{chunk-TJ6GGN4B.js → chunk-5CA2TJ5F.js} +1 -1
  5. package/dist/{chunk-OLN7VUZA.js → chunk-C2Z42DI5.js} +3 -1
  6. package/dist/{chunk-TCOC4AUI.js → chunk-GX3HSGJX.js} +690 -169
  7. package/dist/{chunk-PNTLA3MN.js → chunk-H2UIHGHH.js} +23 -22
  8. package/dist/{chunk-GY3C3C2Y.js → chunk-L4UREAID.js} +3 -3
  9. package/dist/{chunk-77HDCGTN.js → chunk-MWKE2TNS.js} +1 -1
  10. package/dist/{chunk-7YWS3JLU.js → chunk-NTCB7CMT.js} +3 -1
  11. package/dist/{chunk-6HLKCSB3.js → chunk-OUC75QCF.js} +1 -1
  12. package/dist/{chunk-WAI3WPV2.js → chunk-P4VBLXKS.js} +1 -1
  13. package/dist/{chunk-VW7Y27WW.js → chunk-VGFTM3XT.js} +1 -1
  14. package/dist/{chunk-OQGVGPEK.js → chunk-VHY6NVMQ.js} +2 -0
  15. package/dist/{ci-6ZTFO3LX.js → ci-L6GH2WVC.js} +4 -4
  16. package/dist/{ci-format-CLZ6QJRL.js → ci-format-WW7454AY.js} +2 -2
  17. package/dist/{constants-APSORFOH.js → constants-NCTFSHDU.js} +3 -1
  18. package/dist/{doctor-cli-GUIX4X5F.js → doctor-cli-EWMFBP5Q.js} +4 -4
  19. package/dist/electron-server.js +965 -377
  20. package/dist/{hub-ZGHQWNWE.js → hub-CDL6T7CP.js} +1 -1
  21. package/dist/index.js +259 -97
  22. package/dist/{pr-JOL3IAGV.js → pr-D6PEKEGK.js} +4 -4
  23. package/dist/{run-tests-4PKSIVK5.js → run-tests-NXVVKAK2.js} +1 -1
  24. package/dist/{run-tests-ZRK4TQUN.js → run-tests-SWU2XEV7.js} +2 -2
  25. package/dist/{server-FXUF5P64.js → server-LHYSS6CK.js} +207 -100
  26. package/dist/{server-O3XHT56X.js → server-WUT7VYTD.js} +5 -4
  27. package/dist/{task-orchestrator-TUMDTOAX.js → task-orchestrator-C5AA2BI5.js} +5 -4
  28. package/dist/{usage-X7MJX4YD.js → usage-6ZUUJBI2.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-C2Z42DI5.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 join23, dirname as dirname8, resolve as resolve11, relative as relative4, sep as sep3 } from "path";
68
+ import { existsSync as existsSync28, readFileSync as readFileSync22, readdirSync as readdirSync14, statSync as statSync10, realpathSync } from "fs";
66
69
  import { networkInterfaces } from "os";
67
70
 
68
71
  // src/config/config-manager.ts
@@ -1844,12 +1847,12 @@ function findPhantomClaims(content, extraMessages) {
1844
1847
  const claimed = extractClaimedFilePaths(content);
1845
1848
  if (claimed.length === 0) return [];
1846
1849
  const normalize = (p) => p.replace(/\\/g, "/").toLowerCase().replace(/^\.\//, "");
1847
- const basename6 = (p) => {
1850
+ const basename7 = (p) => {
1848
1851
  const parts = normalize(p).split("/");
1849
1852
  return parts[parts.length - 1] ?? "";
1850
1853
  };
1851
1854
  const written = extractWrittenFilePaths(extraMessages).map(normalize);
1852
- const writtenBases = new Set(written.map(basename6));
1855
+ const writtenBases = new Set(written.map(basename7));
1853
1856
  const writtenFull = new Set(written);
1854
1857
  const bashText = extractBashCommands(extraMessages).map((c) => c.replace(/\\/g, "/").toLowerCase()).join("\n");
1855
1858
  return claimed.filter((raw) => {
@@ -1858,8 +1861,8 @@ function findPhantomClaims(content, extraMessages) {
1858
1861
  for (const w of writtenFull) {
1859
1862
  if (w.endsWith("/" + norm2) || norm2.endsWith("/" + w)) return false;
1860
1863
  }
1861
- if (writtenBases.has(basename6(norm2))) return false;
1862
- const base = basename6(norm2);
1864
+ if (writtenBases.has(basename7(norm2))) return false;
1865
+ const base = basename7(norm2);
1863
1866
  if (base && bashText.includes(base)) return false;
1864
1867
  return true;
1865
1868
  });
@@ -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((resolve12) => {
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
+ resolve12(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((resolve12) => {
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
+ resolve12(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((resolve12) => {
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
+ resolve12(`${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
+ resolve12(`${prefixWarnings}Exit code ${code}:
8666
8669
  ${output}`);
8667
8670
  } else {
8668
- resolve10(`${prefixWarnings}${output || "(no output)"}`);
8671
+ resolve12(`${prefixWarnings}${output || "(no output)"}`);
8669
8672
  }
8670
8673
  });
8671
8674
  child.on("error", (err) => {
8672
8675
  clearTimeout(timer);
8673
- resolve10(
8676
+ resolve12(
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((resolve12) => {
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
+ resolve12(answer);
9436
9737
  };
9437
9738
  const onLine = (line) => {
9438
9739
  cleanup(line);
@@ -9664,9 +9965,292 @@ 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 existsSync17, readdirSync as readdirSync8, readFileSync as readFileSync13 } from "fs";
9969
+ import { join as join13 } from "path";
9669
9970
  import { homedir as homedir6 } from "os";
9971
+
9972
+ // src/plugins/plugin-manager.ts
9973
+ import { existsSync as existsSync16, mkdirSync as mkdirSync8, readdirSync as readdirSync7, readFileSync as readFileSync12, cpSync, rmSync, statSync as statSync6 } from "fs";
9974
+ import { basename as basename5, dirname as dirname6, join as join12, resolve as resolve7 } from "path";
9975
+ import { createHash as createHash3 } from "crypto";
9976
+ import { z as z2 } from "zod";
9977
+ var MANIFEST_RELATIVE = ".aicli-plugin/plugin.json";
9978
+ var STATE_FILE = "plugin-state.json";
9979
+ var HookCommandSchema = z2.union([
9980
+ z2.string(),
9981
+ z2.object({
9982
+ command: z2.string(),
9983
+ source: z2.enum(["user", "project", "managed"]).optional(),
9984
+ description: z2.string().optional(),
9985
+ required: z2.boolean().optional(),
9986
+ timeoutMs: z2.number().int().min(100).max(3e4).optional(),
9987
+ disabled: z2.boolean().optional()
9988
+ }),
9989
+ z2.array(z2.union([
9990
+ z2.string(),
9991
+ z2.object({
9992
+ command: z2.string(),
9993
+ source: z2.enum(["user", "project", "managed"]).optional(),
9994
+ description: z2.string().optional(),
9995
+ required: z2.boolean().optional(),
9996
+ timeoutMs: z2.number().int().min(100).max(3e4).optional(),
9997
+ disabled: z2.boolean().optional()
9998
+ })
9999
+ ]))
10000
+ ]);
10001
+ var HookEventNameSchema = z2.enum([
10002
+ "SessionStart",
10003
+ "UserPromptSubmit",
10004
+ "PreToolUse",
10005
+ "PermissionRequest",
10006
+ "PostToolUse",
10007
+ "PreCompact",
10008
+ "PostCompact",
10009
+ "Stop",
10010
+ "SubagentStart",
10011
+ "SubagentStop"
10012
+ ]);
10013
+ var McpServerSchema = z2.object({
10014
+ command: z2.string(),
10015
+ args: z2.array(z2.string()).default([]),
10016
+ env: z2.record(z2.string()).optional(),
10017
+ timeout: z2.number().default(3e4)
10018
+ });
10019
+ var PluginManifestSchema = z2.object({
10020
+ name: z2.string().regex(/^[a-zA-Z0-9._-]{1,80}$/),
10021
+ version: z2.string().min(1),
10022
+ description: z2.string().optional(),
10023
+ skills: z2.array(z2.string()).default([]),
10024
+ hooks: z2.object({
10025
+ enabled: z2.boolean().optional(),
10026
+ events: z2.record(HookEventNameSchema, HookCommandSchema).default({})
10027
+ }).default({ events: {} }),
10028
+ commands: z2.array(z2.string()).default([]),
10029
+ mcpServers: z2.record(McpServerSchema).default({}),
10030
+ agents: z2.array(z2.string()).default([]),
10031
+ permissionHints: z2.array(z2.string()).default([])
10032
+ }).strict();
10033
+ function pluginRoot(configDir) {
10034
+ return join12(configDir, PLUGINS_DIR_NAME);
10035
+ }
10036
+ function pluginManifestPath(pluginDir) {
10037
+ return join12(pluginDir, MANIFEST_RELATIVE);
10038
+ }
10039
+ function statePath(configDir) {
10040
+ return join12(pluginRoot(configDir), STATE_FILE);
10041
+ }
10042
+ function loadState(configDir) {
10043
+ const file = statePath(configDir);
10044
+ if (!existsSync16(file)) return { version: 1, plugins: [] };
10045
+ try {
10046
+ const parsed = JSON.parse(readFileSync12(file, "utf-8"));
10047
+ return { version: 1, plugins: Array.isArray(parsed.plugins) ? parsed.plugins : [] };
10048
+ } catch {
10049
+ return { version: 1, plugins: [] };
10050
+ }
10051
+ }
10052
+ function saveState(configDir, state2) {
10053
+ mkdirSync8(pluginRoot(configDir), { recursive: true });
10054
+ atomicWriteFileSync(statePath(configDir), JSON.stringify(state2, null, 2));
10055
+ }
10056
+ function hashPluginManifest(manifestPath) {
10057
+ return createHash3("sha256").update(readFileSync12(manifestPath)).digest("hex");
10058
+ }
10059
+ function readPluginManifest(manifestPath) {
10060
+ return PluginManifestSchema.parse(JSON.parse(readFileSync12(manifestPath, "utf-8")));
10061
+ }
10062
+ function findPluginSourceDir(inputPath) {
10063
+ const abs = resolve7(inputPath);
10064
+ const direct = statSync6(abs).isDirectory() ? abs : dirname6(abs);
10065
+ if (existsSync16(pluginManifestPath(direct))) return direct;
10066
+ const nested = join12(direct, ".aicli-plugin");
10067
+ if (existsSync16(join12(nested, "plugin.json"))) return direct;
10068
+ throw new Error(`plugin manifest not found: ${MANIFEST_RELATIVE}`);
10069
+ }
10070
+ function installPlugin(configDir, inputPath) {
10071
+ const sourceDir = findPluginSourceDir(inputPath);
10072
+ const manifest = readPluginManifest(pluginManifestPath(sourceDir));
10073
+ const targetDir = join12(pluginRoot(configDir), manifest.name);
10074
+ mkdirSync8(pluginRoot(configDir), { recursive: true });
10075
+ if (existsSync16(targetDir)) rmSync(targetDir, { recursive: true, force: true });
10076
+ cpSync(sourceDir, targetDir, { recursive: true });
10077
+ const manifestPath = pluginManifestPath(targetDir);
10078
+ const hash = hashPluginManifest(manifestPath);
10079
+ const state2 = loadState(configDir);
10080
+ const previous = state2.plugins.find((p) => p.name === manifest.name);
10081
+ const next = {
10082
+ name: manifest.name,
10083
+ enabled: previous?.enabled ?? false,
10084
+ trusted: false,
10085
+ hash,
10086
+ installedAt: (/* @__PURE__ */ new Date()).toISOString()
10087
+ };
10088
+ state2.plugins = [...state2.plugins.filter((p) => p.name !== manifest.name), next];
10089
+ saveState(configDir, state2);
10090
+ return { name: manifest.name, dir: targetDir, manifestPath, manifest, hash, enabled: next.enabled, trusted: false, valid: true };
10091
+ }
10092
+ function setPluginEnabled(configDir, name, enabled) {
10093
+ const plugin = getInstalledPlugin(configDir, name);
10094
+ if (!plugin.valid) throw new Error(plugin.error ?? `invalid plugin: ${name}`);
10095
+ const state2 = loadState(configDir);
10096
+ const current = state2.plugins.find((p) => p.name === plugin.name) ?? {
10097
+ name: plugin.name,
10098
+ enabled: false,
10099
+ trusted: false,
10100
+ hash: plugin.hash,
10101
+ installedAt: (/* @__PURE__ */ new Date()).toISOString()
10102
+ };
10103
+ const next = { ...current, enabled, hash: plugin.hash, enabledAt: enabled ? (/* @__PURE__ */ new Date()).toISOString() : current.enabledAt };
10104
+ state2.plugins = [...state2.plugins.filter((p) => p.name !== plugin.name), next];
10105
+ saveState(configDir, state2);
10106
+ return next;
10107
+ }
10108
+ function trustPlugin(configDir, name) {
10109
+ const plugin = getInstalledPlugin(configDir, name);
10110
+ if (!plugin.valid) throw new Error(plugin.error ?? `invalid plugin: ${name}`);
10111
+ const state2 = loadState(configDir);
10112
+ const current = state2.plugins.find((p) => p.name === plugin.name) ?? {
10113
+ name: plugin.name,
10114
+ enabled: false,
10115
+ trusted: false,
10116
+ hash: plugin.hash,
10117
+ installedAt: (/* @__PURE__ */ new Date()).toISOString()
10118
+ };
10119
+ const next = { ...current, trusted: true, hash: plugin.hash, trustedAt: (/* @__PURE__ */ new Date()).toISOString() };
10120
+ state2.plugins = [...state2.plugins.filter((p) => p.name !== plugin.name), next];
10121
+ saveState(configDir, state2);
10122
+ return next;
10123
+ }
10124
+ function getInstalledPlugin(configDir, name) {
10125
+ const plugins = listInstalledPlugins(configDir);
10126
+ const matches = plugins.filter((p) => p.name === name || p.name.startsWith(name));
10127
+ if (matches.length === 0) throw new Error(`plugin not found: ${name}`);
10128
+ if (matches.length > 1) throw new Error(`plugin name is ambiguous: ${name}`);
10129
+ return matches[0];
10130
+ }
10131
+ function listInstalledPlugins(configDir) {
10132
+ const root = pluginRoot(configDir);
10133
+ if (!existsSync16(root)) return [];
10134
+ const state2 = loadState(configDir);
10135
+ const out = [];
10136
+ for (const entry of readdirSync7(root)) {
10137
+ const dir = join12(root, entry);
10138
+ try {
10139
+ if (!statSync6(dir).isDirectory()) continue;
10140
+ } catch {
10141
+ continue;
10142
+ }
10143
+ const manifestPath = pluginManifestPath(dir);
10144
+ if (!existsSync16(manifestPath)) continue;
10145
+ try {
10146
+ const manifest = readPluginManifest(manifestPath);
10147
+ const hash = hashPluginManifest(manifestPath);
10148
+ const st = state2.plugins.find((p) => p.name === manifest.name);
10149
+ const trusted = st?.trusted === true && st.hash === hash;
10150
+ out.push({
10151
+ name: manifest.name,
10152
+ dir,
10153
+ manifestPath,
10154
+ manifest,
10155
+ hash,
10156
+ enabled: st?.enabled === true,
10157
+ trusted,
10158
+ valid: true
10159
+ });
10160
+ } catch (err) {
10161
+ out.push({
10162
+ name: entry,
10163
+ dir,
10164
+ manifestPath,
10165
+ manifest: { name: entry, version: "invalid", skills: [], commands: [], agents: [], hooks: { events: {} }, mcpServers: {}, permissionHints: [] },
10166
+ hash: "",
10167
+ enabled: false,
10168
+ trusted: false,
10169
+ valid: false,
10170
+ error: err instanceof Error ? err.message : String(err)
10171
+ });
10172
+ }
10173
+ }
10174
+ return out.sort((a, b) => a.name.localeCompare(b.name));
10175
+ }
10176
+ function activePlugins(configDir) {
10177
+ return listInstalledPlugins(configDir).filter((p) => p.valid && p.enabled && p.trusted);
10178
+ }
10179
+ function uniqueDirs(plugin, rels) {
10180
+ const dirs = /* @__PURE__ */ new Set();
10181
+ for (const rel of rels) {
10182
+ const abs = resolve7(plugin.dir, rel);
10183
+ if (!abs.startsWith(resolve7(plugin.dir))) continue;
10184
+ if (existsSync16(abs)) dirs.add(statSync6(abs).isDirectory() ? abs : dirname6(abs));
10185
+ }
10186
+ return [...dirs];
10187
+ }
10188
+ function prefixCommand(plugin, command) {
10189
+ return command.replaceAll("{pluginDir}", plugin.dir).replaceAll("$PLUGIN_DIR", plugin.dir);
10190
+ }
10191
+ function pluginHooks(plugin) {
10192
+ const events = plugin.manifest.hooks?.events ?? {};
10193
+ const out = {};
10194
+ for (const [event, raw] of Object.entries(events)) {
10195
+ const normalize = (item) => {
10196
+ if (typeof item === "string") return { command: prefixCommand(plugin, item), source: "project", description: `plugin:${plugin.name}` };
10197
+ if (item && typeof item === "object") {
10198
+ const obj = item;
10199
+ return { ...obj, command: prefixCommand(plugin, String(obj.command ?? "")), source: "project", description: obj.description ?? `plugin:${plugin.name}` };
10200
+ }
10201
+ return item;
10202
+ };
10203
+ out[event] = Array.isArray(raw) ? raw.map(normalize) : normalize(raw);
10204
+ }
10205
+ return Object.keys(out).length > 0 ? { enabled: plugin.manifest.hooks?.enabled ?? true, events: out } : void 0;
10206
+ }
10207
+ function mergePluginHooks(base, configDir) {
10208
+ const plugins = activePlugins(configDir);
10209
+ if (plugins.length === 0) return base;
10210
+ const merged = { ...base ?? {}, enabled: base?.enabled ?? true, events: { ...base?.events ?? {} } };
10211
+ for (const plugin of plugins) {
10212
+ const hooks = pluginHooks(plugin);
10213
+ if (!hooks?.events) continue;
10214
+ for (const [event, entry] of Object.entries(hooks.events)) {
10215
+ const key = event;
10216
+ const existing = merged.events?.[key];
10217
+ const existingItems = existing === void 0 ? [] : Array.isArray(existing) ? existing : [existing];
10218
+ const newItems = Array.isArray(entry) ? entry : [entry];
10219
+ merged.events[key] = [...existingItems, ...newItems];
10220
+ }
10221
+ }
10222
+ return merged;
10223
+ }
10224
+ function getActivePluginAssets(configDir) {
10225
+ const plugins = activePlugins(configDir);
10226
+ const mcpServers = {};
10227
+ const skillDirs = [];
10228
+ const commandDirs = [];
10229
+ const agentDirs = [];
10230
+ for (const plugin of plugins) {
10231
+ skillDirs.push(...uniqueDirs(plugin, plugin.manifest.skills));
10232
+ commandDirs.push(...uniqueDirs(plugin, plugin.manifest.commands));
10233
+ agentDirs.push(...uniqueDirs(plugin, plugin.manifest.agents));
10234
+ for (const [serverName, server] of Object.entries(plugin.manifest.mcpServers)) {
10235
+ mcpServers[`plugin_${plugin.name}_${serverName}`] = server;
10236
+ }
10237
+ }
10238
+ return { plugins, skillDirs, commandDirs, agentDirs, hooks: mergePluginHooks(void 0, configDir), mcpServers };
10239
+ }
10240
+ function describePlugin(plugin) {
10241
+ const m = plugin.manifest;
10242
+ return [
10243
+ `${plugin.name}@${m.version}`,
10244
+ ` status: ${plugin.enabled ? "enabled" : "disabled"} \xB7 ${plugin.trusted ? "trusted" : "untrusted"}${plugin.valid ? "" : " \xB7 invalid"}`,
10245
+ ` dir: ${plugin.dir}`,
10246
+ m.description ? ` description: ${m.description}` : void 0,
10247
+ ` assets: ${m.skills.length} skill(s), ${m.commands.length} command(s), ${m.agents.length} agent(s), ${Object.keys(m.mcpServers).length} MCP server(s), ${Object.keys(m.hooks?.events ?? {}).length} hook event(s)`,
10248
+ m.permissionHints.length ? ` permission hints: ${m.permissionHints.join("; ")}` : void 0,
10249
+ plugin.error ? ` error: ${plugin.error}` : void 0
10250
+ ].filter((line) => Boolean(line));
10251
+ }
10252
+
10253
+ // src/agents/agent-config.ts
9670
10254
  var READ_ONLY_TOOLS2 = /* @__PURE__ */ new Set([
9671
10255
  "read_file",
9672
10256
  "list_dir",
@@ -9760,13 +10344,13 @@ function parseAgentConfig(raw, source, path3) {
9760
10344
  return cfg;
9761
10345
  }
9762
10346
  function loadAgentDir(dir, source) {
9763
- if (!existsSync15(dir)) return [];
10347
+ if (!existsSync17(dir)) return [];
9764
10348
  const out = [];
9765
- for (const file of readdirSync7(dir)) {
10349
+ for (const file of readdirSync8(dir)) {
9766
10350
  if (!file.endsWith(".json")) continue;
9767
- const path3 = join10(dir, file);
10351
+ const path3 = join13(dir, file);
9768
10352
  try {
9769
- const parsed = JSON.parse(readFileSync12(path3, "utf-8"));
10353
+ const parsed = JSON.parse(readFileSync13(path3, "utf-8"));
9770
10354
  const cfg = parseAgentConfig(parsed, source, path3);
9771
10355
  if (cfg) out.push(cfg);
9772
10356
  } catch {
@@ -9776,8 +10360,8 @@ function loadAgentDir(dir, source) {
9776
10360
  }
9777
10361
  function getAgentDirs(configDir, cwd = process.cwd()) {
9778
10362
  return {
9779
- user: join10(configDir ?? join10(homedir6(), CONFIG_DIR_NAME), "agents"),
9780
- project: join10(cwd, CONFIG_DIR_NAME, "agents")
10363
+ user: join13(configDir ?? join13(homedir6(), CONFIG_DIR_NAME), "agents"),
10364
+ project: join13(cwd, CONFIG_DIR_NAME, "agents")
9781
10365
  };
9782
10366
  }
9783
10367
  function listAgentConfigs(configDir, cwd = process.cwd()) {
@@ -9785,6 +10369,11 @@ function listAgentConfigs(configDir, cwd = process.cwd()) {
9785
10369
  const byName = /* @__PURE__ */ new Map();
9786
10370
  for (const cfg of BUILTIN_AGENTS) byName.set(normalizeName(cfg.name), { ...cfg });
9787
10371
  for (const cfg of loadAgentDir(dirs.user, "user")) byName.set(normalizeName(cfg.name), cfg);
10372
+ if (configDir) {
10373
+ for (const dir of getActivePluginAssets(configDir).agentDirs) {
10374
+ for (const cfg of loadAgentDir(dir, "plugin")) byName.set(normalizeName(cfg.name), cfg);
10375
+ }
10376
+ }
9788
10377
  for (const cfg of loadAgentDir(dirs.project, "project")) byName.set(normalizeName(cfg.name), cfg);
9789
10378
  return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
9790
10379
  }
@@ -9819,7 +10408,7 @@ var nextId = 1;
9819
10408
  var preferredAgentName = "worker";
9820
10409
  var runs = [];
9821
10410
  var MAX_RUNS = 50;
9822
- function nowIso() {
10411
+ function nowIso2() {
9823
10412
  return (/* @__PURE__ */ new Date()).toISOString();
9824
10413
  }
9825
10414
  function startAgentRun(input) {
@@ -9828,7 +10417,7 @@ function startAgentRun(input) {
9828
10417
  agentName: input.agentName,
9829
10418
  task: input.task,
9830
10419
  status: "running",
9831
- startedAt: nowIso(),
10420
+ startedAt: nowIso2(),
9832
10421
  toolNames: [],
9833
10422
  agentIndex: input.agentIndex,
9834
10423
  stopRequested: false
@@ -9846,7 +10435,7 @@ function finishAgentRun(runId, status, summary, error) {
9846
10435
  const run = runs.find((r) => r.id === runId);
9847
10436
  if (!run) return;
9848
10437
  run.status = status;
9849
- run.finishedAt = nowIso();
10438
+ run.finishedAt = nowIso2();
9850
10439
  if (summary) run.summary = summary;
9851
10440
  if (error) run.error = error;
9852
10441
  }
@@ -10272,7 +10861,7 @@ var spawnAgentTool = {
10272
10861
 
10273
10862
  // src/tools/builtin/task-manager.ts
10274
10863
  import { spawn as spawn3 } from "child_process";
10275
- import { randomUUID } from "crypto";
10864
+ import { randomUUID as randomUUID2 } from "crypto";
10276
10865
  import { platform as platform4 } from "os";
10277
10866
  var MAX_OUTPUT_CHARS = 1e4;
10278
10867
  var MAX_OUTPUT_BYTES = 64 * 1024;
@@ -10305,7 +10894,7 @@ function createTask(command, description) {
10305
10894
  }
10306
10895
  }
10307
10896
  }
10308
- const id = randomUUID().slice(0, 8);
10897
+ const id = randomUUID2().slice(0, 8);
10309
10898
  const { shell, args } = shellInvocation(command);
10310
10899
  const proc = spawn3(shell, args, {
10311
10900
  stdio: ["ignore", "pipe", "pipe"],
@@ -10487,20 +11076,20 @@ var taskStopTool = {
10487
11076
 
10488
11077
  // src/tools/builtin/git-tools.ts
10489
11078
  import { execFileSync as execFileSync2 } from "child_process";
10490
- import { existsSync as existsSync16 } from "fs";
10491
- import { join as join11 } from "path";
11079
+ import { existsSync as existsSync18 } from "fs";
11080
+ import { join as join14 } from "path";
10492
11081
  function assertGitRepo(cwd) {
10493
11082
  let dir = cwd;
10494
11083
  const root = dir.split(/[\\/]/)[0] + (dir.includes("\\") ? "\\" : "/");
10495
11084
  while (dir && dir !== root) {
10496
- if (existsSync16(join11(dir, ".git"))) return;
10497
- const parent = join11(dir, "..");
11085
+ if (existsSync18(join14(dir, ".git"))) return;
11086
+ const parent = join14(dir, "..");
10498
11087
  if (parent === dir) break;
10499
11088
  dir = parent;
10500
11089
  }
10501
11090
  throw new ToolError("git", "Not inside a git repository. Run this command from a directory under a git-tracked project.");
10502
11091
  }
10503
- function runGit(args, cwd, maxBuffer = 10 * 1024 * 1024) {
11092
+ function runGit2(args, cwd, maxBuffer = 10 * 1024 * 1024) {
10504
11093
  try {
10505
11094
  const output = execFileSync2("git", args, {
10506
11095
  cwd,
@@ -10535,11 +11124,11 @@ var gitStatusTool = {
10535
11124
  assertGitRepo(cwd);
10536
11125
  let branch = "(unknown)";
10537
11126
  try {
10538
- branch = runGit(["symbolic-ref", "--short", "HEAD"], cwd).trim();
11127
+ branch = runGit2(["symbolic-ref", "--short", "HEAD"], cwd).trim();
10539
11128
  } catch {
10540
11129
  branch = "(detached)";
10541
11130
  }
10542
- const porcelain = runGit(["status", "--porcelain=v1", "-b"], cwd);
11131
+ const porcelain = runGit2(["status", "--porcelain=v1", "-b"], cwd);
10543
11132
  const lines = porcelain.split("\n").filter((l) => l.length > 0);
10544
11133
  const branchLine = lines[0]?.startsWith("##") ? lines[0].slice(3) : `${branch}`;
10545
11134
  const fileLines = lines.slice(lines[0]?.startsWith("##") ? 1 : 0);
@@ -10626,7 +11215,7 @@ var gitDiffTool = {
10626
11215
  if (args["file"]) {
10627
11216
  gitArgs.push("--", String(args["file"]));
10628
11217
  }
10629
- const output = runGit(gitArgs, cwd);
11218
+ const output = runGit2(gitArgs, cwd);
10630
11219
  if (output.trim().length === 0) {
10631
11220
  const label = args["staged"] ? "staged" : args["commit_range"] ? String(args["commit_range"]) : "unstaged";
10632
11221
  return `No ${label} changes.`;
@@ -10688,7 +11277,7 @@ var gitLogTool = {
10688
11277
  if (args["file"]) {
10689
11278
  gitArgs.push("--", String(args["file"]));
10690
11279
  }
10691
- const output = runGit(gitArgs, cwd);
11280
+ const output = runGit2(gitArgs, cwd);
10692
11281
  if (output.trim().length === 0) {
10693
11282
  return "No commits found matching the filter.";
10694
11283
  }
@@ -10735,9 +11324,9 @@ var gitCommitTool = {
10735
11324
  throw new ToolError("git_commit", "Cannot use both stage_all and files at the same time.");
10736
11325
  }
10737
11326
  if (files.length > 0) {
10738
- runGit(["add", "--", ...files], cwd);
11327
+ runGit2(["add", "--", ...files], cwd);
10739
11328
  }
10740
- const statusBefore = runGit(["status", "--porcelain"], cwd);
11329
+ const statusBefore = runGit2(["status", "--porcelain"], cwd);
10741
11330
  const hasStaged = statusBefore.split("\n").some((l) => l.length > 0 && l[0] !== " " && l[0] !== "?");
10742
11331
  const hasUnstaged = statusBefore.split("\n").some((l) => l.length > 1 && l[1] !== " " && l[1] !== "?");
10743
11332
  if (!hasStaged && !stageAll) {
@@ -10748,9 +11337,9 @@ var gitCommitTool = {
10748
11337
  }
10749
11338
  const commitArgs = ["commit", "-m", message];
10750
11339
  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();
11340
+ const commitOutput = runGit2(commitArgs, cwd);
11341
+ const hash = runGit2(["rev-parse", "--short", "HEAD"], cwd).trim();
11342
+ const summary = runGit2(["log", "-1", "--pretty=format:%h %s"], cwd).trim();
10754
11343
  return `\u2713 Committed ${hash}
10755
11344
  ${summary}
10756
11345
 
@@ -10759,9 +11348,9 @@ ${commitOutput.trim()}`;
10759
11348
  };
10760
11349
 
10761
11350
  // src/tools/builtin/notebook-edit.ts
10762
- import { readFileSync as readFileSync13, existsSync as existsSync17 } from "fs";
11351
+ import { readFileSync as readFileSync14, existsSync as existsSync19 } from "fs";
10763
11352
  import { writeFile } from "fs/promises";
10764
- import { resolve as resolve6, extname as extname2 } from "path";
11353
+ import { resolve as resolve8, extname as extname2 } from "path";
10765
11354
  var notebookEditTool = {
10766
11355
  definition: {
10767
11356
  name: "notebook_edit",
@@ -10810,14 +11399,14 @@ var notebookEditTool = {
10810
11399
  if (!Number.isInteger(cellIndexRaw) || cellIndexRaw < 1) {
10811
11400
  throw new ToolError("notebook_edit", "cell_index must be a positive integer (1-based)");
10812
11401
  }
10813
- const absPath = resolve6(filePath);
11402
+ const absPath = resolve8(filePath);
10814
11403
  if (extname2(absPath).toLowerCase() !== ".ipynb") {
10815
11404
  throw new ToolError("notebook_edit", "path must point to a .ipynb file");
10816
11405
  }
10817
- if (!existsSync17(absPath)) {
11406
+ if (!existsSync19(absPath)) {
10818
11407
  throw new ToolError("notebook_edit", `Notebook not found: ${filePath}`);
10819
11408
  }
10820
- const raw = readFileSync13(absPath, "utf-8");
11409
+ const raw = readFileSync14(absPath, "utf-8");
10821
11410
  const nb = parseNotebook(raw);
10822
11411
  const cellIdx0 = cellIndexRaw - 1;
10823
11412
  undoStack.push(absPath, `notebook_edit (${action}): ${filePath}`);
@@ -11234,8 +11823,8 @@ function estimateToolDefinitionTokens(def) {
11234
11823
 
11235
11824
  // src/tools/registry.ts
11236
11825
  import { pathToFileURL } from "url";
11237
- import { existsSync as existsSync18, mkdirSync as mkdirSync8, readdirSync as readdirSync8 } from "fs";
11238
- import { join as join12 } from "path";
11826
+ import { existsSync as existsSync20, mkdirSync as mkdirSync9, readdirSync as readdirSync9 } from "fs";
11827
+ import { join as join15 } from "path";
11239
11828
  var ToolRegistry = class {
11240
11829
  tools = /* @__PURE__ */ new Map();
11241
11830
  pluginToolNames = /* @__PURE__ */ new Set();
@@ -11396,16 +11985,16 @@ var ToolRegistry = class {
11396
11985
  * Returns the number of successfully loaded plugins.
11397
11986
  */
11398
11987
  async loadPlugins(pluginsDir, allowPlugins = false) {
11399
- if (!existsSync18(pluginsDir)) {
11988
+ if (!existsSync20(pluginsDir)) {
11400
11989
  try {
11401
- mkdirSync8(pluginsDir, { recursive: true });
11990
+ mkdirSync9(pluginsDir, { recursive: true });
11402
11991
  } catch {
11403
11992
  }
11404
11993
  return 0;
11405
11994
  }
11406
11995
  let files;
11407
11996
  try {
11408
- files = readdirSync8(pluginsDir).filter((f) => f.endsWith(".js"));
11997
+ files = readdirSync9(pluginsDir).filter((f) => f.endsWith(".js"));
11409
11998
  } catch {
11410
11999
  return 0;
11411
12000
  }
@@ -11422,12 +12011,12 @@ var ToolRegistry = class {
11422
12011
  process.stderr.write(
11423
12012
  `
11424
12013
  [plugins] \u26A0 Loading ${files.length} plugin(s) with FULL system privileges:
11425
- ` + files.map((f) => ` + ${join12(pluginsDir, f)}`).join("\n") + "\n\n"
12014
+ ` + files.map((f) => ` + ${join15(pluginsDir, f)}`).join("\n") + "\n\n"
11426
12015
  );
11427
12016
  let loaded = 0;
11428
12017
  for (const file of files) {
11429
12018
  try {
11430
- const fileUrl = pathToFileURL(join12(pluginsDir, file)).href;
12019
+ const fileUrl = pathToFileURL(join15(pluginsDir, file)).href;
11431
12020
  const mod = await import(fileUrl);
11432
12021
  const tool = mod.tool ?? mod.default?.tool ?? mod.default;
11433
12022
  if (!tool || typeof tool.execute !== "function" || !tool.definition?.name) {
@@ -11589,7 +12178,7 @@ var McpClient = class {
11589
12178
  // 内部方法:JSON-RPC 通信
11590
12179
  // ══════════════════════════════════════════════════════════════════
11591
12180
  sendRequest(method, params) {
11592
- return new Promise((resolve10, reject) => {
12181
+ return new Promise((resolve12, reject) => {
11593
12182
  if (!this.process?.stdin?.writable) {
11594
12183
  return reject(new Error(`MCP server [${this.serverId}] stdin not writable`));
11595
12184
  }
@@ -11613,7 +12202,7 @@ var McpClient = class {
11613
12202
  this.pendingRequests.set(id, {
11614
12203
  resolve: (result) => {
11615
12204
  cleanup();
11616
- resolve10(result);
12205
+ resolve12(result);
11617
12206
  },
11618
12207
  reject: (error) => {
11619
12208
  cleanup();
@@ -11690,13 +12279,13 @@ var McpClient = class {
11690
12279
  }
11691
12280
  /** Promise 超时包装 */
11692
12281
  withTimeout(promise, ms, label) {
11693
- return new Promise((resolve10, reject) => {
12282
+ return new Promise((resolve12, reject) => {
11694
12283
  const timer = setTimeout(() => {
11695
12284
  reject(new Error(`MCP [${this.serverId}] ${label} timed out after ${ms}ms`));
11696
12285
  }, ms);
11697
12286
  promise.then((val) => {
11698
12287
  clearTimeout(timer);
11699
- resolve10(val);
12288
+ resolve12(val);
11700
12289
  }).catch((err) => {
11701
12290
  clearTimeout(timer);
11702
12291
  reject(err);
@@ -11973,12 +12562,12 @@ var McpManager = class {
11973
12562
  };
11974
12563
 
11975
12564
  // 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";
12565
+ import { existsSync as existsSync21, readdirSync as readdirSync10, mkdirSync as mkdirSync10, statSync as statSync7 } from "fs";
12566
+ import { join as join16 } from "path";
11978
12567
 
11979
12568
  // src/skills/types.ts
11980
- import { readFileSync as readFileSync14 } from "fs";
11981
- import { basename as basename5 } from "path";
12569
+ import { readFileSync as readFileSync15 } from "fs";
12570
+ import { basename as basename6 } from "path";
11982
12571
  function parseSimpleYaml(yaml) {
11983
12572
  const result = {};
11984
12573
  for (const line of yaml.split("\n")) {
@@ -11999,7 +12588,7 @@ function parseYamlArray(value) {
11999
12588
  function parseSkillFile(filePath) {
12000
12589
  let raw;
12001
12590
  try {
12002
- raw = readFileSync14(filePath, "utf-8");
12591
+ raw = readFileSync15(filePath, "utf-8");
12003
12592
  } catch {
12004
12593
  return null;
12005
12594
  }
@@ -12007,7 +12596,7 @@ function parseSkillFile(filePath) {
12007
12596
  if (!frontmatterMatch) {
12008
12597
  return {
12009
12598
  meta: {
12010
- name: basename5(filePath, ".md"),
12599
+ name: basename6(filePath, ".md"),
12011
12600
  description: ""
12012
12601
  },
12013
12602
  content: raw.trim(),
@@ -12018,7 +12607,7 @@ function parseSkillFile(filePath) {
12018
12607
  const parsed = parseSimpleYaml(yaml);
12019
12608
  return {
12020
12609
  meta: {
12021
- name: parsed["name"] ?? basename5(filePath, ".md"),
12610
+ name: parsed["name"] ?? basename6(filePath, ".md"),
12022
12611
  description: parsed["description"] ?? "",
12023
12612
  tools: parsed["tools"] ? parseYamlArray(parsed["tools"]) : void 0
12024
12613
  },
@@ -12039,36 +12628,40 @@ var SkillManager = class {
12039
12628
  this.skillsDir = skillsDir;
12040
12629
  this.warnThreshold = warnThreshold ?? SKILL_CONTENT_WARN_CHARS_DEFAULT;
12041
12630
  }
12042
- /** 发现并加载 skillsDir 下所有 .md 文件,返回加载数量 */
12043
- loadSkills() {
12631
+ /** 发现并加载 skillsDir 以及额外插件目录下的技能,返回加载数量 */
12632
+ loadSkills(extraDirs = []) {
12044
12633
  this.skills.clear();
12045
- if (!existsSync19(this.skillsDir)) {
12046
- try {
12047
- mkdirSync9(this.skillsDir, { recursive: true });
12048
- } catch {
12634
+ this.loadSkillDir(this.skillsDir, true);
12635
+ for (const dir of extraDirs) this.loadSkillDir(dir, false);
12636
+ return this.skills.size;
12637
+ }
12638
+ loadSkillDir(dir, createIfMissing) {
12639
+ if (!existsSync21(dir)) {
12640
+ if (createIfMissing) {
12641
+ try {
12642
+ mkdirSync10(dir, { recursive: true });
12643
+ } catch {
12644
+ }
12049
12645
  }
12050
- return 0;
12646
+ return;
12051
12647
  }
12052
12648
  let entries;
12053
12649
  try {
12054
- entries = readdirSync9(this.skillsDir);
12650
+ entries = readdirSync10(dir);
12055
12651
  } catch {
12056
- return 0;
12652
+ return;
12057
12653
  }
12058
12654
  for (const entry of entries) {
12059
12655
  let filePath;
12060
- const fullPath = join13(this.skillsDir, entry);
12656
+ const fullPath = join16(dir, entry);
12061
12657
  if (entry.endsWith(".md")) {
12062
12658
  filePath = fullPath;
12063
12659
  } else {
12064
12660
  try {
12065
12661
  if (statSync7(fullPath).isDirectory()) {
12066
- const skillMd = join13(fullPath, "SKILL.md");
12067
- if (existsSync19(skillMd)) {
12068
- filePath = skillMd;
12069
- } else {
12070
- continue;
12071
- }
12662
+ const skillMd = join16(fullPath, "SKILL.md");
12663
+ if (existsSync21(skillMd)) filePath = skillMd;
12664
+ else continue;
12072
12665
  } else {
12073
12666
  continue;
12074
12667
  }
@@ -12078,13 +12671,10 @@ var SkillManager = class {
12078
12671
  }
12079
12672
  const skill = parseSkillFile(filePath);
12080
12673
  if (skill) {
12081
- if (skill.meta.name === "SKILL" && !entry.endsWith(".md")) {
12082
- skill.meta.name = entry;
12083
- }
12084
- this.skills.set(skill.meta.name, skill);
12674
+ if (skill.meta.name === "SKILL" && !entry.endsWith(".md")) skill.meta.name = entry;
12675
+ if (!this.skills.has(skill.meta.name)) this.skills.set(skill.meta.name, skill);
12085
12676
  }
12086
12677
  }
12087
- return this.skills.size;
12088
12678
  }
12089
12679
  /** 列出所有已加载的技能 */
12090
12680
  listSkills() {
@@ -12127,9 +12717,9 @@ var SkillManager = class {
12127
12717
  };
12128
12718
 
12129
12719
  // src/web/tool-executor-web.ts
12130
- import { randomUUID as randomUUID2 } from "crypto";
12720
+ import { randomUUID as randomUUID3 } from "crypto";
12131
12721
  import { tmpdir as tmpdir2 } from "os";
12132
- import { existsSync as existsSync20, readFileSync as readFileSync15 } from "fs";
12722
+ import { existsSync as existsSync22, readFileSync as readFileSync16 } from "fs";
12133
12723
  var ToolExecutorWeb = class _ToolExecutorWeb {
12134
12724
  constructor(registry, ws) {
12135
12725
  this.registry = registry;
@@ -12199,33 +12789,33 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
12199
12789
  }
12200
12790
  /** Resolve a pending confirm from client response */
12201
12791
  resolveConfirm(requestId, approved) {
12202
- const resolve10 = this.pendingConfirms.get(requestId);
12203
- if (resolve10) {
12792
+ const resolve12 = this.pendingConfirms.get(requestId);
12793
+ if (resolve12) {
12204
12794
  this.clearPendingTimer(requestId);
12205
12795
  this.pendingConfirms.delete(requestId);
12206
12796
  this.confirming = false;
12207
- resolve10(approved);
12797
+ resolve12(approved);
12208
12798
  }
12209
12799
  }
12210
12800
  /** Resolve a pending batch confirm from client response */
12211
12801
  resolveBatchConfirm(requestId, decision) {
12212
- const resolve10 = this.pendingBatchConfirms.get(requestId);
12213
- if (resolve10) {
12802
+ const resolve12 = this.pendingBatchConfirms.get(requestId);
12803
+ if (resolve12) {
12214
12804
  this.clearPendingTimer(requestId);
12215
12805
  this.pendingBatchConfirms.delete(requestId);
12216
12806
  this.confirming = false;
12217
12807
  if (decision === "all" || decision === "none") {
12218
- resolve10(decision);
12808
+ resolve12(decision);
12219
12809
  } else {
12220
- resolve10(new Set(decision));
12810
+ resolve12(new Set(decision));
12221
12811
  }
12222
12812
  }
12223
12813
  }
12224
12814
  /** Cancel all pending confirms (e.g., on disconnect) */
12225
12815
  cancelAll() {
12226
- for (const resolve10 of this.pendingConfirms.values()) resolve10(false);
12816
+ for (const resolve12 of this.pendingConfirms.values()) resolve12(false);
12227
12817
  this.pendingConfirms.clear();
12228
- for (const resolve10 of this.pendingBatchConfirms.values()) resolve10("none");
12818
+ for (const resolve12 of this.pendingBatchConfirms.values()) resolve12("none");
12229
12819
  this.pendingBatchConfirms.clear();
12230
12820
  this.confirming = false;
12231
12821
  }
@@ -12268,9 +12858,9 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
12268
12858
  if (call.name === "write_file") {
12269
12859
  const filePath = String(call.arguments["path"] ?? "");
12270
12860
  const newContent = String(call.arguments["content"] ?? "");
12271
- if (filePath && existsSync20(filePath)) {
12861
+ if (filePath && existsSync22(filePath)) {
12272
12862
  try {
12273
- const old = readFileSync15(filePath, "utf-8");
12863
+ const old = readFileSync16(filePath, "utf-8");
12274
12864
  return renderDiff(old, newContent, { filePath });
12275
12865
  } catch {
12276
12866
  }
@@ -12290,7 +12880,7 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
12290
12880
  }
12291
12881
  /** WebSocket-based confirm — sends request, returns Promise resolved by client */
12292
12882
  confirm(call, level) {
12293
- const requestId = randomUUID2();
12883
+ const requestId = randomUUID3();
12294
12884
  this.confirming = true;
12295
12885
  const msg = {
12296
12886
  type: "confirm_request",
@@ -12301,8 +12891,8 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
12301
12891
  diff: this.getDiffPreview(call)
12302
12892
  };
12303
12893
  this.send(msg);
12304
- return new Promise((resolve10) => {
12305
- this.pendingConfirms.set(requestId, resolve10);
12894
+ return new Promise((resolve12) => {
12895
+ this.pendingConfirms.set(requestId, resolve12);
12306
12896
  this.pendingTimers.set(requestId, setTimeout(() => {
12307
12897
  if (this.pendingConfirms.has(requestId)) {
12308
12898
  this.resolveConfirm(requestId, false);
@@ -12312,7 +12902,7 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
12312
12902
  }
12313
12903
  /** WebSocket-based batch confirm */
12314
12904
  batchConfirm(calls) {
12315
- const requestId = randomUUID2();
12905
+ const requestId = randomUUID3();
12316
12906
  this.confirming = true;
12317
12907
  const files = calls.map((call, i) => ({
12318
12908
  index: i + 1,
@@ -12326,8 +12916,8 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
12326
12916
  files
12327
12917
  };
12328
12918
  this.send(msg);
12329
- return new Promise((resolve10) => {
12330
- this.pendingBatchConfirms.set(requestId, resolve10);
12919
+ return new Promise((resolve12) => {
12920
+ this.pendingBatchConfirms.set(requestId, resolve12);
12331
12921
  this.pendingTimers.set(requestId, setTimeout(() => {
12332
12922
  if (this.pendingBatchConfirms.has(requestId)) {
12333
12923
  this.resolveBatchConfirm(requestId, "none");
@@ -12572,21 +13162,17 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
12572
13162
  }
12573
13163
  };
12574
13164
 
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
13165
  // 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";
13166
+ import { existsSync as existsSync23, readFileSync as readFileSync17, unlinkSync as unlinkSync4, mkdirSync as mkdirSync11 } from "fs";
13167
+ import { join as join17 } from "path";
12582
13168
  import { homedir as homedir7 } from "os";
12583
13169
  function getDevStatePath() {
12584
- return join14(homedir7(), CONFIG_DIR_NAME, DEV_STATE_FILE_NAME);
13170
+ return join17(homedir7(), CONFIG_DIR_NAME, DEV_STATE_FILE_NAME);
12585
13171
  }
12586
13172
  function loadDevState() {
12587
13173
  const path3 = getDevStatePath();
12588
- if (!existsSync21(path3)) return null;
12589
- const content = readFileSync16(path3, "utf-8").trim();
13174
+ if (!existsSync23(path3)) return null;
13175
+ const content = readFileSync17(path3, "utf-8").trim();
12590
13176
  return content || null;
12591
13177
  }
12592
13178
 
@@ -12644,19 +13230,7 @@ ${ctx.activeSkill.content}`);
12644
13230
  return { stable: stableParts.join("\n\n---\n\n"), volatile };
12645
13231
  }
12646
13232
  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 };
13233
+ return formatMemoryForPrompt(configDir, process.cwd());
12660
13234
  }
12661
13235
 
12662
13236
  // src/core/pricing.ts
@@ -12855,8 +13429,8 @@ function autoTrimSessionIfNeeded(session, sizeLimit = SESSION_SIZE_LIMIT) {
12855
13429
  }
12856
13430
 
12857
13431
  // 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";
13432
+ import { existsSync as existsSync24, readFileSync as readFileSync18 } from "fs";
13433
+ import { join as join18, relative as relative3, resolve as resolve9 } from "path";
12860
13434
  function uniqueNonEmpty(values) {
12861
13435
  const seen = /* @__PURE__ */ new Set();
12862
13436
  const out = [];
@@ -12879,7 +13453,7 @@ function displayPath(filePath, cwd) {
12879
13453
  return filePath;
12880
13454
  }
12881
13455
  function isInsideOrEqual(parent, child) {
12882
- const rel = relative3(resolve7(parent), resolve7(child));
13456
+ const rel = relative3(resolve9(parent), resolve9(child));
12883
13457
  return rel === "" || !!rel && !rel.startsWith("..") && !rel.startsWith("/") && !rel.startsWith("\\");
12884
13458
  }
12885
13459
  function readContextFile(level, filePath, cwd, maxBytes) {
@@ -12933,8 +13507,8 @@ function readContextFile(level, filePath, cwd, maxBytes) {
12933
13507
  function findFirstContextFile(level, dir, cwd, candidates, maxBytes) {
12934
13508
  const skipped = [];
12935
13509
  for (const candidate of candidates) {
12936
- const filePath = join16(dir, candidate);
12937
- if (!existsSync23(filePath)) continue;
13510
+ const filePath = join18(dir, candidate);
13511
+ if (!existsSync24(filePath)) continue;
12938
13512
  const result = readContextFile(level, filePath, cwd, maxBytes);
12939
13513
  if (result.skipped) skipped.push(result.skipped);
12940
13514
  if (result.layer) return { layer: result.layer, skipped };
@@ -12942,7 +13516,7 @@ function findFirstContextFile(level, dir, cwd, candidates, maxBytes) {
12942
13516
  return { layer: null, skipped };
12943
13517
  }
12944
13518
  function loadContextFiles(options) {
12945
- const cwd = resolve7(options.cwd);
13519
+ const cwd = resolve9(options.cwd);
12946
13520
  const maxBytes = options.maxBytes ?? CONTEXT_FILE_MAX_BYTES;
12947
13521
  const candidates = options.candidates ?? buildContextFileCandidates(options.fallbackFilenames);
12948
13522
  const skipped = [];
@@ -12950,7 +13524,7 @@ function loadContextFiles(options) {
12950
13524
  return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
12951
13525
  }
12952
13526
  if (options.setting && options.setting !== "auto") {
12953
- const filePath = resolve7(cwd, String(options.setting));
13527
+ const filePath = resolve9(cwd, String(options.setting));
12954
13528
  if (!isInsideOrEqual(cwd, filePath)) {
12955
13529
  skipped.push({
12956
13530
  level: "single",
@@ -12961,7 +13535,7 @@ function loadContextFiles(options) {
12961
13535
  });
12962
13536
  return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
12963
13537
  }
12964
- if (!existsSync23(filePath)) {
13538
+ if (!existsSync24(filePath)) {
12965
13539
  skipped.push({
12966
13540
  level: "single",
12967
13541
  filePath,
@@ -12994,7 +13568,7 @@ function loadContextFiles(options) {
12994
13568
  skipped.push(...result.skipped);
12995
13569
  if (result.layer) layers.push(result.layer);
12996
13570
  }
12997
- if (resolve7(options.cwd) !== resolve7(options.projectRoot)) {
13571
+ if (resolve9(options.cwd) !== resolve9(options.projectRoot)) {
12998
13572
  const result = findFirstContextFile("local", options.cwd, cwd, candidates, maxBytes);
12999
13573
  skipped.push(...result.skipped);
13000
13574
  if (result.layer) layers.push(result.layer);
@@ -13012,98 +13586,8 @@ function loadContextFiles(options) {
13012
13586
  }
13013
13587
 
13014
13588
  // 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
- }
13589
+ import { existsSync as existsSync26, readFileSync as readFileSync20, writeFileSync as writeFileSync2, mkdirSync as mkdirSync12, readdirSync as readdirSync12, statSync as statSync9, createWriteStream } from "fs";
13590
+ import { join as join21, resolve as resolve10, dirname as dirname7 } from "path";
13107
13591
 
13108
13592
  // src/cli/review-prompts.ts
13109
13593
  function buildReviewPrompt(diff, gitContextStr, detailed) {
@@ -13164,8 +13648,8 @@ If no security issues found, state "\u2705 No security vulnerabilities detected"
13164
13648
  }
13165
13649
 
13166
13650
  // src/repl/commands/project-init.ts
13167
- import { existsSync as existsSync25, readFileSync as readFileSync19, readdirSync as readdirSync10, statSync as statSync8 } from "fs";
13168
- import { join as join18 } from "path";
13651
+ import { existsSync as existsSync25, readFileSync as readFileSync19, readdirSync as readdirSync11, statSync as statSync8 } from "fs";
13652
+ import { join as join19 } from "path";
13169
13653
  var SCAN_SKIP_DIRS = /* @__PURE__ */ new Set([
13170
13654
  "node_modules",
13171
13655
  ".git",
@@ -13201,7 +13685,7 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
13201
13685
  if (depth > maxDepth || count >= maxEntries) return;
13202
13686
  let entries;
13203
13687
  try {
13204
- entries = readdirSync10(d);
13688
+ entries = readdirSync11(d);
13205
13689
  } catch {
13206
13690
  return;
13207
13691
  }
@@ -13209,11 +13693,11 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
13209
13693
  const sorted = filtered.sort((a, b) => {
13210
13694
  let aIsDir = false, bIsDir = false;
13211
13695
  try {
13212
- aIsDir = statSync8(join18(d, a)).isDirectory();
13696
+ aIsDir = statSync8(join19(d, a)).isDirectory();
13213
13697
  } catch {
13214
13698
  }
13215
13699
  try {
13216
- bIsDir = statSync8(join18(d, b)).isDirectory();
13700
+ bIsDir = statSync8(join19(d, b)).isDirectory();
13217
13701
  } catch {
13218
13702
  }
13219
13703
  if (aIsDir !== bIsDir) return aIsDir ? -1 : 1;
@@ -13221,7 +13705,7 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
13221
13705
  });
13222
13706
  for (let i = 0; i < sorted.length && count < maxEntries; i++) {
13223
13707
  const name = sorted[i];
13224
- const fullPath = join18(d, name);
13708
+ const fullPath = join19(d, name);
13225
13709
  const isLast = i === sorted.length - 1;
13226
13710
  const connector = isLast ? "+-- " : "|-- ";
13227
13711
  let isDir;
@@ -13248,7 +13732,7 @@ function scanProject(cwd) {
13248
13732
  configFiles: [],
13249
13733
  directoryStructure: ""
13250
13734
  };
13251
- const check = (file) => existsSync25(join18(cwd, file));
13735
+ const check = (file) => existsSync25(join19(cwd, file));
13252
13736
  const configCandidates = [
13253
13737
  "package.json",
13254
13738
  "tsconfig.json",
@@ -13275,7 +13759,7 @@ function scanProject(cwd) {
13275
13759
  info.type = "node";
13276
13760
  info.language = check("tsconfig.json") ? "TypeScript" : "JavaScript";
13277
13761
  try {
13278
- const pkg = JSON.parse(readFileSync19(join18(cwd, "package.json"), "utf-8"));
13762
+ const pkg = JSON.parse(readFileSync19(join19(cwd, "package.json"), "utf-8"));
13279
13763
  const scripts = pkg.scripts ?? {};
13280
13764
  info.buildCommand = scripts.build ? "npm run build" : void 0;
13281
13765
  info.testCommand = scripts.test ? "npm test" : void 0;
@@ -13935,7 +14419,7 @@ function resolveRoleProviders(roles, lookup, defaultProvider, defaultModel, avai
13935
14419
  }
13936
14420
 
13937
14421
  // src/hub/persist.ts
13938
- import { join as join19 } from "path";
14422
+ import { join as join20 } from "path";
13939
14423
  function discussionToMessages(state2) {
13940
14424
  const out = [];
13941
14425
  const t0 = state2.messages[0]?.timestamp ?? /* @__PURE__ */ new Date();
@@ -13973,7 +14457,7 @@ async function persistDiscussion(state2, config, defaultProvider, defaultModel)
13973
14457
  session.title = `[Hub] ${state2.topic.slice(0, 48)}`.replace(/\n/g, " ");
13974
14458
  session.titleAiGenerated = true;
13975
14459
  await sm.save();
13976
- return { id: session.id, path: join19(config.getHistoryDir(), `${session.id}.json`) };
14460
+ return { id: session.id, path: join20(config.getHistoryDir(), `${session.id}.json`) };
13977
14461
  }
13978
14462
 
13979
14463
  // src/web/session-handler.ts
@@ -14069,7 +14553,7 @@ var SessionHandler = class {
14069
14553
  }
14070
14554
  this.updateContextWindow();
14071
14555
  this.activeSystemPrompt = this.loadContextFiles();
14072
- const hooks = this.config.get("hooks");
14556
+ const hooks = mergePluginHooks(this.config.get("hooks") ?? void 0, this.config.getConfigDir());
14073
14557
  const permissionRules = this.config.get("permissionRules");
14074
14558
  const defaultPermission = this.config.get("defaultPermission");
14075
14559
  const permissionProfileName = this.config.get("defaultPermissionProfile") ?? "legacy";
@@ -14169,10 +14653,10 @@ var SessionHandler = class {
14169
14653
  return;
14170
14654
  }
14171
14655
  case "ask_user_response": {
14172
- const resolve10 = this.pendingAskUser.get(msg.requestId);
14173
- if (resolve10) {
14656
+ const resolve12 = this.pendingAskUser.get(msg.requestId);
14657
+ if (resolve12) {
14174
14658
  this.pendingAskUser.delete(msg.requestId);
14175
- resolve10(msg.answer);
14659
+ resolve12(msg.answer);
14176
14660
  }
14177
14661
  return;
14178
14662
  }
@@ -14182,11 +14666,13 @@ var SessionHandler = class {
14182
14666
  return this.handleMemoryStatus();
14183
14667
  case "memory_rebuild":
14184
14668
  return this.handleMemoryRebuild(Boolean(msg.full));
14669
+ case "memory_manage":
14670
+ return this.handleMemoryManage(msg.action, msg.id, msg.expiresAt);
14185
14671
  case "auto_pause_response": {
14186
- const resolve10 = this.pendingAutoPause.get(msg.requestId);
14187
- if (resolve10) {
14672
+ const resolve12 = this.pendingAutoPause.get(msg.requestId);
14673
+ if (resolve12) {
14188
14674
  this.pendingAutoPause.delete(msg.requestId);
14189
- resolve10({ action: msg.action, message: msg.message });
14675
+ resolve12({ action: msg.action, message: msg.message });
14190
14676
  }
14191
14677
  return;
14192
14678
  }
@@ -14201,10 +14687,10 @@ var SessionHandler = class {
14201
14687
  this.hubOrchestrator?.abort();
14202
14688
  return;
14203
14689
  case "hub_steer": {
14204
- const resolve10 = this.pendingHubReview.get(msg.requestId);
14205
- if (resolve10) {
14690
+ const resolve12 = this.pendingHubReview.get(msg.requestId);
14691
+ if (resolve12) {
14206
14692
  this.pendingHubReview.delete(msg.requestId);
14207
- resolve10({ action: msg.action, message: msg.message });
14693
+ resolve12({ action: msg.action, message: msg.message });
14208
14694
  }
14209
14695
  return;
14210
14696
  }
@@ -14214,12 +14700,12 @@ var SessionHandler = class {
14214
14700
  }
14215
14701
  }
14216
14702
  onDisconnect() {
14217
- runLifecycleHooks(this.config.get("hooks") ?? void 0, "Stop", { sessionId: this.sessions.current?.id }, { configDir: this.config.getConfigDir() });
14703
+ runLifecycleHooks(mergePluginHooks(this.config.get("hooks") ?? void 0, this.config.getConfigDir()), "Stop", { sessionId: this.sessions.current?.id }, { configDir: this.config.getConfigDir() });
14218
14704
  this.toolExecutor.cancelAll();
14219
14705
  if (this.abortController) this.abortController.abort();
14220
- for (const resolve10 of this.pendingAskUser.values()) resolve10(null);
14706
+ for (const resolve12 of this.pendingAskUser.values()) resolve12(null);
14221
14707
  this.pendingAskUser.clear();
14222
- for (const resolve10 of this.pendingAutoPause.values()) resolve10({ action: "stop" });
14708
+ for (const resolve12 of this.pendingAutoPause.values()) resolve12({ action: "stop" });
14223
14709
  this.pendingAutoPause.clear();
14224
14710
  this.saveIfNeeded();
14225
14711
  }
@@ -14353,9 +14839,9 @@ var SessionHandler = class {
14353
14839
  this.hubOrchestrator = orchestrator;
14354
14840
  orchestrator.onEvent = (event) => this.send({ type: "hub_event", event });
14355
14841
  if (config.humanSteer) {
14356
- orchestrator.onRoundReview = ({ round, maxRounds }) => new Promise((resolve10) => {
14842
+ orchestrator.onRoundReview = ({ round, maxRounds }) => new Promise((resolve12) => {
14357
14843
  const requestId = `hubrev_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
14358
- this.pendingHubReview.set(requestId, resolve10);
14844
+ this.pendingHubReview.set(requestId, resolve12);
14359
14845
  this.send({ type: "hub_review", requestId, round, maxRounds });
14360
14846
  });
14361
14847
  }
@@ -14393,7 +14879,7 @@ var SessionHandler = class {
14393
14879
  this.ensureSession();
14394
14880
  const session = this.sessions.current;
14395
14881
  const promptDecisions = runLifecycleHooks(
14396
- this.config.get("hooks") ?? void 0,
14882
+ mergePluginHooks(this.config.get("hooks") ?? void 0, this.config.getConfigDir()),
14397
14883
  "UserPromptSubmit",
14398
14884
  { sessionId: session.id, prompt: content },
14399
14885
  { configDir: this.config.getConfigDir(), onSummary: (message) => this.send({ type: "info", message: "[hook] " + message }) }
@@ -14688,7 +15174,7 @@ Try: /compact to reduce context, /clear to reset, or switch to a larger-context
14688
15174
  this.send({ type: "response_done", content, usage });
14689
15175
  if (pendingTeeSave && isCleanDocumentBody(content)) {
14690
15176
  try {
14691
- mkdirSync11(dirname5(pendingTeeSave), { recursive: true });
15177
+ mkdirSync12(dirname7(pendingTeeSave), { recursive: true });
14692
15178
  const bodyToSave = stripOuterCodeFence(content);
14693
15179
  atomicWriteFileSync(pendingTeeSave, bodyToSave);
14694
15180
  undoStack.push(pendingTeeSave, `save_last_response (deferred): ${pendingTeeSave}`);
@@ -14713,8 +15199,8 @@ Try: /compact to reduce context, /clear to reset, or switch to a larger-context
14713
15199
  onMcpToolUsed: (name) => this.usedMcpToolNames.add(name),
14714
15200
  requestAutoPause: async ({ effectiveRound, maxToolRounds: totalRounds, toolSummary }) => {
14715
15201
  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);
15202
+ const pauseResp = await new Promise((resolve12) => {
15203
+ this.pendingAutoPause.set(requestId, resolve12);
14718
15204
  this.send({
14719
15205
  type: "auto_pause_request",
14720
15206
  requestId,
@@ -14864,7 +15350,7 @@ ${summaryContent}`,
14864
15350
  let isError = false;
14865
15351
  let summary;
14866
15352
  try {
14867
- mkdirSync11(dirname5(saveToFile), { recursive: true });
15353
+ mkdirSync12(dirname7(saveToFile), { recursive: true });
14868
15354
  fileStream = createWriteStream(saveToFile);
14869
15355
  const teeSystemPrompt = stripToolCallReminder(systemPrompt ?? "") + CONTENT_ONLY_STREAM_REMINDER;
14870
15356
  const teeExtraMessages = extraMessages.length > 0 ? [...extraMessages, { role: "user", content: TEE_FINAL_USER_NUDGE }] : [{ role: "user", content: TEE_FINAL_USER_NUDGE }];
@@ -14893,8 +15379,8 @@ ${summaryContent}`,
14893
15379
  }
14894
15380
  if (chunk.done) break;
14895
15381
  }
14896
- await new Promise((resolve10, reject) => {
14897
- fileStream.end((err) => err ? reject(err) : resolve10());
15382
+ await new Promise((resolve12, reject) => {
15383
+ fileStream.end((err) => err ? reject(err) : resolve12());
14898
15384
  });
14899
15385
  const verdict = evaluateTeeContent(fullContent, saveToFile, priorContent);
14900
15386
  if (verdict.kind === "reject") {
@@ -14920,7 +15406,7 @@ ${summaryContent}`,
14920
15406
  } catch (err) {
14921
15407
  if (fileStream) {
14922
15408
  try {
14923
- await new Promise((resolve10) => fileStream.end(() => resolve10()));
15409
+ await new Promise((resolve12) => fileStream.end(() => resolve12()));
14924
15410
  } catch {
14925
15411
  }
14926
15412
  }
@@ -15235,6 +15721,7 @@ Tokens: in=${this.sessionTokenUsage.inputTokens} out=${this.sessionTokenUsage.ou
15235
15721
  " /add-dir [path|remove] \u2014 Add/remove directory from AI context",
15236
15722
  " /mcp [reconnect] \u2014 Show/manage MCP servers",
15237
15723
  " /plugins \u2014 Show loaded plugins",
15724
+ " /plugin <cmd> \u2014 Manage package plugins",
15238
15725
  " /commands \u2014 List custom commands",
15239
15726
  " /doctor \u2014 Health check (API keys, config, MCP)",
15240
15727
  " /bug \u2014 Generate bug report template",
@@ -15333,7 +15820,7 @@ ${lines.join("\n")}` });
15333
15820
  this.sendToolsList();
15334
15821
  this.sendStatus();
15335
15822
  } else if (sub === "reload") {
15336
- this.skillManager.loadSkills();
15823
+ this.skillManager.loadSkills(getActivePluginAssets(this.config.getConfigDir()).skillDirs);
15337
15824
  this.send({ type: "info", message: `\u{1F504} Reloaded ${this.skillManager.listSkills().length} skill(s).` });
15338
15825
  this.sendToolsList();
15339
15826
  } else {
@@ -15655,7 +16142,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
15655
16142
  break;
15656
16143
  }
15657
16144
  const sub = args[0]?.toLowerCase();
15658
- const resolve10 = (ref) => {
16145
+ const resolve12 = (ref) => {
15659
16146
  const r = session.resolveBranchRef(ref);
15660
16147
  if (r.ok) return r.id;
15661
16148
  if (r.reason === "ambiguous") {
@@ -15709,7 +16196,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
15709
16196
  this.send({ type: "error", message: "Usage: /branch switch <id|title>" });
15710
16197
  break;
15711
16198
  }
15712
- const id = resolve10(ref);
16199
+ const id = resolve12(ref);
15713
16200
  if (!id) break;
15714
16201
  const ok = session.switchBranch(id);
15715
16202
  if (ok) {
@@ -15729,7 +16216,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
15729
16216
  this.send({ type: "error", message: "Usage: /branch delete <id|title>" });
15730
16217
  break;
15731
16218
  }
15732
- const id = resolve10(ref);
16219
+ const id = resolve12(ref);
15733
16220
  if (!id) break;
15734
16221
  const ok = session.deleteBranch(id);
15735
16222
  if (ok) {
@@ -15748,7 +16235,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
15748
16235
  this.send({ type: "error", message: "Usage: /branch rename <id|title> <new title>" });
15749
16236
  break;
15750
16237
  }
15751
- const id = resolve10(ref);
16238
+ const id = resolve12(ref);
15752
16239
  if (!id) break;
15753
16240
  const ok = session.renameBranch(id, title);
15754
16241
  if (ok) {
@@ -15766,7 +16253,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
15766
16253
  this.send({ type: "error", message: "Usage: /branch diff <id|title>" });
15767
16254
  break;
15768
16255
  }
15769
- const id = resolve10(ref);
16256
+ const id = resolve12(ref);
15770
16257
  if (!id) break;
15771
16258
  const d = session.diffBranches(id);
15772
16259
  if (!d) {
@@ -15808,7 +16295,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
15808
16295
  this.send({ type: "error", message: "Usage: /branch cherry-pick <source-id|title> <msg-index>" });
15809
16296
  break;
15810
16297
  }
15811
- const id = resolve10(ref);
16298
+ const id = resolve12(ref);
15812
16299
  if (!id) break;
15813
16300
  const idx = parseInt(idxStr, 10);
15814
16301
  if (Number.isNaN(idx)) {
@@ -16070,7 +16557,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
16070
16557
  case "test": {
16071
16558
  this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
16072
16559
  try {
16073
- const { executeTests } = await import("./run-tests-4PKSIVK5.js");
16560
+ const { executeTests } = await import("./run-tests-NXVVKAK2.js");
16074
16561
  const argStr = args.join(" ").trim();
16075
16562
  let testArgs = {};
16076
16563
  if (argStr) {
@@ -16087,7 +16574,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
16087
16574
  // ── /init ───────────────────────────────────────────────────────
16088
16575
  case "init": {
16089
16576
  const cwd = process.cwd();
16090
- const targetPath = join20(cwd, "AICLI.md");
16577
+ const targetPath = join21(cwd, "AICLI.md");
16091
16578
  const force = args.includes("--force");
16092
16579
  if (existsSync26(targetPath) && !force) {
16093
16580
  this.send({ type: "info", message: `AICLI.md already exists at ${targetPath}
@@ -16132,9 +16619,9 @@ Use /context reload to load it.` });
16132
16619
  }
16133
16620
  lines.push(` ${exists ? "\u2713" : "\u2013"} ${label.padEnd(14)} ${exists ? filePath + extra : "(not found)"}`);
16134
16621
  };
16135
- checkFile("config.json", join20(configDir, "config.json"));
16136
- checkFile("memory.md", join20(configDir, MEMORY_FILE_NAME));
16137
- checkFile("dev-state.md", join20(configDir, "dev-state.md"));
16622
+ checkFile("config.json", join21(configDir, "config.json"));
16623
+ checkFile("memory.md", join21(configDir, MEMORY_FILE_NAME));
16624
+ checkFile("dev-state.md", join21(configDir, "dev-state.md"));
16138
16625
  lines.push("");
16139
16626
  if (this.mcpManager) {
16140
16627
  lines.push("**MCP Servers:**");
@@ -16237,7 +16724,7 @@ ${this.config.toFormattedJSON()}
16237
16724
  const result = this.contextLoadResult;
16238
16725
  const layers = ["\u{1F4DA} **Context Layers:**", ""];
16239
16726
  if (result && result.layers.length > 0) {
16240
- const labels = { global: "Global ", project: "Project", local: "Local " };
16727
+ const labels = { global: "Global ", project: "Project", plugin: "Plugin ", local: "Local " };
16241
16728
  for (const layer of result.layers) {
16242
16729
  layers.push(` \u2713 ${labels[layer.level] ?? layer.level}: ${layer.displayPath} (${layer.charCount} chars${layer.truncated ? ", truncated" : ""})`);
16243
16730
  }
@@ -16321,7 +16808,7 @@ Use /add-dir remove to clear.` : "No directories added.\nUsage: /add-dir <path>
16321
16808
  this.send({ type: "info", message: "\u2713 All added directories removed from context." });
16322
16809
  break;
16323
16810
  }
16324
- const dirPath = resolve8(sub);
16811
+ const dirPath = resolve10(sub);
16325
16812
  if (!existsSync26(dirPath)) {
16326
16813
  this.send({ type: "error", message: `Directory not found: ${dirPath}` });
16327
16814
  break;
@@ -16338,14 +16825,14 @@ It will be included in AI context for subsequent messages.` });
16338
16825
  // ── /commands ───────────────────────────────────────────────────
16339
16826
  case "commands": {
16340
16827
  const configDir = this.config.getConfigDir();
16341
- const commandsDir = join20(configDir, CUSTOM_COMMANDS_DIR_NAME);
16828
+ const commandsDir = join21(configDir, CUSTOM_COMMANDS_DIR_NAME);
16342
16829
  if (!existsSync26(commandsDir)) {
16343
16830
  this.send({ type: "info", message: `No custom commands directory.
16344
16831
  Create: ${commandsDir}/ with .md files.` });
16345
16832
  break;
16346
16833
  }
16347
16834
  try {
16348
- const files = readdirSync11(commandsDir).filter((f) => f.endsWith(".md"));
16835
+ const files = readdirSync12(commandsDir).filter((f) => f.endsWith(".md"));
16349
16836
  if (files.length === 0) {
16350
16837
  this.send({ type: "info", message: `No custom commands found in ${commandsDir}
16351
16838
  Add .md files to create commands.` });
@@ -16362,23 +16849,75 @@ Add .md files to create commands.` });
16362
16849
  }
16363
16850
  break;
16364
16851
  }
16365
- // ── /plugins ────────────────────────────────────────────────────
16852
+ // ── /plugins / /plugin ──────────────────────────────────────────
16366
16853
  case "plugins": {
16367
16854
  const configDir = this.config.getConfigDir();
16368
- const pluginsDir = join20(configDir, PLUGINS_DIR_NAME);
16855
+ const pluginsDir = join21(configDir, PLUGINS_DIR_NAME);
16369
16856
  const pluginTools = this.toolRegistry.listPluginTools();
16857
+ const pluginPackages = listInstalledPlugins(configDir);
16370
16858
  const lines = [`\u{1F50C} **Plugins:**`, `Dir: ${pluginsDir}`, ""];
16371
- if (pluginTools.length === 0) {
16859
+ if (pluginTools.length === 0 && pluginPackages.length === 0) {
16372
16860
  lines.push("No plugins loaded.");
16373
- lines.push("Add .js files to the plugins directory and set allowPlugins:true in config.");
16861
+ lines.push("Use /plugin install <path>, /plugin trust <name>, then /plugin enable <name>.");
16374
16862
  } else {
16375
- for (const t of pluginTools) {
16376
- lines.push(` \u2022 ${t.definition.name} \u2014 ${t.definition.description}`);
16863
+ if (pluginPackages.length > 0) {
16864
+ lines.push(`Package plugins (${pluginPackages.length}):`);
16865
+ for (const p of pluginPackages) lines.push(` \u2022 ${p.name}@${p.manifest.version} \u2014 ${p.enabled ? "enabled" : "disabled"} / ${p.trusted ? "trusted" : "untrusted"}`);
16866
+ lines.push("");
16867
+ }
16868
+ if (pluginTools.length > 0) {
16869
+ lines.push(`Legacy JS plugin tools (${pluginTools.length}):`);
16870
+ for (const t of pluginTools) lines.push(` \u2022 ${t.definition.name} \u2014 ${t.definition.description}`);
16377
16871
  }
16378
16872
  }
16379
16873
  this.send({ type: "info", message: lines.join("\n") });
16380
16874
  break;
16381
16875
  }
16876
+ case "plugin": {
16877
+ const configDir = this.config.getConfigDir();
16878
+ const sub = args[0] ?? "list";
16879
+ try {
16880
+ if (sub === "list") {
16881
+ const plugins = listInstalledPlugins(configDir);
16882
+ const lines = [`\u{1F50C} **Package Plugins**`, `Dir: ${pluginRoot(configDir)}`, ""];
16883
+ if (plugins.length === 0) lines.push("No package plugins installed.");
16884
+ for (const p of plugins) lines.push(` \u2022 ${p.name}@${p.manifest.version} \u2014 ${p.enabled ? "enabled" : "disabled"} / ${p.trusted ? "trusted" : "untrusted"}`);
16885
+ this.send({ type: "info", message: lines.join("\n") });
16886
+ break;
16887
+ }
16888
+ if (sub === "install") {
16889
+ const input = args[1];
16890
+ if (!input) throw new Error("Usage: /plugin install <path>");
16891
+ const plugin = installPlugin(configDir, input);
16892
+ this.send({ type: "info", message: `Installed plugin ${plugin.name}@${plugin.manifest.version}. Run /plugin trust ${plugin.name}, then /plugin enable ${plugin.name}.` });
16893
+ break;
16894
+ }
16895
+ if (sub === "trust") {
16896
+ const name2 = args[1];
16897
+ if (!name2) throw new Error("Usage: /plugin trust <name>");
16898
+ const state2 = trustPlugin(configDir, name2);
16899
+ this.send({ type: "info", message: `Trusted plugin ${state2.name}.` });
16900
+ break;
16901
+ }
16902
+ if (sub === "enable" || sub === "disable") {
16903
+ const name2 = args[1];
16904
+ if (!name2) throw new Error(`Usage: /plugin ${sub} <name>`);
16905
+ const state2 = setPluginEnabled(configDir, name2, sub === "enable");
16906
+ this.send({ type: "info", message: `${sub === "enable" ? "Enabled" : "Disabled"} plugin ${state2.name}. Restart Web UI for changes to take effect.` });
16907
+ break;
16908
+ }
16909
+ if (sub === "inspect") {
16910
+ const name2 = args[1];
16911
+ if (!name2) throw new Error("Usage: /plugin inspect <name>");
16912
+ this.send({ type: "info", message: describePlugin(getInstalledPlugin(configDir, name2)).join("\n") });
16913
+ break;
16914
+ }
16915
+ throw new Error("Usage: /plugin [list|install|enable|disable|inspect|trust]");
16916
+ } catch (err) {
16917
+ this.send({ type: "error", message: err instanceof Error ? err.message : String(err) });
16918
+ }
16919
+ break;
16920
+ }
16382
16921
  // ── /bug ────────────────────────────────────────────────────────
16383
16922
  case "bug": {
16384
16923
  const session = this.sessions.current;
@@ -16421,7 +16960,7 @@ Add .md files to create commands.` });
16421
16960
  return;
16422
16961
  }
16423
16962
  const preDecisions = runLifecycleHooks(
16424
- this.config.get("hooks") ?? void 0,
16963
+ mergePluginHooks(this.config.get("hooks") ?? void 0, this.config.getConfigDir()),
16425
16964
  "PreCompact",
16426
16965
  { sessionId: session.id, messageCount: session.messages.length, instruction },
16427
16966
  { configDir: this.config.getConfigDir(), onSummary: (message) => this.send({ type: "info", message: "[hook] " + message }) }
@@ -16458,7 +16997,7 @@ Add .md files to create commands.` });
16458
16997
  const removedCount = session.compact(summaryMsg, ackMsg, keepLast);
16459
16998
  const afterCount = session.messages.length;
16460
16999
  runLifecycleHooks(
16461
- this.config.get("hooks") ?? void 0,
17000
+ mergePluginHooks(this.config.get("hooks") ?? void 0, this.config.getConfigDir()),
16462
17001
  "PostCompact",
16463
17002
  { sessionId: session.id, beforeCount, afterCount, removedCount, keepLast, instruction },
16464
17003
  { configDir: this.config.getConfigDir(), onSummary: (message) => this.send({ type: "info", message: "[hook] " + message }) }
@@ -16562,48 +17101,95 @@ Add .md files to create commands.` });
16562
17101
  }
16563
17102
  memoryShow() {
16564
17103
  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
- }
17104
+ const entries = listMemoryEntries(configDir, { includeExpired: true, includeRejected: true });
17105
+ const content = entries.length === 0 ? "(empty \u2014 no persistent memory entries yet)" : entries.map((entry) => {
17106
+ const status = isMemoryExpired(entry) ? "expired" : entry.approved ? "approved" : "pending";
17107
+ const source = entry.sourceSession ? `session ${entry.sourceSession.slice(0, 8)}` : entry.sourceProject ? `project ${entry.sourceProject}` : "manual/tool";
17108
+ return `## ${entry.id.slice(0, 8)} \xB7 ${status} \xB7 scope:${entry.scope} \xB7 sensitivity:${entry.sensitivity}
17109
+ Source: ${source}
17110
+ ${entry.content}`;
17111
+ }).join("\n\n");
16575
17112
  this.send({
16576
17113
  type: "memory_content",
16577
- content: content || "(empty \u2014 no persistent memory entries yet)",
16578
- filePath: memPath
17114
+ content,
17115
+ filePath: memoryStorePath(configDir)
16579
17116
  });
17117
+ this.sendMemoryEntries();
16580
17118
  }
16581
17119
  memoryAdd(text) {
16582
- const configDir = this.config.getConfigDir();
16583
- const memPath = join20(configDir, MEMORY_FILE_NAME);
16584
17120
  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}"` });
17121
+ const entry = addMemoryEntry(this.config.getConfigDir(), text, {
17122
+ scope: "personal",
17123
+ cwd: process.cwd(),
17124
+ redact: true
17125
+ });
17126
+ const status = entry.approved ? "approved" : "pending approval";
17127
+ this.send({ type: "info", message: `\u{1F4DD} Memory entry ${entry.id.slice(0, 8)} added (${status}).` });
17128
+ this.sendMemoryEntries();
16593
17129
  } catch (err) {
16594
17130
  this.send({ type: "error", message: `Failed to write memory: ${err.message}` });
16595
17131
  }
16596
17132
  }
16597
17133
  memoryClear() {
16598
17134
  const configDir = this.config.getConfigDir();
16599
- const memPath = join20(configDir, MEMORY_FILE_NAME);
16600
17135
  try {
16601
- atomicWriteFileSync(memPath, "");
17136
+ atomicWriteFileSync(memoryStorePath(configDir), "");
17137
+ atomicWriteFileSync(join21(configDir, MEMORY_FILE_NAME), "");
16602
17138
  this.send({ type: "info", message: "\u{1F5D1}\uFE0F Persistent memory cleared." });
17139
+ this.sendMemoryEntries();
16603
17140
  } catch (err) {
16604
17141
  this.send({ type: "error", message: `Failed to clear memory: ${err.message}` });
16605
17142
  }
16606
17143
  }
17144
+ sendMemoryEntries() {
17145
+ const configDir = this.config.getConfigDir();
17146
+ const entries = listMemoryEntries(configDir, { includeExpired: true, includeRejected: true });
17147
+ this.send({
17148
+ type: "memory_entries",
17149
+ storePath: memoryStorePath(configDir),
17150
+ entries: entries.map((entry) => ({
17151
+ id: entry.id,
17152
+ content: entry.content,
17153
+ scope: entry.scope,
17154
+ sourceSession: entry.sourceSession,
17155
+ sourceProject: entry.sourceProject,
17156
+ createdAt: entry.createdAt,
17157
+ updatedAt: entry.updatedAt,
17158
+ expiresAt: entry.expiresAt,
17159
+ sensitivity: entry.sensitivity,
17160
+ approved: entry.approved,
17161
+ expired: isMemoryExpired(entry),
17162
+ redactedKinds: entry.redactedKinds
17163
+ }))
17164
+ });
17165
+ }
17166
+ handleMemoryManage(action, id, expiresAt) {
17167
+ const configDir = this.config.getConfigDir();
17168
+ try {
17169
+ if (action === "approve" || action === "reject") {
17170
+ if (!id) throw new Error(`${action} requires id`);
17171
+ updateMemoryApproval(configDir, id, action === "approve");
17172
+ } else if (action === "delete") {
17173
+ if (!id) throw new Error("delete requires id");
17174
+ deleteMemoryEntry(configDir, id);
17175
+ } else if (action === "expire") {
17176
+ if (!id) throw new Error("expire requires id");
17177
+ expireMemoryEntry(configDir, id, expiresAt);
17178
+ } else if (action === "export") {
17179
+ this.send({
17180
+ type: "export_data",
17181
+ format: "json",
17182
+ filename: `aicli-memory-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.json`,
17183
+ content: exportMemoryEntries(configDir, process.cwd())
17184
+ });
17185
+ } else if (action !== "list") {
17186
+ throw new Error(`unknown memory action: ${action}`);
17187
+ }
17188
+ this.sendMemoryEntries();
17189
+ } catch (err) {
17190
+ this.send({ type: "error", message: `Memory management failed: ${err instanceof Error ? err.message : String(err)}` });
17191
+ }
17192
+ }
16607
17193
  // ── B4 chat memory recall (v0.4.90+) ──────────────────────────────
16608
17194
  // Lazy-imported so the 117 MB embedder stays out of the load path for
16609
17195
  // clients that never open the Memory panel.
@@ -16614,7 +17200,7 @@ Add .md files to create commands.` });
16614
17200
  return;
16615
17201
  }
16616
17202
  try {
16617
- const { searchChatMemory: searchChatMemory2, loadChatIndex: loadChatIndex2 } = await import("./chat-index-R2E27VXN.js");
17203
+ const { searchChatMemory: searchChatMemory2, loadChatIndex: loadChatIndex2 } = await import("./chat-index-PS274XM7.js");
16618
17204
  const loaded = loadChatIndex2();
16619
17205
  if (!loaded || loaded.idx.chunks.length === 0) {
16620
17206
  this.send({ type: "memory_hits", query: q, hits: [], indexMissing: true });
@@ -16650,7 +17236,7 @@ Add .md files to create commands.` });
16650
17236
  }
16651
17237
  async handleMemoryStatus() {
16652
17238
  try {
16653
- const { getChatIndexStatus } = await import("./chat-index-R2E27VXN.js");
17239
+ const { getChatIndexStatus } = await import("./chat-index-PS274XM7.js");
16654
17240
  const s = getChatIndexStatus();
16655
17241
  this.send({
16656
17242
  type: "memory_status",
@@ -16675,7 +17261,7 @@ Add .md files to create commands.` });
16675
17261
  type: "info",
16676
17262
  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
17263
  });
16678
- const { buildChatIndex } = await import("./chat-index-R2E27VXN.js");
17264
+ const { buildChatIndex } = await import("./chat-index-PS274XM7.js");
16679
17265
  const stats = await buildChatIndex({
16680
17266
  full,
16681
17267
  onProgress: (p) => {
@@ -16830,8 +17416,8 @@ async function setupProxy(configProxy) {
16830
17416
  }
16831
17417
 
16832
17418
  // src/web/auth.ts
16833
- import { existsSync as existsSync27, readFileSync as readFileSync21, writeFileSync as writeFileSync3, mkdirSync as mkdirSync12, readdirSync as readdirSync12, copyFileSync } from "fs";
16834
- import { join as join21 } from "path";
17419
+ import { existsSync as existsSync27, readFileSync as readFileSync21, writeFileSync as writeFileSync3, mkdirSync as mkdirSync13, readdirSync as readdirSync13, copyFileSync } from "fs";
17420
+ import { join as join22 } from "path";
16835
17421
  import { createHmac, randomBytes, timingSafeEqual, pbkdf2Sync } from "crypto";
16836
17422
  var USERS_FILE = "users.json";
16837
17423
  var TOKEN_EXPIRY_HOURS = 24;
@@ -16846,7 +17432,7 @@ var AuthManager = class {
16846
17432
  db;
16847
17433
  constructor(baseDir) {
16848
17434
  this.baseDir = baseDir;
16849
- this.usersFile = join21(baseDir, USERS_FILE);
17435
+ this.usersFile = join22(baseDir, USERS_FILE);
16850
17436
  this.db = this.loadOrCreate();
16851
17437
  }
16852
17438
  // ── Public API ─────────────────────────────────────────────────
@@ -16875,9 +17461,9 @@ var AuthManager = class {
16875
17461
  }
16876
17462
  const salt = randomBytes(16).toString("hex");
16877
17463
  const passwordHash = this.hashPassword(password, salt);
16878
- const dataDir = join21(USERS_DIR, username);
16879
- const fullDataDir = join21(this.baseDir, dataDir);
16880
- mkdirSync12(join21(fullDataDir, "history"), { recursive: true });
17464
+ const dataDir = join22(USERS_DIR, username);
17465
+ const fullDataDir = join22(this.baseDir, dataDir);
17466
+ mkdirSync13(join22(fullDataDir, "history"), { recursive: true });
16881
17467
  const user = {
16882
17468
  username,
16883
17469
  passwordHash,
@@ -16992,7 +17578,7 @@ var AuthManager = class {
16992
17578
  getUserDataDir(username) {
16993
17579
  const user = this.db.users.find((u) => u.username === username);
16994
17580
  if (!user) throw new Error(`User not found: ${username}`);
16995
- return join21(this.baseDir, user.dataDir);
17581
+ return join22(this.baseDir, user.dataDir);
16996
17582
  }
16997
17583
  /** List all usernames */
16998
17584
  listUsers() {
@@ -17028,30 +17614,30 @@ var AuthManager = class {
17028
17614
  const err = this.register(username, password);
17029
17615
  if (err) return err;
17030
17616
  const userDir = this.getUserDataDir(username);
17031
- const globalConfig = join21(this.baseDir, "config.json");
17617
+ const globalConfig = join22(this.baseDir, "config.json");
17032
17618
  if (existsSync27(globalConfig)) {
17033
17619
  try {
17034
17620
  const content = readFileSync21(globalConfig, "utf-8");
17035
- writeFileSync3(join21(userDir, "config.json"), content, "utf-8");
17621
+ writeFileSync3(join22(userDir, "config.json"), content, "utf-8");
17036
17622
  } catch {
17037
17623
  }
17038
17624
  }
17039
- const globalMemory = join21(this.baseDir, "memory.md");
17625
+ const globalMemory = join22(this.baseDir, "memory.md");
17040
17626
  if (existsSync27(globalMemory)) {
17041
17627
  try {
17042
17628
  const content = readFileSync21(globalMemory, "utf-8");
17043
- writeFileSync3(join21(userDir, "memory.md"), content, "utf-8");
17629
+ writeFileSync3(join22(userDir, "memory.md"), content, "utf-8");
17044
17630
  } catch {
17045
17631
  }
17046
17632
  }
17047
- const globalHistory = join21(this.baseDir, "history");
17633
+ const globalHistory = join22(this.baseDir, "history");
17048
17634
  if (existsSync27(globalHistory)) {
17049
17635
  try {
17050
- const files = readdirSync12(globalHistory).filter((f) => f.endsWith(".json"));
17051
- const userHistory = join21(userDir, "history");
17636
+ const files = readdirSync13(globalHistory).filter((f) => f.endsWith(".json"));
17637
+ const userHistory = join22(userDir, "history");
17052
17638
  for (const f of files) {
17053
17639
  try {
17054
- copyFileSync(join21(globalHistory, f), join21(userHistory, f));
17640
+ copyFileSync(join22(globalHistory, f), join22(userHistory, f));
17055
17641
  } catch {
17056
17642
  }
17057
17643
  }
@@ -17080,7 +17666,7 @@ var AuthManager = class {
17080
17666
  this.saveDB(this.db);
17081
17667
  }
17082
17668
  saveDB(db) {
17083
- mkdirSync12(this.baseDir, { recursive: true });
17669
+ mkdirSync13(this.baseDir, { recursive: true });
17084
17670
  atomicWriteFileSync(this.usersFile, JSON.stringify(db, null, 2));
17085
17671
  }
17086
17672
  /** Legacy hash — kept only for migrating old users (v0.2.x) */
@@ -17112,11 +17698,11 @@ function getModuleDir() {
17112
17698
  if (typeof import.meta?.url === "string") {
17113
17699
  const url = new URL(import.meta.url);
17114
17700
  const filePath = url.pathname.replace(/^\/([A-Z]:)/i, "$1");
17115
- return dirname6(filePath);
17701
+ return dirname8(filePath);
17116
17702
  }
17117
17703
  } catch {
17118
17704
  }
17119
- return resolve9(".");
17705
+ return resolve11(".");
17120
17706
  }
17121
17707
  async function startWebServer(options = {}) {
17122
17708
  const port = options.port ?? 3e3;
@@ -17151,6 +17737,8 @@ async function startWebServer(options = {}) {
17151
17737
  let mcpManager = null;
17152
17738
  const mcpEnabled = config.get("mcpEnabled") !== false;
17153
17739
  const globalMcpServers = mcpEnabled ? config.get("mcpServers") ?? {} : {};
17740
+ const pluginAssets = getActivePluginAssets(config.getConfigDir());
17741
+ const pluginMcpServers = mcpEnabled ? pluginAssets.mcpServers : {};
17154
17742
  const projectMcpResolved = mcpEnabled ? resolveProjectMcpPath() : null;
17155
17743
  let projectMcpServers = {};
17156
17744
  if (projectMcpResolved) {
@@ -17163,7 +17751,7 @@ async function startWebServer(options = {}) {
17163
17751
  console.log(` Skipped to prevent unintended RCE. Run "aicli" then "/mcp trust-project" to approve.`);
17164
17752
  }
17165
17753
  }
17166
- const mergedMcpServers = { ...globalMcpServers, ...projectMcpServers };
17754
+ const mergedMcpServers = { ...globalMcpServers, ...pluginMcpServers, ...projectMcpServers };
17167
17755
  if (!mcpEnabled) {
17168
17756
  console.log(" \u{1F50C} MCP: disabled (config.mcpEnabled=false)");
17169
17757
  }
@@ -17181,10 +17769,10 @@ async function startWebServer(options = {}) {
17181
17769
  }
17182
17770
  }
17183
17771
  let skillManager = null;
17184
- const skillsDir = join22(config.getConfigDir(), SKILLS_DIR_NAME);
17772
+ const skillsDir = join23(config.getConfigDir(), SKILLS_DIR_NAME);
17185
17773
  if (existsSync28(skillsDir)) {
17186
17774
  skillManager = new SkillManager(skillsDir, config.get("ui").skillSizeWarn);
17187
- skillManager.loadSkills();
17775
+ skillManager.loadSkills(pluginAssets.skillDirs);
17188
17776
  const count = skillManager.listSkills().length;
17189
17777
  if (count > 0) {
17190
17778
  console.log(` Skills: ${count} available`);
@@ -17253,18 +17841,18 @@ async function startWebServer(options = {}) {
17253
17841
  next();
17254
17842
  };
17255
17843
  const moduleDir = getModuleDir();
17256
- let clientDir = join22(moduleDir, "web", "client");
17844
+ let clientDir = join23(moduleDir, "web", "client");
17257
17845
  if (!existsSync28(clientDir)) {
17258
- clientDir = join22(moduleDir, "client");
17846
+ clientDir = join23(moduleDir, "client");
17259
17847
  }
17260
17848
  if (!existsSync28(clientDir)) {
17261
- clientDir = join22(moduleDir, "..", "..", "src", "web", "client");
17849
+ clientDir = join23(moduleDir, "..", "..", "src", "web", "client");
17262
17850
  }
17263
17851
  if (!existsSync28(clientDir)) {
17264
- clientDir = join22(process.cwd(), "src", "web", "client");
17852
+ clientDir = join23(process.cwd(), "src", "web", "client");
17265
17853
  }
17266
17854
  console.log(` Static files: ${clientDir}`);
17267
- app.use("/vendor", express.static(join22(clientDir, "vendor"), { maxAge: "1y", immutable: true }));
17855
+ app.use("/vendor", express.static(join23(clientDir, "vendor"), { maxAge: "1y", immutable: true }));
17268
17856
  app.use(express.static(clientDir));
17269
17857
  app.get("/api/status", (_req, res) => {
17270
17858
  res.json({
@@ -17331,10 +17919,10 @@ async function startWebServer(options = {}) {
17331
17919
  app.get("/api/files", requireAuth, (req, res) => {
17332
17920
  const cwd = process.cwd();
17333
17921
  const prefix = req.query.prefix || "";
17334
- const targetDir = join22(cwd, prefix);
17922
+ const targetDir = join23(cwd, prefix);
17335
17923
  try {
17336
- const canonicalTarget = realpathSync(resolve9(targetDir));
17337
- const canonicalCwd = realpathSync(resolve9(cwd));
17924
+ const canonicalTarget = realpathSync(resolve11(targetDir));
17925
+ const canonicalCwd = realpathSync(resolve11(cwd));
17338
17926
  if (!canonicalTarget.startsWith(canonicalCwd + sep3) && canonicalTarget !== canonicalCwd) {
17339
17927
  res.json({ files: [] });
17340
17928
  return;
@@ -17345,10 +17933,10 @@ async function startWebServer(options = {}) {
17345
17933
  }
17346
17934
  try {
17347
17935
  const SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "dist-cjs", "release", "__pycache__", ".next", ".nuxt", "coverage", ".cache"]);
17348
- const entries = readdirSync13(targetDir, { withFileTypes: true });
17936
+ const entries = readdirSync14(targetDir, { withFileTypes: true });
17349
17937
  const files = entries.filter((e) => !SKIP.has(e.name) && !e.name.startsWith(".")).slice(0, 50).map((e) => ({
17350
17938
  name: e.name,
17351
- path: relative4(cwd, join22(targetDir, e.name)).replace(/\\/g, "/"),
17939
+ path: relative4(cwd, join23(targetDir, e.name)).replace(/\\/g, "/"),
17352
17940
  isDir: e.isDirectory()
17353
17941
  }));
17354
17942
  res.json({ files });
@@ -17384,7 +17972,7 @@ async function startWebServer(options = {}) {
17384
17972
  try {
17385
17973
  const authUser = req._authUser;
17386
17974
  const histDir = authUser ? getUserShared(authUser).config.getHistoryDir() : config.getHistoryDir();
17387
- const filePath = join22(histDir, `${id}.json`);
17975
+ const filePath = join23(histDir, `${id}.json`);
17388
17976
  if (!existsSync28(filePath)) {
17389
17977
  res.status(404).json({ error: "Session not found" });
17390
17978
  return;
@@ -17413,10 +18001,10 @@ async function startWebServer(options = {}) {
17413
18001
  return;
17414
18002
  }
17415
18003
  const cwd = process.cwd();
17416
- const fullPath = resolve9(join22(cwd, filePath));
18004
+ const fullPath = resolve11(join23(cwd, filePath));
17417
18005
  try {
17418
18006
  const canonicalFull = realpathSync(fullPath);
17419
- const canonicalCwd = realpathSync(resolve9(cwd));
18007
+ const canonicalCwd = realpathSync(resolve11(cwd));
17420
18008
  if (!canonicalFull.startsWith(canonicalCwd + sep3) && canonicalFull !== canonicalCwd) {
17421
18009
  res.json({ error: "Access denied" });
17422
18010
  return;
@@ -17635,7 +18223,7 @@ async function startWebServer(options = {}) {
17635
18223
  });
17636
18224
  const MAX_PORT_ATTEMPTS = 10;
17637
18225
  let actualPort = port;
17638
- const result = await new Promise((resolve10, reject) => {
18226
+ const result = await new Promise((resolve12, reject) => {
17639
18227
  const tryListen = (attempt) => {
17640
18228
  server.once("error", (err) => {
17641
18229
  if (err.code === "EADDRINUSE" && attempt < MAX_PORT_ATTEMPTS) {
@@ -17666,7 +18254,7 @@ async function startWebServer(options = {}) {
17666
18254
  }
17667
18255
  console.log(` Press Ctrl+C to stop
17668
18256
  `);
17669
- resolve10({ port: actualPort, host, url });
18257
+ resolve12({ port: actualPort, host, url });
17670
18258
  });
17671
18259
  };
17672
18260
  tryListen(1);
@@ -17686,14 +18274,14 @@ function resolveProjectMcpPath() {
17686
18274
  const cwd = process.cwd();
17687
18275
  const gitRoot = getGitRoot(cwd);
17688
18276
  const projectRoot = gitRoot ?? cwd;
17689
- const configPath = join22(projectRoot, MCP_PROJECT_CONFIG_NAME);
18277
+ const configPath = join23(projectRoot, MCP_PROJECT_CONFIG_NAME);
17690
18278
  return existsSync28(configPath) ? configPath : null;
17691
18279
  }
17692
18280
  function loadProjectMcpConfig() {
17693
18281
  const cwd = process.cwd();
17694
18282
  const gitRoot = getGitRoot(cwd);
17695
18283
  const projectRoot = gitRoot ?? cwd;
17696
- const configPath = join22(projectRoot, MCP_PROJECT_CONFIG_NAME);
18284
+ const configPath = join23(projectRoot, MCP_PROJECT_CONFIG_NAME);
17697
18285
  if (!existsSync28(configPath)) return null;
17698
18286
  try {
17699
18287
  const raw = JSON.parse(readFileSync22(configPath, "utf-8"));