@epoch-agent/core 0.18.0 → 0.19.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);
@@ -7926,14 +7926,14 @@ var CheckpointStore = class {
7926
7926
  }
7927
7927
  async enforceTotalSize(protectSession, protectTurnIndex) {
7928
7928
  const turns = await this.collectTurns();
7929
- let total = turns.reduce((sum, t81) => sum + t81.bytes, 0);
7929
+ let total = turns.reduce((sum, t80) => sum + t80.bytes, 0);
7930
7930
  if (total <= this.maxTotalBytes) return;
7931
7931
  turns.sort((a, b) => a.createdAt - b.createdAt);
7932
- for (const t81 of turns) {
7932
+ for (const t80 of turns) {
7933
7933
  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;
7934
+ if (t80.sessionId === protectSession && t80.turnIndex === protectTurnIndex) continue;
7935
+ await this.removeTurn(t80.sessionId, t80.turnIndex);
7936
+ total -= t80.bytes;
7937
7937
  }
7938
7938
  }
7939
7939
  async collectTurns() {
@@ -10734,11 +10734,11 @@ var PromptBuilder = class {
10734
10734
  memoryInitialized = false;
10735
10735
  projectBlock(tools) {
10736
10736
  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");
10737
+ const hasPlanTool = tools.some((t80) => t80.name === "enter_plan_mode");
10738
+ const hasLspTool = tools.some((t80) => t80.name === "lsp_diagnostics");
10739
+ const hasAskTool = tools.some((t80) => t80.name === "ask_user_question");
10740
+ const hasTodoTool = tools.some((t80) => t80.name === "todo");
10741
+ const hasTerminal = tools.some((t80) => t80.name === "terminal");
10742
10742
  const needsCommandApproval = hasTerminal && permLevel === "default";
10743
10743
  const plan = this.deps.approvedPlan?.()?.trim();
10744
10744
  const codeSdk = renderSdkDeclaration(this.deps.codeModeTools?.() ?? []);
@@ -10775,7 +10775,7 @@ ${approvedPlanBlock(plan)}` : "");
10775
10775
  return systemPromptSegments({
10776
10776
  workDir: this.deps.site().workDir,
10777
10777
  os: platform(),
10778
- date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0] ?? "",
10778
+ date: localDateString(/* @__PURE__ */ new Date()),
10779
10779
  ...role ? { role } : {},
10780
10780
  ...skillIndex ? { skillIndex } : {},
10781
10781
  ...model ? { model } : {},
@@ -11428,8 +11428,8 @@ function imageParts(content) {
11428
11428
  return typeof content === "string" ? [] : content.filter((p) => p.type === "image");
11429
11429
  }
11430
11430
  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");
11431
+ const direct = tools.map((t80) => t80.name).join("\0");
11432
+ const declared = (config.codeModeTools?.() ?? []).map((t80) => t80.name).join("\0");
11433
11433
  return declared ? `${direct}|${declared}` : direct;
11434
11434
  }
11435
11435
  function modelSignatureOf(info) {
@@ -11676,6 +11676,7 @@ var AgentLoop = class {
11676
11676
  let toolSignature = toolSignatureOf(this.config, this.toolProvider.list());
11677
11677
  let modelSignature = modelSignatureOf(this.modelInfo());
11678
11678
  let planSignature = this.config.approvedPlan?.() ?? "";
11679
+ let dateSignature = localDateString(/* @__PURE__ */ new Date());
11679
11680
  const streaming = this.config.streaming ?? true;
11680
11681
  let totalInput = 0;
11681
11682
  let totalOutput = 0;
@@ -11779,10 +11780,12 @@ var AgentLoop = class {
11779
11780
  const signature = toolSignatureOf(this.config, tools);
11780
11781
  const planNow = this.config.approvedPlan?.() ?? "";
11781
11782
  const modelNow = modelSignatureOf(this.modelInfo());
11782
- if (signature !== toolSignature || planNow !== planSignature || modelNow !== modelSignature) {
11783
+ const dateNow = localDateString(/* @__PURE__ */ new Date());
11784
+ if (signature !== toolSignature || planNow !== planSignature || modelNow !== modelSignature || dateNow !== dateSignature) {
11783
11785
  toolSignature = signature;
11784
11786
  planSignature = planNow;
11785
11787
  modelSignature = modelNow;
11788
+ dateSignature = dateNow;
11786
11789
  system = await this.prompt.buildSystem(input.sessionId);
11787
11790
  }
11788
11791
  if (turn === 1) {
@@ -11826,6 +11829,9 @@ var AgentLoop = class {
11826
11829
  };
11827
11830
  }
11828
11831
  }
11832
+ for (const notice3 of this.config.memory?.takeNotices?.() ?? []) {
11833
+ yield { type: "notice", code: notice3.code, text: notice3.text };
11834
+ }
11829
11835
  }
11830
11836
  const turnStart = Date.now();
11831
11837
  const llmSpan = this.telemetry.startSpan(
@@ -15068,11 +15074,11 @@ function buildSearchQuery(query) {
15068
15074
  }
15069
15075
  const latinPart = cleaned.replace(CJK_RUN, " ");
15070
15076
  for (const w of latinPart.split(/\s+/)) {
15071
- const t81 = w.trim();
15072
- if (t81.length >= 2) terms.add(t81);
15077
+ const t80 = w.trim();
15078
+ if (t80.length >= 2) terms.add(t80);
15073
15079
  }
15074
15080
  if (terms.size === 0) return null;
15075
- return [...terms].map((t81) => `"${t81.replace(/"/g, "")}"`).join(" OR ");
15081
+ return [...terms].map((t80) => `"${t80.replace(/"/g, "")}"`).join(" OR ");
15076
15082
  }
15077
15083
  // src/memory/db.ts
15078
15084
  var NS2 = "memory";
@@ -15105,6 +15111,14 @@ var MIGRATIONS2 = [
15105
15111
  );
15106
15112
  `);
15107
15113
  }
15114
+ },
15115
+ {
15116
+ id: "memory-blocked-flag",
15117
+ version: 2,
15118
+ 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",
15119
+ up: (db) => {
15120
+ db.exec("ALTER TABLE memory_entries ADD COLUMN blocked INTEGER NOT NULL DEFAULT 0");
15121
+ }
15108
15122
  }
15109
15123
  ];
15110
15124
  var MemoryDB = class {
@@ -15151,14 +15165,15 @@ var MemoryDB = class {
15151
15165
  insert(entry) {
15152
15166
  this.db.prepare(
15153
15167
  `
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)
15168
+ INSERT INTO memory_entries (id, target, content, position, char_length, access_count, last_accessed_at, created_at, updated_at, blocked)
15169
+ VALUES (@id, @target, @content, @position, @charLength, @accessCount, @lastAccessedAt, @createdAt, @updatedAt, @blocked)
15156
15170
  `
15157
15171
  ).run({
15158
15172
  ...entry,
15159
15173
  createdAt: entry.createdAt.getTime(),
15160
15174
  updatedAt: entry.updatedAt.getTime(),
15161
- lastAccessedAt: entry.lastAccessedAt.getTime()
15175
+ lastAccessedAt: entry.lastAccessedAt.getTime(),
15176
+ blocked: entry.blocked ? 1 : 0
15162
15177
  });
15163
15178
  if (entry.position >= this.nextPosition[entry.target]) {
15164
15179
  this.nextPosition[entry.target] = entry.position + 1;
@@ -15196,11 +15211,12 @@ var MemoryDB = class {
15196
15211
  const existing = this.db.prepare("SELECT rowid FROM memory_entries WHERE target = ? AND content = ?").get(target, oldContent);
15197
15212
  if (!existing) return;
15198
15213
  this.db.prepare(
15199
- "UPDATE memory_entries SET content = @content, char_length = @charLength, updated_at = @updatedAt WHERE rowid = @rowid"
15214
+ "UPDATE memory_entries SET content = @content, char_length = @charLength, updated_at = @updatedAt, blocked = @blocked WHERE rowid = @rowid"
15200
15215
  ).run({
15201
15216
  content: newEntry.content,
15202
15217
  charLength: newEntry.charLength,
15203
15218
  updatedAt: newEntry.updatedAt.getTime(),
15219
+ blocked: newEntry.blocked ? 1 : 0,
15204
15220
  rowid: existing.rowid
15205
15221
  });
15206
15222
  this.unsyncFts(existing.rowid);
@@ -15212,6 +15228,9 @@ var MemoryDB = class {
15212
15228
  this.db.prepare("DELETE FROM memory_entries WHERE rowid = ?").run(existing.rowid);
15213
15229
  this.unsyncFts(existing.rowid);
15214
15230
  }
15231
+ setBlocked(id, blocked) {
15232
+ this.db.prepare("UPDATE memory_entries SET blocked = @blocked WHERE id = @id").run({ id, blocked: blocked ? 1 : 0 });
15233
+ }
15215
15234
  updatePosition(id, position) {
15216
15235
  this.db.prepare("UPDATE memory_entries SET position = @position WHERE id = @id").run({
15217
15236
  id,
@@ -15280,7 +15299,8 @@ var MemoryDB = class {
15280
15299
  createdAt: new Date(row["created_at"]),
15281
15300
  updatedAt: new Date(row["updated_at"]),
15282
15301
  accessCount: row["access_count"],
15283
- lastAccessedAt: new Date(row["last_accessed_at"] ?? 0)
15302
+ lastAccessedAt: new Date(row["last_accessed_at"] ?? 0),
15303
+ blocked: Number(row["blocked"] ?? 0) !== 0
15284
15304
  };
15285
15305
  }
15286
15306
  };
@@ -15298,6 +15318,7 @@ var MEM_DIR = join(homedir(), ".epoch", "memories");
15298
15318
  var ENTRY_DELIMITER = "\n\xA7 ";
15299
15319
  var MAX_ENTRY_CHARS = 4e3;
15300
15320
  var MAX_CONSOLIDATION_FAILURES_PER_TURN = 3;
15321
+ var QUOTA_WARN_PCT = 90;
15301
15322
  var SHORT_ID_HEX = 6;
15302
15323
  var INDEX_SUMMARY_CHARS = 80;
15303
15324
  var INJECTION_PATTERNS = [
@@ -15309,6 +15330,9 @@ var INJECTION_PATTERNS = [
15309
15330
  { pattern: /^assistant:/im, label: "role prefix" },
15310
15331
  { pattern: /^user:/im, label: "role prefix" }
15311
15332
  ];
15333
+ function injectionLabelOf(content) {
15334
+ return INJECTION_PATTERNS.find((p) => p.pattern.test(content))?.label;
15335
+ }
15312
15336
  function contentKey(target, content) {
15313
15337
  return `${target}\0${content}`;
15314
15338
  }
@@ -15326,6 +15350,9 @@ var MemoryManager = class {
15326
15350
  initialized = false;
15327
15351
  consolidationFailures = 0;
15328
15352
  reviewIssues = [];
15353
+ notices = [];
15354
+ notifiedBlocked = false;
15355
+ notifiedQuota = false;
15329
15356
  constructor(dbPath, memDir, config) {
15330
15357
  const dp = dbPath ?? DB_PATH;
15331
15358
  const md = memDir ?? MEM_DIR;
@@ -15353,55 +15380,103 @@ var MemoryManager = class {
15353
15380
  await this.flushToDisk();
15354
15381
  }
15355
15382
  async reloadFromDisk() {
15383
+ let blockedCount = 0;
15356
15384
  for (const target of ["memory", "user"]) {
15357
15385
  const file = target === "memory" ? "MEMORY.md" : "USER.md";
15358
15386
  const fromDisk = this.readFile(join(this.memDir, file));
15359
15387
  const deduped = [...new Set(fromDisk)];
15360
- const safe = deduped.map((c) => this.scanInjection(c)).filter((c) => c.length > 0);
15361
- this.syncTarget(target, safe);
15388
+ const classified = deduped.map((c) => this.classifyEntry(c));
15389
+ blockedCount += classified.filter((e) => e.blocked).length;
15390
+ this.syncTarget(target, classified);
15362
15391
  }
15363
15392
  this.assignIds();
15364
15393
  this.snapshot = this.renderIndexBlock();
15394
+ if (blockedCount > 0 && !this.notifiedBlocked) {
15395
+ this.notifiedBlocked = true;
15396
+ this.notices.push({
15397
+ code: "memory-blocked",
15398
+ text: t("memory_review.blocked_notice", {
15399
+ count: blockedCount,
15400
+ path: join(this.memDir, "MEMORY.md")
15401
+ })
15402
+ });
15403
+ }
15404
+ this.checkQuota();
15405
+ }
15406
+ blockedPlaceholder(content) {
15407
+ return t("memory_review.blocked_placeholder", {
15408
+ pattern: injectionLabelOf(content) ?? "unknown"
15409
+ });
15410
+ }
15411
+ refuseInjection(label) {
15412
+ this.reviewIssues.push({
15413
+ type: "injection",
15414
+ severity: "high",
15415
+ message: t("memory_review.injection", { pattern: label }),
15416
+ suggestion: t("memory_review.injection_suggestion")
15417
+ });
15418
+ return {
15419
+ success: false,
15420
+ done: true,
15421
+ error: t("memory_review.write_blocked", { pattern: label })
15422
+ };
15423
+ }
15424
+ takeNotices() {
15425
+ const out = this.notices;
15426
+ this.notices = [];
15427
+ return out;
15428
+ }
15429
+ checkQuota() {
15430
+ if (this.notifiedQuota) return;
15431
+ const b = this.getBudget().memory;
15432
+ if (b.percent < QUOTA_WARN_PCT) return;
15433
+ this.notifiedQuota = true;
15434
+ this.notices.push({
15435
+ code: "memory-quota",
15436
+ text: t("memory_review.quota", { percent: b.percent, current: b.current, limit: b.limit })
15437
+ });
15365
15438
  }
15366
15439
  assignIds() {
15367
15440
  this.ids.clear();
15368
15441
  this.idByContent.clear();
15369
15442
  for (const target of ["memory", "user"]) {
15370
15443
  const prefix = target === "memory" ? "m" : "u";
15371
- for (const content of this.db.getAllContent(target)) {
15372
- const hash = createHash("sha256").update(content).digest("hex");
15444
+ for (const entry of this.db.getAll(target)) {
15445
+ const hash = createHash("sha256").update(entry.content).digest("hex");
15373
15446
  let id = `${prefix}:${hash.slice(0, SHORT_ID_HEX)}`;
15374
15447
  for (let len = SHORT_ID_HEX + 4; this.ids.has(id) && len <= hash.length; len += 4) {
15375
15448
  id = `${prefix}:${hash.slice(0, len)}`;
15376
15449
  }
15377
- this.ids.set(id, { target, content });
15378
- this.idByContent.set(contentKey(target, content), id);
15450
+ this.ids.set(id, { target, content: entry.content, blocked: entry.blocked });
15451
+ this.idByContent.set(contentKey(target, entry.content), id);
15379
15452
  }
15380
15453
  }
15381
15454
  }
15382
15455
  syncTarget(target, desired) {
15383
15456
  const existing = new Map(this.db.getAll(target).map((e) => [e.content, e]));
15384
- const desiredSet = new Set(desired);
15457
+ const desiredSet = new Set(desired.map((d) => d.content));
15385
15458
  for (const [content, entry] of existing) {
15386
15459
  if (!desiredSet.has(content)) this.db.removeById(entry.id);
15387
15460
  }
15388
- for (const [i, content] of desired.entries()) {
15389
- const prev = existing.get(content);
15461
+ for (const [i, item] of desired.entries()) {
15462
+ const prev = existing.get(item.content);
15390
15463
  if (prev) {
15391
15464
  if (prev.position !== i) this.db.updatePosition(prev.id, i);
15465
+ if (prev.blocked !== item.blocked) this.db.setBlocked(prev.id, item.blocked);
15392
15466
  continue;
15393
15467
  }
15394
15468
  const now = /* @__PURE__ */ new Date();
15395
15469
  this.db.insert({
15396
15470
  id: randomUUID(),
15397
15471
  target,
15398
- content,
15472
+ content: item.content,
15399
15473
  position: i,
15400
- charLength: content.length,
15474
+ charLength: item.content.length,
15401
15475
  accessCount: 0,
15402
15476
  lastAccessedAt: now,
15403
15477
  createdAt: now,
15404
- updatedAt: now
15478
+ updatedAt: now,
15479
+ blocked: item.blocked
15405
15480
  });
15406
15481
  }
15407
15482
  }
@@ -15420,7 +15495,7 @@ var MemoryManager = class {
15420
15495
  for (const r of results) this.db.markAccessed(r.id);
15421
15496
  return results.map((r) => ({
15422
15497
  id: this.idByContent.get(contentKey(r.target, r.content)) ?? r.id,
15423
- content: r.content,
15498
+ content: r.blocked ? this.blockedPlaceholder(r.content) : r.content,
15424
15499
  target: r.target,
15425
15500
  score: r.score
15426
15501
  }));
@@ -15438,22 +15513,27 @@ var MemoryManager = class {
15438
15513
  missing.push(id);
15439
15514
  continue;
15440
15515
  }
15441
- found.push({ id, target: hit.target, content: hit.content });
15516
+ found.push({
15517
+ id,
15518
+ target: hit.target,
15519
+ content: hit.blocked ? this.blockedPlaceholder(hit.content) : hit.content
15520
+ });
15442
15521
  const entry = this.db.findByContent(hit.target, hit.content);
15443
15522
  if (entry) this.db.markAccessed(entry.id);
15444
15523
  }
15445
15524
  return { found, missing };
15446
15525
  }
15447
15526
  getAllEntries() {
15448
- return [
15449
- ...this.db.getAllContent("memory").map((c) => ({ content: c })),
15450
- ...this.db.getAllContent("user").map((c) => ({ content: c }))
15451
- ];
15527
+ return ["memory", "user"].flatMap(
15528
+ (target) => this.db.getAll(target).map((e) => ({ content: e.content, target, blocked: e.blocked }))
15529
+ );
15452
15530
  }
15453
15531
  async semanticSearch(query, limit) {
15454
15532
  return this.search(query, limit);
15455
15533
  }
15456
15534
  async add(target, content) {
15535
+ const label = injectionLabelOf(content);
15536
+ if (label !== void 0) return this.refuseInjection(label);
15457
15537
  const budget = this.getBudget()[target];
15458
15538
  if (budget.current + content.length > budget.limit) {
15459
15539
  this.consolidationFailures++;
@@ -15487,7 +15567,8 @@ var MemoryManager = class {
15487
15567
  accessCount: 0,
15488
15568
  lastAccessedAt: /* @__PURE__ */ new Date(),
15489
15569
  createdAt: /* @__PURE__ */ new Date(),
15490
- updatedAt: /* @__PURE__ */ new Date()
15570
+ updatedAt: /* @__PURE__ */ new Date(),
15571
+ blocked: false
15491
15572
  };
15492
15573
  this.db.insert(entry);
15493
15574
  this.consolidationFailures = 0;
@@ -15499,6 +15580,8 @@ var MemoryManager = class {
15499
15580
  if (!existing) {
15500
15581
  return { success: false, done: true, error: this.notFoundHint(target, oldText) };
15501
15582
  }
15583
+ const label = injectionLabelOf(content);
15584
+ if (label !== void 0) return this.refuseInjection(label);
15502
15585
  const budget = this.getBudget()[target];
15503
15586
  const delta = content.length - existing.charLength;
15504
15587
  if (budget.current + delta > budget.limit) {
@@ -15516,7 +15599,8 @@ var MemoryManager = class {
15516
15599
  ...existing,
15517
15600
  content,
15518
15601
  charLength: content.length,
15519
- updatedAt: /* @__PURE__ */ new Date()
15602
+ updatedAt: /* @__PURE__ */ new Date(),
15603
+ blocked: false
15520
15604
  });
15521
15605
  this.consolidationFailures = 0;
15522
15606
  await this.flushToDisk();
@@ -15580,31 +15664,42 @@ var MemoryManager = class {
15580
15664
  if (current) entries2.push(current.join("\n").trim());
15581
15665
  return entries2.filter((e) => e.length > 0);
15582
15666
  }
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
- }
15667
+ classifyEntry(content) {
15668
+ const label = injectionLabelOf(content);
15669
+ if (label !== void 0) {
15670
+ this.reviewIssues.push({
15671
+ type: "injection",
15672
+ severity: "high",
15673
+ message: t("memory_review.injection", { pattern: label }),
15674
+ suggestion: t("memory_review.injection_suggestion")
15675
+ });
15676
+ return { content, blocked: true };
15594
15677
  }
15595
15678
  if (countCodePoints(content) > MAX_ENTRY_CHARS) {
15596
- return headCodePoints(content, MAX_ENTRY_CHARS) + " \u2026[\u6761\u76EE\u8FC7\u957F\u5DF2\u622A\u65AD]";
15679
+ return {
15680
+ content: headCodePoints(content, MAX_ENTRY_CHARS) + " \u2026[\u6761\u76EE\u8FC7\u957F\u5DF2\u622A\u65AD]",
15681
+ blocked: false
15682
+ };
15597
15683
  }
15598
- return content;
15684
+ return { content, blocked: false };
15599
15685
  }
15600
15686
  indexEntries(target) {
15601
15687
  const out = [];
15602
- for (const content of this.db.getAllContent(target)) {
15603
- const id = this.idByContent.get(contentKey(target, content));
15688
+ for (const entry of this.db.getAll(target)) {
15689
+ const id = this.idByContent.get(contentKey(target, entry.content));
15604
15690
  if (!id) continue;
15605
- const firstLine4 = (content.split("\n")[0] ?? "").trim();
15691
+ if (entry.blocked) {
15692
+ out.push({
15693
+ id,
15694
+ target,
15695
+ summary: this.blockedPlaceholder(entry.content),
15696
+ charLength: entry.content.length
15697
+ });
15698
+ continue;
15699
+ }
15700
+ const firstLine4 = (entry.content.split("\n")[0] ?? "").trim();
15606
15701
  const summary = countCodePoints(firstLine4) > INDEX_SUMMARY_CHARS ? `${headCodePoints(firstLine4, INDEX_SUMMARY_CHARS)}\u2026` : firstLine4;
15607
- out.push({ id, target, summary, charLength: content.length });
15702
+ out.push({ id, target, summary, charLength: entry.content.length });
15608
15703
  }
15609
15704
  return out;
15610
15705
  }
@@ -15696,148 +15791,6 @@ var MemoryManager = class {
15696
15791
  ].join("\n");
15697
15792
  }
15698
15793
  };
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
15794
  var ATTACHMENT_RETENTION_DEFAULTS = {
15842
15795
  maxAgeDays: 7,
15843
15796
  maxTotalBytes: ATTACHMENT_MAX_TOTAL_BYTES
@@ -17144,7 +17097,7 @@ function checkFtsHealth(db) {
17144
17097
  messages,
17145
17098
  tables,
17146
17099
  missingTriggers,
17147
- healthy: missingTriggers.length === 0 && tables.every((t81) => t81.error === null && t81.drift === null && t81.rows === messages),
17100
+ healthy: missingTriggers.length === 0 && tables.every((t80) => t80.error === null && t80.drift === null && t80.rows === messages),
17148
17101
  indexBytes: indexBytesOf(db),
17149
17102
  space: spaceOf(db)
17150
17103
  };
@@ -18488,11 +18441,11 @@ var ApprovalCache = class {
18488
18441
  return !rel.startsWith("..") && !isAbsolute(rel);
18489
18442
  }
18490
18443
  normalizeTarget(target) {
18491
- const t81 = target.trim();
18492
- if (!t81) return "";
18493
- const unix = t81.replace(/\\/g, "/");
18444
+ const t80 = target.trim();
18445
+ if (!t80) return "";
18446
+ const unix = t80.replace(/\\/g, "/");
18494
18447
  if (unix.startsWith("/") || /^[A-Za-z]:\//.test(unix)) return resolve(unix);
18495
- return t81;
18448
+ return t80;
18496
18449
  }
18497
18450
  load() {
18498
18451
  try {
@@ -18595,9 +18548,9 @@ function createSharedPermissionState(cache) {
18595
18548
  };
18596
18549
  }
18597
18550
  function cacheKeyFor(raw) {
18598
- const t81 = raw.trim();
18599
- if (!t81) return null;
18600
- return t81.slice(0, 500);
18551
+ const t80 = raw.trim();
18552
+ if (!t80) return null;
18553
+ return t80.slice(0, 500);
18601
18554
  }
18602
18555
  // src/permission/manager.ts
18603
18556
  var log3 = createLogger("permission");
@@ -22044,7 +21997,8 @@ function loadOne(mdFile, catPath, category, dirName, spec, skills, issues) {
22044
21997
  }
22045
21998
  const parsed = parseSkillFrontmatter(raw, label);
22046
21999
  issues.push(...parsed.issues);
22047
- const base = parsed.fm.name ?? dirName;
22000
+ const declared = parsed.fm.name?.trim();
22001
+ const base = declared ? declared : dirName;
22048
22002
  const name = spec.prefix ? `${spec.prefix}:${base}` : base;
22049
22003
  const previous = skills.get(name);
22050
22004
  if (previous && previous.scope !== scope) {
@@ -22094,6 +22048,18 @@ function normalizeDirs(input) {
22094
22048
  }
22095
22049
  return dirs.length > 0 ? dirs : [{ dir: SKILLS_DIR, source: "user" }];
22096
22050
  }
22051
+ function realpathOfDeepestExisting(target) {
22052
+ const tail = [];
22053
+ let cur = target;
22054
+ while (!existsSync(cur)) {
22055
+ const parent = dirname(cur);
22056
+ if (parent === cur) return target;
22057
+ tail.unshift(basename(cur));
22058
+ cur = parent;
22059
+ }
22060
+ const real = realpathSync(cur);
22061
+ return tail.length === 0 ? real : join(real, ...tail);
22062
+ }
22097
22063
  var SkillSystem = class {
22098
22064
  dirs;
22099
22065
  skillsDir;
@@ -22186,12 +22152,16 @@ var SkillSystem = class {
22186
22152
  }
22187
22153
  async create(name, content, category = "general") {
22188
22154
  this.scanIfNeeded();
22189
- if (this.skills.has(name)) throw new Error(`Skill "${name}" already exists`);
22190
- const dir = join(this.skillsDir, category, name);
22155
+ const declared = parseSkillFrontmatter(content, name).fm.name?.trim();
22156
+ const indexName = declared ? declared : name;
22157
+ if (this.skills.has(indexName)) throw new Error(`Skill "${indexName}" already exists`);
22158
+ const dir = join(this.skillsDir, category, indexName);
22191
22159
  const base = resolve(this.skillsDir);
22192
22160
  const resolved = resolve(dir);
22193
22161
  if (resolved === base || !resolved.startsWith(base + sep)) {
22194
- throw new Error(`Skill name or category escapes the skills directory: ${category}/${name}`);
22162
+ throw new Error(
22163
+ `Skill name or category escapes the skills directory: ${category}/${indexName}`
22164
+ );
22195
22165
  }
22196
22166
  mkdirSync(dir, { recursive: true });
22197
22167
  writeFileSync(join(dir, "SKILL.md"), content, "utf-8");
@@ -22205,7 +22175,7 @@ var SkillSystem = class {
22205
22175
  }
22206
22176
  async patch(name, oldString, newString, filePath) {
22207
22177
  const skill = this.requireWritable(name);
22208
- const targetPath = filePath ? join(skill.path, filePath) : join(skill.path, "SKILL.md");
22178
+ const targetPath = this.resolveInsideSkill(skill.path, filePath ?? "SKILL.md");
22209
22179
  const raw = readFileSync(targetPath, "utf-8");
22210
22180
  if (!raw.includes(oldString)) throw new Error("old_string not found");
22211
22181
  writeFileSync(targetPath, raw.replace(oldString, newString), "utf-8");
@@ -22225,21 +22195,30 @@ var SkillSystem = class {
22225
22195
  `filePath must be under references/templates/scripts/assets, got: ${filePath}`
22226
22196
  );
22227
22197
  }
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
- }
22198
+ const resolved = this.resolveInsideSkill(skill.path, filePath);
22233
22199
  mkdirSync(dirname(resolved), { recursive: true });
22234
22200
  writeFileSync(resolved, content, "utf-8");
22235
22201
  this.invalidateCache();
22236
22202
  }
22237
22203
  async removeFile(name, filePath) {
22238
22204
  const skill = this.requireWritable(name);
22239
- const resolved = join(skill.path, filePath);
22205
+ const resolved = this.resolveInsideSkill(skill.path, filePath);
22240
22206
  rmSync(resolved);
22241
22207
  this.invalidateCache();
22242
22208
  }
22209
+ resolveInsideSkill(skillPath, relPath) {
22210
+ const base = resolve(skillPath);
22211
+ const resolved = resolve(join(skillPath, relPath));
22212
+ if (resolved !== base && !resolved.startsWith(base + sep)) {
22213
+ throw new Error("Path traversal denied");
22214
+ }
22215
+ const realBase = realpathSync(base);
22216
+ const realTarget = realpathOfDeepestExisting(resolved);
22217
+ if (realTarget !== realBase && !realTarget.startsWith(realBase + sep)) {
22218
+ throw new Error("Path traversal denied");
22219
+ }
22220
+ return resolved;
22221
+ }
22243
22222
  requireWritable(name) {
22244
22223
  this.scanIfNeeded();
22245
22224
  const skill = this.skills.get(name);
@@ -22648,12 +22627,21 @@ var SkillLearner = class {
22648
22627
  if (!parsed.name || !parsed.content) {
22649
22628
  return { created: false, reason: "missing-name-or-content" };
22650
22629
  }
22651
- if (!parsed.content.includes("---")) {
22630
+ const { fm } = parseSkillFrontmatter(parsed.content, parsed.name);
22631
+ const declared = fm.name?.trim();
22632
+ if (!declared) {
22652
22633
  return { created: false, reason: "missing-frontmatter" };
22653
22634
  }
22635
+ if ((fm.description ?? "").trim() === "") {
22636
+ return { created: false, reason: "missing-description" };
22637
+ }
22638
+ const category = parsed.category?.trim() || "general";
22639
+ if (skillSegmentIssue(declared) !== void 0 || skillSegmentIssue(category) !== void 0) {
22640
+ return { created: false, reason: "invalid-name" };
22641
+ }
22654
22642
  try {
22655
- await this.skillSystem.create(parsed.name, parsed.content, parsed.category || "general");
22656
- return { created: true, skillName: parsed.name };
22643
+ await this.skillSystem.create(declared, parsed.content, category);
22644
+ return { created: true, skillName: declared };
22657
22645
  } catch (err) {
22658
22646
  return { created: false, reason: "create-failed", error: messageOf2(err) };
22659
22647
  }
@@ -23804,4 +23792,4 @@ function trunc(stdout, stderr) {
23804
23792
  * `--tree` 的命令帮助里,让用户自己知道 `--tree` 意味着什么。
23805
23793
  */
23806
23794
 
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 };
23795
+ 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 };