scream-code 0.14.8 → 0.15.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.
@@ -4,9 +4,9 @@ import { dirname as __cjsShimDirname } from 'node:path';
4
4
  const __filename = __cjsShimFileURLToPath(import.meta.url);
5
5
  const __dirname = __cjsShimDirname(__filename);
6
6
  import { i as __require, o as __toESM, r as __exportAll, t as __commonJSMin } from "./chunk-D90kvbyJ.mjs";
7
- import { C as join$1, D as resolve$1, E as relative$1, S as isAbsolute$1, T as parse$7, a as isSupportedFile, b as basename$1, i as ingestFile, r as ingestDirectory, t as multiSearch, w as normalize, x as dirname$2, y as KnowledgeStore } from "./src-BH9W5k24.mjs";
7
+ import { C as join$1, D as resolve$1, E as relative$1, S as isAbsolute$1, T as parse$7, a as isSupportedFile, b as basename$1, i as ingestFile, r as ingestDirectory, t as multiSearch, w as normalize, x as dirname$2, y as KnowledgeStore } from "./src-tDEINaMV.mjs";
8
8
  import { t as require_base64_js } from "./base64-js-DzVmk6Nb.mjs";
9
- import { a as setLocale, i as getLocale, n as assertScreamHostIdentity, o as t, r as createScreamDefaultHeaders, t as TextInputDialogComponent } from "./text-input-dialog-B0yHChBj.mjs";
9
+ import { a as setLocale, i as getLocale, n as assertScreamHostIdentity, o as t, r as createScreamDefaultHeaders, t as TextInputDialogComponent } from "./text-input-dialog-BUXAKycx.mjs";
10
10
  import { createRequire } from "node:module";
11
11
  import { createHash, randomBytes, randomInt, randomUUID } from "node:crypto";
12
12
  import * as fs$1 from "node:fs/promises";
@@ -575,7 +575,7 @@ function createToolMessage(toolCallId, output) {
575
575
  }
576
576
  //#endregion
577
577
  //#region ../../packages/ltod/src/rate-limit-utils.ts
578
- const QUOTA_EXHAUSTED_BACKOFF_MS = 1800 * 1e3;
578
+ const QUOTA_EXHAUSTED_BACKOFF_MS = 30 * 1e3;
579
579
  const RATE_LIMIT_EXCEEDED_BACKOFF_MS = 30 * 1e3;
580
580
  const MODEL_CAPACITY_BASE_MS = 45 * 1e3;
581
581
  const MODEL_CAPACITY_JITTER_MS = 30 * 1e3;
