@epoch-agent/core 0.18.0 → 0.20.0

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.
package/dist/index.js CHANGED
@@ -650,7 +650,7 @@ var ToolRegistry = class {
650
650
  this.addTool(tool, source);
651
651
  }
652
652
  registerTools(tools, source = "builtin") {
653
- for (const t81 of tools) this.addTool(t81, source);
653
+ for (const t80 of tools) this.addTool(t80, source);
654
654
  }
655
655
  addTool(tool, source) {
656
656
  const existing = this.tools.get(tool.name);
@@ -699,14 +699,14 @@ var ToolRegistry = class {
699
699
  return isCodeModeDeferred(tool) ? ToolExposure.Deferred : declared;
700
700
  }
701
701
  exposed() {
702
- return this.list().filter((t81) => this.exposureOf(t81) === ToolExposure.Direct);
702
+ return this.list().filter((t80) => this.exposureOf(t80) === ToolExposure.Direct);
703
703
  }
704
704
  byExposure(exposure) {
705
- return this.list().filter((t81) => this.exposureOf(t81) === exposure);
705
+ return this.list().filter((t80) => this.exposureOf(t80) === exposure);
706
706
  }
707
707
  codeModeDeclared() {
708
708
  if (!this.codeMode) return [];
709
- return this.list().filter((t81) => isCodeModeDeferred(t81));
709
+ return this.list().filter((t80) => isCodeModeDeferred(t80));
710
710
  }
711
711
  sourceOf(name) {
712
712
  return this.tools.get(name)?.source;
@@ -755,7 +755,7 @@ var ToolRegistry = class {
755
755
  function staticToolProvider(tools) {
756
756
  return {
757
757
  list: () => tools,
758
- get: (name) => tools.find((t81) => t81.name === name)
758
+ get: (name) => tools.find((t80) => t80.name === name)
759
759
  };
760
760
  }
761
761
  var log = createLogger("tokenizer");
@@ -1388,7 +1388,7 @@ var DelegateManager = class {
1388
1388
  }
1389
1389
  normalizeTasks(args) {
1390
1390
  if (args.tasks && args.tasks.length > 0) {
1391
- return args.role ? args.tasks.map((t81) => ({ ...t81, role: t81.role ?? args.role })) : args.tasks;
1391
+ return args.role ? args.tasks.map((t80) => ({ ...t80, role: t80.role ?? args.role })) : args.tasks;
1392
1392
  }
1393
1393
  if (args.goal) return [{ goal: args.goal, ...args.role ? { role: args.role } : {} }];
1394
1394
  return [];
@@ -1398,7 +1398,7 @@ var DelegateManager = class {
1398
1398
  for (let i = 0; i < tasks.length; i += maxConcurrent) {
1399
1399
  const batch = tasks.slice(i, i + maxConcurrent);
1400
1400
  const batchResults = await Promise.allSettled(
1401
- batch.map((t81) => this.runSingleTask(t81, parentSessionId, signal))
1401
+ batch.map((t80) => this.runSingleTask(t80, parentSessionId, signal))
1402
1402
  );
1403
1403
  for (const r of batchResults) {
1404
1404
  if (r.status === "fulfilled") {
@@ -1556,21 +1556,21 @@ function createDelegateTool(runner, config, roles) {
1556
1556
  operation: "command",
1557
1557
  describeTarget: (args) => {
1558
1558
  const tasks = Array.isArray(args["tasks"]) ? args["tasks"] : [];
1559
- const goals = tasks.map((t81) => String(t81["goal"] ?? ""));
1559
+ const goals = tasks.map((t80) => String(t80["goal"] ?? ""));
1560
1560
  const single = typeof args["goal"] === "string" ? args["goal"] : "";
1561
1561
  return [...goals, single].filter(Boolean).join(" | ");
1562
1562
  },
1563
1563
  async execute(args, ctx) {
1564
1564
  const start = Date.now();
1565
1565
  const rawTasks = Array.isArray(args["tasks"]) ? args["tasks"] : void 0;
1566
- const tasks = rawTasks?.map((t81) => {
1567
- const o = t81;
1566
+ const tasks = rawTasks?.map((t80) => {
1567
+ const o = t80;
1568
1568
  return {
1569
1569
  goal: String(o["goal"] ?? "").trim(),
1570
1570
  context: String(o["context"] ?? ""),
1571
1571
  ...typeof o["role"] === "string" && o["role"].trim() ? { role: o["role"].trim() } : {}
1572
1572
  };
1573
- }).filter((t81) => t81.goal.length > 0);
1573
+ }).filter((t80) => t80.goal.length > 0);
1574
1574
  const goal = typeof args["goal"] === "string" ? args["goal"].trim() : "";
1575
1575
  const topRole = typeof args["role"] === "string" ? args["role"].trim() : "";
1576
1576
  if ((!tasks || tasks.length === 0) && !goal) {
@@ -1586,7 +1586,7 @@ function createDelegateTool(runner, config, roles) {
1586
1586
  };
1587
1587
  }
1588
1588
  const known = listRoles();
1589
- const requested = [...(tasks ?? []).map((t81) => t81.role), topRole].filter(
1589
+ const requested = [...(tasks ?? []).map((t80) => t80.role), topRole].filter(
1590
1590
  (r) => Boolean(r)
1591
1591
  );
1592
1592
  const unknown = [...new Set(requested)].filter((r) => !known.some((k) => k.name === r));
@@ -1760,8 +1760,8 @@ function createGoalTool(deps) {
1760
1760
  }
1761
1761
  var VALID_TARGETS = ["memory", "user"];
1762
1762
  var VALID_ACTIONS = ["add", "replace", "remove", "search", "read", "budget"];
1763
- function isValidTarget(t81) {
1764
- return typeof t81 === "string" && VALID_TARGETS.includes(t81);
1763
+ function isValidTarget(t80) {
1764
+ return typeof t80 === "string" && VALID_TARGETS.includes(t80);
1765
1765
  }
1766
1766
  function isValidAction(a) {
1767
1767
  return VALID_ACTIONS.includes(a);
@@ -4915,7 +4915,7 @@ function available(tools) {
4915
4915
  if (tools.length === 0) {
4916
4916
  return "No deferred tools in this session \u2014 every tool is already in your tool list.";
4917
4917
  }
4918
- const names = tools.map((t81) => t81.name).sort();
4918
+ const names = tools.map((t80) => t80.name).sort();
4919
4919
  const shown = names.slice(0, MAX_LISTED);
4920
4920
  const rest = names.length - shown.length;
4921
4921
  return `Searchable tools: ${shown.join(", ")}${rest > 0 ? ` (\u2026and ${rest} more)` : ""}.`;
@@ -5035,15 +5035,15 @@ var TrackerDB = class {
5035
5035
  );
5036
5036
  this.db.transaction(() => {
5037
5037
  this.db.prepare("DELETE FROM todos WHERE session_id = ?").run(sessionId);
5038
- for (const t81 of todos) {
5038
+ for (const t80 of todos) {
5039
5039
  insertStmt.run({
5040
- id: t81.id,
5040
+ id: t80.id,
5041
5041
  session_id: sessionId,
5042
- content: t81.content,
5043
- status: t81.status,
5044
- reason: t81.reason || null,
5045
- created_at: t81.created_at,
5046
- updated_at: t81.updated_at
5042
+ content: t80.content,
5043
+ status: t80.status,
5044
+ reason: t80.reason || null,
5045
+ created_at: t80.created_at,
5046
+ updated_at: t80.updated_at
5047
5047
  });
5048
5048
  }
5049
5049
  })();
@@ -5065,9 +5065,9 @@ function formatTodoList(todos, pendingCount) {
5065
5065
  cancelled: "\u5DF2\u53D6\u6D88"
5066
5066
  };
5067
5067
  lines.push(`[todo] \u72B6\u6001\u6C47\u603B: ${pendingCount} \u4E2A\u5F85\u5B8C\u6210`);
5068
- for (const t81 of todos) {
5069
- const label = statusLabel[t81.status] || t81.status;
5070
- lines.push(`- [${label}] ${t81.content}${t81.reason ? ` \u2014\u2014 ${t81.reason}` : ""}`);
5068
+ for (const t80 of todos) {
5069
+ const label = statusLabel[t80.status] || t80.status;
5070
+ lines.push(`- [${label}] ${t80.content}${t80.reason ? ` \u2014\u2014 ${t80.reason}` : ""}`);
5071
5071
  }
5072
5072
  return lines.join("\n");
5073
5073
  }
@@ -5141,14 +5141,14 @@ function createTodoTool(db) {
5141
5141
  };
5142
5142
  }
5143
5143
  const now = (/* @__PURE__ */ new Date()).toISOString();
5144
- const existing = new Map(db.listBySession(ctx.sessionId).map((t81) => [t81.id, t81]));
5145
- const todos = rawTodos.map((t81) => {
5146
- const prev = t81.id ? existing.get(t81.id) : void 0;
5147
- const status = isValidStatus(t81.status) ? t81.status : "pending";
5148
- const reason9 = normalizeReason(status, t81.reason);
5144
+ const existing = new Map(db.listBySession(ctx.sessionId).map((t80) => [t80.id, t80]));
5145
+ const todos = rawTodos.map((t80) => {
5146
+ const prev = t80.id ? existing.get(t80.id) : void 0;
5147
+ const status = isValidStatus(t80.status) ? t80.status : "pending";
5148
+ const reason9 = normalizeReason(status, t80.reason);
5149
5149
  return {
5150
- id: t81.id || generateShortId(),
5151
- content: String(t81.content || "").slice(0, 500),
5150
+ id: t80.id || generateShortId(),
5151
+ content: String(t80.content || "").slice(0, 500),
5152
5152
  status,
5153
5153
  ...reason9 ? { reason: reason9 } : {},
5154
5154
  created_at: prev?.created_at || now,
@@ -5156,7 +5156,7 @@ function createTodoTool(db) {
5156
5156
  };
5157
5157
  });
5158
5158
  db.replaceAll(ctx.sessionId, todos);
5159
- const pendingCount = todos.filter((t81) => isOpenTodo(t81.status)).length;
5159
+ const pendingCount = todos.filter((t80) => isOpenTodo(t80.status)).length;
5160
5160
  return {
5161
5161
  success: true,
5162
5162
  output: formatTodoList(todos, pendingCount),
@@ -5491,7 +5491,7 @@ function intersectNames(base, incoming) {
5491
5491
  if (!base) return incoming ? [...incoming] : void 0;
5492
5492
  if (!incoming) return [...base];
5493
5493
  const baseSet = new Set(base);
5494
- return incoming.filter((t81) => baseSet.has(t81));
5494
+ return incoming.filter((t80) => baseSet.has(t80));
5495
5495
  }
5496
5496
  function findRole(roles, name) {
5497
5497
  return roles.find((r) => r.name === name);
@@ -5635,7 +5635,7 @@ function roleScopedProvider(base, role, blocked) {
5635
5635
  return allow ? allow.has(name) : true;
5636
5636
  };
5637
5637
  return {
5638
- list: () => base.list().filter((t81) => permitted(t81.name)),
5638
+ list: () => base.list().filter((t80) => permitted(t80.name)),
5639
5639
  get: (name) => permitted(name) ? base.get(name) : void 0
5640
5640
  };
5641
5641
  }
@@ -5662,7 +5662,7 @@ function createRoleScope(base, sessionRole, blocked) {
5662
5662
  }
5663
5663
  function unknownRoleTools(base, role, blocked) {
5664
5664
  if (!role?.tools) return [];
5665
- const available2 = new Set(base.list().map((t81) => t81.name));
5665
+ const available2 = new Set(base.list().map((t80) => t80.name));
5666
5666
  const blockedSet = new Set(blocked);
5667
5667
  return role.tools.filter((name) => !available2.has(name) && !blockedSet.has(name));
5668
5668
  }
@@ -5998,7 +5998,7 @@ function createToolScope(base) {
5998
5998
  const permitted = (name) => allow ? allow.has(name) : true;
5999
5999
  return {
6000
6000
  provider: {
6001
- list: () => base.list().filter((t81) => permitted(t81.name)),
6001
+ list: () => base.list().filter((t80) => permitted(t80.name)),
6002
6002
  get: (name) => permitted(name) ? base.get(name) : void 0
6003
6003
  },
6004
6004
  enter: (next) => {
@@ -6168,7 +6168,7 @@ function scanHooks(dir, issues) {
6168
6168
  return [...parsed.entries].map(([type, list]) => ({
6169
6169
  type,
6170
6170
  count: list.reduce((sum, entry) => sum + entry.hooks.length, 0)
6171
- })).filter((t81) => t81.count > 0).sort((a, b) => a.type.localeCompare(b.type));
6171
+ })).filter((t80) => t80.count > 0).sort((a, b) => a.type.localeCompare(b.type));
6172
6172
  }
6173
6173
  function scanMcp(dir, plugin, issues) {
6174
6174
  const path = join(dir, PLUGIN_LAYOUT.mcp);
@@ -7386,11 +7386,12 @@ async function updatePlugin(name, opts) {
7386
7386
  }
7387
7387
  var MARKETPLACE_FILE = "epoch-marketplace.json";
7388
7388
  var NAME_PATTERN = PLUGIN_NAME_PATTERN;
7389
- var EntrySchema = z.object({
7389
+ var EntryCategoryInput = z.union([z.string().min(1), z.array(z.string().min(1))]).optional().catch(void 0);
7390
+ var EntryShape = z.object({
7390
7391
  name: z.string().regex(NAME_PATTERN),
7391
7392
  description: z.string().min(1).optional(),
7392
7393
  source: z.string().min(1),
7393
- category: z.string().min(1).optional(),
7394
+ category: EntryCategoryInput,
7394
7395
  keywords: z.array(z.string().min(1)).optional(),
7395
7396
  author: PluginAuthorSchema.optional(),
7396
7397
  downloads: z.number().int().nonnegative().optional(),
@@ -7404,6 +7405,10 @@ var EntrySchema = z.object({
7404
7405
  visibility: z.enum(PLUGIN_VISIBILITY).optional().catch(void 0),
7405
7406
  status: z.enum(["listed", "reviewing", "rejected", "offline"]).optional().catch(void 0)
7406
7407
  });
7408
+ var EntrySchema = EntryShape.transform(({ category, ...rest }) => {
7409
+ const picked = category === void 0 ? [] : typeof category === "string" ? [category] : category;
7410
+ return { ...rest, ...picked.length === 0 ? {} : { category: [...picked] } };
7411
+ });
7407
7412
  var MARKETPLACE_DISPLAY_FIELDS = [
7408
7413
  "status",
7409
7414
  "downloads",
@@ -7554,7 +7559,7 @@ function searchMarketplaces(keyword, statePath) {
7554
7559
  const haystack = [
7555
7560
  entry.name,
7556
7561
  entry.description ?? "",
7557
- entry.category ?? "",
7562
+ ...entry.category ?? [],
7558
7563
  entry.author?.name ?? "",
7559
7564
  ...entry.keywords ?? []
7560
7565
  ].join(" ").toLowerCase();
@@ -7926,14 +7931,14 @@ var CheckpointStore = class {
7926
7931
  }
7927
7932
  async enforceTotalSize(protectSession, protectTurnIndex) {
7928
7933
  const turns = await this.collectTurns();
7929
- let total = turns.reduce((sum, t81) => sum + t81.bytes, 0);
7934
+ let total = turns.reduce((sum, t80) => sum + t80.bytes, 0);
7930
7935
  if (total <= this.maxTotalBytes) return;
7931
7936
  turns.sort((a, b) => a.createdAt - b.createdAt);
7932
- for (const t81 of turns) {
7937
+ for (const t80 of turns) {
7933
7938
  if (total <= this.maxTotalBytes) return;
7934
- if (t81.sessionId === protectSession && t81.turnIndex === protectTurnIndex) continue;
7935
- await this.removeTurn(t81.sessionId, t81.turnIndex);
7936
- total -= t81.bytes;
7939
+ if (t80.sessionId === protectSession && t80.turnIndex === protectTurnIndex) continue;
7940
+ await this.removeTurn(t80.sessionId, t80.turnIndex);
7941
+ total -= t80.bytes;
7937
7942
  }
7938
7943
  }
7939
7944
  async collectTurns() {
@@ -10734,11 +10739,11 @@ var PromptBuilder = class {
10734
10739
  memoryInitialized = false;
10735
10740
  projectBlock(tools) {
10736
10741
  const permLevel = this.deps.permissionManager?.getLevel() || "default";
10737
- const hasPlanTool = tools.some((t81) => t81.name === "enter_plan_mode");
10738
- const hasLspTool = tools.some((t81) => t81.name === "lsp_diagnostics");
10739
- const hasAskTool = tools.some((t81) => t81.name === "ask_user_question");
10740
- const hasTodoTool = tools.some((t81) => t81.name === "todo");
10741
- const hasTerminal = tools.some((t81) => t81.name === "terminal");
10742
+ const hasPlanTool = tools.some((t80) => t80.name === "enter_plan_mode");
10743
+ const hasLspTool = tools.some((t80) => t80.name === "lsp_diagnostics");
10744
+ const hasAskTool = tools.some((t80) => t80.name === "ask_user_question");
10745
+ const hasTodoTool = tools.some((t80) => t80.name === "todo");
10746
+ const hasTerminal = tools.some((t80) => t80.name === "terminal");
10742
10747
  const needsCommandApproval = hasTerminal && permLevel === "default";
10743
10748
  const plan = this.deps.approvedPlan?.()?.trim();
10744
10749
  const codeSdk = renderSdkDeclaration(this.deps.codeModeTools?.() ?? []);
@@ -10775,7 +10780,7 @@ ${approvedPlanBlock(plan)}` : "");
10775
10780
  return systemPromptSegments({
10776
10781
  workDir: this.deps.site().workDir,
10777
10782
  os: platform(),
10778
- date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0] ?? "",
10783
+ date: localDateString(/* @__PURE__ */ new Date()),
10779
10784
  ...role ? { role } : {},
10780
10785
  ...skillIndex ? { skillIndex } : {},
10781
10786
  ...model ? { model } : {},
@@ -11428,8 +11433,8 @@ function imageParts(content) {
11428
11433
  return typeof content === "string" ? [] : content.filter((p) => p.type === "image");
11429
11434
  }
11430
11435
  function toolSignatureOf(config, tools) {
11431
- const direct = tools.map((t81) => t81.name).join("\0");
11432
- const declared = (config.codeModeTools?.() ?? []).map((t81) => t81.name).join("\0");
11436
+ const direct = tools.map((t80) => t80.name).join("\0");
11437
+ const declared = (config.codeModeTools?.() ?? []).map((t80) => t80.name).join("\0");
11433
11438
  return declared ? `${direct}|${declared}` : direct;
11434
11439
  }
11435
11440
  function modelSignatureOf(info) {
@@ -11676,6 +11681,7 @@ var AgentLoop = class {
11676
11681
  let toolSignature = toolSignatureOf(this.config, this.toolProvider.list());
11677
11682
  let modelSignature = modelSignatureOf(this.modelInfo());
11678
11683
  let planSignature = this.config.approvedPlan?.() ?? "";
11684
+ let dateSignature = localDateString(/* @__PURE__ */ new Date());
11679
11685
  const streaming = this.config.streaming ?? true;
11680
11686
  let totalInput = 0;
11681
11687
  let totalOutput = 0;
@@ -11779,10 +11785,12 @@ var AgentLoop = class {
11779
11785
  const signature = toolSignatureOf(this.config, tools);
11780
11786
  const planNow = this.config.approvedPlan?.() ?? "";
11781
11787
  const modelNow = modelSignatureOf(this.modelInfo());
11782
- if (signature !== toolSignature || planNow !== planSignature || modelNow !== modelSignature) {
11788
+ const dateNow = localDateString(/* @__PURE__ */ new Date());
11789
+ if (signature !== toolSignature || planNow !== planSignature || modelNow !== modelSignature || dateNow !== dateSignature) {
11783
11790
  toolSignature = signature;
11784
11791
  planSignature = planNow;
11785
11792
  modelSignature = modelNow;
11793
+ dateSignature = dateNow;
11786
11794
  system = await this.prompt.buildSystem(input.sessionId);
11787
11795
  }
11788
11796
  if (turn === 1) {
@@ -11826,6 +11834,9 @@ var AgentLoop = class {
11826
11834
  };
11827
11835
  }
11828
11836
  }
11837
+ for (const notice3 of this.config.memory?.takeNotices?.() ?? []) {
11838
+ yield { type: "notice", code: notice3.code, text: notice3.text };
11839
+ }
11829
11840
  }
11830
11841
  const turnStart = Date.now();
11831
11842
  const llmSpan = this.telemetry.startSpan(
@@ -15068,11 +15079,11 @@ function buildSearchQuery(query) {
15068
15079
  }
15069
15080
  const latinPart = cleaned.replace(CJK_RUN, " ");
15070
15081
  for (const w of latinPart.split(/\s+/)) {
15071
- const t81 = w.trim();
15072
- if (t81.length >= 2) terms.add(t81);
15082
+ const t80 = w.trim();
15083
+ if (t80.length >= 2) terms.add(t80);
15073
15084
  }
15074
15085
  if (terms.size === 0) return null;
15075
- return [...terms].map((t81) => `"${t81.replace(/"/g, "")}"`).join(" OR ");
15086
+ return [...terms].map((t80) => `"${t80.replace(/"/g, "")}"`).join(" OR ");
15076
15087
  }
15077
15088
  // src/memory/db.ts
15078
15089
  var NS2 = "memory";
@@ -15105,6 +15116,14 @@ var MIGRATIONS2 = [
15105
15116
  );
15106
15117
  `);
15107
15118
  }
15119
+ },
15120
+ {
15121
+ id: "memory-blocked-flag",
15122
+ version: 2,
15123
+ description: "\u6761\u76EE\u52A0 blocked \u6807\u8BB0\uFF1A\u547D\u4E2D\u6CE8\u5165\u6A21\u5F0F\u7684\u539F\u6587\u7559\u5728\u76D8\u4E0A\uFF0C\u53EA\u5728\u6E32\u67D3\u7ED9\u6A21\u578B\u65F6\u906E\u7F69",
15124
+ up: (db) => {
15125
+ db.exec("ALTER TABLE memory_entries ADD COLUMN blocked INTEGER NOT NULL DEFAULT 0");
15126
+ }
15108
15127
  }
15109
15128
  ];
15110
15129
  var MemoryDB = class {
@@ -15151,14 +15170,15 @@ var MemoryDB = class {
15151
15170
  insert(entry) {
15152
15171
  this.db.prepare(
15153
15172
  `
15154
- INSERT INTO memory_entries (id, target, content, position, char_length, access_count, last_accessed_at, created_at, updated_at)
15155
- VALUES (@id, @target, @content, @position, @charLength, @accessCount, @lastAccessedAt, @createdAt, @updatedAt)
15173
+ INSERT INTO memory_entries (id, target, content, position, char_length, access_count, last_accessed_at, created_at, updated_at, blocked)
15174
+ VALUES (@id, @target, @content, @position, @charLength, @accessCount, @lastAccessedAt, @createdAt, @updatedAt, @blocked)
15156
15175
  `
15157
15176
  ).run({
15158
15177
  ...entry,
15159
15178
  createdAt: entry.createdAt.getTime(),
15160
15179
  updatedAt: entry.updatedAt.getTime(),
15161
- lastAccessedAt: entry.lastAccessedAt.getTime()
15180
+ lastAccessedAt: entry.lastAccessedAt.getTime(),
15181
+ blocked: entry.blocked ? 1 : 0
15162
15182
  });
15163
15183
  if (entry.position >= this.nextPosition[entry.target]) {
15164
15184
  this.nextPosition[entry.target] = entry.position + 1;
@@ -15196,11 +15216,12 @@ var MemoryDB = class {
15196
15216
  const existing = this.db.prepare("SELECT rowid FROM memory_entries WHERE target = ? AND content = ?").get(target, oldContent);
15197
15217
  if (!existing) return;
15198
15218
  this.db.prepare(
15199
- "UPDATE memory_entries SET content = @content, char_length = @charLength, updated_at = @updatedAt WHERE rowid = @rowid"
15219
+ "UPDATE memory_entries SET content = @content, char_length = @charLength, updated_at = @updatedAt, blocked = @blocked WHERE rowid = @rowid"
15200
15220
  ).run({
15201
15221
  content: newEntry.content,
15202
15222
  charLength: newEntry.charLength,
15203
15223
  updatedAt: newEntry.updatedAt.getTime(),
15224
+ blocked: newEntry.blocked ? 1 : 0,
15204
15225
  rowid: existing.rowid
15205
15226
  });
15206
15227
  this.unsyncFts(existing.rowid);
@@ -15212,6 +15233,9 @@ var MemoryDB = class {
15212
15233
  this.db.prepare("DELETE FROM memory_entries WHERE rowid = ?").run(existing.rowid);
15213
15234
  this.unsyncFts(existing.rowid);
15214
15235
  }
15236
+ setBlocked(id, blocked) {
15237
+ this.db.prepare("UPDATE memory_entries SET blocked = @blocked WHERE id = @id").run({ id, blocked: blocked ? 1 : 0 });
15238
+ }
15215
15239
  updatePosition(id, position) {
15216
15240
  this.db.prepare("UPDATE memory_entries SET position = @position WHERE id = @id").run({
15217
15241
  id,
@@ -15280,7 +15304,8 @@ var MemoryDB = class {
15280
15304
  createdAt: new Date(row["created_at"]),
15281
15305
  updatedAt: new Date(row["updated_at"]),
15282
15306
  accessCount: row["access_count"],
15283
- lastAccessedAt: new Date(row["last_accessed_at"] ?? 0)
15307
+ lastAccessedAt: new Date(row["last_accessed_at"] ?? 0),
15308
+ blocked: Number(row["blocked"] ?? 0) !== 0
15284
15309
  };
15285
15310
  }
15286
15311
  };
@@ -15298,6 +15323,7 @@ var MEM_DIR = join(homedir(), ".epoch", "memories");
15298
15323
  var ENTRY_DELIMITER = "\n\xA7 ";
15299
15324
  var MAX_ENTRY_CHARS = 4e3;
15300
15325
  var MAX_CONSOLIDATION_FAILURES_PER_TURN = 3;
15326
+ var QUOTA_WARN_PCT = 90;
15301
15327
  var SHORT_ID_HEX = 6;
15302
15328
  var INDEX_SUMMARY_CHARS = 80;
15303
15329
  var INJECTION_PATTERNS = [
@@ -15309,6 +15335,9 @@ var INJECTION_PATTERNS = [
15309
15335
  { pattern: /^assistant:/im, label: "role prefix" },
15310
15336
  { pattern: /^user:/im, label: "role prefix" }
15311
15337
  ];
15338
+ function injectionLabelOf(content) {
15339
+ return INJECTION_PATTERNS.find((p) => p.pattern.test(content))?.label;
15340
+ }
15312
15341
  function contentKey(target, content) {
15313
15342
  return `${target}\0${content}`;
15314
15343
  }
@@ -15326,6 +15355,9 @@ var MemoryManager = class {
15326
15355
  initialized = false;
15327
15356
  consolidationFailures = 0;
15328
15357
  reviewIssues = [];
15358
+ notices = [];
15359
+ notifiedBlocked = false;
15360
+ notifiedQuota = false;
15329
15361
  constructor(dbPath, memDir, config) {
15330
15362
  const dp = dbPath ?? DB_PATH;
15331
15363
  const md = memDir ?? MEM_DIR;
@@ -15353,55 +15385,103 @@ var MemoryManager = class {
15353
15385
  await this.flushToDisk();
15354
15386
  }
15355
15387
  async reloadFromDisk() {
15388
+ let blockedCount = 0;
15356
15389
  for (const target of ["memory", "user"]) {
15357
15390
  const file = target === "memory" ? "MEMORY.md" : "USER.md";
15358
15391
  const fromDisk = this.readFile(join(this.memDir, file));
15359
15392
  const deduped = [...new Set(fromDisk)];
15360
- const safe = deduped.map((c) => this.scanInjection(c)).filter((c) => c.length > 0);
15361
- this.syncTarget(target, safe);
15393
+ const classified = deduped.map((c) => this.classifyEntry(c));
15394
+ blockedCount += classified.filter((e) => e.blocked).length;
15395
+ this.syncTarget(target, classified);
15362
15396
  }
15363
15397
  this.assignIds();
15364
15398
  this.snapshot = this.renderIndexBlock();
15399
+ if (blockedCount > 0 && !this.notifiedBlocked) {
15400
+ this.notifiedBlocked = true;
15401
+ this.notices.push({
15402
+ code: "memory-blocked",
15403
+ text: t("memory_review.blocked_notice", {
15404
+ count: blockedCount,
15405
+ path: join(this.memDir, "MEMORY.md")
15406
+ })
15407
+ });
15408
+ }
15409
+ this.checkQuota();
15410
+ }
15411
+ blockedPlaceholder(content) {
15412
+ return t("memory_review.blocked_placeholder", {
15413
+ pattern: injectionLabelOf(content) ?? "unknown"
15414
+ });
15415
+ }
15416
+ refuseInjection(label) {
15417
+ this.reviewIssues.push({
15418
+ type: "injection",
15419
+ severity: "high",
15420
+ message: t("memory_review.injection", { pattern: label }),
15421
+ suggestion: t("memory_review.injection_suggestion")
15422
+ });
15423
+ return {
15424
+ success: false,
15425
+ done: true,
15426
+ error: t("memory_review.write_blocked", { pattern: label })
15427
+ };
15428
+ }
15429
+ takeNotices() {
15430
+ const out = this.notices;
15431
+ this.notices = [];
15432
+ return out;
15433
+ }
15434
+ checkQuota() {
15435
+ if (this.notifiedQuota) return;
15436
+ const b = this.getBudget().memory;
15437
+ if (b.percent < QUOTA_WARN_PCT) return;
15438
+ this.notifiedQuota = true;
15439
+ this.notices.push({
15440
+ code: "memory-quota",
15441
+ text: t("memory_review.quota", { percent: b.percent, current: b.current, limit: b.limit })
15442
+ });
15365
15443
  }
15366
15444
  assignIds() {
15367
15445
  this.ids.clear();
15368
15446
  this.idByContent.clear();
15369
15447
  for (const target of ["memory", "user"]) {
15370
15448
  const prefix = target === "memory" ? "m" : "u";
15371
- for (const content of this.db.getAllContent(target)) {
15372
- const hash = createHash("sha256").update(content).digest("hex");
15449
+ for (const entry of this.db.getAll(target)) {
15450
+ const hash = createHash("sha256").update(entry.content).digest("hex");
15373
15451
  let id = `${prefix}:${hash.slice(0, SHORT_ID_HEX)}`;
15374
15452
  for (let len = SHORT_ID_HEX + 4; this.ids.has(id) && len <= hash.length; len += 4) {
15375
15453
  id = `${prefix}:${hash.slice(0, len)}`;
15376
15454
  }
15377
- this.ids.set(id, { target, content });
15378
- this.idByContent.set(contentKey(target, content), id);
15455
+ this.ids.set(id, { target, content: entry.content, blocked: entry.blocked });
15456
+ this.idByContent.set(contentKey(target, entry.content), id);
15379
15457
  }
15380
15458
  }
15381
15459
  }
15382
15460
  syncTarget(target, desired) {
15383
15461
  const existing = new Map(this.db.getAll(target).map((e) => [e.content, e]));
15384
- const desiredSet = new Set(desired);
15462
+ const desiredSet = new Set(desired.map((d) => d.content));
15385
15463
  for (const [content, entry] of existing) {
15386
15464
  if (!desiredSet.has(content)) this.db.removeById(entry.id);
15387
15465
  }
15388
- for (const [i, content] of desired.entries()) {
15389
- const prev = existing.get(content);
15466
+ for (const [i, item] of desired.entries()) {
15467
+ const prev = existing.get(item.content);
15390
15468
  if (prev) {
15391
15469
  if (prev.position !== i) this.db.updatePosition(prev.id, i);
15470
+ if (prev.blocked !== item.blocked) this.db.setBlocked(prev.id, item.blocked);
15392
15471
  continue;
15393
15472
  }
15394
15473
  const now = /* @__PURE__ */ new Date();
15395
15474
  this.db.insert({
15396
15475
  id: randomUUID(),
15397
15476
  target,
15398
- content,
15477
+ content: item.content,
15399
15478
  position: i,
15400
- charLength: content.length,
15479
+ charLength: item.content.length,
15401
15480
  accessCount: 0,
15402
15481
  lastAccessedAt: now,
15403
15482
  createdAt: now,
15404
- updatedAt: now
15483
+ updatedAt: now,
15484
+ blocked: item.blocked
15405
15485
  });
15406
15486
  }
15407
15487
  }
@@ -15420,7 +15500,7 @@ var MemoryManager = class {
15420
15500
  for (const r of results) this.db.markAccessed(r.id);
15421
15501
  return results.map((r) => ({
15422
15502
  id: this.idByContent.get(contentKey(r.target, r.content)) ?? r.id,
15423
- content: r.content,
15503
+ content: r.blocked ? this.blockedPlaceholder(r.content) : r.content,
15424
15504
  target: r.target,
15425
15505
  score: r.score
15426
15506
  }));
@@ -15438,22 +15518,27 @@ var MemoryManager = class {
15438
15518
  missing.push(id);
15439
15519
  continue;
15440
15520
  }
15441
- found.push({ id, target: hit.target, content: hit.content });
15521
+ found.push({
15522
+ id,
15523
+ target: hit.target,
15524
+ content: hit.blocked ? this.blockedPlaceholder(hit.content) : hit.content
15525
+ });
15442
15526
  const entry = this.db.findByContent(hit.target, hit.content);
15443
15527
  if (entry) this.db.markAccessed(entry.id);
15444
15528
  }
15445
15529
  return { found, missing };
15446
15530
  }
15447
15531
  getAllEntries() {
15448
- return [
15449
- ...this.db.getAllContent("memory").map((c) => ({ content: c })),
15450
- ...this.db.getAllContent("user").map((c) => ({ content: c }))
15451
- ];
15532
+ return ["memory", "user"].flatMap(
15533
+ (target) => this.db.getAll(target).map((e) => ({ content: e.content, target, blocked: e.blocked }))
15534
+ );
15452
15535
  }
15453
15536
  async semanticSearch(query, limit) {
15454
15537
  return this.search(query, limit);
15455
15538
  }
15456
15539
  async add(target, content) {
15540
+ const label = injectionLabelOf(content);
15541
+ if (label !== void 0) return this.refuseInjection(label);
15457
15542
  const budget = this.getBudget()[target];
15458
15543
  if (budget.current + content.length > budget.limit) {
15459
15544
  this.consolidationFailures++;
@@ -15487,7 +15572,8 @@ var MemoryManager = class {
15487
15572
  accessCount: 0,
15488
15573
  lastAccessedAt: /* @__PURE__ */ new Date(),
15489
15574
  createdAt: /* @__PURE__ */ new Date(),
15490
- updatedAt: /* @__PURE__ */ new Date()
15575
+ updatedAt: /* @__PURE__ */ new Date(),
15576
+ blocked: false
15491
15577
  };
15492
15578
  this.db.insert(entry);
15493
15579
  this.consolidationFailures = 0;
@@ -15499,6 +15585,8 @@ var MemoryManager = class {
15499
15585
  if (!existing) {
15500
15586
  return { success: false, done: true, error: this.notFoundHint(target, oldText) };
15501
15587
  }
15588
+ const label = injectionLabelOf(content);
15589
+ if (label !== void 0) return this.refuseInjection(label);
15502
15590
  const budget = this.getBudget()[target];
15503
15591
  const delta = content.length - existing.charLength;
15504
15592
  if (budget.current + delta > budget.limit) {
@@ -15516,7 +15604,8 @@ var MemoryManager = class {
15516
15604
  ...existing,
15517
15605
  content,
15518
15606
  charLength: content.length,
15519
- updatedAt: /* @__PURE__ */ new Date()
15607
+ updatedAt: /* @__PURE__ */ new Date(),
15608
+ blocked: false
15520
15609
  });
15521
15610
  this.consolidationFailures = 0;
15522
15611
  await this.flushToDisk();
@@ -15580,31 +15669,42 @@ var MemoryManager = class {
15580
15669
  if (current) entries2.push(current.join("\n").trim());
15581
15670
  return entries2.filter((e) => e.length > 0);
15582
15671
  }
15583
- scanInjection(content) {
15584
- for (const { pattern, label } of INJECTION_PATTERNS) {
15585
- if (pattern.test(content)) {
15586
- this.reviewIssues.push({
15587
- type: "injection",
15588
- severity: "high",
15589
- message: t("memory_review.injection", { pattern: label }),
15590
- suggestion: t("memory_review.injection_suggestion")
15591
- });
15592
- return "";
15593
- }
15672
+ classifyEntry(content) {
15673
+ const label = injectionLabelOf(content);
15674
+ if (label !== void 0) {
15675
+ this.reviewIssues.push({
15676
+ type: "injection",
15677
+ severity: "high",
15678
+ message: t("memory_review.injection", { pattern: label }),
15679
+ suggestion: t("memory_review.injection_suggestion")
15680
+ });
15681
+ return { content, blocked: true };
15594
15682
  }
15595
15683
  if (countCodePoints(content) > MAX_ENTRY_CHARS) {
15596
- return headCodePoints(content, MAX_ENTRY_CHARS) + " \u2026[\u6761\u76EE\u8FC7\u957F\u5DF2\u622A\u65AD]";
15684
+ return {
15685
+ content: headCodePoints(content, MAX_ENTRY_CHARS) + " \u2026[\u6761\u76EE\u8FC7\u957F\u5DF2\u622A\u65AD]",
15686
+ blocked: false
15687
+ };
15597
15688
  }
15598
- return content;
15689
+ return { content, blocked: false };
15599
15690
  }
15600
15691
  indexEntries(target) {
15601
15692
  const out = [];
15602
- for (const content of this.db.getAllContent(target)) {
15603
- const id = this.idByContent.get(contentKey(target, content));
15693
+ for (const entry of this.db.getAll(target)) {
15694
+ const id = this.idByContent.get(contentKey(target, entry.content));
15604
15695
  if (!id) continue;
15605
- const firstLine4 = (content.split("\n")[0] ?? "").trim();
15696
+ if (entry.blocked) {
15697
+ out.push({
15698
+ id,
15699
+ target,
15700
+ summary: this.blockedPlaceholder(entry.content),
15701
+ charLength: entry.content.length
15702
+ });
15703
+ continue;
15704
+ }
15705
+ const firstLine4 = (entry.content.split("\n")[0] ?? "").trim();
15606
15706
  const summary = countCodePoints(firstLine4) > INDEX_SUMMARY_CHARS ? `${headCodePoints(firstLine4, INDEX_SUMMARY_CHARS)}\u2026` : firstLine4;
15607
- out.push({ id, target, summary, charLength: content.length });
15707
+ out.push({ id, target, summary, charLength: entry.content.length });
15608
15708
  }
15609
15709
  return out;
15610
15710
  }
@@ -15696,148 +15796,6 @@ var MemoryManager = class {
15696
15796
  ].join("\n");
15697
15797
  }
15698
15798
  };
15699
- var DEFAULT_INTERVAL = 5 * 60 * 1e3;
15700
- var QUOTA_WARN_PCT = 90;
15701
- var MemoryReviewer = class {
15702
- manager;
15703
- timer = null;
15704
- issues = [];
15705
- interval;
15706
- constructor(manager, interval = DEFAULT_INTERVAL) {
15707
- this.manager = manager;
15708
- this.interval = interval;
15709
- }
15710
- start() {
15711
- if (this.timer) return;
15712
- this.scanForIssues().catch(() => {
15713
- });
15714
- this.timer = setInterval(() => {
15715
- this.scanForIssues().catch(() => {
15716
- });
15717
- }, this.interval);
15718
- }
15719
- stop() {
15720
- if (this.timer) {
15721
- clearInterval(this.timer);
15722
- this.timer = null;
15723
- }
15724
- }
15725
- getIssues() {
15726
- return [...this.issues];
15727
- }
15728
- clearIssues() {
15729
- this.issues = [];
15730
- }
15731
- async scanForIssues() {
15732
- const newIssues = [];
15733
- const dups = await this.checkDuplicates();
15734
- newIssues.push(...dups);
15735
- const contras = await this.checkContradictions();
15736
- newIssues.push(...contras);
15737
- const warns = this.checkQuota();
15738
- newIssues.push(...warns);
15739
- this.issues = newIssues;
15740
- return newIssues;
15741
- }
15742
- async checkDuplicates() {
15743
- const issues = [];
15744
- const allEntries = this.manager.getAllEntries();
15745
- const seen = /* @__PURE__ */ new Set();
15746
- for (let i = 0; i < allEntries.length; i++) {
15747
- if (seen.has(i)) continue;
15748
- const entryI = allEntries[i];
15749
- if (!entryI) continue;
15750
- for (let j = i + 1; j < allEntries.length; j++) {
15751
- if (seen.has(j)) continue;
15752
- const entryJ = allEntries[j];
15753
- if (!entryJ) continue;
15754
- const sim = textSimilarity(entryI.content, entryJ.content);
15755
- if (sim > 0.7) {
15756
- seen.add(i);
15757
- seen.add(j);
15758
- issues.push({
15759
- type: "duplicate",
15760
- severity: sim > 0.95 ? "high" : "medium",
15761
- message: t("memory_review.duplicate", { percent: (sim * 100).toFixed(0) }),
15762
- entries: [entryI.content, entryJ.content],
15763
- suggestion: t("memory_review.duplicate_suggestion")
15764
- });
15765
- }
15766
- }
15767
- }
15768
- return issues;
15769
- }
15770
- async checkContradictions() {
15771
- const issues = [];
15772
- const entries2 = this.manager.getAllEntries();
15773
- const contents = entries2.map((e) => e.content.toLowerCase());
15774
- const patterns = [
15775
- {
15776
- a: /pnpm|yarn|npm/,
15777
- b: /pnpm|yarn|npm/,
15778
- label: t("memory_review.label_package_manager")
15779
- },
15780
- {
15781
- a: /typescript|javascript|ts|js/,
15782
- b: /typescript|javascript|ts|js/,
15783
- label: t("memory_review.label_language")
15784
- },
15785
- {
15786
- a: /node\s*(18|20|22)/,
15787
- b: /node\s*(18|20|22)/,
15788
- label: t("memory_review.label_node_version")
15789
- }
15790
- ];
15791
- for (let i = 0; i < contents.length; i++) {
15792
- for (let j = i + 1; j < contents.length; j++) {
15793
- const contI = contents[i];
15794
- const contJ = contents[j];
15795
- if (!contI || !contJ) continue;
15796
- for (const { a, b, label } of patterns) {
15797
- const ma = a.exec(contI), mb = b.exec(contJ);
15798
- if (ma && mb && ma[0] !== mb[0]) {
15799
- issues.push({
15800
- type: "contradiction",
15801
- severity: "medium",
15802
- message: t("memory_review.contradiction", { label }),
15803
- entries: [entries2[i]?.content ?? "", entries2[j]?.content ?? ""],
15804
- suggestion: t("memory_review.contradiction_suggestion", { label })
15805
- });
15806
- }
15807
- }
15808
- }
15809
- }
15810
- return issues;
15811
- }
15812
- checkQuota() {
15813
- const budget = this.manager.getBudget();
15814
- const issues = [];
15815
- if (budget.memory.percent >= QUOTA_WARN_PCT) {
15816
- issues.push({
15817
- type: "quota_warning",
15818
- severity: budget.memory.percent >= 98 ? "high" : "medium",
15819
- message: t("memory_review.quota", {
15820
- percent: budget.memory.percent,
15821
- current: budget.memory.current,
15822
- limit: budget.memory.limit
15823
- }),
15824
- suggestion: t("memory_review.quota_suggestion")
15825
- });
15826
- }
15827
- return issues;
15828
- }
15829
- };
15830
- function textSimilarity(a, b) {
15831
- const wordsA = new Set(a.toLowerCase().split(/\s+/));
15832
- const wordsB = new Set(b.toLowerCase().split(/\s+/));
15833
- if (wordsA.size === 0 && wordsB.size === 0) return 1;
15834
- let intersection = 0;
15835
- for (const w of wordsA) {
15836
- if (wordsB.has(w)) intersection++;
15837
- }
15838
- const union = /* @__PURE__ */ new Set([...wordsA, ...wordsB]);
15839
- return intersection / union.size;
15840
- }
15841
15799
  var ATTACHMENT_RETENTION_DEFAULTS = {
15842
15800
  maxAgeDays: 7,
15843
15801
  maxTotalBytes: ATTACHMENT_MAX_TOTAL_BYTES
@@ -17144,7 +17102,7 @@ function checkFtsHealth(db) {
17144
17102
  messages,
17145
17103
  tables,
17146
17104
  missingTriggers,
17147
- healthy: missingTriggers.length === 0 && tables.every((t81) => t81.error === null && t81.drift === null && t81.rows === messages),
17105
+ healthy: missingTriggers.length === 0 && tables.every((t80) => t80.error === null && t80.drift === null && t80.rows === messages),
17148
17106
  indexBytes: indexBytesOf(db),
17149
17107
  space: spaceOf(db)
17150
17108
  };
@@ -18488,11 +18446,11 @@ var ApprovalCache = class {
18488
18446
  return !rel.startsWith("..") && !isAbsolute(rel);
18489
18447
  }
18490
18448
  normalizeTarget(target) {
18491
- const t81 = target.trim();
18492
- if (!t81) return "";
18493
- const unix = t81.replace(/\\/g, "/");
18449
+ const t80 = target.trim();
18450
+ if (!t80) return "";
18451
+ const unix = t80.replace(/\\/g, "/");
18494
18452
  if (unix.startsWith("/") || /^[A-Za-z]:\//.test(unix)) return resolve(unix);
18495
- return t81;
18453
+ return t80;
18496
18454
  }
18497
18455
  load() {
18498
18456
  try {
@@ -18595,9 +18553,9 @@ function createSharedPermissionState(cache) {
18595
18553
  };
18596
18554
  }
18597
18555
  function cacheKeyFor(raw) {
18598
- const t81 = raw.trim();
18599
- if (!t81) return null;
18600
- return t81.slice(0, 500);
18556
+ const t80 = raw.trim();
18557
+ if (!t80) return null;
18558
+ return t80.slice(0, 500);
18601
18559
  }
18602
18560
  // src/permission/manager.ts
18603
18561
  var log3 = createLogger("permission");
@@ -22044,7 +22002,8 @@ function loadOne(mdFile, catPath, category, dirName, spec, skills, issues) {
22044
22002
  }
22045
22003
  const parsed = parseSkillFrontmatter(raw, label);
22046
22004
  issues.push(...parsed.issues);
22047
- const base = parsed.fm.name ?? dirName;
22005
+ const declared = parsed.fm.name?.trim();
22006
+ const base = declared ? declared : dirName;
22048
22007
  const name = spec.prefix ? `${spec.prefix}:${base}` : base;
22049
22008
  const previous = skills.get(name);
22050
22009
  if (previous && previous.scope !== scope) {
@@ -22094,6 +22053,18 @@ function normalizeDirs(input) {
22094
22053
  }
22095
22054
  return dirs.length > 0 ? dirs : [{ dir: SKILLS_DIR, source: "user" }];
22096
22055
  }
22056
+ function realpathOfDeepestExisting(target) {
22057
+ const tail = [];
22058
+ let cur = target;
22059
+ while (!existsSync(cur)) {
22060
+ const parent = dirname(cur);
22061
+ if (parent === cur) return target;
22062
+ tail.unshift(basename(cur));
22063
+ cur = parent;
22064
+ }
22065
+ const real = realpathSync(cur);
22066
+ return tail.length === 0 ? real : join(real, ...tail);
22067
+ }
22097
22068
  var SkillSystem = class {
22098
22069
  dirs;
22099
22070
  skillsDir;
@@ -22186,12 +22157,16 @@ var SkillSystem = class {
22186
22157
  }
22187
22158
  async create(name, content, category = "general") {
22188
22159
  this.scanIfNeeded();
22189
- if (this.skills.has(name)) throw new Error(`Skill "${name}" already exists`);
22190
- const dir = join(this.skillsDir, category, name);
22160
+ const declared = parseSkillFrontmatter(content, name).fm.name?.trim();
22161
+ const indexName = declared ? declared : name;
22162
+ if (this.skills.has(indexName)) throw new Error(`Skill "${indexName}" already exists`);
22163
+ const dir = join(this.skillsDir, category, indexName);
22191
22164
  const base = resolve(this.skillsDir);
22192
22165
  const resolved = resolve(dir);
22193
22166
  if (resolved === base || !resolved.startsWith(base + sep)) {
22194
- throw new Error(`Skill name or category escapes the skills directory: ${category}/${name}`);
22167
+ throw new Error(
22168
+ `Skill name or category escapes the skills directory: ${category}/${indexName}`
22169
+ );
22195
22170
  }
22196
22171
  mkdirSync(dir, { recursive: true });
22197
22172
  writeFileSync(join(dir, "SKILL.md"), content, "utf-8");
@@ -22205,7 +22180,7 @@ var SkillSystem = class {
22205
22180
  }
22206
22181
  async patch(name, oldString, newString, filePath) {
22207
22182
  const skill = this.requireWritable(name);
22208
- const targetPath = filePath ? join(skill.path, filePath) : join(skill.path, "SKILL.md");
22183
+ const targetPath = this.resolveInsideSkill(skill.path, filePath ?? "SKILL.md");
22209
22184
  const raw = readFileSync(targetPath, "utf-8");
22210
22185
  if (!raw.includes(oldString)) throw new Error("old_string not found");
22211
22186
  writeFileSync(targetPath, raw.replace(oldString, newString), "utf-8");
@@ -22225,21 +22200,30 @@ var SkillSystem = class {
22225
22200
  `filePath must be under references/templates/scripts/assets, got: ${filePath}`
22226
22201
  );
22227
22202
  }
22228
- const resolved = resolve(join(skill.path, filePath));
22229
- const baseDir = resolve(skill.path);
22230
- if (resolved !== baseDir && !resolved.startsWith(baseDir + sep)) {
22231
- throw new Error("Path traversal denied");
22232
- }
22203
+ const resolved = this.resolveInsideSkill(skill.path, filePath);
22233
22204
  mkdirSync(dirname(resolved), { recursive: true });
22234
22205
  writeFileSync(resolved, content, "utf-8");
22235
22206
  this.invalidateCache();
22236
22207
  }
22237
22208
  async removeFile(name, filePath) {
22238
22209
  const skill = this.requireWritable(name);
22239
- const resolved = join(skill.path, filePath);
22210
+ const resolved = this.resolveInsideSkill(skill.path, filePath);
22240
22211
  rmSync(resolved);
22241
22212
  this.invalidateCache();
22242
22213
  }
22214
+ resolveInsideSkill(skillPath, relPath) {
22215
+ const base = resolve(skillPath);
22216
+ const resolved = resolve(join(skillPath, relPath));
22217
+ if (resolved !== base && !resolved.startsWith(base + sep)) {
22218
+ throw new Error("Path traversal denied");
22219
+ }
22220
+ const realBase = realpathSync(base);
22221
+ const realTarget = realpathOfDeepestExisting(resolved);
22222
+ if (realTarget !== realBase && !realTarget.startsWith(realBase + sep)) {
22223
+ throw new Error("Path traversal denied");
22224
+ }
22225
+ return resolved;
22226
+ }
22243
22227
  requireWritable(name) {
22244
22228
  this.scanIfNeeded();
22245
22229
  const skill = this.skills.get(name);
@@ -22648,12 +22632,21 @@ var SkillLearner = class {
22648
22632
  if (!parsed.name || !parsed.content) {
22649
22633
  return { created: false, reason: "missing-name-or-content" };
22650
22634
  }
22651
- if (!parsed.content.includes("---")) {
22635
+ const { fm } = parseSkillFrontmatter(parsed.content, parsed.name);
22636
+ const declared = fm.name?.trim();
22637
+ if (!declared) {
22652
22638
  return { created: false, reason: "missing-frontmatter" };
22653
22639
  }
22640
+ if ((fm.description ?? "").trim() === "") {
22641
+ return { created: false, reason: "missing-description" };
22642
+ }
22643
+ const category = parsed.category?.trim() || "general";
22644
+ if (skillSegmentIssue(declared) !== void 0 || skillSegmentIssue(category) !== void 0) {
22645
+ return { created: false, reason: "invalid-name" };
22646
+ }
22654
22647
  try {
22655
- await this.skillSystem.create(parsed.name, parsed.content, parsed.category || "general");
22656
- return { created: true, skillName: parsed.name };
22648
+ await this.skillSystem.create(declared, parsed.content, category);
22649
+ return { created: true, skillName: declared };
22657
22650
  } catch (err) {
22658
22651
  return { created: false, reason: "create-failed", error: messageOf2(err) };
22659
22652
  }
@@ -23804,4 +23797,4 @@ function trunc(stdout, stderr) {
23804
23797
  * `--tree` 的命令帮助里,让用户自己知道 `--tree` 意味着什么。
23805
23798
  */
23806
23799
 
23807
- export { AGENT_ROLE_DIAG_MODULE, ARTIFACT_MAX_TOTAL_BYTES, ARTIFACT_RETENTION_DEFAULTS, ATTACHMENT_MAX_TOTAL_BYTES, ATTACHMENT_RETENTION_DEFAULTS, AgentLoop, ApprovalCache, BLOCKED_TOOLS, BudgetGuard, BudgetStore, CHECKPOINT_MAX_TOTAL_BYTES, CLAUDE_EVENT_NAMES, CLAUDE_TOOL_NAMES, CODE_MODE_LIMITS, CODE_MODE_SDK_TAG, CONFIG_KEYS, CURRENT_CONFIG_VERSION, CheckpointManager, CheckpointStore, CodeSandbox, ComplianceEngine, ContextCompressor, DEFAULT_COMPLIANCE_ACTIONS, DEFAULT_COMPLIANCE_SETTINGS, DEFAULT_COMPRESSION_THRESHOLD, DEFAULT_GOAL_MAX_ROUNDS, DEFAULT_MAX_HOLD_CHARS, DelegateManager, EMPTY_RULES, EPOCH_DISK_LEDGER, EpochConfigSchema, FTS_TABLES, FTS_TRIGGERS, GOAL_BLOCK_CODES, GoalService, GoalStore, HEADLESS_AUDIT_CODES, HookManager, INSTRUCTION_BUDGET_CHARS, INSTRUCTION_FILES, ImageLoadError, ImportTrustStore, LAUNCHD_LABEL_PREFIX, LOCK_HEARTBEAT_MS, LOCK_STALE_MS, LastOpenSessions, MARKETPLACE_DISPLAY_FIELDS, MARKETPLACE_FILE, MAX_AUDIT_ENTRIES, MAX_COMMAND_DEPTH, MAX_GOAL_MAX_ROUNDS, MAX_IMPORT_DEPTH, MAX_LAST_OPEN_SESSIONS, MAX_OBJECTIVE_CHARS, MAX_REASON_CHARS, MAX_RECENT_WORKSPACES, MODEL_GIVE_UP_THRESHOLD, MULTIMODAL_DEFAULTS, ManagedPolicyError, ManagedSettingsSchema, MemoryManager, MemoryReviewer, ModelFailureTracker, NOMINAL_TOTAL_BYTES, NO_REVEALED_TOOLS, PACK_EXCLUDES, PACK_MAX_BYTES, PACK_MAX_FILES, PERMISSION_AUDIT_CODES, PLUGIN_LAYOUT, PLUGIN_MANIFEST_FILE, PLUGIN_MANIFEST_KEYS, PLUGIN_NAME_PATTERN, PLUGIN_ROOT_ENV, PLUGIN_ROOT_TOKEN, PLUGIN_VISIBILITY, POLICY_DECISIONS, PROBE_VERSION, PROJECT_AGENTS_SUBDIR, PROVIDER_NO_CREDENTIAL, PermissionManager, PlanModeState, PluginAuthorSchema, PluginStore, PolicyRuleSchema, ProviderError, ProviderRouter, RECORDING_KEEP_RUNS, RECORDING_MAX_BYTES, ROLE_FRONTMATTER_SCHEMA, RUN_CODE_TOOL, RecentWorkspaces, SANDBOX_COVERS, SANDBOX_EXCLUDES, SCALAR_KEYS, SCHEDULE_DEFAULTS, SCHEDULE_DEFAULT_ALLOWLIST, SCHEDULE_FORM_BUDGET_USD, SCHEDULE_ISSUE_CODES, SCHTASKS_FOLDER, SESSION_MESSAGES_READ_TOOL, SESSION_MESSAGES_SEARCH_TOOL, SESSION_QUERY_TOOLS, SESSION_SEARCH_TOOL, SESSION_TRACE_TOOL, SETTINGS_LAYER_ORDER, SETTING_WRITE_KEYS, SETTING_WRITE_LAYERS, SKILL_CREATE_TOOL, SKILL_EDIT_TOOL, SKILL_PATCH_TOOL, SKILL_VIEW_TOOL, SKILL_WRITE_FILE_TOOL, SLASH_COMMANDS_DIAG_MODULE, ScheduleRecorder, ScheduleRegistrar, ScheduleStore, SessionManager, SessionQueries, SessionReferences, SettingsFileError, SettingsFileSchema, SkillLearner, SkillSystem, TOOL_SEARCH_TOOL, TaskNoticeTracker, ToolRegistry, TrackerDB, TrustManager, Workspace, acquireScheduleLock, addMarketplace, addUsage, admitArtifact, admitArtifacts, affectedPaths, artifactLaunchArgv, artifactOpenRefusal, assertLevelAllowed, blockedRoleTools, buildContextBreakdown, buildFireArgv, buildFtsMatchQuery, buildSchemaExports, buildSystemPrompt, buildTrigramMatchQuery, builtinRoles, checkEpochVersion, checkFtsHealth, checkShadowing, classifyPersistFailure, classifyProviderError, clearModelCache, coalesceRecording, codeModeWorkerPath, commandNameFromRelPath, compileRuleLists, compressionOptions, computeCost, computeCostMicros, computeExecHash, countRules, createAllowAllGate, createAllowAllImportGate, createAskQuestionTool, createCodeExecTool, createDelegateTool, createDenyAllGate, createDenyAllImportGate, createGoalTool, createImportGate, createMemoryTool, createPlanModeTools, createRoleFile, createRoleScope, createRunCodeTool, createScheduleDraftTool, createSessionMessagesReadTool, createSessionMessagesSearchTool, createSessionSearchTool, createSessionSearchTools, createSessionTraceTool, createSkillViewTool, createSkillWriteTools, createTodoTool, createToolRevealState, createToolScope, createToolSearchTool, createTrustGate, currentEpochVersion, decideImport, decideTrust, defaultConfig, defaultModelOf, describeArtifactRefusal, describeBackgroundTask, describeHeadlessPolicy, describeIsolation, describeJsonError, diagnoseSchedules, discoverMarkdown, discoverModels, discoverProject, displayAllows, displayCurrency, draftCommandLine, draftWidening, escapeRuleContent, estimateImageTokens, estimateRunTokens, estimateTokens, evaluateSelection, expandCommand, expandImports, explainSetting, extractArchive, findInstructionFiles, findProjectInstructions, findRole, formatBytes, formatSkillIndex, formatUsd, gateForTool, getCapability, getConfigIssues, getConfigOrigins, getImportGate, getTrustGate, hasDedicatedExecContent, hasExecTierContent, hasPromptCaching, hasProviderCredentials, hasReadTierContent, imageFromBase64, imageFromPath, imagePartTokens, importParts, importSkills, installPlugin, instructionDirs, instructionNotices, intervalSlots, inventoryProjectExtensions, inventoryTotal, isBlockCode, isCodeModeDeferred, isEmptyRules, isGoalPhase, isNonInteractive, isReadOnlySubCall, isSettingWriteLayer, isTransientPersistFailure, isValidPluginName, isWithinWindow, isWritableSettingKey, labelSlug, layerRank, listRunningSchedules, listSessionCandidates, listWorkspaceFiles, loadCommandDefinitions, loadCommandFile, loadCommands, loadConfig, loadHookSources, loadKeybindings, loadLexiconDir, loadLexiconFiles, loadPlugins, loadPolicyFiles, loadPolicyRules, loadRoleDefinitions, loadRoleDir, loadedImports, localDateString, managedSwitchNotes, mapClaudeToolName, mapEpochToolName, mapOperationType, matchPreauthorization, matchRuleLists, matchesHook, mentionsPluginRoot, mergeHeadlessPolicy, mergeRoles, mergeSchedulePatch, microsToUsd, modelListCandidates, needsAllowlist, nextFallbackModel, nextRunAt, noConfigOrigins, noManagedPolicy, noPlugins, openArtifact, originOf, packFileName, packPlugin, parseClaudeSettingsHooks, parseClock, parseDate, parseFrontmatter, parseHooksConfig, parseHooksConfigVerbose, parseImportLine, parseModelList, parseRoleDefinition, parseScheduleDraft, parseSkillFrontmatter, parseTrigger, pendingImageTokens, persistFailureCode, persistParts, persistToolResults, persistedUsage, planPack, previewSkillImport, pricingFor, projectEntryForDisplay, pruneArtifacts, pruneAttachments, pruneRecordings, pruneSchedules, readImageSize, readMarketplaces, readPluginManifest, readPluginRecords, readProviderKeyEnv, readRecording, readSessionSurface, reclaimFtsSpace, redactLine, refreshOpenRouterMetadata, removeMarketplace, removeRecordings, removeRoleFile, removeSessionArtifacts, removeSessionAttachments, renderImportTree, renderProjectContext, renderSdkDeclaration, renderSelfIndex, repairSchedules, resolveArtifactRetention, resolveCodeModeWorker, resolveComplianceDirs, resolveEntrySource, resolveInWorkspace, resolveMarketplaceRef, resolveMentions, resolveMultimodalPolicy, resolvePolicyDirs, resolvePricingAt, resolveProjectRoot, resolveScheduleBackend, resolveSessionReference, resolveSessionVisibility, resolveSettings, resolveSkillDirs, resolveSource, resolveTar, resolveWorkspace, restoreParts, revealScopedProvider, roleScopedProvider, roleSourceLabel, ruleCovers, ruleFromString, ruleToString, runCode, runCommand2 as runCommand, sanitizeArgNames, sanitizeCommand, sanitizeFtsQuery, sanitizePath, sanitizeToolOutput, scanInstructions, scanPluginDir, scanSkillDirs, searchMarketplaces, setDisplayCurrency, setImportGate, setPluginEnabled, setTrustGate, settingValueChoices, settingValueKind, settingWriteApply, settingWriteLayers, settingWriteTargets, skillIndexDescription, skillIndexResidency, skillIndexStats, sniffMediaType, splitFrontmatter, staticToolProvider, stripArtifactData, substitutePluginRoot, suggestRuleFromApproval, systemOpenArgv, systemPromptSegments, toPosix, toolDefinitionTokens, toolMatches, toolTableTokens, totalTokens, traceSession, triggerPeriodMs, unescapeRuleContent, uninstallPlugin, unknownRoleSkills, unknownRoleTools, unloadedInstructionFiles, updateMarketplace, updatePlugin, usdToMicros, validateSchedule, validateTrigger, validateWindow, withGoalCompression, withGoalPrompt, workspaceDisplayPath, writeSettingValue };
23800
+ export { AGENT_ROLE_DIAG_MODULE, ARTIFACT_MAX_TOTAL_BYTES, ARTIFACT_RETENTION_DEFAULTS, ATTACHMENT_MAX_TOTAL_BYTES, ATTACHMENT_RETENTION_DEFAULTS, AgentLoop, ApprovalCache, BLOCKED_TOOLS, BudgetGuard, BudgetStore, CHECKPOINT_MAX_TOTAL_BYTES, CLAUDE_EVENT_NAMES, CLAUDE_TOOL_NAMES, CODE_MODE_LIMITS, CODE_MODE_SDK_TAG, CONFIG_KEYS, CURRENT_CONFIG_VERSION, CheckpointManager, CheckpointStore, CodeSandbox, ComplianceEngine, ContextCompressor, DEFAULT_COMPLIANCE_ACTIONS, DEFAULT_COMPLIANCE_SETTINGS, DEFAULT_COMPRESSION_THRESHOLD, DEFAULT_GOAL_MAX_ROUNDS, DEFAULT_MAX_HOLD_CHARS, DelegateManager, EMPTY_RULES, EPOCH_DISK_LEDGER, EpochConfigSchema, FTS_TABLES, FTS_TRIGGERS, GOAL_BLOCK_CODES, GoalService, GoalStore, HEADLESS_AUDIT_CODES, HookManager, INSTRUCTION_BUDGET_CHARS, INSTRUCTION_FILES, ImageLoadError, ImportTrustStore, LAUNCHD_LABEL_PREFIX, LOCK_HEARTBEAT_MS, LOCK_STALE_MS, LastOpenSessions, MARKETPLACE_DISPLAY_FIELDS, MARKETPLACE_FILE, MAX_AUDIT_ENTRIES, MAX_COMMAND_DEPTH, MAX_GOAL_MAX_ROUNDS, MAX_IMPORT_DEPTH, MAX_LAST_OPEN_SESSIONS, MAX_OBJECTIVE_CHARS, MAX_REASON_CHARS, MAX_RECENT_WORKSPACES, MODEL_GIVE_UP_THRESHOLD, MULTIMODAL_DEFAULTS, ManagedPolicyError, ManagedSettingsSchema, MemoryManager, ModelFailureTracker, NOMINAL_TOTAL_BYTES, NO_REVEALED_TOOLS, PACK_EXCLUDES, PACK_MAX_BYTES, PACK_MAX_FILES, PERMISSION_AUDIT_CODES, PLUGIN_LAYOUT, PLUGIN_MANIFEST_FILE, PLUGIN_MANIFEST_KEYS, PLUGIN_NAME_PATTERN, PLUGIN_ROOT_ENV, PLUGIN_ROOT_TOKEN, PLUGIN_VISIBILITY, POLICY_DECISIONS, PROBE_VERSION, PROJECT_AGENTS_SUBDIR, PROVIDER_NO_CREDENTIAL, PermissionManager, PlanModeState, PluginAuthorSchema, PluginStore, PolicyRuleSchema, ProviderError, ProviderRouter, RECORDING_KEEP_RUNS, RECORDING_MAX_BYTES, ROLE_FRONTMATTER_SCHEMA, RUN_CODE_TOOL, RecentWorkspaces, SANDBOX_COVERS, SANDBOX_EXCLUDES, SCALAR_KEYS, SCHEDULE_DEFAULTS, SCHEDULE_DEFAULT_ALLOWLIST, SCHEDULE_FORM_BUDGET_USD, SCHEDULE_ISSUE_CODES, SCHTASKS_FOLDER, SESSION_MESSAGES_READ_TOOL, SESSION_MESSAGES_SEARCH_TOOL, SESSION_QUERY_TOOLS, SESSION_SEARCH_TOOL, SESSION_TRACE_TOOL, SETTINGS_LAYER_ORDER, SETTING_WRITE_KEYS, SETTING_WRITE_LAYERS, SKILL_CREATE_TOOL, SKILL_EDIT_TOOL, SKILL_PATCH_TOOL, SKILL_VIEW_TOOL, SKILL_WRITE_FILE_TOOL, SLASH_COMMANDS_DIAG_MODULE, ScheduleRecorder, ScheduleRegistrar, ScheduleStore, SessionManager, SessionQueries, SessionReferences, SettingsFileError, SettingsFileSchema, SkillLearner, SkillSystem, TOOL_SEARCH_TOOL, TaskNoticeTracker, ToolRegistry, TrackerDB, TrustManager, Workspace, acquireScheduleLock, addMarketplace, addUsage, admitArtifact, admitArtifacts, affectedPaths, artifactLaunchArgv, artifactOpenRefusal, assertLevelAllowed, blockedRoleTools, buildContextBreakdown, buildFireArgv, buildFtsMatchQuery, buildSchemaExports, buildSystemPrompt, buildTrigramMatchQuery, builtinRoles, checkEpochVersion, checkFtsHealth, checkShadowing, classifyPersistFailure, classifyProviderError, clearModelCache, coalesceRecording, codeModeWorkerPath, commandNameFromRelPath, compileRuleLists, compressionOptions, computeCost, computeCostMicros, computeExecHash, countRules, createAllowAllGate, createAllowAllImportGate, createAskQuestionTool, createCodeExecTool, createDelegateTool, createDenyAllGate, createDenyAllImportGate, createGoalTool, createImportGate, createMemoryTool, createPlanModeTools, createRoleFile, createRoleScope, createRunCodeTool, createScheduleDraftTool, createSessionMessagesReadTool, createSessionMessagesSearchTool, createSessionSearchTool, createSessionSearchTools, createSessionTraceTool, createSkillViewTool, createSkillWriteTools, createTodoTool, createToolRevealState, createToolScope, createToolSearchTool, createTrustGate, currentEpochVersion, decideImport, decideTrust, defaultConfig, defaultModelOf, describeArtifactRefusal, describeBackgroundTask, describeHeadlessPolicy, describeIsolation, describeJsonError, diagnoseSchedules, discoverMarkdown, discoverModels, discoverProject, displayAllows, displayCurrency, draftCommandLine, draftWidening, escapeRuleContent, estimateImageTokens, estimateRunTokens, estimateTokens, evaluateSelection, expandCommand, expandImports, explainSetting, extractArchive, findInstructionFiles, findProjectInstructions, findRole, formatBytes, formatSkillIndex, formatUsd, gateForTool, getCapability, getConfigIssues, getConfigOrigins, getImportGate, getTrustGate, hasDedicatedExecContent, hasExecTierContent, hasPromptCaching, hasProviderCredentials, hasReadTierContent, imageFromBase64, imageFromPath, imagePartTokens, importParts, importSkills, installPlugin, instructionDirs, instructionNotices, intervalSlots, inventoryProjectExtensions, inventoryTotal, isBlockCode, isCodeModeDeferred, isEmptyRules, isGoalPhase, isNonInteractive, isReadOnlySubCall, isSettingWriteLayer, isTransientPersistFailure, isValidPluginName, isWithinWindow, isWritableSettingKey, labelSlug, layerRank, listRunningSchedules, listSessionCandidates, listWorkspaceFiles, loadCommandDefinitions, loadCommandFile, loadCommands, loadConfig, loadHookSources, loadKeybindings, loadLexiconDir, loadLexiconFiles, loadPlugins, loadPolicyFiles, loadPolicyRules, loadRoleDefinitions, loadRoleDir, loadedImports, localDateString, managedSwitchNotes, mapClaudeToolName, mapEpochToolName, mapOperationType, matchPreauthorization, matchRuleLists, matchesHook, mentionsPluginRoot, mergeHeadlessPolicy, mergeRoles, mergeSchedulePatch, microsToUsd, modelListCandidates, needsAllowlist, nextFallbackModel, nextRunAt, noConfigOrigins, noManagedPolicy, noPlugins, openArtifact, originOf, packFileName, packPlugin, parseClaudeSettingsHooks, parseClock, parseDate, parseFrontmatter, parseHooksConfig, parseHooksConfigVerbose, parseImportLine, parseModelList, parseRoleDefinition, parseScheduleDraft, parseSkillFrontmatter, parseTrigger, pendingImageTokens, persistFailureCode, persistParts, persistToolResults, persistedUsage, planPack, previewSkillImport, pricingFor, projectEntryForDisplay, pruneArtifacts, pruneAttachments, pruneRecordings, pruneSchedules, readImageSize, readMarketplaces, readPluginManifest, readPluginRecords, readProviderKeyEnv, readRecording, readSessionSurface, reclaimFtsSpace, redactLine, refreshOpenRouterMetadata, removeMarketplace, removeRecordings, removeRoleFile, removeSessionArtifacts, removeSessionAttachments, renderImportTree, renderProjectContext, renderSdkDeclaration, renderSelfIndex, repairSchedules, resolveArtifactRetention, resolveCodeModeWorker, resolveComplianceDirs, resolveEntrySource, resolveInWorkspace, resolveMarketplaceRef, resolveMentions, resolveMultimodalPolicy, resolvePolicyDirs, resolvePricingAt, resolveProjectRoot, resolveScheduleBackend, resolveSessionReference, resolveSessionVisibility, resolveSettings, resolveSkillDirs, resolveSource, resolveTar, resolveWorkspace, restoreParts, revealScopedProvider, roleScopedProvider, roleSourceLabel, ruleCovers, ruleFromString, ruleToString, runCode, runCommand2 as runCommand, sanitizeArgNames, sanitizeCommand, sanitizeFtsQuery, sanitizePath, sanitizeToolOutput, scanInstructions, scanPluginDir, scanSkillDirs, searchMarketplaces, setDisplayCurrency, setImportGate, setPluginEnabled, setTrustGate, settingValueChoices, settingValueKind, settingWriteApply, settingWriteLayers, settingWriteTargets, skillIndexDescription, skillIndexResidency, skillIndexStats, sniffMediaType, splitFrontmatter, staticToolProvider, stripArtifactData, substitutePluginRoot, suggestRuleFromApproval, systemOpenArgv, systemPromptSegments, toPosix, toolDefinitionTokens, toolMatches, toolTableTokens, totalTokens, traceSession, triggerPeriodMs, unescapeRuleContent, uninstallPlugin, unknownRoleSkills, unknownRoleTools, unloadedInstructionFiles, updateMarketplace, updatePlugin, usdToMicros, validateSchedule, validateTrigger, validateWindow, withGoalCompression, withGoalPrompt, workspaceDisplayPath, writeSettingValue };