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
@@ -24,7 +24,7 @@ import {
24
24
  scanDirTree,
25
25
  scanProject,
26
26
  setupProxy
27
- } from "./chunk-PNTLA3MN.js";
27
+ } from "./chunk-H2UIHGHH.js";
28
28
  import {
29
29
  buildReviewPrompt,
30
30
  buildSecurityReviewPrompt
@@ -32,21 +32,36 @@ import {
32
32
  import {
33
33
  ToolExecutor,
34
34
  ToolRegistry,
35
+ addMemoryEntry,
35
36
  askUserContext,
36
37
  checkNetworkPolicy,
37
38
  checkPermissionWithProfile,
38
39
  cleanupRejectedTeeFile,
39
40
  clearRecentlyDeniedAutoActions,
40
41
  defaultActionClassifier,
42
+ deleteMemoryEntry,
43
+ describePlugin,
41
44
  estimateTokens,
45
+ expireMemoryEntry,
46
+ exportMemoryEntries,
47
+ formatMemoryForPrompt,
42
48
  formatPermissionProfileWarning,
49
+ getActivePluginAssets,
50
+ getInstalledPlugin,
43
51
  getPendingHookTrust,
44
52
  getPreferredAgentName,
45
53
  getRecentlyDeniedAutoActions,
46
54
  googleSearchContext,
47
55
  groupCallsByPhase,
56
+ installPlugin,
57
+ isMemoryExpired,
48
58
  listAgentConfigs,
49
59
  listAgentRuns,
60
+ listInstalledPlugins,
61
+ listMemoryEntries,
62
+ memoryStorePath,
63
+ mergePluginHooks,
64
+ pluginRoot,
50
65
  recordRecentlyDeniedAutoAction,
51
66
  renderDiff,
52
67
  requestAgentStop,
@@ -57,14 +72,17 @@ import {
57
72
  runWithSessionKey,
58
73
  setContextWindow,
59
74
  setMaxOutputCap,
75
+ setPluginEnabled,
60
76
  setPreferredAgentName,
61
77
  spawnAgentContext,
62
78
  truncateOutput,
63
- undoStack
64
- } from "./chunk-TCOC4AUI.js";
79
+ trustPlugin,
80
+ undoStack,
81
+ updateMemoryApproval
82
+ } from "./chunk-GX3HSGJX.js";
65
83
  import "./chunk-T2NL5ZIA.js";
66
84
  import "./chunk-BXP6YZ2P.js";
67
- import "./chunk-VW7Y27WW.js";
85
+ import "./chunk-VGFTM3XT.js";
68
86
  import {
69
87
  SessionManager,
70
88
  getContentText
@@ -78,7 +96,7 @@ import {
78
96
  } from "./chunk-QMXC327F.js";
79
97
  import {
80
98
  runTool
81
- } from "./chunk-77HDCGTN.js";
99
+ } from "./chunk-MWKE2TNS.js";
82
100
  import {
83
101
  CONTENT_ONLY_STREAM_REMINDER,
84
102
  TEE_FINAL_USER_NUDGE,
@@ -97,7 +115,7 @@ import {
97
115
  } from "./chunk-XPBEJB27.js";
98
116
  import {
99
117
  ConfigManager
100
- } from "./chunk-WAI3WPV2.js";
118
+ } from "./chunk-P4VBLXKS.js";
101
119
  import "./chunk-TZQHYZKT.js";
102
120
  import {
103
121
  AGENTIC_BEHAVIOR_GUIDELINE,
@@ -109,14 +127,13 @@ import {
109
127
  DESCRIPTION,
110
128
  MCP_PROJECT_CONFIG_NAME,
111
129
  MEMORY_FILE_NAME,
112
- MEMORY_MAX_CHARS,
113
130
  PLAN_MODE_READONLY_TOOLS,
114
131
  PLAN_MODE_SYSTEM_ADDON,
115
132
  PLUGINS_DIR_NAME,
116
133
  SKILLS_DIR_NAME,
117
134
  VERSION,
118
135
  buildUserIdentityPrompt
119
- } from "./chunk-7YWS3JLU.js";
136
+ } from "./chunk-NTCB7CMT.js";
120
137
  import {
121
138
  formatGitContextForPrompt,
122
139
  getGitContext,
@@ -138,8 +155,8 @@ import {
138
155
  import express from "express";
139
156
  import { createServer } from "http";
140
157
  import { WebSocketServer } from "ws";
141
- import { join as join3, dirname as dirname2, resolve as resolve2, relative, sep } from "path";
142
- import { existsSync as existsSync4, readFileSync as readFileSync4, readdirSync as readdirSync2, statSync as statSync2, realpathSync } from "fs";
158
+ import { join as join2, dirname as dirname2, resolve as resolve2, relative, sep } from "path";
159
+ import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync as readdirSync2, statSync as statSync2, realpathSync } from "fs";
143
160
  import { networkInterfaces } from "os";
144
161
 
145
162
  // src/web/tool-executor-web.ts
@@ -589,8 +606,6 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
589
606
  };
590
607
 
591
608
  // src/core/system-prompt-builder.ts
592
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
593
- import { join } from "path";
594
609
  function buildSystemPromptParts(ctx) {
595
610
  const now = /* @__PURE__ */ new Date();
596
611
  const WEEKDAYS = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
@@ -644,24 +659,12 @@ ${ctx.activeSkill.content}`);
644
659
  return { stable: stableParts.join("\n\n---\n\n"), volatile };
645
660
  }
646
661
  function loadMemoryContent(configDir) {
647
- const memoryPath = join(configDir, MEMORY_FILE_NAME);
648
- if (!existsSync2(memoryPath)) return null;
649
- let content = readFileSync2(memoryPath, "utf-8").trim();
650
- if (!content) return null;
651
- if (content.length > MEMORY_MAX_CHARS) {
652
- content = content.slice(-MEMORY_MAX_CHARS);
653
- const firstEntry = content.indexOf("\n## ");
654
- if (firstEntry !== -1) {
655
- content = content.slice(firstEntry + 1);
656
- }
657
- }
658
- const entryCount = (content.match(/^## \d{4}-\d{2}-\d{2}/gm) ?? []).length;
659
- return { content, entryCount };
662
+ return formatMemoryForPrompt(configDir, process.cwd());
660
663
  }
661
664
 
662
665
  // src/web/session-handler.ts
663
- import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync, mkdirSync, readdirSync, statSync, createWriteStream } from "fs";
664
- import { join as join2, resolve, dirname } from "path";
666
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync, mkdirSync, readdirSync, statSync, createWriteStream } from "fs";
667
+ import { join, resolve, dirname } from "path";
665
668
  function lastAssistantText(messages) {
666
669
  for (let i = messages.length - 1; i >= 0; i--) {
667
670
  const m = messages[i];
@@ -754,7 +757,7 @@ var SessionHandler = class {
754
757
  }
755
758
  this.updateContextWindow();
756
759
  this.activeSystemPrompt = this.loadContextFiles();
757
- const hooks = this.config.get("hooks");
760
+ const hooks = mergePluginHooks(this.config.get("hooks") ?? void 0, this.config.getConfigDir());
758
761
  const permissionRules = this.config.get("permissionRules");
759
762
  const defaultPermission = this.config.get("defaultPermission");
760
763
  const permissionProfileName = this.config.get("defaultPermissionProfile") ?? "legacy";
@@ -867,6 +870,8 @@ var SessionHandler = class {
867
870
  return this.handleMemoryStatus();
868
871
  case "memory_rebuild":
869
872
  return this.handleMemoryRebuild(Boolean(msg.full));
873
+ case "memory_manage":
874
+ return this.handleMemoryManage(msg.action, msg.id, msg.expiresAt);
870
875
  case "auto_pause_response": {
871
876
  const resolve3 = this.pendingAutoPause.get(msg.requestId);
872
877
  if (resolve3) {
@@ -899,7 +904,7 @@ var SessionHandler = class {
899
904
  }
900
905
  }
901
906
  onDisconnect() {
902
- runLifecycleHooks(this.config.get("hooks") ?? void 0, "Stop", { sessionId: this.sessions.current?.id }, { configDir: this.config.getConfigDir() });
907
+ runLifecycleHooks(mergePluginHooks(this.config.get("hooks") ?? void 0, this.config.getConfigDir()), "Stop", { sessionId: this.sessions.current?.id }, { configDir: this.config.getConfigDir() });
903
908
  this.toolExecutor.cancelAll();
904
909
  if (this.abortController) this.abortController.abort();
905
910
  for (const resolve3 of this.pendingAskUser.values()) resolve3(null);
@@ -1078,7 +1083,7 @@ var SessionHandler = class {
1078
1083
  this.ensureSession();
1079
1084
  const session = this.sessions.current;
1080
1085
  const promptDecisions = runLifecycleHooks(
1081
- this.config.get("hooks") ?? void 0,
1086
+ mergePluginHooks(this.config.get("hooks") ?? void 0, this.config.getConfigDir()),
1082
1087
  "UserPromptSubmit",
1083
1088
  { sessionId: session.id, prompt: content },
1084
1089
  { configDir: this.config.getConfigDir(), onSummary: (message) => this.send({ type: "info", message: "[hook] " + message }) }
@@ -1920,6 +1925,7 @@ Tokens: in=${this.sessionTokenUsage.inputTokens} out=${this.sessionTokenUsage.ou
1920
1925
  " /add-dir [path|remove] \u2014 Add/remove directory from AI context",
1921
1926
  " /mcp [reconnect] \u2014 Show/manage MCP servers",
1922
1927
  " /plugins \u2014 Show loaded plugins",
1928
+ " /plugin <cmd> \u2014 Manage package plugins",
1923
1929
  " /commands \u2014 List custom commands",
1924
1930
  " /doctor \u2014 Health check (API keys, config, MCP)",
1925
1931
  " /bug \u2014 Generate bug report template",
@@ -2018,7 +2024,7 @@ ${lines.join("\n")}` });
2018
2024
  this.sendToolsList();
2019
2025
  this.sendStatus();
2020
2026
  } else if (sub === "reload") {
2021
- this.skillManager.loadSkills();
2027
+ this.skillManager.loadSkills(getActivePluginAssets(this.config.getConfigDir()).skillDirs);
2022
2028
  this.send({ type: "info", message: `\u{1F504} Reloaded ${this.skillManager.listSkills().length} skill(s).` });
2023
2029
  this.sendToolsList();
2024
2030
  } else {
@@ -2237,9 +2243,9 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
2237
2243
  let modifiedFiles = 0;
2238
2244
  const diffLines = [];
2239
2245
  for (const [filePath, { earliest }] of fileMap) {
2240
- const currentContent = existsSync3(filePath) ? (() => {
2246
+ const currentContent = existsSync2(filePath) ? (() => {
2241
2247
  try {
2242
- return readFileSync3(filePath, "utf-8");
2248
+ return readFileSync2(filePath, "utf-8");
2243
2249
  } catch {
2244
2250
  return null;
2245
2251
  }
@@ -2755,7 +2761,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
2755
2761
  case "test": {
2756
2762
  this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
2757
2763
  try {
2758
- const { executeTests } = await import("./run-tests-ZRK4TQUN.js");
2764
+ const { executeTests } = await import("./run-tests-SWU2XEV7.js");
2759
2765
  const argStr = args.join(" ").trim();
2760
2766
  let testArgs = {};
2761
2767
  if (argStr) {
@@ -2772,9 +2778,9 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
2772
2778
  // ── /init ───────────────────────────────────────────────────────
2773
2779
  case "init": {
2774
2780
  const cwd = process.cwd();
2775
- const targetPath = join2(cwd, "AICLI.md");
2781
+ const targetPath = join(cwd, "AICLI.md");
2776
2782
  const force = args.includes("--force");
2777
- if (existsSync3(targetPath) && !force) {
2783
+ if (existsSync2(targetPath) && !force) {
2778
2784
  this.send({ type: "info", message: `AICLI.md already exists at ${targetPath}
2779
2785
  Use /init --force to overwrite.` });
2780
2786
  break;
@@ -2807,7 +2813,7 @@ Use /context reload to load it.` });
2807
2813
  lines.push("**Config Files:**");
2808
2814
  lines.push(` Dir: ${configDir}`);
2809
2815
  const checkFile = (label, filePath) => {
2810
- const exists = existsSync3(filePath);
2816
+ const exists = existsSync2(filePath);
2811
2817
  let extra = "";
2812
2818
  if (exists) {
2813
2819
  try {
@@ -2817,9 +2823,9 @@ Use /context reload to load it.` });
2817
2823
  }
2818
2824
  lines.push(` ${exists ? "\u2713" : "\u2013"} ${label.padEnd(14)} ${exists ? filePath + extra : "(not found)"}`);
2819
2825
  };
2820
- checkFile("config.json", join2(configDir, "config.json"));
2821
- checkFile("memory.md", join2(configDir, MEMORY_FILE_NAME));
2822
- checkFile("dev-state.md", join2(configDir, "dev-state.md"));
2826
+ checkFile("config.json", join(configDir, "config.json"));
2827
+ checkFile("memory.md", join(configDir, MEMORY_FILE_NAME));
2828
+ checkFile("dev-state.md", join(configDir, "dev-state.md"));
2823
2829
  lines.push("");
2824
2830
  if (this.mcpManager) {
2825
2831
  lines.push("**MCP Servers:**");
@@ -2922,7 +2928,7 @@ ${this.config.toFormattedJSON()}
2922
2928
  const result = this.contextLoadResult;
2923
2929
  const layers = ["\u{1F4DA} **Context Layers:**", ""];
2924
2930
  if (result && result.layers.length > 0) {
2925
- const labels = { global: "Global ", project: "Project", local: "Local " };
2931
+ const labels = { global: "Global ", project: "Project", plugin: "Plugin ", local: "Local " };
2926
2932
  for (const layer of result.layers) {
2927
2933
  layers.push(` \u2713 ${labels[layer.level] ?? layer.level}: ${layer.displayPath} (${layer.charCount} chars${layer.truncated ? ", truncated" : ""})`);
2928
2934
  }
@@ -3007,7 +3013,7 @@ Use /add-dir remove to clear.` : "No directories added.\nUsage: /add-dir <path>
3007
3013
  break;
3008
3014
  }
3009
3015
  const dirPath = resolve(sub);
3010
- if (!existsSync3(dirPath)) {
3016
+ if (!existsSync2(dirPath)) {
3011
3017
  this.send({ type: "error", message: `Directory not found: ${dirPath}` });
3012
3018
  break;
3013
3019
  }
@@ -3023,8 +3029,8 @@ It will be included in AI context for subsequent messages.` });
3023
3029
  // ── /commands ───────────────────────────────────────────────────
3024
3030
  case "commands": {
3025
3031
  const configDir = this.config.getConfigDir();
3026
- const commandsDir = join2(configDir, CUSTOM_COMMANDS_DIR_NAME);
3027
- if (!existsSync3(commandsDir)) {
3032
+ const commandsDir = join(configDir, CUSTOM_COMMANDS_DIR_NAME);
3033
+ if (!existsSync2(commandsDir)) {
3028
3034
  this.send({ type: "info", message: `No custom commands directory.
3029
3035
  Create: ${commandsDir}/ with .md files.` });
3030
3036
  break;
@@ -3047,23 +3053,75 @@ Add .md files to create commands.` });
3047
3053
  }
3048
3054
  break;
3049
3055
  }
3050
- // ── /plugins ────────────────────────────────────────────────────
3056
+ // ── /plugins / /plugin ──────────────────────────────────────────
3051
3057
  case "plugins": {
3052
3058
  const configDir = this.config.getConfigDir();
3053
- const pluginsDir = join2(configDir, PLUGINS_DIR_NAME);
3059
+ const pluginsDir = join(configDir, PLUGINS_DIR_NAME);
3054
3060
  const pluginTools = this.toolRegistry.listPluginTools();
3061
+ const pluginPackages = listInstalledPlugins(configDir);
3055
3062
  const lines = [`\u{1F50C} **Plugins:**`, `Dir: ${pluginsDir}`, ""];
3056
- if (pluginTools.length === 0) {
3063
+ if (pluginTools.length === 0 && pluginPackages.length === 0) {
3057
3064
  lines.push("No plugins loaded.");
3058
- lines.push("Add .js files to the plugins directory and set allowPlugins:true in config.");
3065
+ lines.push("Use /plugin install <path>, /plugin trust <name>, then /plugin enable <name>.");
3059
3066
  } else {
3060
- for (const t of pluginTools) {
3061
- lines.push(` \u2022 ${t.definition.name} \u2014 ${t.definition.description}`);
3067
+ if (pluginPackages.length > 0) {
3068
+ lines.push(`Package plugins (${pluginPackages.length}):`);
3069
+ for (const p of pluginPackages) lines.push(` \u2022 ${p.name}@${p.manifest.version} \u2014 ${p.enabled ? "enabled" : "disabled"} / ${p.trusted ? "trusted" : "untrusted"}`);
3070
+ lines.push("");
3071
+ }
3072
+ if (pluginTools.length > 0) {
3073
+ lines.push(`Legacy JS plugin tools (${pluginTools.length}):`);
3074
+ for (const t of pluginTools) lines.push(` \u2022 ${t.definition.name} \u2014 ${t.definition.description}`);
3062
3075
  }
3063
3076
  }
3064
3077
  this.send({ type: "info", message: lines.join("\n") });
3065
3078
  break;
3066
3079
  }
3080
+ case "plugin": {
3081
+ const configDir = this.config.getConfigDir();
3082
+ const sub = args[0] ?? "list";
3083
+ try {
3084
+ if (sub === "list") {
3085
+ const plugins = listInstalledPlugins(configDir);
3086
+ const lines = [`\u{1F50C} **Package Plugins**`, `Dir: ${pluginRoot(configDir)}`, ""];
3087
+ if (plugins.length === 0) lines.push("No package plugins installed.");
3088
+ for (const p of plugins) lines.push(` \u2022 ${p.name}@${p.manifest.version} \u2014 ${p.enabled ? "enabled" : "disabled"} / ${p.trusted ? "trusted" : "untrusted"}`);
3089
+ this.send({ type: "info", message: lines.join("\n") });
3090
+ break;
3091
+ }
3092
+ if (sub === "install") {
3093
+ const input = args[1];
3094
+ if (!input) throw new Error("Usage: /plugin install <path>");
3095
+ const plugin = installPlugin(configDir, input);
3096
+ this.send({ type: "info", message: `Installed plugin ${plugin.name}@${plugin.manifest.version}. Run /plugin trust ${plugin.name}, then /plugin enable ${plugin.name}.` });
3097
+ break;
3098
+ }
3099
+ if (sub === "trust") {
3100
+ const name2 = args[1];
3101
+ if (!name2) throw new Error("Usage: /plugin trust <name>");
3102
+ const state = trustPlugin(configDir, name2);
3103
+ this.send({ type: "info", message: `Trusted plugin ${state.name}.` });
3104
+ break;
3105
+ }
3106
+ if (sub === "enable" || sub === "disable") {
3107
+ const name2 = args[1];
3108
+ if (!name2) throw new Error(`Usage: /plugin ${sub} <name>`);
3109
+ const state = setPluginEnabled(configDir, name2, sub === "enable");
3110
+ this.send({ type: "info", message: `${sub === "enable" ? "Enabled" : "Disabled"} plugin ${state.name}. Restart Web UI for changes to take effect.` });
3111
+ break;
3112
+ }
3113
+ if (sub === "inspect") {
3114
+ const name2 = args[1];
3115
+ if (!name2) throw new Error("Usage: /plugin inspect <name>");
3116
+ this.send({ type: "info", message: describePlugin(getInstalledPlugin(configDir, name2)).join("\n") });
3117
+ break;
3118
+ }
3119
+ throw new Error("Usage: /plugin [list|install|enable|disable|inspect|trust]");
3120
+ } catch (err) {
3121
+ this.send({ type: "error", message: err instanceof Error ? err.message : String(err) });
3122
+ }
3123
+ break;
3124
+ }
3067
3125
  // ── /bug ────────────────────────────────────────────────────────
3068
3126
  case "bug": {
3069
3127
  const session = this.sessions.current;
@@ -3106,7 +3164,7 @@ Add .md files to create commands.` });
3106
3164
  return;
3107
3165
  }
3108
3166
  const preDecisions = runLifecycleHooks(
3109
- this.config.get("hooks") ?? void 0,
3167
+ mergePluginHooks(this.config.get("hooks") ?? void 0, this.config.getConfigDir()),
3110
3168
  "PreCompact",
3111
3169
  { sessionId: session.id, messageCount: session.messages.length, instruction },
3112
3170
  { configDir: this.config.getConfigDir(), onSummary: (message) => this.send({ type: "info", message: "[hook] " + message }) }
@@ -3143,7 +3201,7 @@ Add .md files to create commands.` });
3143
3201
  const removedCount = session.compact(summaryMsg, ackMsg, keepLast);
3144
3202
  const afterCount = session.messages.length;
3145
3203
  runLifecycleHooks(
3146
- this.config.get("hooks") ?? void 0,
3204
+ mergePluginHooks(this.config.get("hooks") ?? void 0, this.config.getConfigDir()),
3147
3205
  "PostCompact",
3148
3206
  { sessionId: session.id, beforeCount, afterCount, removedCount, keepLast, instruction },
3149
3207
  { configDir: this.config.getConfigDir(), onSummary: (message) => this.send({ type: "info", message: "[hook] " + message }) }
@@ -3247,48 +3305,95 @@ Add .md files to create commands.` });
3247
3305
  }
3248
3306
  memoryShow() {
3249
3307
  const configDir = this.config.getConfigDir();
3250
- const memPath = join2(configDir, MEMORY_FILE_NAME);
3251
- let content = "";
3252
- try {
3253
- if (existsSync3(memPath)) {
3254
- content = readFileSync3(memPath, "utf-8");
3255
- }
3256
- } catch (err) {
3257
- process.stderr.write(`[web] Failed to read memory file: ${err instanceof Error ? err.message : err}
3258
- `);
3259
- }
3308
+ const entries = listMemoryEntries(configDir, { includeExpired: true, includeRejected: true });
3309
+ const content = entries.length === 0 ? "(empty \u2014 no persistent memory entries yet)" : entries.map((entry) => {
3310
+ const status = isMemoryExpired(entry) ? "expired" : entry.approved ? "approved" : "pending";
3311
+ const source = entry.sourceSession ? `session ${entry.sourceSession.slice(0, 8)}` : entry.sourceProject ? `project ${entry.sourceProject}` : "manual/tool";
3312
+ return `## ${entry.id.slice(0, 8)} \xB7 ${status} \xB7 scope:${entry.scope} \xB7 sensitivity:${entry.sensitivity}
3313
+ Source: ${source}
3314
+ ${entry.content}`;
3315
+ }).join("\n\n");
3260
3316
  this.send({
3261
3317
  type: "memory_content",
3262
- content: content || "(empty \u2014 no persistent memory entries yet)",
3263
- filePath: memPath
3318
+ content,
3319
+ filePath: memoryStorePath(configDir)
3264
3320
  });
3321
+ this.sendMemoryEntries();
3265
3322
  }
3266
3323
  memoryAdd(text) {
3267
- const configDir = this.config.getConfigDir();
3268
- const memPath = join2(configDir, MEMORY_FILE_NAME);
3269
3324
  try {
3270
- mkdirSync(configDir, { recursive: true });
3271
- const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace("T", " ");
3272
- const previous = existsSync3(memPath) ? readFileSync3(memPath, "utf-8") : "";
3273
- const entry = `
3274
- - [${timestamp}] ${text}
3275
- `;
3276
- atomicWriteFileSync(memPath, previous + entry);
3277
- this.send({ type: "info", message: `\u{1F4DD} Memory entry added: "${text}"` });
3325
+ const entry = addMemoryEntry(this.config.getConfigDir(), text, {
3326
+ scope: "personal",
3327
+ cwd: process.cwd(),
3328
+ redact: true
3329
+ });
3330
+ const status = entry.approved ? "approved" : "pending approval";
3331
+ this.send({ type: "info", message: `\u{1F4DD} Memory entry ${entry.id.slice(0, 8)} added (${status}).` });
3332
+ this.sendMemoryEntries();
3278
3333
  } catch (err) {
3279
3334
  this.send({ type: "error", message: `Failed to write memory: ${err.message}` });
3280
3335
  }
3281
3336
  }
3282
3337
  memoryClear() {
3283
3338
  const configDir = this.config.getConfigDir();
3284
- const memPath = join2(configDir, MEMORY_FILE_NAME);
3285
3339
  try {
3286
- atomicWriteFileSync(memPath, "");
3340
+ atomicWriteFileSync(memoryStorePath(configDir), "");
3341
+ atomicWriteFileSync(join(configDir, MEMORY_FILE_NAME), "");
3287
3342
  this.send({ type: "info", message: "\u{1F5D1}\uFE0F Persistent memory cleared." });
3343
+ this.sendMemoryEntries();
3288
3344
  } catch (err) {
3289
3345
  this.send({ type: "error", message: `Failed to clear memory: ${err.message}` });
3290
3346
  }
3291
3347
  }
3348
+ sendMemoryEntries() {
3349
+ const configDir = this.config.getConfigDir();
3350
+ const entries = listMemoryEntries(configDir, { includeExpired: true, includeRejected: true });
3351
+ this.send({
3352
+ type: "memory_entries",
3353
+ storePath: memoryStorePath(configDir),
3354
+ entries: entries.map((entry) => ({
3355
+ id: entry.id,
3356
+ content: entry.content,
3357
+ scope: entry.scope,
3358
+ sourceSession: entry.sourceSession,
3359
+ sourceProject: entry.sourceProject,
3360
+ createdAt: entry.createdAt,
3361
+ updatedAt: entry.updatedAt,
3362
+ expiresAt: entry.expiresAt,
3363
+ sensitivity: entry.sensitivity,
3364
+ approved: entry.approved,
3365
+ expired: isMemoryExpired(entry),
3366
+ redactedKinds: entry.redactedKinds
3367
+ }))
3368
+ });
3369
+ }
3370
+ handleMemoryManage(action, id, expiresAt) {
3371
+ const configDir = this.config.getConfigDir();
3372
+ try {
3373
+ if (action === "approve" || action === "reject") {
3374
+ if (!id) throw new Error(`${action} requires id`);
3375
+ updateMemoryApproval(configDir, id, action === "approve");
3376
+ } else if (action === "delete") {
3377
+ if (!id) throw new Error("delete requires id");
3378
+ deleteMemoryEntry(configDir, id);
3379
+ } else if (action === "expire") {
3380
+ if (!id) throw new Error("expire requires id");
3381
+ expireMemoryEntry(configDir, id, expiresAt);
3382
+ } else if (action === "export") {
3383
+ this.send({
3384
+ type: "export_data",
3385
+ format: "json",
3386
+ filename: `aicli-memory-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.json`,
3387
+ content: exportMemoryEntries(configDir, process.cwd())
3388
+ });
3389
+ } else if (action !== "list") {
3390
+ throw new Error(`unknown memory action: ${action}`);
3391
+ }
3392
+ this.sendMemoryEntries();
3393
+ } catch (err) {
3394
+ this.send({ type: "error", message: `Memory management failed: ${err instanceof Error ? err.message : String(err)}` });
3395
+ }
3396
+ }
3292
3397
  // ── B4 chat memory recall (v0.4.90+) ──────────────────────────────
3293
3398
  // Lazy-imported so the 117 MB embedder stays out of the load path for
3294
3399
  // clients that never open the Memory panel.
@@ -3548,6 +3653,8 @@ async function startWebServer(options = {}) {
3548
3653
  let mcpManager = null;
3549
3654
  const mcpEnabled = config.get("mcpEnabled") !== false;
3550
3655
  const globalMcpServers = mcpEnabled ? config.get("mcpServers") ?? {} : {};
3656
+ const pluginAssets = getActivePluginAssets(config.getConfigDir());
3657
+ const pluginMcpServers = mcpEnabled ? pluginAssets.mcpServers : {};
3551
3658
  const projectMcpResolved = mcpEnabled ? resolveProjectMcpPath() : null;
3552
3659
  let projectMcpServers = {};
3553
3660
  if (projectMcpResolved) {
@@ -3560,7 +3667,7 @@ async function startWebServer(options = {}) {
3560
3667
  console.log(` Skipped to prevent unintended RCE. Run "aicli" then "/mcp trust-project" to approve.`);
3561
3668
  }
3562
3669
  }
3563
- const mergedMcpServers = { ...globalMcpServers, ...projectMcpServers };
3670
+ const mergedMcpServers = { ...globalMcpServers, ...pluginMcpServers, ...projectMcpServers };
3564
3671
  if (!mcpEnabled) {
3565
3672
  console.log(" \u{1F50C} MCP: disabled (config.mcpEnabled=false)");
3566
3673
  }
@@ -3578,10 +3685,10 @@ async function startWebServer(options = {}) {
3578
3685
  }
3579
3686
  }
3580
3687
  let skillManager = null;
3581
- const skillsDir = join3(config.getConfigDir(), SKILLS_DIR_NAME);
3582
- if (existsSync4(skillsDir)) {
3688
+ const skillsDir = join2(config.getConfigDir(), SKILLS_DIR_NAME);
3689
+ if (existsSync3(skillsDir)) {
3583
3690
  skillManager = new SkillManager(skillsDir, config.get("ui").skillSizeWarn);
3584
- skillManager.loadSkills();
3691
+ skillManager.loadSkills(pluginAssets.skillDirs);
3585
3692
  const count = skillManager.listSkills().length;
3586
3693
  if (count > 0) {
3587
3694
  console.log(` Skills: ${count} available`);
@@ -3650,18 +3757,18 @@ async function startWebServer(options = {}) {
3650
3757
  next();
3651
3758
  };
3652
3759
  const moduleDir = getModuleDir();
3653
- let clientDir = join3(moduleDir, "web", "client");
3654
- if (!existsSync4(clientDir)) {
3655
- clientDir = join3(moduleDir, "client");
3760
+ let clientDir = join2(moduleDir, "web", "client");
3761
+ if (!existsSync3(clientDir)) {
3762
+ clientDir = join2(moduleDir, "client");
3656
3763
  }
3657
- if (!existsSync4(clientDir)) {
3658
- clientDir = join3(moduleDir, "..", "..", "src", "web", "client");
3764
+ if (!existsSync3(clientDir)) {
3765
+ clientDir = join2(moduleDir, "..", "..", "src", "web", "client");
3659
3766
  }
3660
- if (!existsSync4(clientDir)) {
3661
- clientDir = join3(process.cwd(), "src", "web", "client");
3767
+ if (!existsSync3(clientDir)) {
3768
+ clientDir = join2(process.cwd(), "src", "web", "client");
3662
3769
  }
3663
3770
  console.log(` Static files: ${clientDir}`);
3664
- app.use("/vendor", express.static(join3(clientDir, "vendor"), { maxAge: "1y", immutable: true }));
3771
+ app.use("/vendor", express.static(join2(clientDir, "vendor"), { maxAge: "1y", immutable: true }));
3665
3772
  app.use(express.static(clientDir));
3666
3773
  app.get("/api/status", (_req, res) => {
3667
3774
  res.json({
@@ -3728,7 +3835,7 @@ async function startWebServer(options = {}) {
3728
3835
  app.get("/api/files", requireAuth, (req, res) => {
3729
3836
  const cwd = process.cwd();
3730
3837
  const prefix = req.query.prefix || "";
3731
- const targetDir = join3(cwd, prefix);
3838
+ const targetDir = join2(cwd, prefix);
3732
3839
  try {
3733
3840
  const canonicalTarget = realpathSync(resolve2(targetDir));
3734
3841
  const canonicalCwd = realpathSync(resolve2(cwd));
@@ -3745,7 +3852,7 @@ async function startWebServer(options = {}) {
3745
3852
  const entries = readdirSync2(targetDir, { withFileTypes: true });
3746
3853
  const files = entries.filter((e) => !SKIP.has(e.name) && !e.name.startsWith(".")).slice(0, 50).map((e) => ({
3747
3854
  name: e.name,
3748
- path: relative(cwd, join3(targetDir, e.name)).replace(/\\/g, "/"),
3855
+ path: relative(cwd, join2(targetDir, e.name)).replace(/\\/g, "/"),
3749
3856
  isDir: e.isDirectory()
3750
3857
  }));
3751
3858
  res.json({ files });
@@ -3781,8 +3888,8 @@ async function startWebServer(options = {}) {
3781
3888
  try {
3782
3889
  const authUser = req._authUser;
3783
3890
  const histDir = authUser ? getUserShared(authUser).config.getHistoryDir() : config.getHistoryDir();
3784
- const filePath = join3(histDir, `${id}.json`);
3785
- if (!existsSync4(filePath)) {
3891
+ const filePath = join2(histDir, `${id}.json`);
3892
+ if (!existsSync3(filePath)) {
3786
3893
  res.status(404).json({ error: "Session not found" });
3787
3894
  return;
3788
3895
  }
@@ -3797,7 +3904,7 @@ async function startWebServer(options = {}) {
3797
3904
  res.status(404).json({ error: "Session not found" });
3798
3905
  return;
3799
3906
  }
3800
- const data = JSON.parse(readFileSync4(filePath, "utf-8"));
3907
+ const data = JSON.parse(readFileSync3(filePath, "utf-8"));
3801
3908
  res.json({ session: data });
3802
3909
  } catch (err) {
3803
3910
  res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
@@ -3810,7 +3917,7 @@ async function startWebServer(options = {}) {
3810
3917
  return;
3811
3918
  }
3812
3919
  const cwd = process.cwd();
3813
- const fullPath = resolve2(join3(cwd, filePath));
3920
+ const fullPath = resolve2(join2(cwd, filePath));
3814
3921
  try {
3815
3922
  const canonicalFull = realpathSync(fullPath);
3816
3923
  const canonicalCwd = realpathSync(resolve2(cwd));
@@ -3828,7 +3935,7 @@ async function startWebServer(options = {}) {
3828
3935
  res.json({ error: `File too large (${(stat.size / 1024).toFixed(0)} KB, max 512 KB)` });
3829
3936
  return;
3830
3937
  }
3831
- const content = readFileSync4(fullPath, "utf-8");
3938
+ const content = readFileSync3(fullPath, "utf-8");
3832
3939
  res.json({ content, size: stat.size });
3833
3940
  } catch {
3834
3941
  res.json({ error: "Cannot read file" });
@@ -4083,17 +4190,17 @@ function resolveProjectMcpPath() {
4083
4190
  const cwd = process.cwd();
4084
4191
  const gitRoot = getGitRoot(cwd);
4085
4192
  const projectRoot = gitRoot ?? cwd;
4086
- const configPath = join3(projectRoot, MCP_PROJECT_CONFIG_NAME);
4087
- return existsSync4(configPath) ? configPath : null;
4193
+ const configPath = join2(projectRoot, MCP_PROJECT_CONFIG_NAME);
4194
+ return existsSync3(configPath) ? configPath : null;
4088
4195
  }
4089
4196
  function loadProjectMcpConfig() {
4090
4197
  const cwd = process.cwd();
4091
4198
  const gitRoot = getGitRoot(cwd);
4092
4199
  const projectRoot = gitRoot ?? cwd;
4093
- const configPath = join3(projectRoot, MCP_PROJECT_CONFIG_NAME);
4094
- if (!existsSync4(configPath)) return null;
4200
+ const configPath = join2(projectRoot, MCP_PROJECT_CONFIG_NAME);
4201
+ if (!existsSync3(configPath)) return null;
4095
4202
  try {
4096
- const raw = JSON.parse(readFileSync4(configPath, "utf-8"));
4203
+ const raw = JSON.parse(readFileSync3(configPath, "utf-8"));
4097
4204
  return raw.mcpServers ?? raw;
4098
4205
  } catch {
4099
4206
  return null;
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ToolRegistry
4
- } from "./chunk-TCOC4AUI.js";
4
+ } from "./chunk-GX3HSGJX.js";
5
5
  import "./chunk-T2NL5ZIA.js";
6
6
  import "./chunk-BXP6YZ2P.js";
7
- import "./chunk-VW7Y27WW.js";
7
+ import "./chunk-VGFTM3XT.js";
8
8
  import {
9
9
  runTool
10
- } from "./chunk-77HDCGTN.js";
10
+ } from "./chunk-MWKE2TNS.js";
11
11
  import {
12
12
  getDangerLevel,
13
13
  schemaToJsonSchema
@@ -15,7 +15,8 @@ import {
15
15
  import "./chunk-TZQHYZKT.js";
16
16
  import {
17
17
  VERSION
18
- } from "./chunk-7YWS3JLU.js";
18
+ } from "./chunk-NTCB7CMT.js";
19
+ import "./chunk-HOSJZMQS.js";
19
20
  import "./chunk-4BKXL7SM.js";
20
21
  import "./chunk-TB4W4Y4T.js";
21
22
  import "./chunk-KHYD3WXE.js";