@@ -605,9 +605,9 @@ function parseRateLimitReason(errorMessage) {
605
605
  /**
606
606
  * Backoff in ms for a given reason. MODEL_CAPACITY gets jitter to avoid
607
607
  * thundering herd. UNKNOWN (a classified 429 with no recognized reason)
608
- * uses the short 20s SERVER_ERROR backoff — a 30min x ~9 attempt hang
609
- * would be far worse than a failed turn, and UNKNOWN is not evidence of
610
- * quota exhaustion.
608
+ * uses the short 20s SERVER_ERROR backoff — a long quota backoff on an
609
+ * unrecoverable error would hang a turn for far longer than the value of
610
+ * retrying, and UNKNOWN is not evidence of quota exhaustion.
611
611
  */
612
612
  function calculateRateLimitBackoffMs(reason) {
613
613
  switch (reason) {
@@ -765,14 +765,9 @@ var APIEmptyResponseError = class extends ChatProviderError {
765
765
  function isRetryableGenerateError(error) {
766
766
  if (error instanceof APIConnectionError$3 || error instanceof APITimeoutError) return true;
767
767
  if (error instanceof APIEmptyResponseError) return true;
768
- if (error instanceof APIProviderRateLimitError) return error.reason !== "QUOTA_EXHAUSTED";
769
- return error instanceof APIStatusError && [
770
- 429,
771
- 500,
772
- 502,
773
- 503,
774
- 504
775
- ].includes(error.statusCode);
768
+ if (error instanceof APIProviderRateLimitError) return true;
769
+ if (error instanceof APIStatusError) return error.statusCode === 429 || error.statusCode === 408 || error.statusCode >= 500 && error.statusCode < 600;
770
+ return false;
776
771
  }
777
772
  const CONTEXT_OVERFLOW_MESSAGE_PATTERNS = [
778
773
  /context[ _-]?length/,
@@ -47489,12 +47484,9 @@ async function chatWithRetry(input) {
47489
47484
  logRequestFailure(input, error, attempt, maxAttempts);
47490
47485
  throw error;
47491
47486
  }
47492
- if (error instanceof APIProviderRateLimitError && error.reason === "QUOTA_EXHAUSTED") {
47493
- logRequestFailure(input, error, attempt, maxAttempts);
47494
- throw error;
47495
- }
47496
- if (attempt >= maxAttempts || !input.llm.isRetryableError(error)) {
47497
- logRequestFailure(input, error, attempt, maxAttempts);
47487
+ const attemptLimit = error instanceof APIProviderRateLimitError && error.reason === "QUOTA_EXHAUSTED" ? Math.min(3, maxAttempts) : maxAttempts;
47488
+ if (attempt >= attemptLimit || !input.llm.isRetryableError(error)) {
47489
+ logRequestFailure(input, error, attempt, attemptLimit);
47498
47490
  throw error;
47499
47491
  }
47500
47492
  const delayMs = computeDelayMs(error, delays, attempt);
@@ -47506,7 +47498,7 @@ async function chatWithRetry(input) {
47506
47498
  stepUuid: input.stepUuid,
47507
47499
  failedAttempt: attempt,
47508
47500
  nextAttempt: attempt + 1,
47509
- maxAttempts,
47501
+ maxAttempts: attemptLimit,
47510
47502
  delayMs,
47511
47503
  ...retryErrorFields(error)
47512
47504
  });
@@ -53192,12 +53184,15 @@ function createFastEmbedEngine(cacheDir) {
53192
53184
  engineCache.set(key, engine);
53193
53185
  return engine;
53194
53186
  }
53187
+ /** Stable identity of the model used by every engine created here. */
53188
+ const EMBEDDING_MODEL_NAME = "bge-small-zh-v1.5";
53195
53189
  function createFastEmbedEngineImpl(cacheDir) {
53196
53190
  let embedder = null;
53197
53191
  let initPromise = null;
53198
53192
  let loadFailed = false;
53199
53193
  let lastError;
53200
53194
  return {
53195
+ modelName: EMBEDDING_MODEL_NAME,
53201
53196
  get available() {
53202
53197
  return embedder !== null && !loadFailed;
53203
53198
  },
@@ -53595,7 +53590,7 @@ var MemoryMemoStore = class MemoryMemoStore {
53595
53590
  CREATE TABLE IF NOT EXISTS memory_embeddings (
53596
53591
  memory_id TEXT PRIMARY KEY REFERENCES memos(id) ON DELETE CASCADE,
53597
53592
  embedding_json TEXT NOT NULL,
53598
- model TEXT NOT NULL DEFAULT 'bge-small-zh-v1.5',
53593
+ model TEXT NOT NULL DEFAULT '${EMBEDDING_MODEL_NAME}',
53599
53594
  created_at INTEGER NOT NULL
53600
53595
  );
53601
53596
 
@@ -53901,7 +53896,7 @@ var MemoryMemoStore = class MemoryMemoStore {
53901
53896
  const now = Date.now();
53902
53897
  this.db.exec("BEGIN TRANSACTION");
53903
53898
  try {
53904
- for (let i = 0; i < pending.length; i++) insert.run(pending[i].id, JSON.stringify([...vectors[i]]), "bge-small-zh-v1.5", now);
53899
+ for (let i = 0; i < pending.length; i++) insert.run(pending[i].id, JSON.stringify([...vectors[i]]), EMBEDDING_MODEL_NAME, now);
53905
53900
  this.db.exec("COMMIT");
53906
53901
  } catch (error) {
53907
53902
  this.db.exec("ROLLBACK");
@@ -54910,7 +54905,7 @@ var KnowledgeLookupTool = class {
54910
54905
  const llm = { generate: async (systemPrompt, userPrompt) => {
54911
54906
  return this.agent.generateText(systemPrompt, userPrompt);
54912
54907
  } };
54913
- const { multiSearchWithTrace } = await import("./src-DCp4eCi5.mjs");
54908
+ const { multiSearchWithTrace } = await import("./src-h6ZExh4A.mjs");
54914
54909
  const { results, trace } = await multiSearchWithTrace(store, llm, query, { topK });
54915
54910
  if (results.length === 0) return {
54916
54911
  isError: false,
@@ -64668,7 +64663,7 @@ function renderPrompt(template, vars) {
64668
64663
  }
64669
64664
  //#endregion
64670
64665
  //#region ../../packages/agent-core/src/tools/builtin/collaboration/skill-tool.md
64671
- var skill_tool_default = "Invoke a registered skill from the current skill listing. BLOCKING REQUIREMENT: when a skill from the listing matches the user's request, you MUST call this tool (not free-form text). Do NOT call the same skill repeatedly inside one turn — recursive depth is capped at {{ MAX_SKILL_QUERY_DEPTH }}.\n\n## Currently model-invocable skills\n{{ AVAILABLE_SKILLS }}\nThis list is a construction-time snapshot; for the live catalog inspect the skill registry.\n\n## Matching guide\n\n- A skill matches when the user's request involves the scenarios/trigger conditions listed in the skill's \"When to use\" line.\n- Look for: (a) keywords in the user's request that match the skill's trigger phrases, (b) the task type or scenario described in the skill's \"When to use\", (c) similar requests the skill was designed for.\n- If a skill seems relevant, invoke it first — if it doesn't fit, you can always fall back to doing it yourself.";
64666
+ var skill_tool_default = "Invoke a registered skill from the current skill listing. BLOCKING REQUIREMENT: when a skill from the listing matches the user's request, you MUST call this tool (not free-form text). Do NOT call the same skill repeatedly inside one turn — recursive depth is capped at {{ MAX_SKILL_QUERY_DEPTH }}.\n\n## Currently model-invocable skills\n{{ AVAILABLE_SKILLS }}\nThis list is a construction-time snapshot; for the live catalog inspect the skill registry.\n\n**Not for inventory questions**: when the user asks what you have — in any wording (skills / plugins / capabilities / MCP / tools) — answer with **InspectOwnAssets** (the full read-only catalog), not this listing. To find or install new capabilities, use **ManagePlugin**. This listing exists for **invocation**: when one of the skills above matches the task at hand.\n\n## Matching guide\n\n- A skill matches when the user's request involves the scenarios/trigger conditions listed in the skill's \"When to use\" line.\n- Look for: (a) keywords in the user's request that match the skill's trigger phrases, (b) the task type or scenario described in the skill's \"When to use\", (c) similar requests the skill was designed for.\n- If a skill seems relevant, invoke it first — if it doesn't fit, you can always fall back to doing it yourself.";
64672
64667
  /** Max skills to list inline in the tool description; rest are summarized. */
64673
64668
  const MAX_INLINE_SKILLS = 15;
64674
64669
  /** Max description length per skill line. */
@@ -65280,7 +65275,7 @@ var import_yauzl = (/* @__PURE__ */ __commonJSMin(((exports) => {
65280
65275
  var EventEmitter$3 = __require("events").EventEmitter;
65281
65276
  var Transform$1 = __require("stream").Transform;
65282
65277
  var PassThrough$2 = __require("stream").PassThrough;
65283
- var Writable = __require("stream").Writable;
65278
+ var Writable$1 = __require("stream").Writable;
65284
65279
  exports.fromBuffer = fromBuffer;
65285
65280
  function fromBuffer(buffer, options, callback) {
65286
65281
  if (typeof options === "function") {
@@ -65804,7 +65799,7 @@ var import_yauzl = (/* @__PURE__ */ __commonJSMin(((exports) => {
65804
65799
  start: position,
65805
65800
  end: position + length
65806
65801
  });
65807
- var writeStream = new Writable();
65802
+ var writeStream = new Writable$1();
65808
65803
  var written = 0;
65809
65804
  writeStream._write = function(chunk, encoding, cb) {
65810
65805
  chunk.copy(buffer, offset + written, 0, chunk.length);
@@ -65973,9 +65968,9 @@ async function hasManifest(dir) {
65973
65968
  const dirManifest = path.join(dir, ".scream-plugin", "plugin.json");
65974
65969
  const claudeDirManifest = path.join(dir, ".claude-plugin", "plugin.json");
65975
65970
  const skillMd = path.join(dir, "SKILL.md");
65976
- return await isFile$2(rootManifest) || await isFile$2(dirManifest) || await isFile$2(claudeDirManifest) || await isFile$2(skillMd);
65971
+ return await isFile$3(rootManifest) || await isFile$3(dirManifest) || await isFile$3(claudeDirManifest) || await isFile$3(skillMd);
65977
65972
  }
65978
- async function isFile$2(p) {
65973
+ async function isFile$3(p) {
65979
65974
  try {
65980
65975
  return (await stat(p)).isFile();
65981
65976
  } catch {
@@ -66438,12 +66433,12 @@ async function parseManifest(pluginRoot) {
66438
66433
  const rootJsonPath = path.join(pluginRoot, SCREAM_PLUGIN_ROOT_PATH);
66439
66434
  const dirJsonPath = path.join(pluginRoot, SCREAM_PLUGIN_DIR_PATH);
66440
66435
  const claudeDirJsonPath = path.join(pluginRoot, CLAUDE_PLUGIN_DIR_PATH);
66441
- const rootJsonExists = await isFile$1(rootJsonPath);
66442
- const dirJsonExists = await isFile$1(dirJsonPath);
66443
- const claudeDirJsonExists = await isFile$1(claudeDirJsonPath);
66436
+ const rootJsonExists = await isFile$2(rootJsonPath);
66437
+ const dirJsonExists = await isFile$2(dirJsonPath);
66438
+ const claudeDirJsonExists = await isFile$2(claudeDirJsonPath);
66444
66439
  if (!rootJsonExists && !dirJsonExists && !claudeDirJsonExists) {
66445
66440
  const skillMdPath = path.join(pluginRoot, BARE_SKILL_PATH);
66446
- if (await isFile$1(skillMdPath)) return {
66441
+ if (await isFile$2(skillMdPath)) return {
66447
66442
  manifest: {
66448
66443
  name: path.basename(pluginRoot),
66449
66444
  skills: [pluginRoot]
@@ -66521,7 +66516,7 @@ async function parseManifest(pluginRoot) {
66521
66516
  }
66522
66517
  let skills = await resolveSkillsField(pluginRoot, raw["skills"], diagnostics);
66523
66518
  if (raw["skills"] === void 0) {
66524
- if (await isFile$1(path.join(pluginRoot, "SKILL.md"))) skills = [pluginRoot];
66519
+ if (await isFile$2(path.join(pluginRoot, "SKILL.md"))) skills = [pluginRoot];
66525
66520
  }
66526
66521
  const skillInstructions = typeof raw["skillInstructions"] === "string" ? raw["skillInstructions"] : void 0;
66527
66522
  const config = typeof raw["config"] === "object" && raw["config"] !== null && !Array.isArray(raw["config"]) ? raw["config"] : void 0;
@@ -66853,7 +66848,7 @@ async function discoverSkillDirs(root, maxDepth) {
66853
66848
  for (const entry of entries) {
66854
66849
  if (!entry.isDirectory()) continue;
66855
66850
  const child = path.join(dir, entry.name);
66856
- if (await isFile$1(path.join(child, BARE_SKILL_PATH))) found.push(child);
66851
+ if (await isFile$2(path.join(child, BARE_SKILL_PATH))) found.push(child);
66857
66852
  await walk(child, depth + 1);
66858
66853
  }
66859
66854
  }
@@ -66863,7 +66858,7 @@ async function discoverSkillDirs(root, maxDepth) {
66863
66858
  for (const dir of sorted) if (!result.some((parent) => dir.startsWith(parent + path.sep))) result.push(dir);
66864
66859
  return result;
66865
66860
  }
66866
- async function isFile$1(p) {
66861
+ async function isFile$2(p) {
66867
66862
  try {
66868
66863
  return (await stat(p)).isFile();
66869
66864
  } catch {
@@ -67860,7 +67855,10 @@ async function keepNewestBackupOnly(backupsForId) {
67860
67855
  async function recordFrom(input) {
67861
67856
  const { parsed } = input;
67862
67857
  const hasError = parsed.diagnostics.some((d) => d.severity === "error");
67863
- const skills = hasError || parsed.manifest === void 0 ? [] : await discoverPluginSkills(input.id, parsed.manifest);
67858
+ const discovery = hasError || parsed.manifest === void 0 ? {
67859
+ skills: [],
67860
+ diagnostics: []
67861
+ } : await discoverPluginSkills(input.id, parsed.manifest);
67864
67862
  return {
67865
67863
  id: input.id,
67866
67864
  root: input.root,
@@ -67872,13 +67870,13 @@ async function recordFrom(input) {
67872
67870
  originalSource: input.originalSource,
67873
67871
  capabilities: input.capabilities,
67874
67872
  github: input.github,
67875
- skills,
67876
- skillCount: skills.length,
67873
+ skills: discovery.skills,
67874
+ skillCount: discovery.skills.length,
67877
67875
  manifest: parsed.manifest,
67878
67876
  manifestKind: parsed.manifestKind,
67879
67877
  manifestPath: parsed.manifestPath,
67880
67878
  shadowedManifestPath: parsed.shadowedManifestPath,
67881
- diagnostics: parsed.diagnostics,
67879
+ diagnostics: [...parsed.diagnostics, ...discovery.diagnostics],
67882
67880
  skillInstructions: parsed.manifest?.skillInstructions
67883
67881
  };
67884
67882
  }
@@ -67908,11 +67906,26 @@ async function discoverPluginSkills(pluginId, manifest) {
67908
67906
  instructions: manifest?.skillInstructions
67909
67907
  }
67910
67908
  }));
67911
- if (roots.length === 0) return [];
67912
- return (await discoverSkills({ roots })).map((skill) => ({
67913
- name: skill.name,
67914
- description: skill.description
67915
- }));
67909
+ if (roots.length === 0) return {
67910
+ skills: [],
67911
+ diagnostics: []
67912
+ };
67913
+ const diagnostics = [];
67914
+ return {
67915
+ skills: (await discoverSkills({
67916
+ roots,
67917
+ onWarning: (message) => {
67918
+ diagnostics.push({
67919
+ severity: "warn",
67920
+ message
67921
+ });
67922
+ }
67923
+ })).map((skill) => ({
67924
+ name: skill.name,
67925
+ description: skill.description
67926
+ })),
67927
+ diagnostics
67928
+ };
67916
67929
  }
67917
67930
  function recordToInfo(record) {
67918
67931
  return {
@@ -72402,10 +72415,10 @@ async function findExistingRg(shareDir) {
72402
72415
  source: "share-bin-cached"
72403
72416
  };
72404
72417
  }
72405
- let downloadPromise;
72418
+ let downloadPromise$1;
72406
72419
  async function downloadRgWithLock(shareDir) {
72407
- if (downloadPromise !== void 0) return downloadPromise;
72408
- downloadPromise = (async () => {
72420
+ if (downloadPromise$1 !== void 0) return downloadPromise$1;
72421
+ downloadPromise$1 = (async () => {
72409
72422
  try {
72410
72423
  const existing = await findExistingRg(shareDir);
72411
72424
  if (existing) return existing;
@@ -72414,10 +72427,10 @@ async function downloadRgWithLock(shareDir) {
72414
72427
  source: "share-bin-downloaded"
72415
72428
  };
72416
72429
  } finally {
72417
- downloadPromise = void 0;
72430
+ downloadPromise$1 = void 0;
72418
72431
  }
72419
72432
  })();
72420
- return downloadPromise;
72433
+ return downloadPromise$1;
72421
72434
  }
72422
72435
  function rgBinaryName() {
72423
72436
  return process.platform === "win32" ? "rg.exe" : "rg";
@@ -75402,7 +75415,7 @@ function messageOf(error) {
75402
75415
  }
75403
75416
  //#endregion
75404
75417
  //#region ../../packages/agent-core/src/tools/builtin/plugin/manage-plugin.md
75405
- var manage_plugin_default = "Manage this product's own plugins. Plugins are the vehicle for add-on capabilities: each one can contribute skills, MCP servers, shell hooks, and — only after an explicit activate — code that runs in this process.\n\nUse this tool in two situations: (a) a task needs a capability you do not currently have — exhaust your existing tools first and add a plugin only when the gap is real; (b) the user asks you to find, install, configure, inspect, or clean up a capability (e.g. \"install something that can render mermaid\", \"what plugins do you have\"). In both cases, remove or disable additions again when the work is done unless the capability is broadly reusable.\n\nChoose the lightest vehicle that solves the problem, in this order: a skill (prompt instructions — safest, no code, no process), an MCP server (an external process), a code plugin (runs in this process — the most powerful, needs its own approved `activate`).\n\n## Workflow\n\n1. `{\"action\":\"list\"}` — see what is already installed before adding anything. Do not grow a second plugin that duplicates an existing capability; enable, disable, or extend the one you already have.\n2. `{\"action\":\"marketplace\",\"query\":\"pdf\"}` — browse the catalog for a matching capability. Pass `source` to read a different catalog. `tier` is a display label only; judge an entry by its description and source. Entries whose origin previously produced a tripped plugin carry `quarantined: true`.\n3. `{\"action\":\"precheck\",\"source\":\"https://github.com/owner/repo\"}` — free read-only origin check before installing anything external that you have not vetted in this session: it reports past incidents recorded by the plugin center's immune memory (`quarantined:true` + the reason). Surface such a warning to the user BEFORE asking for the install approval.\n4. If nothing in the catalog fits and precheck is clean, you may search the web (`WebSearch` / `FetchURL`) and evaluate a repository yourself: read its README and manifest, prefer a pinned branch/tag/commit URL over a moving default, and check that it does something you cannot already do.\n5. To **make a capability yourself**: if the user invoked `/make-skill`, use the `MakeSkillPlan` → `MakeSkillApply` pair (it produces a valid skill package and registers it into this table automatically). Otherwise build a plugin directory by hand (a `scream.plugin.json` manifest plus your SKILL.md under `skills/`) and register it with `register_generated` — that route lands in this table, hot-applies the same turn, and stays manageable here (disable/remove/circuit). Writing a plain SKILL.md into the user skills directory is also a legitimate skill, but it does NOT appear in this table and cannot be managed with this tool.\n6. `{\"action\":\"install\",\"source\":\"https://github.com/owner/repo/tree/v1.2.0\"}` — install from a local path, a GitHub URL, or a zip URL. **Installing never executes code.** The plugin's files are copied and its record is registered; a manifest `entryPoint` stays dormant until step 8.\n7. `{\"action\":\"install\",\"source\":\"...\",\"upgrade\":true}` — same-name refresh: backs up the current files (single slot under `plugins/backups/<id>/`), swaps in the new ones, and reloads; `from`/`to` versions and any downgrade warning are reported. If activation then fails, `rollback` restores the previous files in one approved step.\n8. `{\"action\":\"enable\",\"id\":\"...\"}` / `{\"action\":\"disable\",\"id\":\"...\"}` / `{\"action\":\"set_mcp_enabled\",\"id\":\"...\",\"server\":\"...\",\"enabled\":false}` — turn contributed capabilities on and off.\n9. `{\"action\":\"activate\",\"id\":\"...\"}` — the only action that runs a plugin's code entry point in this process. Skip it for skill-only and MCP-only plugins; they need no activation.\n10. `{\"action\":\"info\",\"id\":\"...\"}` / `{\"action\":\"check\"}` — inspect one record in full, or the health of the table. Each entry also carries `usage: {calls, okCalls, okRate, lastUsedAt}` from this machine: zero calls since install marks it as a cleanup candidate; high usage with a high okRate is worth keeping when you must choose.\n11. `{\"action\":\"reset\",\"id\":\"...\"}` — recover a circuit-tripped or manually disabled plugin: clears its failure ledger, re-derives the record from disk, enables it, and hot-applies its capabilities. It never runs code — a code plugin still needs its own approved `activate`.\n12. `{\"action\":\"rollback\",\"id\":\"...\"}` — restore the pre-upgrade backup taken by step 7 in one approved step.\n13. `{\"action\":\"reload\"}` — re-read the plugin table from disk and get an added/removed/error summary.\n14. `{\"action\":\"remove\",\"id\":\"...\"}` (or `disable` to keep the record) — retreat once the capability is no longer wanted.\n\n## Circuit breaker (limbs that fail get pulled, the loop never dies)\n\nPlugin-owned capabilities are counted per plugin: every failed tool call (in-process plugin tools and plugin MCP servers) and every faulty event handler. A success clears the streak; three consecutive failures trip the breaker, and the failing result arrives with a `[circuit]` advisory: the plugin was marked with the reason, disabled persistently, its code deactivated, and its tools, MCP servers, skills, and hooks pulled from every live session. Nothing retries automatically — read the state with `check` (each plugin carries `circuit: {failures, tripped}`), then `reset` to give it another chance or `remove` to let it go. User-configured MCP servers and built-in tools are never charged to anyone's breaker.\n\nEvery trip ALSO writes the origin into the plugin center's immune memory (quarantine ledger): future `precheck`/`install`/`marketplace` calls for the same repository will surface that history before anything is executed again, even across sessions — and even if the broken plugin was removed afterwards.\n\nChanges are hot-applied to the running session wherever the host supports it: mutating results carry a `sync` report (`{ok, applied[], failed[]}`) naming exactly what landed — `skills.inject`, `mcp.add`/`mcp.remove`, tool teardown. An `install` deliberately reports no `mcp.add`: installing never starts a plugin's MCP process; that waits for an approved `enable`/`activate`/`reload`. Treat `sync.failed` as advisory (the mutation itself already succeeded) and re-check with `info`/`check` rather than re-running the mutation. Reload before judging whether a change landed.\n\n## Registration of your own output\n\n`{\"action\":\"register_generated\",\"source\":\"/abs/path/to/generated/plugin\"}` registers a plugin directory you produced locally, without a copy step. It refuses any manifest that declares an `entryPoint`: this action is normally auto-approved and must never become a route to running code. For a code plugin, use `install` and then `activate`.\n\n## Approval contract\n\n- Read-only actions (`list`, `info`, `check`, `marketplace`) run without prompting.\n- Every mutating action (`install`, `register_generated`, `enable`, `disable`, `set_mcp_enabled`, `activate`, `deactivate`, `remove`, `reset`, `reload`) requires the user's approval. State what you are about to do and why in one line, then call the tool — never describe a plugin change you have not made.\n- The approval prompt for `install` and `register_generated` shows the full source string, and a session grant applies to that exact source only.\n- `activate` is the step that executes third-party code; treat it as its own decision, made after you have read what the plugin does.\n- A rejected approval is an answer, not a retry cue. Report the refusal and continue without the capability.\n\n## Output\n\nEvery action returns a JSON string. Success is a compact result object; failure is\n`{\"error\":{\"code\",\"message\",\"next\"}}` with `isError: true`. Follow `next` rather than\nguessing at a repair — it names the call that resolves the problem.\n";
75418
+ var manage_plugin_default = "Manage this product's own plugins. Plugins are the vehicle for add-on capabilities: each one can contribute skills, MCP servers, shell hooks, and — only after an explicit activate — code that runs in this process.\n\nUse this tool in two situations: (a) a task needs a capability you do not currently have — exhaust your existing tools first and add a plugin only when the gap is real; (b) the user asks you to find, install, configure, inspect, or clean up a capability (e.g. \"install something that can render mermaid\", \"disable the plugin I no longer use\"). In both cases, remove or disable additions again when the work is done unless the capability is broadly reusable.\n\n**Not for inventory questions**: when the user asks what you have — in any wording (skills / plugins / capabilities / MCP / tools / 技能 / 插件 / 能力) — answer with **InspectOwnAssets** (the read-only inventory: every skill package on disk with live `invocable` status, plus MCP declarations). `{\"action\":\"list\"}` here is the **plugin management view**: a pre-change check of the plugin table before installing or modifying, not the answer to \"what can you do\".\n\nChoose the lightest vehicle that solves the problem, in this order: a skill (prompt instructions — safest, no code, no process), an MCP server (an external process), a code plugin (runs in this process — the most powerful, needs its own approved `activate`).\n\n## Workflow\n\n1. `{\"action\":\"list\"}` — see what is already installed before adding anything. To confirm the gap is real, start from an `InspectOwnAssets` inventory (what exists on disk and what is invocable), then check this table for an existing plugin that already covers it. Do not grow a second plugin that duplicates an existing capability; enable, disable, or extend the one you already have.\n2. `{\"action\":\"marketplace\",\"query\":\"pdf\"}` — browse the catalog for a matching capability. Pass `source` to read a different catalog. `tier` is a display label only; judge an entry by its description and source. Entries whose origin previously produced a tripped plugin carry `quarantined: true`.\n3. `{\"action\":\"precheck\",\"source\":\"https://github.com/owner/repo\"}` — free read-only origin check before installing anything external that you have not vetted in this session: it reports past incidents recorded by the plugin center's immune memory (`quarantined:true` + the reason). Surface such a warning to the user BEFORE asking for the install approval.\n4. If nothing in the catalog fits and precheck is clean, you may search the web (`WebSearch` / `FetchURL`) and evaluate a repository yourself: read its README and manifest, prefer a pinned branch/tag/commit URL over a moving default, and check that it does something you cannot already do.\n5. To **make a capability yourself**: if the user invoked `/make-skill`, use the `MakeSkillPlan` → `MakeSkillApply` pair (it produces a valid skill package and registers it into this table automatically). Otherwise build a plugin directory by hand (a `scream.plugin.json` manifest plus your SKILL.md under `skills/`) and register it with `register_generated` — that route lands in this table, hot-applies the same turn, and stays manageable here (disable/remove/circuit). Writing a plain SKILL.md into the user skills directory is also a legitimate skill, but it does NOT appear in this table and cannot be managed with this tool.\n6. `{\"action\":\"install\",\"source\":\"https://github.com/owner/repo/tree/v1.2.0\"}` — install from a local path, a GitHub URL, or a zip URL. **Installing never executes code.** The plugin's files are copied and its record is registered; a manifest `entryPoint` stays dormant until step 8.\n7. `{\"action\":\"install\",\"source\":\"...\",\"upgrade\":true}` — same-name refresh: backs up the current files (single slot under `plugins/backups/<id>/`), swaps in the new ones, and reloads; `from`/`to` versions and any downgrade warning are reported. If activation then fails, `rollback` restores the previous files in one approved step.\n8. `{\"action\":\"enable\",\"id\":\"...\"}` / `{\"action\":\"disable\",\"id\":\"...\"}` / `{\"action\":\"set_mcp_enabled\",\"id\":\"...\",\"server\":\"...\",\"enabled\":false}` — turn contributed capabilities on and off.\n9. `{\"action\":\"activate\",\"id\":\"...\"}` — the only action that runs a plugin's code entry point in this process. Skip it for skill-only and MCP-only plugins; they need no activation.\n10. `{\"action\":\"info\",\"id\":\"...\"}` / `{\"action\":\"check\"}` — inspect one record in full, or the health of the table. Each entry also carries `usage: {calls, okCalls, okRate, lastUsedAt}` from this machine: zero calls since install marks it as a cleanup candidate; high usage with a high okRate is worth keeping when you must choose.\n11. `{\"action\":\"reset\",\"id\":\"...\"}` — recover a circuit-tripped or manually disabled plugin: clears its failure ledger, re-derives the record from disk, enables it, and hot-applies its capabilities. It never runs code — a code plugin still needs its own approved `activate`.\n12. `{\"action\":\"rollback\",\"id\":\"...\"}` — restore the pre-upgrade backup taken by step 7 in one approved step.\n13. `{\"action\":\"reload\"}` — re-read the plugin table from disk and get an added/removed/error summary.\n14. `{\"action\":\"remove\",\"id\":\"...\"}` (or `disable` to keep the record) — retreat once the capability is no longer wanted.\n\n## Circuit breaker (limbs that fail get pulled, the loop never dies)\n\nPlugin-owned capabilities are counted per plugin: every failed tool call (in-process plugin tools and plugin MCP servers) and every faulty event handler. A success clears the streak; three consecutive failures trip the breaker, and the failing result arrives with a `[circuit]` advisory: the plugin was marked with the reason, disabled persistently, its code deactivated, and its tools, MCP servers, skills, and hooks pulled from every live session. Nothing retries automatically — read the state with `check` (each plugin carries `circuit: {failures, tripped}`), then `reset` to give it another chance or `remove` to let it go. User-configured MCP servers and built-in tools are never charged to anyone's breaker.\n\nEvery trip ALSO writes the origin into the plugin center's immune memory (quarantine ledger): future `precheck`/`install`/`marketplace` calls for the same repository will surface that history before anything is executed again, even across sessions — and even if the broken plugin was removed afterwards.\n\nChanges are hot-applied to the running session wherever the host supports it: mutating results carry a `sync` report (`{ok, applied[], failed[]}`) naming exactly what landed — `skills.inject`, `mcp.add`/`mcp.remove`, tool teardown. An `install` deliberately reports no `mcp.add`: installing never starts a plugin's MCP process; that waits for an approved `enable`/`activate`/`reload`. Treat `sync.failed` as advisory (the mutation itself already succeeded) and re-check with `info`/`check` rather than re-running the mutation. Reload before judging whether a change landed.\n\n## Registration of your own output\n\n`{\"action\":\"register_generated\",\"source\":\"/abs/path/to/generated/plugin\"}` registers a plugin directory you produced locally, without a copy step. It refuses any manifest that declares an `entryPoint`: this action is normally auto-approved and must never become a route to running code. For a code plugin, use `install` and then `activate`.\n\n## Approval contract\n\n- Read-only actions (`list`, `info`, `check`, `marketplace`) run without prompting.\n- Every mutating action (`install`, `register_generated`, `enable`, `disable`, `set_mcp_enabled`, `activate`, `deactivate`, `remove`, `reset`, `reload`) requires the user's approval. State what you are about to do and why in one line, then call the tool — never describe a plugin change you have not made.\n- The approval prompt for `install` and `register_generated` shows the full source string, and a session grant applies to that exact source only.\n- `activate` is the step that executes third-party code; treat it as its own decision, made after you have read what the plugin does.\n- A rejected approval is an answer, not a retry cue. Report the refusal and continue without the capability.\n\n## Output\n\nEvery action returns a JSON string. Success is a compact result object; failure is\n`{\"error\":{\"code\",\"message\",\"next\"}}` with `isError: true`. Follow `next` rather than\nguessing at a repair — it names the call that resolves the problem.\n";
75406
75419
  //#endregion
75407
75420
  //#region ../../packages/agent-core/src/tools/builtin/plugin/manage-plugin.ts
75408
75421
  /** Every action this tool understands. */
@@ -77139,7 +77152,7 @@ function describeError(error) {
77139
77152
  }
77140
77153
  //#endregion
77141
77154
  //#region ../../packages/agent-core/src/tools/builtin/state/inspect-own-assets.md
77142
- var inspect_own_assets_default = "Use this tool to inspect the agent's own persistent assets: skills, MCP server declarations, configuration files, the memory store, and the knowledge base. It reports what exists, where it lives, and whether it looks valid.\n\n**When to use:**\n- The user asks \"what skills do you have?\", \"show me your mcp config\", \"how is your memory set up?\", \"where is your knowledge base?\"\n- Auditing your own configuration and data (e.g. checking whether mcp.json parses, whether skill frontmatter is intact)\n\nYou must NOT modify any of these assets unless the user explicitly asks you to — this tool is strictly read-only.\n\n**When NOT to use:**\n- Reading the user's workspace files — use `read` / `glob` / `grep` instead\n- Writing or editing anything — this tool is strictly read-only\n\n**How to use:**\n- Call with no arguments (or `scope: \"all\"`) to inspect everything\n- Narrow with `scope: \"skills\"` / `\"mcp\"` / `\"config\"` / `\"memory\"` / `\"knowledge\"` to inspect a single category\n\nThis tool never writes, creates, or modifies any file.\n";
77155
+ var inspect_own_assets_default = "Use this tool to inspect the agent's own persistent assets: skills, MCP server declarations, configuration files, the memory store, and the knowledge base. It reports what exists, where it lives, and whether it looks valid.\n\n**When to use:**\n- The user asks \"what skills do you have?\", \"show me your mcp config\", \"how is your memory set up?\", \"where is your knowledge base?\", \"检查技能/插件状态\"\n- Auditing your own configuration and data (e.g. checking whether mcp.json parses, why a skill is not usable, which plugin directories are unregistered)\n\n**Inventory questions — this tool is the single answering view.** When the user asks what you have, in ANY wording (skills / plugins / capabilities / MCP / tools / 技能 / 插件 / 能力), treat it as one intent and answer from this inventory: it reports the skill packages in the three standard skill scopes (user, plugin-managed, project) with live `invocable` status from the skill registry, plus MCP declarations. Every \"not invocable\" entry carries its concrete reason (registered but not invocable / broken frontmatter with the parse message / unregistered), and plugin-managed entries show whether their directory is registered in the plugin table, with unregistered directories listed separately — so a status question is answered by this single call, no need to consult ManagePlugin for the same facts. Skills discovered from non-standard locations (nested directories, `.agents/skills`) may not appear here. Pair it with the system-prompt skills section for the \"callable right now\" view. Division of labor: **InspectOwnAssets = inventory (read-only)**, **ManagePlugin = changes (install/enable/remove)**, **Skill tool = invocation**.\n\nYou must NOT modify any of these assets unless the user explicitly asks you to — this tool is strictly read-only.\n\n**When NOT to use:**\n- Reading the user's workspace files — use `read` / `glob` / `grep` instead\n- Writing or editing anything — this tool is strictly read-only\n\n**How to use:**\n- Call with no arguments (or `scope: \"all\"`) to inspect everything\n- Narrow with `scope: \"skills\"` / `\"mcp\"` / `\"config\"` / `\"memory\"` / `\"knowledge\"` to inspect a single category\n\nThis tool never writes, creates, or modifies any file.\n";
77143
77156
  const InspectOwnAssetsInputSchema = z.object({ scope: z.enum([
77144
77157
  "all",
77145
77158
  "skills",
@@ -77148,8 +77161,14 @@ const InspectOwnAssetsInputSchema = z.object({ scope: z.enum([
77148
77161
  "memory",
77149
77162
  "knowledge"
77150
77163
  ]).optional().describe("Which self-assets to inspect: 'all' (default) reports everything; narrow to 'skills', 'mcp', 'config', 'memory', or 'knowledge'.") });
77151
- /** Bytes to read from the head of a file when checking frontmatter. */
77152
- const FRONTMATTER_READ_LIMIT = 32 * 1024;
77164
+ /**
77165
+ * Skills larger than this are reported as broken rather than parsed. Real
77166
+ * skill bundles are a few KB; the cap exists only to guard against reading a
77167
+ * pathological file (e.g. a multi-GB binary misnamed SKILL.md) in one shot.
77168
+ */
77169
+ const SKILL_FILE_READ_LIMIT = 4 * 1024 * 1024;
77170
+ /** Recursion bound for counting nested SKILL.md files inside unregistered dirs. */
77171
+ const ORPHAN_SCAN_DEPTH = 4;
77153
77172
  /**
77154
77173
  * Common documentation files shipped inside skill/plugin bundles are not
77155
77174
  * skills; matched case-insensitively against top-level flat `.md` entries
@@ -77189,34 +77208,73 @@ function describeFile(info) {
77189
77208
  if (!info.exists) return "missing";
77190
77209
  return `${info.size} bytes`;
77191
77210
  }
77192
- /** Frontmatter check (bounded read): starts with `---` and contains a `name:` line. */
77193
- async function checkFrontmatter(path) {
77194
- let handle;
77211
+ async function isFile$1(path) {
77195
77212
  try {
77196
- handle = await open(path, "r");
77197
- const buffer = Buffer.alloc(FRONTMATTER_READ_LIMIT);
77198
- const { bytesRead } = await handle.read(buffer, 0, FRONTMATTER_READ_LIMIT, 0);
77199
- const head = buffer.subarray(0, bytesRead).toString("utf-8").split("\n").slice(0, 25);
77200
- if (head[0]?.trim() !== "---") return "missing";
77201
- return head.some((line) => /^name\s*:/.test(line)) ? "ok" : "broken";
77213
+ return (await stat(path)).isFile();
77202
77214
  } catch {
77203
- return "missing";
77204
- } finally {
77205
- await handle?.close().catch(() => {});
77215
+ return false;
77206
77216
  }
77207
77217
  }
77208
- /** True if a directory is a directory-based skill (contains SKILL.md). */
77209
- async function isSkillDir(dir) {
77218
+ /**
77219
+ * Parse a skill file the same way the registry does (`skill/parser`), so the
77220
+ * inventory's status and names agree with what is actually invocable: a file
77221
+ * whose frontmatter fails YAML parsing is `broken` with the real message, not
77222
+ * a heuristic "ok" based on the presence of a `name:` line.
77223
+ */
77224
+ async function parseSkillEntry(path, fallbackName, requireFrontmatter) {
77225
+ let info;
77210
77226
  try {
77211
- return (await stat(join$1(dir, "SKILL.md"))).isFile();
77227
+ info = await stat(path);
77212
77228
  } catch {
77213
- return false;
77229
+ return {
77230
+ name: fallbackName,
77231
+ frontmatter: "missing"
77232
+ };
77233
+ }
77234
+ if (info.size > SKILL_FILE_READ_LIMIT) return {
77235
+ name: fallbackName,
77236
+ frontmatter: "ok"
77237
+ };
77238
+ let text;
77239
+ try {
77240
+ text = await readFile(path, "utf8");
77241
+ } catch {
77242
+ return {
77243
+ name: fallbackName,
77244
+ frontmatter: "missing"
77245
+ };
77246
+ }
77247
+ if (text.split(/\r?\n/, 1)[0]?.trim() !== "---") {
77248
+ if (requireFrontmatter) return {
77249
+ name: fallbackName,
77250
+ frontmatter: "missing"
77251
+ };
77252
+ return {
77253
+ name: fallbackName,
77254
+ frontmatter: "ok"
77255
+ };
77256
+ }
77257
+ try {
77258
+ return {
77259
+ name: (await parseSkillFromFile({
77260
+ skillMdPath: path,
77261
+ skillDirName: fallbackName,
77262
+ source: "user"
77263
+ })).name,
77264
+ frontmatter: "ok"
77265
+ };
77266
+ } catch (error) {
77267
+ return {
77268
+ name: fallbackName,
77269
+ frontmatter: "broken",
77270
+ reason: error instanceof SkillParseError ? error.message : error instanceof Error ? error.message : String(error)
77271
+ };
77214
77272
  }
77215
77273
  }
77216
77274
  /**
77217
- * List skill entries under a managed skills directory, mirroring the loader's
77218
- * rules: skip dot-entries, node_modules and README.md; directory skills must
77219
- * contain SKILL.md; flat skills are non-README `.md` files.
77275
+ * List skill entries under a skills directory, mirroring the loader's rules:
77276
+ * skip dot-entries, node_modules and README.md; directory skills must contain
77277
+ * SKILL.md; flat skills are non-README `.md` files (frontmatter optional).
77220
77278
  */
77221
77279
  async function listSkills(dir) {
77222
77280
  let entries;
@@ -77229,29 +77287,141 @@ async function listSkills(dir) {
77229
77287
  for (const entry of entries) {
77230
77288
  if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
77231
77289
  if (entry.isDirectory()) {
77232
- if (!await isSkillDir(join$1(dir, entry.name))) continue;
77233
77290
  const skillMd = join$1(dir, entry.name, "SKILL.md");
77234
- const fm = await checkFrontmatter(skillMd);
77291
+ if (!await isFile$1(skillMd)) continue;
77292
+ const parsed = await parseSkillEntry(skillMd, entry.name, true);
77235
77293
  out.push({
77236
- name: entry.name,
77294
+ ...parsed,
77237
77295
  path: skillMd,
77238
- kind: "dir",
77239
- frontmatter: fm
77296
+ kind: "dir"
77240
77297
  });
77241
77298
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
77242
77299
  if (DOCUMENTATION_MARKDOWN_LOWER.has(entry.name.toLowerCase())) continue;
77300
+ const path = join$1(dir, entry.name);
77301
+ const parsed = await parseSkillEntry(path, entry.name.slice(0, -3), false);
77243
77302
  out.push({
77244
- name: entry.name.slice(0, -3),
77245
- path: join$1(dir, entry.name),
77246
- kind: "flat",
77247
- frontmatter: "ok"
77303
+ ...parsed,
77304
+ path,
77305
+ kind: "flat"
77248
77306
  });
77249
77307
  }
77250
77308
  }
77251
77309
  return out.toSorted((a, b) => a.name.localeCompare(b.name));
77252
77310
  }
77253
- function formatSkillEntry(entry) {
77254
- return `- ${entry.name} ${entry.kind === "dir" ? "dir" : "flat"} — ${entry.frontmatter} — \`${entry.path}\``;
77311
+ /** Count every SKILL.md reachable under `dirPath` (inclusive), depth-limited. */
77312
+ async function countNestedSkillFiles(dirPath, depth) {
77313
+ if (depth > ORPHAN_SCAN_DEPTH) return 0;
77314
+ let entries;
77315
+ try {
77316
+ entries = await readdir(dirPath, { withFileTypes: true });
77317
+ } catch {
77318
+ return 0;
77319
+ }
77320
+ let count = 0;
77321
+ for (const entry of entries) {
77322
+ if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
77323
+ if (entry.isDirectory()) count += await countNestedSkillFiles(join$1(dirPath, entry.name), depth + 1);
77324
+ else if (entry.name === "SKILL.md") count++;
77325
+ }
77326
+ return count;
77327
+ }
77328
+ /**
77329
+ * Describe an unregistered directory under the managed plugins root so the
77330
+ * inventory can surface things the plugin table cannot see: orphan skill
77331
+ * bundles, nested skill trees, and dormant code plugins (entryPoint) that
77332
+ * would run arbitrary code on install+activate.
77333
+ */
77334
+ async function describeUnregisteredDir(dirPath) {
77335
+ const parts = [];
77336
+ const topLevelSkillMd = await isFile$1(join$1(dirPath, "SKILL.md"));
77337
+ const totalSkills = await countNestedSkillFiles(dirPath, 0);
77338
+ if (topLevelSkillMd) parts.push(totalSkills > 1 ? `skill bundle (${totalSkills} SKILL.md)` : "skill bundle");
77339
+ else if (totalSkills > 0) parts.push(`${totalSkills} nested skills (not registered)`);
77340
+ try {
77341
+ const manifestText = await readFile(join$1(dirPath, "scream.plugin.json"), "utf8");
77342
+ let hasEntryPoint = false;
77343
+ try {
77344
+ const manifest = JSON.parse(manifestText);
77345
+ hasEntryPoint = typeof manifest.entryPoint === "string" && manifest.entryPoint.length > 0;
77346
+ } catch {
77347
+ parts.push("scream.plugin.json (unparseable)");
77348
+ return parts.join("; ");
77349
+ }
77350
+ parts.push(hasEntryPoint ? "code plugin (entryPoint)" : "manifest but not registered");
77351
+ } catch {}
77352
+ if (parts.length === 0) parts.push("no manifest or skill files");
77353
+ return parts.join("; ");
77354
+ }
77355
+ /**
77356
+ * Plugin-managed skills live under `<managedDir>/<plugin>/SKILL.md`. When the
77357
+ * plugin table (via `agent.toolServices.plugins`) is reachable, entries carry
77358
+ * their registration status and unregistered directories are reported in a
77359
+ * dedicated section; without it, the listing falls back to a pure file scan.
77360
+ */
77361
+ async function listManagedSkills(managedDir, records) {
77362
+ let plugins;
77363
+ try {
77364
+ plugins = await readdir(managedDir, { withFileTypes: true });
77365
+ } catch {
77366
+ return {
77367
+ entries: [],
77368
+ orphanLines: []
77369
+ };
77370
+ }
77371
+ const entries = [];
77372
+ const orphanLines = [];
77373
+ for (const plugin of plugins) {
77374
+ if (!plugin.isDirectory() || plugin.name.startsWith(".")) continue;
77375
+ const dirPath = join$1(managedDir, plugin.name);
77376
+ const skillMd = join$1(dirPath, "SKILL.md");
77377
+ const record = records?.get(plugin.name);
77378
+ if (records === void 0 || record === void 0) {
77379
+ if (records === void 0) {
77380
+ if (!await isFile$1(skillMd)) continue;
77381
+ const parsed = await parseSkillEntry(skillMd, plugin.name, true);
77382
+ entries.push({
77383
+ ...parsed,
77384
+ path: skillMd,
77385
+ kind: "dir",
77386
+ plugin: plugin.name
77387
+ });
77388
+ } else if (await isFile$1(skillMd)) orphanLines.push(`- ${plugin.name} — ${await describeUnregisteredDir(dirPath)} — \`${dirPath}\``);
77389
+ else if (await isFile$1(join$1(dirPath, "scream.plugin.json"))) orphanLines.push(`- ${plugin.name} — ${await describeUnregisteredDir(dirPath)} — \`${dirPath}\``);
77390
+ else if (await countNestedSkillFiles(dirPath, 0) > 0) orphanLines.push(`- ${plugin.name} — ${await describeUnregisteredDir(dirPath)} — \`${dirPath}\``);
77391
+ continue;
77392
+ }
77393
+ if (await isFile$1(skillMd)) {
77394
+ const parsed = await parseSkillEntry(skillMd, plugin.name, true);
77395
+ entries.push({
77396
+ ...parsed,
77397
+ path: skillMd,
77398
+ kind: "dir",
77399
+ plugin: plugin.name,
77400
+ registered: true
77401
+ });
77402
+ }
77403
+ }
77404
+ return {
77405
+ entries,
77406
+ orphanLines
77407
+ };
77408
+ }
77409
+ /**
77410
+ * Traffic-light status for a skill entry. The registry is the single source of
77411
+ * truth for what is callable; everything else is a disk-side explanation.
77412
+ */
77413
+ function classify(entry, invocableNames, registeredNames) {
77414
+ const variants = [entry.name];
77415
+ if (entry.plugin !== void 0) variants.push(`${entry.plugin}:${entry.name}`);
77416
+ if (variants.some((v) => invocableNames.has(v))) return "invocable";
77417
+ if (variants.some((v) => registeredNames.has(v))) return "not invocable — registered but not invocable";
77418
+ if (entry.frontmatter === "broken") return `not invocable — broken: ${entry.reason ?? "frontmatter failed to parse"}`;
77419
+ return "not invocable — unregistered";
77420
+ }
77421
+ function formatSkillEntry(entry, status) {
77422
+ const origin = entry.plugin === void 0 ? "" : ` — plugin: ${entry.plugin}`;
77423
+ const registration = entry.registered === true ? " — registered" : "";
77424
+ return `- ${entry.name} — ${entry.kind} — ${entry.frontmatter} — ${status}${origin}${registration} — \`${entry.path}\``;
77255
77425
  }
77256
77426
  async function inspectConfig(home, userHome) {
77257
77427
  const items = [
@@ -77267,52 +77437,50 @@ async function inspectConfig(home, userHome) {
77267
77437
  }
77268
77438
  return lines.join("\n");
77269
77439
  }
77270
- async function inspectSkills(home, userHome, cwd) {
77440
+ async function inspectSkills(home, userHome, cwd, agent) {
77271
77441
  const { userDir, projectDir } = await resolveSkillInstallPaths({
77272
77442
  userHomeDir: userHome,
77273
77443
  workDir: cwd
77274
77444
  });
77445
+ const registry = agent.skills?.registry;
77446
+ const invocableNames = new Set((registry?.listInvocableSkills() ?? []).map((s) => s.name));
77447
+ const registeredNames = new Set((registry?.listSkills?.() ?? []).map((s) => s.name));
77448
+ const manager = agent.toolServices?.plugins;
77449
+ const pluginRecords = manager === void 0 ? void 0 : new Map(manager.list().map((r) => [r.id, r]));
77275
77450
  const sections = ["## Skills", ""];
77451
+ let invocableCount = 0;
77452
+ let totalCount = 0;
77453
+ const track = (entry) => {
77454
+ totalCount++;
77455
+ if (classify(entry, invocableNames, registeredNames) === "invocable") invocableCount++;
77456
+ return formatSkillEntry(entry, classify(entry, invocableNames, registeredNames));
77457
+ };
77276
77458
  const userEntries = await listSkills(userDir);
77277
77459
  sections.push(`User skills (${userDir}): ${userEntries.length === 0 ? "none" : ""}`);
77278
- sections.push(...userEntries.length > 0 ? userEntries.map(formatSkillEntry) : []);
77460
+ sections.push(...userEntries.map(track));
77279
77461
  const extraDir = join$1(home, "plugins", "managed");
77280
- const extraEntries = await listManagedSkills(extraDir);
77462
+ const managed = await listManagedSkills(extraDir, pluginRecords);
77281
77463
  sections.push("");
77282
- sections.push(`Plugin-managed skills (${extraDir}): ${extraEntries.length === 0 ? "none" : ""}`);
77283
- sections.push(...extraEntries.length > 0 ? extraEntries.map(formatSkillEntry) : []);
77464
+ sections.push(`Plugin-managed skills (${extraDir}): ${managed.entries.length === 0 ? "none" : ""}`);
77465
+ sections.push(...managed.entries.map(track));
77466
+ if (pluginRecords !== void 0) for (const record of pluginRecords.values()) {
77467
+ const warns = record.diagnostics.filter((d) => d.severity === "warn");
77468
+ if (warns.length === 0) continue;
77469
+ sections.push(`plugin ${record.id}: warnings: [${warns.map((d) => d.message).join(" | ")}]`);
77470
+ }
77471
+ if (managed.orphanLines.length > 0) {
77472
+ sections.push("");
77473
+ sections.push(`Unregistered plugin dirs (${extraDir}):`);
77474
+ sections.push(...managed.orphanLines);
77475
+ }
77284
77476
  const projectEntries = await listSkills(projectDir);
77285
77477
  sections.push("");
77286
77478
  sections.push(`Project skills (${projectDir}): ${projectEntries.length === 0 ? "none" : ""}`);
77287
- sections.push(...projectEntries.length > 0 ? projectEntries.map(formatSkillEntry) : []);
77479
+ sections.push(...projectEntries.map((e) => track(e)));
77480
+ sections.push("");
77481
+ sections.push(`Invocable now: ${invocableCount}/${totalCount} (against the live skill registry; entries marked "not invocable" carry their reason — registered-but-not, broken frontmatter, or unregistered)`);
77288
77482
  return sections.join("\n");
77289
77483
  }
77290
- /**
77291
- * List plugin-managed skills: each `<dir>/SKILL.md` under a managed plugin
77292
- * directory is a skill entry (Extra source, mirroring plugin/manager.ts).
77293
- */
77294
- async function listManagedSkills(managedDir) {
77295
- let plugins;
77296
- try {
77297
- plugins = await readdir(managedDir, { withFileTypes: true });
77298
- } catch {
77299
- return [];
77300
- }
77301
- const out = [];
77302
- for (const plugin of plugins) {
77303
- if (!plugin.isDirectory() || plugin.name.startsWith(".")) continue;
77304
- const skillMd = join$1(managedDir, plugin.name, "SKILL.md");
77305
- if (!await isSkillDir(join$1(managedDir, plugin.name))) continue;
77306
- const fm = await checkFrontmatter(skillMd);
77307
- out.push({
77308
- name: plugin.name,
77309
- path: skillMd,
77310
- kind: "dir",
77311
- frontmatter: fm
77312
- });
77313
- }
77314
- return out.toSorted((a, b) => a.name.localeCompare(b.name));
77315
- }
77316
77484
  /** mcp.json files larger than this are reported as oversize and not parsed. */
77317
77485
  const MCP_CONFIG_SIZE_LIMIT = 1024 * 1024;
77318
77486
  async function inspectMcp(home, cwd) {
@@ -77404,7 +77572,7 @@ var InspectOwnAssetsTool = class {
77404
77572
  const scope = args.scope ?? "all";
77405
77573
  const sections = [];
77406
77574
  if (scope === "all" || scope === "config") sections.push(await inspectConfig(home, userHome));
77407
- if (scope === "all" || scope === "skills") sections.push(await inspectSkills(home, userHome, cwd));
77575
+ if (scope === "all" || scope === "skills") sections.push(await inspectSkills(home, userHome, cwd, this.agent));
77408
77576
  if (scope === "all" || scope === "mcp") sections.push(await inspectMcp(home, cwd));
77409
77577
  if (scope === "all" || scope === "memory") sections.push(await inspectMemory(home));
77410
77578
  if (scope === "all" || scope === "knowledge") sections.push(await inspectKnowledge(home));
@@ -125389,6 +125557,92 @@ const PULSE_WAVE_FRAMES = [
125389
125557
  }
125390
125558
  ];
125391
125559
  //#endregion
125560
+ //#region src/tui/commands/knowledge-store.ts
125561
+ let knowledgeStoreInstance;
125562
+ let embeddingEngineInstance;
125563
+ let embeddingStatus = "idle";
125564
+ function getEmbeddingCacheDir() {
125565
+ const dir = join(getDataDir(), "cache", "fastembed");
125566
+ mkdirSync(dir, { recursive: true });
125567
+ return dir;
125568
+ }
125569
+ /** Subdirectory fastembed uses for the BGESmallZH model inside the cache dir. */
125570
+ const EMBEDDING_MODEL_DIR = "fast-bge-small-zh-v1.5";
125571
+ /**
125572
+ * Whether the embedding model has been downloaded to the local cache.
125573
+ * Checks the ONNX weights plus the config and tokenizer sidecars — all
125574
+ * required for FlagEmbedding.init to load without network access (a missing
125575
+ * tokenizer would trigger a network fetch and violate the warm-up's
125576
+ * offline-load guarantee).
125577
+ */
125578
+ function isEmbeddingModelCached() {
125579
+ const modelDir = join(getEmbeddingCacheDir(), EMBEDDING_MODEL_DIR);
125580
+ return existsSync(join(modelDir, "model_optimized.onnx")) && existsSync(join(modelDir, "config.json")) && existsSync(join(modelDir, "tokenizer.json"));
125581
+ }
125582
+ async function getKnowledgeStore() {
125583
+ if (knowledgeStoreInstance === void 0) {
125584
+ knowledgeStoreInstance = new KnowledgeStore(getDataDir());
125585
+ await knowledgeStoreInstance.init();
125586
+ embeddingEngineInstance = createFastEmbedEngine(getEmbeddingCacheDir());
125587
+ knowledgeStoreInstance.setEmbeddingEngine(embeddingEngineInstance);
125588
+ }
125589
+ return knowledgeStoreInstance;
125590
+ }
125591
+ function getEmbeddingStatus() {
125592
+ return embeddingStatus;
125593
+ }
125594
+ /**
125595
+ * Manually trigger the embedding model download/load.
125596
+ * Only mutates embeddingStatus; the actual download is delegated to
125597
+ * EmbeddingEngine.ensureReady() and saves the model to the shared cache dir.
125598
+ * Returns { ok: true } on success, or { ok: false, error } on failure.
125599
+ * Concurrent calls join the in-flight download and share its result instead
125600
+ * of failing — the startup warm-up and a user-initiated download must never
125601
+ * race into a spurious "download already in progress" error.
125602
+ */
125603
+ async function startManualEmbeddingDownload() {
125604
+ if (embeddingEngineInstance === void 0) return {
125605
+ ok: false,
125606
+ error: "embedding engine not initialized"
125607
+ };
125608
+ if (embeddingEngineInstance.available) {
125609
+ embeddingStatus = "ready";
125610
+ return {
125611
+ ok: true,
125612
+ alreadyReady: true
125613
+ };
125614
+ }
125615
+ if (downloadPromise !== void 0) return downloadPromise;
125616
+ embeddingStatus = "downloading";
125617
+ downloadPromise = performDownload().finally(() => {
125618
+ downloadPromise = void 0;
125619
+ });
125620
+ return downloadPromise;
125621
+ }
125622
+ let downloadPromise;
125623
+ async function performDownload() {
125624
+ try {
125625
+ let ok = await embeddingEngineInstance.ensureReady();
125626
+ let error = ok ? void 0 : embeddingEngineInstance.lastError;
125627
+ if (!ok) {
125628
+ clearEmbeddingModelCache(getEmbeddingCacheDir());
125629
+ ok = await embeddingEngineInstance.ensureReady();
125630
+ error = ok ? void 0 : embeddingEngineInstance.lastError;
125631
+ }
125632
+ embeddingStatus = ok ? "ready" : "failed";
125633
+ return {
125634
+ ok,
125635
+ error
125636
+ };
125637
+ } catch (error) {
125638
+ embeddingStatus = "failed";
125639
+ return {
125640
+ ok: false,
125641
+ error: error instanceof Error ? error.message : String(error)
125642
+ };
125643
+ }
125644
+ }
125645
+ //#endregion
125392
125646
  //#region src/tui/commands/experimental-flags.ts
125393
125647
  let snapshot = {};
125394
125648
  /** Replace the cached flag snapshot. Call once after fetching via `harness.getExperimentalFlags()`. */
@@ -126698,31 +126952,16 @@ function getWireTypeOptions() {
126698
126952
  }
126699
126953
  function getThinkingOptions() {
126700
126954
  return [
126701
- {
126702
- value: "off",
126703
- label: t("prompts.thinking_off")
126704
- },
126705
- {
126706
- value: "low",
126707
- label: t("prompts.thinking_low")
126708
- },
126709
- {
126710
- value: "medium",
126711
- label: t("prompts.thinking_medium")
126712
- },
126713
- {
126714
- value: "high",
126715
- label: t("prompts.thinking_high")
126716
- },
126717
- {
126718
- value: "xhigh",
126719
- label: t("prompts.thinking_xhigh")
126720
- },
126721
- {
126722
- value: "max",
126723
- label: t("prompts.thinking_max")
126724
- }
126725
- ];
126955
+ "off",
126956
+ "low",
126957
+ "medium",
126958
+ "high",
126959
+ "xhigh",
126960
+ "max"
126961
+ ].map((level) => ({
126962
+ value: level,
126963
+ label: level
126964
+ }));
126726
126965
  }
126727
126966
  function getImageOptions() {
126728
126967
  return [{
@@ -131469,7 +131708,7 @@ async function guidedGoalSetup(host) {
131469
131708
  host.showNotice(t("goal.storm_breaker"), t("goal.conflict_loop"));
131470
131709
  return;
131471
131710
  }
131472
- const { TextInputDialogComponent } = await import("./text-input-dialog-B9uwX1Uy.mjs");
131711
+ const { TextInputDialogComponent } = await import("./text-input-dialog-Dr42qLMU.mjs");
131473
131712
  const initialDesc = await promptText(host, TextInputDialogComponent, {
131474
131713
  title: t("goal.setup_title_initial"),
131475
131714
  subtitle: t("goal.setup_desc_hint"),
@@ -131490,7 +131729,7 @@ async function guidedGoalSetup(host) {
131490
131729
  await showGoalConfigWizard(host, session, confirmed.trim() || objective, false);
131491
131730
  }
131492
131731
  async function showGoalConfigWizard(host, session, objective, replace) {
131493
- const { TextInputDialogComponent } = await import("./text-input-dialog-B9uwX1Uy.mjs");
131732
+ const { TextInputDialogComponent } = await import("./text-input-dialog-Dr42qLMU.mjs");
131494
131733
  const turnInput = await promptNumber(host, TextInputDialogComponent, {
131495
131734
  title: t("goal.wizard_title", { objective }),
131496
131735
  subtitle: t("goal.budget_turns_hint"),
@@ -138296,7 +138535,7 @@ function buildRoleAdditionalText(prefs) {
138296
138535
  if (items.length === 0 && (doNot === void 0 || doNot.length === 0) && !hasToolPriority) return "";
138297
138536
  lines.push("", ...items);
138298
138537
  if (doNot !== void 0 && doNot.length > 0) lines.push("", "## Do NOT (explicit prohibitions — NEVER do these)", doNot);
138299
- if (hasToolPriority) lines.push("", "## Tool priority (set via /like — HIGHEST PRIORITY)", prefs.toolPriority === "skill" ? "For every user request: (1) first analyze the intent, (2) then identify whether any installed skill matches THIS request, (3) if one matches, invoke the Skill tool with it as your FIRST action — before MCP tools or doing it yourself, (4) if none matches, proceed with MCP tools or solve it yourself. Consult the skill list shown in the Skill tool description whenever you are unsure what is installed." : "For every user request: (1) first analyze the intent, (2) then identify whether any available MCP tool matches THIS request, (3) if one matches, use it as your FIRST action — before the Skill tool or doing it yourself, (4) if none matches, proceed with the Skill tool or solve it yourself.");
138538
+ if (hasToolPriority) lines.push("", "## Tool priority (set via /like — HIGHEST PRIORITY)", prefs.toolPriority === "skill" ? "For every user request: (1) first analyze the intent, (2) then identify whether any installed skill matches THIS request, (3) if one matches, invoke the Skill tool with it as your FIRST action — before MCP tools or doing it yourself, (4) if none matches, proceed with MCP tools or solve it yourself. When the user asks what you have (skills / plugins / capabilities, any wording), answer from an InspectOwnAssets inventory; the skills section of the system prompt is the authoritative view of what is invocable right now." : "For every user request: (1) first analyze the intent, (2) then identify whether any available MCP tool matches THIS request, (3) if one matches, use it as your FIRST action — before the Skill tool or doing it yourself, (4) if none matches, proceed with the Skill tool or solve it yourself.");
138300
138539
  lines.push("", t("like.priority"));
138301
138540
  return lines.join("\n");
138302
138541
  }
@@ -138426,65 +138665,6 @@ function openUrl(url) {
138426
138665
  execFile(command[0], command[1], () => {});
138427
138666
  }
138428
138667
  //#endregion
138429
- //#region src/tui/commands/knowledge-store.ts
138430
- let knowledgeStoreInstance;
138431
- let embeddingEngineInstance;
138432
- let embeddingStatus = "idle";
138433
- function getEmbeddingCacheDir() {
138434
- const dir = join(getDataDir(), "cache", "fastembed");
138435
- mkdirSync(dir, { recursive: true });
138436
- return dir;
138437
- }
138438
- async function getKnowledgeStore() {
138439
- if (knowledgeStoreInstance === void 0) {
138440
- knowledgeStoreInstance = new KnowledgeStore(getDataDir());
138441
- await knowledgeStoreInstance.init();
138442
- embeddingEngineInstance = createFastEmbedEngine(getEmbeddingCacheDir());
138443
- knowledgeStoreInstance.setEmbeddingEngine(embeddingEngineInstance);
138444
- }
138445
- return knowledgeStoreInstance;
138446
- }
138447
- function getEmbeddingStatus() {
138448
- return embeddingStatus;
138449
- }
138450
- /**
138451
- * Manually trigger the embedding model download/load.
138452
- * Only mutates embeddingStatus; the actual download is delegated to
138453
- * EmbeddingEngine.ensureReady() and saves the model to the shared cache dir.
138454
- * Returns { ok: true } on success, or { ok: false, error } on failure.
138455
- * Concurrent calls while a download is already in flight are ignored.
138456
- */
138457
- async function startManualEmbeddingDownload() {
138458
- if (embeddingEngineInstance === void 0) return {
138459
- ok: false,
138460
- error: "embedding engine not initialized"
138461
- };
138462
- if (embeddingStatus === "downloading") return {
138463
- ok: false,
138464
- error: "download already in progress"
138465
- };
138466
- if (embeddingEngineInstance.available) {
138467
- embeddingStatus = "ready";
138468
- return {
138469
- ok: true,
138470
- alreadyReady: true
138471
- };
138472
- }
138473
- embeddingStatus = "downloading";
138474
- let ok = await embeddingEngineInstance.ensureReady();
138475
- let error = ok ? void 0 : embeddingEngineInstance.lastError;
138476
- if (!ok) {
138477
- clearEmbeddingModelCache(getEmbeddingCacheDir());
138478
- ok = await embeddingEngineInstance.ensureReady();
138479
- error = ok ? void 0 : embeddingEngineInstance.lastError;
138480
- }
138481
- embeddingStatus = ok ? "ready" : "failed";
138482
- return {
138483
- ok,
138484
- error
138485
- };
138486
- }
138487
- //#endregion
138488
138668
  //#region src/tui/commands/knowledge-web.ts
138489
138669
  /**
138490
138670
  * /knowledge web — 知识图谱可视化
@@ -140174,6 +140354,7 @@ async function handleIngest(host) {
140174
140354
  }
140175
140355
  const store = await getKnowledgeStore();
140176
140356
  const llm = makeLlmCaller(host);
140357
+ if (!await ensureEmbeddingReadyInteractive(host)) return;
140177
140358
  const spinner = host.showProgressSpinner(t("knowledge.ingesting"));
140178
140359
  try {
140179
140360
  if (stats.isDirectory()) {
@@ -140184,21 +140365,35 @@ async function handleIngest(host) {
140184
140365
  ok: result.failed === 0,
140185
140366
  label: t("knowledge.batch_done")
140186
140367
  });
140368
+ const coverage = await store.embeddingCoverage();
140369
+ const syncLine = t("knowledge.sync_counts", {
140370
+ created: String(result.created),
140371
+ updated: String(result.updated),
140372
+ unchanged: String(result.unchanged)
140373
+ });
140187
140374
  if (result.failed > 0) {
140188
140375
  const summary = [
140189
140376
  t("knowledge.succeeded", { count: String(result.succeeded) }),
140377
+ syncLine,
140190
140378
  t("knowledge.failed", { count: String(result.failed) }),
140191
140379
  t("knowledge.ingest_summary", {
140192
140380
  chunks: String(result.totalChunks),
140193
140381
  events: String(result.totalEvents),
140194
140382
  entities: String(result.totalEntities)
140195
140383
  }),
140384
+ t("knowledge.vector_coverage", {
140385
+ embedded: String(coverage.embedded),
140386
+ total: String(coverage.total)
140387
+ }),
140196
140388
  "",
140197
140389
  t("knowledge.failed_files"),
140198
140390
  ...result.errors.map((e) => ` • ${basename(e.filePath)}: ${e.message}`)
140199
140391
  ].join("\n");
140200
140392
  host.showNotice(t("knowledge.batch_partial"), summary);
140201
- } else host.showNotice(t("knowledge.batch_done"), `${t("knowledge.succeeded", { count: String(result.succeeded) })}\n${result.totalChunks} chunks, ${result.totalEvents} events, ${result.totalEntities} entities`);
140393
+ } else host.showNotice(t("knowledge.batch_done"), `${syncLine}\n${result.totalChunks} chunks, ${result.totalEvents} events, ${result.totalEntities} entities\n${t("knowledge.vector_coverage", {
140394
+ embedded: String(coverage.embedded),
140395
+ total: String(coverage.total)
140396
+ })}`);
140202
140397
  } else {
140203
140398
  if (!isSupportedFile(filePath)) {
140204
140399
  spinner.stop({
@@ -140211,11 +140406,19 @@ async function handleIngest(host) {
140211
140406
  const result = await ingestFile(store, llm, filePath, (progress) => {
140212
140407
  spinner.setLabel(formatProgress(progress));
140213
140408
  });
140409
+ const outcomeLabel = result.outcome === "unchanged" ? t("knowledge.ingest_unchanged") : result.outcome === "updated" ? t("knowledge.ingest_updated") : t("knowledge.ingest_done");
140214
140410
  spinner.stop({
140215
140411
  ok: true,
140216
- label: t("knowledge.ingest_done")
140412
+ label: outcomeLabel
140217
140413
  });
140218
- host.showNotice(t("knowledge.ingest_done"), `${t("knowledge.file_label")}: ${basename(filePath)}\nchunks: ${result.chunkCount}\nevents: ${result.eventCount}\nentities: ${result.entityCount}`);
140414
+ if (result.outcome === "unchanged") host.showNotice(outcomeLabel, `${t("knowledge.file_label")}: ${basename(filePath)}`);
140415
+ else {
140416
+ const coverage = await store.embeddingCoverage();
140417
+ host.showNotice(outcomeLabel, `${t("knowledge.file_label")}: ${basename(filePath)}\nchunks: ${result.chunkCount}\nevents: ${result.eventCount}\nentities: ${result.entityCount}\n${t("knowledge.vector_coverage", {
140418
+ embedded: String(coverage.embedded),
140419
+ total: String(coverage.total)
140420
+ })}`);
140421
+ }
140219
140422
  }
140220
140423
  } catch (error) {
140221
140424
  spinner.stop({
@@ -140225,6 +140428,31 @@ async function handleIngest(host) {
140225
140428
  throw error;
140226
140429
  }
140227
140430
  }
140431
+ /**
140432
+ * Ensure the shared embedding engine is ready before a vector-dependent
140433
+ * operation. Returns true when ready; otherwise shows an error (missing
140434
+ * download) or attempts a cached-model load and reports the outcome.
140435
+ */
140436
+ async function ensureEmbeddingReadyInteractive(host) {
140437
+ const engine = (await getKnowledgeStore()).getEmbeddingEngine();
140438
+ if (engine !== void 0 && engine.available) return true;
140439
+ if (!isEmbeddingModelCached()) {
140440
+ host.showError(t("knowledge.model_missing"));
140441
+ return false;
140442
+ }
140443
+ const spinner = host.showProgressSpinner(t("knowledge.loading_model"));
140444
+ const { ok, error } = await startManualEmbeddingDownload();
140445
+ spinner.stop({
140446
+ ok,
140447
+ label: ok ? t("kw.embedding_ready") : t("kw.embedding_failed")
140448
+ });
140449
+ if (!ok) {
140450
+ const detail = [t("knowledge.download_model_retry_hint"), error].filter(Boolean).join("\n");
140451
+ host.showNotice(t("knowledge.model_load_failed"), detail);
140452
+ return false;
140453
+ }
140454
+ return true;
140455
+ }
140228
140456
  async function handleList(host) {
140229
140457
  const store = await getKnowledgeStore();
140230
140458
  const docs = await store.listDocuments();
@@ -140374,6 +140602,97 @@ async function handleDelete(host) {
140374
140602
  if (await store.deleteSource(sourceId)) host.showNotice(t("knowledge.deleted"), t("knowledge.doc_removed"));
140375
140603
  else host.showError(t("knowledge.delete_fail_not_found"));
140376
140604
  }
140605
+ function pickSourceToReembed(host, sources) {
140606
+ const { promise, resolve } = Promise.withResolvers();
140607
+ const options = sources.map((s) => ({
140608
+ value: s.id,
140609
+ label: s.name,
140610
+ description: s.filePath ?? void 0
140611
+ }));
140612
+ const picker = new ChoicePickerComponent({
140613
+ title: t("knowledge.reembed_pick"),
140614
+ options,
140615
+ colors: host.state.theme.colors,
140616
+ onSelect: (value) => {
140617
+ host.restoreEditor();
140618
+ resolve(value);
140619
+ },
140620
+ onCancel: () => {
140621
+ host.restoreEditor();
140622
+ resolve(void 0);
140623
+ }
140624
+ });
140625
+ host.mountEditorReplacement(picker);
140626
+ return promise;
140627
+ }
140628
+ function confirmReembedSource(host, source) {
140629
+ const { promise, resolve } = Promise.withResolvers();
140630
+ const options = [{
140631
+ value: "cancel",
140632
+ label: t("common.cancel")
140633
+ }, {
140634
+ value: "confirm",
140635
+ label: t("knowledge.reembed"),
140636
+ description: t("knowledge.reembed_confirm_desc")
140637
+ }];
140638
+ const picker = new ChoicePickerComponent({
140639
+ title: t("knowledge.reembed_confirm_name", { name: source.name }),
140640
+ options,
140641
+ colors: host.state.theme.colors,
140642
+ onSelect: (value) => {
140643
+ host.restoreEditor();
140644
+ resolve(value === "confirm");
140645
+ },
140646
+ onCancel: () => {
140647
+ host.restoreEditor();
140648
+ resolve(false);
140649
+ }
140650
+ });
140651
+ host.mountEditorReplacement(picker);
140652
+ return promise;
140653
+ }
140654
+ async function handleReembed(host) {
140655
+ const store = await getKnowledgeStore();
140656
+ const sources = await store.listSources();
140657
+ if (sources.length === 0) {
140658
+ host.showNotice(t("knowledge.empty"), t("knowledge.reembed_none"));
140659
+ return;
140660
+ }
140661
+ const sourceId = await pickSourceToReembed(host, sources);
140662
+ if (sourceId === void 0) return;
140663
+ const source = sources.find((s) => s.id === sourceId);
140664
+ if (source === void 0) return;
140665
+ if (!await confirmReembedSource(host, source)) {
140666
+ host.showNotice(t("knowledge.cancelled"));
140667
+ return;
140668
+ }
140669
+ if (!await ensureEmbeddingReadyInteractive(host)) return;
140670
+ const engine = store.getEmbeddingEngine();
140671
+ if (engine === void 0) {
140672
+ host.showError(t("knowledge.model_missing"));
140673
+ return;
140674
+ }
140675
+ const spinner = host.showProgressSpinner(t("knowledge.reembed"));
140676
+ try {
140677
+ const counts = await store.reembedSource(sourceId, engine);
140678
+ spinner.stop({
140679
+ ok: true,
140680
+ label: t("knowledge.reembed_done")
140681
+ });
140682
+ host.showNotice(t("knowledge.reembed_done"), t("knowledge.reembed_summary", {
140683
+ chunks: String(counts.chunks),
140684
+ events: String(counts.events),
140685
+ entities: String(counts.entities),
140686
+ relations: String(counts.relations)
140687
+ }));
140688
+ } catch (error) {
140689
+ spinner.stop({
140690
+ ok: false,
140691
+ label: t("knowledge.reembed")
140692
+ });
140693
+ throw error;
140694
+ }
140695
+ }
140377
140696
  async function handleDownloadModel(host) {
140378
140697
  await getKnowledgeStore();
140379
140698
  const spinner = host.showProgressSpinner(t("kw.embedding_downloading"));
@@ -140455,6 +140774,11 @@ async function handleKnowledgeCommand(host, _args) {
140455
140774
  description: t("knowledge.delete_desc"),
140456
140775
  tone: "danger"
140457
140776
  },
140777
+ {
140778
+ value: "reembed",
140779
+ label: "🔁 " + t("knowledge.reembed"),
140780
+ description: t("knowledge.reembed_desc")
140781
+ },
140458
140782
  {
140459
140783
  value: "stats",
140460
140784
  label: "📊 " + t("knowledge.stats"),
@@ -140480,6 +140804,7 @@ async function handleKnowledgeCommand(host, _args) {
140480
140804
  else if (value === "list") await handleList(host);
140481
140805
  else if (value === "search") await handleSearch(host);
140482
140806
  else if (value === "delete") await handleDelete(host);
140807
+ else if (value === "reembed") await handleReembed(host);
140483
140808
  else if (value === "stats") await handleStats(host);
140484
140809
  else if (value === "web") await handleWeb(host);
140485
140810
  } catch (error) {
@@ -141127,4 +141452,4 @@ async function handleBuiltInSlashCommand(host, name, args) {
141127
141452
  }
141128
141453
  }
141129
141454
  //#endregion
141130
- export { handleTitleCommand as $, isTodoItemShape as $t, renderDiffLinesClustered as A, CLI_COMMAND_NAME as An, ENABLE_TERMINAL_FOCUS_REPORTING as At, BackgroundAgentStatusComponent as B, MemoryMemoStore as Bn, TERMINAL_THEME_LIGHT as Bt, handleRevokeCommand as C, loadTuiConfig as Cn, createThemeStyles as Ct, toggleEmptySessionHint as D, getInputHistoryFile as Dn, parseOsc11BackgroundTheme as Dt, isTurnElapsedEnabled as E, getDataDir as En, detectTerminalTheme as Et, estimateTokens as F, fetchCatalog as Fn, OSC11_RESPONSE_PREFIX_NO_ESC as Ft, getBreathingFrame as G, isOrphanedToolCallError as Gn, handleSearchCommand as Gt, AgentGroupComponent as H, log as Hn, isStreaming as Ht, getSharedSpeedTracker as I, saveCatalogCache as In, QUERY_TERMINAL_THEME as It, refineGoal as J, printableChar as Jt, resetBreathingClock as K, ErrorCodes as Kn, handleConnectCommand as Kt, SkillActivationComponent as L, ScreamHarness as Ln, TERMINAL_FOCUS_IN as Lt, langFromPath as M, CLI_USER_AGENT_PRODUCT as Mn, OSC11_QUERY as Mt, CachedContainer as N, PRODUCT_NAME as Nn, OSC11_RESPONSE as Nt, ToolCallComponent as O, getLogDir as On, DISABLE_TERMINAL_FOCUS_REPORTING as Ot, ThinkingComponent as P, DEFAULT_CATALOG_URL as Pn, OSC11_RESPONSE_PREFIX as Pt, handleInitCommand as Q, formatErrorMessage as Qt, ReadGroupComponent as R, MCP_OAUTH_AUTHORIZATION_URL_TOOL_UPDATE as Rn, TERMINAL_FOCUS_OUT as Rt, getDaemonInstructions as S, TuiLikePreferencesSchema as Sn, createMarkdownTheme as St, isEmptySessionHintDismissed as T, detectShellEnvironment as Tn, getColorPalette as Tt, WelcomeComponent as U, resolveGlobalLogPath as Un, FooterComponent as Ut, AssistantMessageComponent as V, flushDiagnosticLogs as Vn, isBusy as Vt, BREATHE_CYCLE_MS as W, isScreamError as Wn, handleTraceCommand as Wt, handleExportMdCommand as X, appendStreamingArgsPreview as Xt, handleExportDebugZipCommand as Y, STATUS_BULLET as Yt, handleForkCommand as Z, argsRecord as Zt, refreshUpdateCache as _, isExperimentalFlagEnabled as _n, clearInfoPanelState as _t, handleExtensionCommand as a, EMPTY_SESSION_HINT_URL as an, handleEditorCommand as at, readJsonlFile as b, PULSE_WAVE_FRAMES as bn, resolveThemeSync as bt, hasDispose as c, SESSION_TIPS as cn, handlePlanCommand as ct, formatMemoryMemoForInjection as d, getCtrlDHint as dn, handleYoloCommand as dt, parseStreamingArgs as en, toTerminalHyperlink as et, handleMemoryCommand as f, getLlmNotSetMessage as fn, showModelPicker as ft, selectUpdateTarget as g, sortSlashCommands as gn, supportsBalance as gt, handleUpdateCommand as h, BUILTIN_SLASH_COMMANDS as hn, refreshProviderBalance as ht, buildRoleAdditionalText as i, CHARS_PER_TOKEN as in, handleCompactCommand as it, highlightLines as j, CLI_UI_MODE as jn, ENABLE_TERMINAL_THEME_REPORTING as jt, renderDiffLines as k, detectInstallSource as kn, DISABLE_TERMINAL_THEME_REPORTING as kt, isPlanExpandable as l, TIP_ROTATION_INTERVAL_MS as ln, handleThemeCommand as lt, handleMcpCommand as m, buildSkillSlashCommands as mn, showSettingsSelector as mt, clearEvalPanelState as n, stringValue as nn, getModelCycleLevel as nt, handleSkillCommand as o, EXIT_CONFIRM_WINDOW_MS as on, handleFusionPlanCommand as ot, handleChannelCommand as p, getNoActiveSessionMessage as pn, showPermissionPicker as pt, clearGoalState as q, SCREAM_ERROR_INFO as qn, handleLogoutCommand as qt, openUrl as r, truncateErrorMessage as rn, handleAutoCommand as rt, disposeChildren as s, MAIN_AGENT_ID$1 as sn, handleModelCommand as st, dispatchInput as t, serializeToolResultOutput as tn, changeThinkingLevel as tt, MoonLoader as u, getCtrlCHint as un, handleWolfpackCommand as ut, readUpdateCache as v, setExperimentalFlags as vn, showStatusReport as vt, UserMessageComponent as w, saveTuiConfig as wn, contrastTextHex as wt, handleCcCommand as x, TuiConfigParseError as xn, createEditorTheme as xt, appendJsonlLine as y, PIXEL_PULSE_FRAMES as yn, showUsage as yt, parseReadGroupOutput as z, resolveScreamHome as zn, TERMINAL_THEME_DARK as zt };
141455
+ export { handleTitleCommand as $, isTodoItemShape as $t, renderDiffLinesClustered as A, getInputHistoryFile as An, ENABLE_TERMINAL_FOCUS_REPORTING as At, BackgroundAgentStatusComponent as B, ScreamHarness as Bn, TERMINAL_THEME_LIGHT as Bt, handleRevokeCommand as C, PULSE_WAVE_FRAMES as Cn, createThemeStyles as Ct, toggleEmptySessionHint as D, saveTuiConfig as Dn, parseOsc11BackgroundTheme as Dt, isTurnElapsedEnabled as E, loadTuiConfig as En, detectTerminalTheme as Et, estimateTokens as F, CLI_USER_AGENT_PRODUCT as Fn, OSC11_RESPONSE_PREFIX_NO_ESC as Ft, getBreathingFrame as G, log as Gn, handleSearchCommand as Gt, AgentGroupComponent as H, resolveScreamHome as Hn, isStreaming as Ht, getSharedSpeedTracker as I, PRODUCT_NAME as In, QUERY_TERMINAL_THEME as It, refineGoal as J, isOrphanedToolCallError as Jn, printableChar as Jt, resetBreathingClock as K, resolveGlobalLogPath as Kn, handleConnectCommand as Kt, SkillActivationComponent as L, DEFAULT_CATALOG_URL as Ln, TERMINAL_FOCUS_IN as Lt, langFromPath as M, detectInstallSource as Mn, OSC11_QUERY as Mt, CachedContainer as N, CLI_COMMAND_NAME as Nn, OSC11_RESPONSE as Nt, ToolCallComponent as O, detectShellEnvironment as On, DISABLE_TERMINAL_FOCUS_REPORTING as Ot, ThinkingComponent as P, CLI_UI_MODE as Pn, OSC11_RESPONSE_PREFIX as Pt, handleInitCommand as Q, formatErrorMessage as Qt, ReadGroupComponent as R, fetchCatalog as Rn, TERMINAL_FOCUS_OUT as Rt, getDaemonInstructions as S, PIXEL_PULSE_FRAMES as Sn, createMarkdownTheme as St, isEmptySessionHintDismissed as T, TuiLikePreferencesSchema as Tn, getColorPalette as Tt, WelcomeComponent as U, MemoryMemoStore as Un, FooterComponent as Ut, AssistantMessageComponent as V, MCP_OAUTH_AUTHORIZATION_URL_TOOL_UPDATE as Vn, isBusy as Vt, BREATHE_CYCLE_MS as W, flushDiagnosticLogs as Wn, handleTraceCommand as Wt, handleExportMdCommand as X, SCREAM_ERROR_INFO as Xn, appendStreamingArgsPreview as Xt, handleExportDebugZipCommand as Y, ErrorCodes as Yn, STATUS_BULLET as Yt, handleForkCommand as Z, argsRecord as Zt, refreshUpdateCache as _, isExperimentalFlagEnabled as _n, clearInfoPanelState as _t, handleExtensionCommand as a, EMPTY_SESSION_HINT_URL as an, handleEditorCommand as at, readJsonlFile as b, isEmbeddingModelCached as bn, resolveThemeSync as bt, hasDispose as c, SESSION_TIPS as cn, handlePlanCommand as ct, formatMemoryMemoForInjection as d, getCtrlDHint as dn, handleYoloCommand as dt, parseStreamingArgs as en, toTerminalHyperlink as et, handleMemoryCommand as f, getLlmNotSetMessage as fn, showModelPicker as ft, selectUpdateTarget as g, sortSlashCommands as gn, supportsBalance as gt, handleUpdateCommand as h, BUILTIN_SLASH_COMMANDS as hn, refreshProviderBalance as ht, buildRoleAdditionalText as i, CHARS_PER_TOKEN as in, handleCompactCommand as it, highlightLines as j, getLogDir as jn, ENABLE_TERMINAL_THEME_REPORTING as jt, renderDiffLines as k, getDataDir as kn, DISABLE_TERMINAL_THEME_REPORTING as kt, isPlanExpandable as l, TIP_ROTATION_INTERVAL_MS as ln, handleThemeCommand as lt, handleMcpCommand as m, buildSkillSlashCommands as mn, showSettingsSelector as mt, clearEvalPanelState as n, stringValue as nn, getModelCycleLevel as nt, handleSkillCommand as o, EXIT_CONFIRM_WINDOW_MS as on, handleFusionPlanCommand as ot, handleChannelCommand as p, getNoActiveSessionMessage as pn, showPermissionPicker as pt, clearGoalState as q, isScreamError as qn, handleLogoutCommand as qt, openUrl as r, truncateErrorMessage as rn, handleAutoCommand as rt, disposeChildren as s, MAIN_AGENT_ID$1 as sn, handleModelCommand as st, dispatchInput as t, serializeToolResultOutput as tn, changeThinkingLevel as tt, MoonLoader as u, getCtrlCHint as un, handleWolfpackCommand as ut, readUpdateCache as v, setExperimentalFlags as vn, showStatusReport as vt, UserMessageComponent as w, TuiConfigParseError as wn, contrastTextHex as wt, handleCcCommand as x, startManualEmbeddingDownload as xn, createEditorTheme as xt, appendJsonlLine as y, getKnowledgeStore as yn, showUsage as yt, parseReadGroupOutput as z, saveCatalogCache as zn, TERMINAL_THEME_DARK as zt };