scream-code 0.14.7 → 0.14.9

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";
@@ -37,11 +37,11 @@ import { EventEmitter as EventEmitter$1 } from "node:events";
37
37
  import { StringDecoder } from "node:string_decoder";
38
38
  import Pi from "assert";
39
39
  import ro from "node:assert";
40
+ import { fileURLToPath, pathToFileURL } from "node:url";
40
41
  import * as posixPath from "node:path/posix";
41
42
  import * as win32Path from "node:path/win32";
42
43
  import { createServer } from "node:http";
43
44
  import * as z$1 from "zod/v4";
44
- import { fileURLToPath, pathToFileURL } from "node:url";
45
45
  import { parse as parse$1, stringify } from "smol-toml";
46
46
  import "zod/v3";
47
47
  import * as z4mini from "zod/v4-mini";
@@ -751,7 +751,7 @@ function isOrphanedToolCallMessage(lowerMessage) {
751
751
  */
752
752
  function isOrphanedToolCallError(error) {
753
753
  if (error instanceof APIOrphanedToolCallError) return true;
754
- return isOrphanedToolCallMessage(errorMessage$5(error).toLowerCase());
754
+ return isOrphanedToolCallMessage(errorMessage$6(error).toLowerCase());
755
755
  }
756
756
  /**
757
757
  * The API returned an empty response (no content, no tool calls).
@@ -829,7 +829,7 @@ function isRequestTooLargeError(error) {
829
829
  if (error instanceof APIStatusError) return error.statusCode === 413;
830
830
  return false;
831
831
  }
832
- function errorMessage$5(error) {
832
+ function errorMessage$6(error) {
833
833
  return error instanceof Error ? error.message : String(error);
834
834
  }
835
835
  //#endregion
@@ -1841,16 +1841,16 @@ const parseLogLevel$2 = (maybeLevel, sourceName, client) => {
1841
1841
  if (hasOwn$3(levelNumbers$2, maybeLevel)) return maybeLevel;
1842
1842
  loggerFor$2(client).warn(`${sourceName} was set to ${JSON.stringify(maybeLevel)}, expected one of ${JSON.stringify(Object.keys(levelNumbers$2))}`);
1843
1843
  };
1844
- function noop$3() {}
1844
+ function noop$4() {}
1845
1845
  function makeLogFn$2(fnLevel, logger, logLevel) {
1846
- if (!logger || levelNumbers$2[fnLevel] > levelNumbers$2[logLevel]) return noop$3;
1846
+ if (!logger || levelNumbers$2[fnLevel] > levelNumbers$2[logLevel]) return noop$4;
1847
1847
  else return logger[fnLevel].bind(logger);
1848
1848
  }
1849
1849
  const noopLogger$2 = {
1850
- error: noop$3,
1851
- warn: noop$3,
1852
- info: noop$3,
1853
- debug: noop$3
1850
+ error: noop$4,
1851
+ warn: noop$4,
1852
+ info: noop$4,
1853
+ debug: noop$4
1854
1854
  };
1855
1855
  let cachedLoggers$2 = /* @__PURE__ */ new WeakMap();
1856
1856
  function loggerFor$2(client) {
@@ -10411,7 +10411,7 @@ var require_gaxios = /* @__PURE__ */ __commonJSMin(((exports) => {
10411
10411
  }
10412
10412
  static async #getFetch() {
10413
10413
  const hasWindow = typeof window !== "undefined" && !!window;
10414
- this.#fetch ||= hasWindow ? window.fetch : (await import("./src-C8lOjRbK.mjs")).default;
10414
+ this.#fetch ||= hasWindow ? window.fetch : (await import("./src-C3oCiVNb.mjs")).default;
10415
10415
  return this.#fetch;
10416
10416
  }
10417
10417
  /**
@@ -31847,16 +31847,16 @@ const parseLogLevel$1 = (maybeLevel, sourceName, client) => {
31847
31847
  if (hasOwn$2(levelNumbers$1, maybeLevel)) return maybeLevel;
31848
31848
  loggerFor$1(client).warn(`${sourceName} was set to ${JSON.stringify(maybeLevel)}, expected one of ${JSON.stringify(Object.keys(levelNumbers$1))}`);
31849
31849
  };
31850
- function noop$2() {}
31850
+ function noop$3() {}
31851
31851
  function makeLogFn$1(fnLevel, logger, logLevel) {
31852
- if (!logger || levelNumbers$1[fnLevel] > levelNumbers$1[logLevel]) return noop$2;
31852
+ if (!logger || levelNumbers$1[fnLevel] > levelNumbers$1[logLevel]) return noop$3;
31853
31853
  else return logger[fnLevel].bind(logger);
31854
31854
  }
31855
31855
  const noopLogger$1 = {
31856
- error: noop$2,
31857
- warn: noop$2,
31858
- info: noop$2,
31859
- debug: noop$2
31856
+ error: noop$3,
31857
+ warn: noop$3,
31858
+ info: noop$3,
31859
+ debug: noop$3
31860
31860
  };
31861
31861
  let cachedLoggers$1 = /* @__PURE__ */ new WeakMap();
31862
31862
  function loggerFor$1(client) {
@@ -34836,7 +34836,7 @@ function normalizeScreamToolSchema(schema) {
34836
34836
  }
34837
34837
  function ensureScreamPropertyTypes(schema) {
34838
34838
  const normalized = cloneJsonValue(schema);
34839
- if (!isRecord$4(normalized)) throw new Error("JSON Schema root must normalize to an object.");
34839
+ if (!isRecord$5(normalized)) throw new Error("JSON Schema root must normalize to an object.");
34840
34840
  recurseSchema(normalized);
34841
34841
  return normalized;
34842
34842
  }
@@ -34897,7 +34897,7 @@ function resolveLocalJsonPointer(root, ref) {
34897
34897
  let current = root;
34898
34898
  for (const rawPart of ref.slice(2).split("/")) {
34899
34899
  const part = unescapeJsonPointerPart(rawPart);
34900
- if (isRecord$4(current)) {
34900
+ if (isRecord$5(current)) {
34901
34901
  if (!hasOwn$1(current, part)) return { found: false };
34902
34902
  current = current[part];
34903
34903
  } else if (Array.isArray(current)) {
@@ -34919,20 +34919,20 @@ function parseJsonPointerArrayIndex(part) {
34919
34919
  return Number(part);
34920
34920
  }
34921
34921
  function recurseSchema(node) {
34922
- if (!isRecord$4(node)) return;
34922
+ if (!isRecord$5(node)) return;
34923
34923
  visitChildSchemas(node, normalizeProperty);
34924
34924
  }
34925
34925
  function visitChildSchemas(node, visit) {
34926
34926
  for (const { key, kind } of CHILD_SCHEMA_SLOTS) {
34927
34927
  const value = node[key];
34928
34928
  if (kind === "single") {
34929
- if (isRecord$4(value)) visit(value);
34929
+ if (isRecord$5(value)) visit(value);
34930
34930
  } else if (kind === "array") {
34931
34931
  if (Array.isArray(value)) for (const item of value) visit(item);
34932
34932
  } else if (kind === "map") {
34933
- if (isRecord$4(value)) for (const item of Object.values(value)) visit(item);
34933
+ if (isRecord$5(value)) for (const item of Object.values(value)) visit(item);
34934
34934
  } else if (kind === "schema-or-array") {
34935
- if (isRecord$4(value)) visit(value);
34935
+ if (isRecord$5(value)) visit(value);
34936
34936
  else if (Array.isArray(value)) for (const item of value) visit(item);
34937
34937
  }
34938
34938
  }
@@ -34944,7 +34944,7 @@ function childSchemaKeysForParentType(parentType) {
34944
34944
  });
34945
34945
  }
34946
34946
  function normalizeProperty(node) {
34947
- if (!isRecord$4(node)) return;
34947
+ if (!isRecord$5(node)) return;
34948
34948
  if (!hasOwn$1(node, "type") && !hasAnyKey(node, TYPE_COMPLETION_SKIP_KEYS)) {
34949
34949
  const enumValues = node["enum"];
34950
34950
  if (Array.isArray(enumValues) && enumValues.length > 0) node["type"] = inferTypeFromValues(enumValues);
@@ -35008,14 +35008,14 @@ function hasAnyKey(obj, keys) {
35008
35008
  }
35009
35009
  function cloneJsonValue(value) {
35010
35010
  if (Array.isArray(value)) return value.map((item) => cloneJsonValue(item));
35011
- if (isRecord$4(value)) {
35011
+ if (isRecord$5(value)) {
35012
35012
  const cloned = {};
35013
35013
  for (const [key, child] of Object.entries(value)) cloned[key] = cloneJsonValue(child);
35014
35014
  return cloned;
35015
35015
  }
35016
35016
  return value;
35017
35017
  }
35018
- function isRecord$4(value) {
35018
+ function isRecord$5(value) {
35019
35019
  return typeof value === "object" && value !== null && !Array.isArray(value);
35020
35020
  }
35021
35021
  function hasOwn$1(obj, key) {
@@ -35785,16 +35785,16 @@ const parseLogLevel = (maybeLevel, sourceName, client) => {
35785
35785
  if (hasOwn(levelNumbers, maybeLevel)) return maybeLevel;
35786
35786
  loggerFor(client).warn(`${sourceName} was set to ${JSON.stringify(maybeLevel)}, expected one of ${JSON.stringify(Object.keys(levelNumbers))}`);
35787
35787
  };
35788
- function noop$1() {}
35788
+ function noop$2() {}
35789
35789
  function makeLogFn(fnLevel, logger, logLevel) {
35790
- if (!logger || levelNumbers[fnLevel] > levelNumbers[logLevel]) return noop$1;
35790
+ if (!logger || levelNumbers[fnLevel] > levelNumbers[logLevel]) return noop$2;
35791
35791
  else return logger[fnLevel].bind(logger);
35792
35792
  }
35793
35793
  const noopLogger = {
35794
- error: noop$1,
35795
- warn: noop$1,
35796
- info: noop$1,
35797
- debug: noop$1
35794
+ error: noop$2,
35795
+ warn: noop$2,
35796
+ info: noop$2,
35797
+ debug: noop$2
35798
35798
  };
35799
35799
  let cachedLoggers = /* @__PURE__ */ new WeakMap();
35800
35800
  function loggerFor(client) {
@@ -44510,8 +44510,8 @@ function reasoningEffortToThinkingEffort(reasoning) {
44510
44510
  case "minimal": return "low";
44511
44511
  case "medium": return "medium";
44512
44512
  case "high": return "high";
44513
- case "xhigh":
44514
- case "max": return "xhigh";
44513
+ case "xhigh": return "xhigh";
44514
+ case "max": return "max";
44515
44515
  case "none": return "off";
44516
44516
  default: return "off";
44517
44517
  }
@@ -44941,6 +44941,7 @@ var ScreamChatProvider = class {
44941
44941
  _baseUrl;
44942
44942
  _defaultHeaders;
44943
44943
  _generationKwargs;
44944
+ _forceThinking;
44944
44945
  _client;
44945
44946
  _clientFactory;
44946
44947
  _files;
@@ -44952,6 +44953,7 @@ var ScreamChatProvider = class {
44952
44953
  this._clientFactory = options.clientFactory;
44953
44954
  this._model = options.model;
44954
44955
  this._stream = options.stream ?? true;
44956
+ this._forceThinking = options.forceThinking;
44955
44957
  this._generationKwargs = { ...options.generationKwargs };
44956
44958
  this._client = this._apiKey === void 0 ? void 0 : new OpenAI({
44957
44959
  apiKey: this._apiKey,
@@ -45024,12 +45026,8 @@ var ScreamChatProvider = class {
45024
45026
  return UNKNOWN_CAPABILITY;
45025
45027
  }
45026
45028
  withThinking(effort) {
45027
- const thinking = { type: effort === "off" ? "disabled" : "enabled" };
45028
45029
  let reasoningEffort;
45029
45030
  switch (effort) {
45030
- case "off":
45031
- reasoningEffort = void 0;
45032
- break;
45033
45031
  case "low":
45034
45032
  reasoningEffort = "low";
45035
45033
  break;
@@ -45037,11 +45035,18 @@ var ScreamChatProvider = class {
45037
45035
  reasoningEffort = "medium";
45038
45036
  break;
45039
45037
  case "high":
45038
+ reasoningEffort = "high";
45039
+ break;
45040
45040
  case "xhigh":
45041
45041
  case "max":
45042
- reasoningEffort = "high";
45042
+ reasoningEffort = "max";
45043
+ break;
45044
+ case "off":
45045
+ reasoningEffort = void 0;
45043
45046
  break;
45044
45047
  }
45048
+ if (effort === "off" && this._forceThinking === true) return this._withGenerationKwargs({ reasoning_effort: "low" }).withExtraBody({ thinking: { type: "enabled" } });
45049
+ const thinking = { type: effort === "off" ? "disabled" : "enabled" };
45045
45050
  return this._withGenerationKwargs({ reasoning_effort: reasoningEffort }).withExtraBody({ thinking });
45046
45051
  }
45047
45052
  withGenerationKwargs(kwargs) {
@@ -45247,6 +45252,7 @@ var OpenAILegacyChatProvider = class {
45247
45252
  _reasoningKey;
45248
45253
  _reasoningEffort;
45249
45254
  _thinkingExplicitlyOff = false;
45255
+ _forceThinking;
45250
45256
  _generationKwargs;
45251
45257
  _toolMessageConversion;
45252
45258
  _client;
@@ -45262,6 +45268,7 @@ var OpenAILegacyChatProvider = class {
45262
45268
  const normalizedReasoningKey = options.reasoningKey?.trim();
45263
45269
  this._reasoningKey = normalizedReasoningKey !== void 0 && normalizedReasoningKey.length > 0 ? normalizedReasoningKey : void 0;
45264
45270
  this._reasoningEffort = void 0;
45271
+ this._forceThinking = options.forceThinking;
45265
45272
  this._generationKwargs = {};
45266
45273
  if (options.maxTokens !== void 0) this._generationKwargs.max_tokens = options.maxTokens;
45267
45274
  this._toolMessageConversion = options.toolMessageConversion ?? null;
@@ -45315,9 +45322,16 @@ var OpenAILegacyChatProvider = class {
45315
45322
  }
45316
45323
  }
45317
45324
  withThinking(effort) {
45318
- const reasoningEffort = thinkingEffortToReasoningEffort(effort);
45325
+ let mapped = thinkingEffortToReasoningEffort(effort);
45326
+ if (mapped === "xhigh") mapped = "max";
45327
+ if (effort === "off" && this._forceThinking === true) {
45328
+ const clone = this._clone();
45329
+ clone._reasoningEffort = "low";
45330
+ clone._thinkingExplicitlyOff = false;
45331
+ return clone;
45332
+ }
45319
45333
  const clone = this._clone();
45320
- clone._reasoningEffort = reasoningEffort;
45334
+ clone._reasoningEffort = mapped;
45321
45335
  clone._thinkingExplicitlyOff = effort === "off";
45322
45336
  return clone;
45323
45337
  }
@@ -47448,7 +47462,7 @@ function isAbortError$1(err) {
47448
47462
  if (err instanceof Error) return err.name === "AbortError";
47449
47463
  return false;
47450
47464
  }
47451
- function errorMessage$4(err) {
47465
+ function errorMessage$5(err) {
47452
47466
  if (err instanceof Error) return err.message;
47453
47467
  return String(err);
47454
47468
  }
@@ -52717,7 +52731,7 @@ var UpdateGoalTool = class {
52717
52731
  try {
52718
52732
  await goal.pauseGoal({ reason: "verifying" }, "system");
52719
52733
  } catch (error) {
52720
- return toolError(`Failed to pause goal for verification: ${errorMessage$3(error)}`, goal);
52734
+ return toolError(`Failed to pause goal for verification: ${errorMessage$4(error)}`, goal);
52721
52735
  }
52722
52736
  let rawGrade;
52723
52737
  try {
@@ -52725,7 +52739,7 @@ var UpdateGoalTool = class {
52725
52739
  } catch (error) {
52726
52740
  const resumeError = await resumeAfterGrading(goal);
52727
52741
  if (resumeError !== void 0) return resumeError;
52728
- const reason = `Goal verification could not be completed: ${errorMessage$3(error)}`;
52742
+ const reason = `Goal verification could not be completed: ${errorMessage$4(error)}`;
52729
52743
  this.appendGradingFeedback(reason);
52730
52744
  return {
52731
52745
  isError: true,
@@ -52753,7 +52767,7 @@ var UpdateGoalTool = class {
52753
52767
  name: GOAL_COMPLETION_REMINDER_NAME
52754
52768
  });
52755
52769
  } catch (error) {
52756
- return toolError(`Failed to mark verified goal complete: ${errorMessage$3(error)}`, goal);
52770
+ return toolError(`Failed to mark verified goal complete: ${errorMessage$4(error)}`, goal);
52757
52771
  }
52758
52772
  return {
52759
52773
  output: `Goal verified and marked complete.\n${grade.reason}`,
@@ -52788,7 +52802,7 @@ async function resumeAfterGrading(goal) {
52788
52802
  await goal.resumeGoal({}, "system");
52789
52803
  return;
52790
52804
  } catch (error) {
52791
- return toolError(`Failed to restore active goal after verification: ${errorMessage$3(error)}`, goal);
52805
+ return toolError(`Failed to restore active goal after verification: ${errorMessage$4(error)}`, goal);
52792
52806
  }
52793
52807
  }
52794
52808
  function toolError(message, goal) {
@@ -52797,7 +52811,7 @@ function toolError(message, goal) {
52797
52811
  output: `${message}. Current goal status: ${goal.getGoal().goal?.status ?? "missing"}.`
52798
52812
  };
52799
52813
  }
52800
- function errorMessage$3(error) {
52814
+ function errorMessage$4(error) {
52801
52815
  return error instanceof Error ? error.message : String(error);
52802
52816
  }
52803
52817
  //#endregion
@@ -53178,12 +53192,15 @@ function createFastEmbedEngine(cacheDir) {
53178
53192
  engineCache.set(key, engine);
53179
53193
  return engine;
53180
53194
  }
53195
+ /** Stable identity of the model used by every engine created here. */
53196
+ const EMBEDDING_MODEL_NAME = "bge-small-zh-v1.5";
53181
53197
  function createFastEmbedEngineImpl(cacheDir) {
53182
53198
  let embedder = null;
53183
53199
  let initPromise = null;
53184
53200
  let loadFailed = false;
53185
53201
  let lastError;
53186
53202
  return {
53203
+ modelName: EMBEDDING_MODEL_NAME,
53187
53204
  get available() {
53188
53205
  return embedder !== null && !loadFailed;
53189
53206
  },
@@ -53581,7 +53598,7 @@ var MemoryMemoStore = class MemoryMemoStore {
53581
53598
  CREATE TABLE IF NOT EXISTS memory_embeddings (
53582
53599
  memory_id TEXT PRIMARY KEY REFERENCES memos(id) ON DELETE CASCADE,
53583
53600
  embedding_json TEXT NOT NULL,
53584
- model TEXT NOT NULL DEFAULT 'bge-small-zh-v1.5',
53601
+ model TEXT NOT NULL DEFAULT '${EMBEDDING_MODEL_NAME}',
53585
53602
  created_at INTEGER NOT NULL
53586
53603
  );
53587
53604
 
@@ -53887,7 +53904,7 @@ var MemoryMemoStore = class MemoryMemoStore {
53887
53904
  const now = Date.now();
53888
53905
  this.db.exec("BEGIN TRANSACTION");
53889
53906
  try {
53890
- for (let i = 0; i < pending.length; i++) insert.run(pending[i].id, JSON.stringify([...vectors[i]]), "bge-small-zh-v1.5", now);
53907
+ for (let i = 0; i < pending.length; i++) insert.run(pending[i].id, JSON.stringify([...vectors[i]]), EMBEDDING_MODEL_NAME, now);
53891
53908
  this.db.exec("COMMIT");
53892
53909
  } catch (error) {
53893
53910
  this.db.exec("ROLLBACK");
@@ -54896,7 +54913,7 @@ var KnowledgeLookupTool = class {
54896
54913
  const llm = { generate: async (systemPrompt, userPrompt) => {
54897
54914
  return this.agent.generateText(systemPrompt, userPrompt);
54898
54915
  } };
54899
- const { multiSearchWithTrace } = await import("./src-DCp4eCi5.mjs");
54916
+ const { multiSearchWithTrace } = await import("./src-h6ZExh4A.mjs");
54900
54917
  const { results, trace } = await multiSearchWithTrace(store, llm, query, { topK });
54901
54918
  if (results.length === 0) return {
54902
54919
  isError: false,
@@ -59083,7 +59100,7 @@ function parseSkillText(options) {
59083
59100
  throw error;
59084
59101
  }
59085
59102
  const frontmatter = parsed.data ?? {};
59086
- if (!isRecord$3(frontmatter)) throw new SkillParseError(`Frontmatter in ${options.skillMdPath} must be a mapping at the top level`);
59103
+ if (!isRecord$4(frontmatter)) throw new SkillParseError(`Frontmatter in ${options.skillMdPath} must be a mapping at the top level`);
59087
59104
  const metadata = normalizeMetadata(frontmatter);
59088
59105
  if (!isSupportedSkillType(metadata.type)) throw new UnsupportedSkillTypeError(metadata.type ?? String(frontmatter["type"]));
59089
59106
  const name = nonEmptyString$1(metadata.name);
@@ -59196,7 +59213,7 @@ function tokenizeArgs(raw) {
59196
59213
  function nonEmptyString$1(value) {
59197
59214
  return typeof value === "string" && value.trim() !== "" ? value.trim() : void 0;
59198
59215
  }
59199
- function isRecord$3(value) {
59216
+ function isRecord$4(value) {
59200
59217
  return typeof value === "object" && value !== null && !Array.isArray(value);
59201
59218
  }
59202
59219
  //#endregion
@@ -64654,7 +64671,7 @@ function renderPrompt(template, vars) {
64654
64671
  }
64655
64672
  //#endregion
64656
64673
  //#region ../../packages/agent-core/src/tools/builtin/collaboration/skill-tool.md
64657
- 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.";
64674
+ 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.";
64658
64675
  /** Max skills to list inline in the tool description; rest are summarized. */
64659
64676
  const MAX_INLINE_SKILLS = 15;
64660
64677
  /** Max description length per skill line. */
@@ -64662,7 +64679,7 @@ const MAX_SKILL_DESC_CHARS = 100;
64662
64679
  function buildSkillToolDescription(agent) {
64663
64680
  const skills = agent.skills?.registry.listInvocableSkills() ?? [];
64664
64681
  const lines = skills.slice(0, MAX_INLINE_SKILLS).map((s) => {
64665
- const desc = s.description.trim().replaceAll(/\n/g, " ").slice(0, MAX_SKILL_DESC_CHARS);
64682
+ const desc = s.description.trim().replaceAll("\n", " ").slice(0, MAX_SKILL_DESC_CHARS);
64666
64683
  return `- ${s.name}: ${desc}`;
64667
64684
  });
64668
64685
  if (skills.length > MAX_INLINE_SKILLS) lines.push(`…${String(skills.length - MAX_INLINE_SKILLS)} more (see skill registry)`);
@@ -64726,6 +64743,8 @@ var SkillTool = class SkillTool {
64726
64743
  if (skill.metadata.disableModelInvocation === true) return errorResult$1(`Skill "${args.skill}" can only be triggered by the user (model invocation is disabled).`);
64727
64744
  const skillArgs = args.args ?? "";
64728
64745
  if (!isInlineSkillType(skill.metadata.type)) return errorResult$1(`Skill "${skill.name}" is not an inline skill and cannot be invoked by the model in v1.`);
64746
+ const owningPluginId = skill.plugin?.id;
64747
+ if (owningPluginId !== void 0) this.agent.toolServices?.plugins?.recordUsage?.(owningPluginId, true);
64729
64748
  const origin = skillOrigin(skill, skillArgs, currentDepth);
64730
64749
  skills.recordActivation(origin);
64731
64750
  const skillContent = skills.registry.renderSkillPrompt(skill, skillArgs);
@@ -66196,6 +66215,7 @@ const ModelAliasSchema = z.object({
66196
66215
  displayName: z.string().optional(),
66197
66216
  reasoningKey: z.string().optional(),
66198
66217
  adaptiveThinking: z.boolean().optional(),
66218
+ forceThinking: z.boolean().optional(),
66199
66219
  thinkingLevels: z.array(ThinkingEffortSchema).optional()
66200
66220
  });
66201
66221
  const ThinkingConfigSchema = z.object({
@@ -66395,6 +66415,13 @@ const PLUGIN_NAME_REGEX = /^[a-z0-9][a-z0-9_-]{0,63}$/;
66395
66415
  function normalizePluginId(name) {
66396
66416
  return name.toLowerCase();
66397
66417
  }
66418
+ function summarizePluginSync(report) {
66419
+ return {
66420
+ ok: report.ok,
66421
+ applied: report.applied,
66422
+ failed: report.failed
66423
+ };
66424
+ }
66398
66425
  //#endregion
66399
66426
  //#region ../../packages/agent-core/src/plugin/manifest.ts
66400
66427
  const SCREAM_PLUGIN_ROOT_PATH = "scream.plugin.json";
@@ -66507,11 +66534,11 @@ async function parseManifest(pluginRoot) {
66507
66534
  return {
66508
66535
  manifest: {
66509
66536
  name,
66510
- version: stringField$1(raw, "version"),
66511
- description: stringField$1(raw, "description"),
66512
- keywords: stringArrayField$1(raw, "keywords"),
66513
- homepage: stringField$1(raw, "homepage"),
66514
- license: stringField$1(raw, "license"),
66537
+ version: stringField$2(raw, "version"),
66538
+ description: stringField$2(raw, "description"),
66539
+ keywords: stringArrayField$2(raw, "keywords"),
66540
+ homepage: stringField$2(raw, "homepage"),
66541
+ license: stringField$2(raw, "license"),
66515
66542
  author: readAuthor(raw["author"]),
66516
66543
  skills,
66517
66544
  sessionStart: readSessionStart(raw["sessionStart"], diagnostics),
@@ -66578,7 +66605,7 @@ function readHooks(pluginRoot, raw, diagnostics) {
66578
66605
  });
66579
66606
  continue;
66580
66607
  }
66581
- const event = stringField$1(entry, "event");
66608
+ const event = stringField$2(entry, "event");
66582
66609
  if (event === void 0) {
66583
66610
  diagnostics.push({
66584
66611
  severity: "warn",
@@ -66586,7 +66613,7 @@ function readHooks(pluginRoot, raw, diagnostics) {
66586
66613
  });
66587
66614
  continue;
66588
66615
  }
66589
- let command = stringField$1(entry, "command");
66616
+ let command = stringField$2(entry, "command");
66590
66617
  if (command === void 0) {
66591
66618
  diagnostics.push({
66592
66619
  severity: "warn",
@@ -66599,7 +66626,7 @@ function readHooks(pluginRoot, raw, diagnostics) {
66599
66626
  const hook = {
66600
66627
  event,
66601
66628
  command,
66602
- ...stringField$1(entry, "matcher") !== void 0 ? { matcher: stringField$1(entry, "matcher") } : {},
66629
+ ...stringField$2(entry, "matcher") !== void 0 ? { matcher: stringField$2(entry, "matcher") } : {},
66603
66630
  ...timeout !== void 0 ? { timeout } : {}
66604
66631
  };
66605
66632
  out.push(hook);
@@ -66774,8 +66801,8 @@ async function normalizePluginMcpServer(input) {
66774
66801
  function readAuthor(raw) {
66775
66802
  if (typeof raw === "string") return { name: raw };
66776
66803
  if (!isObject(raw)) return void 0;
66777
- const name = stringField$1(raw, "name");
66778
- const email = stringField$1(raw, "email");
66804
+ const name = stringField$2(raw, "name");
66805
+ const email = stringField$2(raw, "email");
66779
66806
  if (name === void 0 && email === void 0) return void 0;
66780
66807
  return {
66781
66808
  name,
@@ -66785,21 +66812,21 @@ function readAuthor(raw) {
66785
66812
  function readInterface(raw) {
66786
66813
  if (!isObject(raw)) return void 0;
66787
66814
  const out = {
66788
- displayName: stringField$1(raw, "displayName"),
66789
- shortDescription: stringField$1(raw, "shortDescription"),
66790
- longDescription: stringField$1(raw, "longDescription"),
66791
- developerName: stringField$1(raw, "developerName"),
66792
- websiteURL: stringField$1(raw, "websiteURL")
66815
+ displayName: stringField$2(raw, "displayName"),
66816
+ shortDescription: stringField$2(raw, "shortDescription"),
66817
+ longDescription: stringField$2(raw, "longDescription"),
66818
+ developerName: stringField$2(raw, "developerName"),
66819
+ websiteURL: stringField$2(raw, "websiteURL")
66793
66820
  };
66794
66821
  return Object.values(out).some((value) => value !== void 0) ? out : void 0;
66795
66822
  }
66796
- function stringField$1(raw, key) {
66823
+ function stringField$2(raw, key) {
66797
66824
  const value = raw[key];
66798
66825
  if (typeof value !== "string") return void 0;
66799
66826
  const trimmed = value.trim();
66800
66827
  return trimmed.length === 0 ? void 0 : trimmed;
66801
66828
  }
66802
- function stringArrayField$1(raw, key) {
66829
+ function stringArrayField$2(raw, key) {
66803
66830
  const value = raw[key];
66804
66831
  if (!Array.isArray(value) || !value.every((entry) => typeof entry === "string")) return;
66805
66832
  return value;
@@ -66945,6 +66972,24 @@ async function writeInstalled(screamHomeDir, data) {
66945
66972
  }
66946
66973
  //#endregion
66947
66974
  //#region ../../packages/agent-core/src/plugin/source.ts
66975
+ /** Compare dotted numeric versions (e.g. "1.2.0"); undefined when either side has no leading dotted-numeric segment. */
66976
+ function compareManifestVersions(a, b) {
66977
+ const parse = (value) => {
66978
+ if (typeof value !== "string") return void 0;
66979
+ const captured = value.trim().match(/^v?(\d+(?:\.\d+){0,3})/)?.[1];
66980
+ if (captured === void 0) return void 0;
66981
+ return captured.split(".").map(Number);
66982
+ };
66983
+ const left = parse(a);
66984
+ const right = parse(b);
66985
+ if (left === void 0 || right === void 0) return void 0;
66986
+ for (let index = 0; index < 3; index += 1) {
66987
+ const lhs = left[index] ?? 0;
66988
+ const rhs = right[index] ?? 0;
66989
+ if (lhs !== rhs) return lhs < rhs ? -1 : 1;
66990
+ }
66991
+ return 0;
66992
+ }
66948
66993
  const SHA_RE = /^[0-9a-f]{7,40}$/;
66949
66994
  const GITHUB_NAME_RE = /^[A-Za-z0-9._-]+$/;
66950
66995
  function resolveInstallSource(source) {
@@ -67036,6 +67081,198 @@ function decodeRefSegments(segments) {
67036
67081
  }).join("/");
67037
67082
  }
67038
67083
  //#endregion
67084
+ //#region ../../packages/agent-core/src/plugin/quarantine.ts
67085
+ function quarantinePath(screamHomeDir) {
67086
+ return path.join(screamHomeDir, "plugins", "quarantine.json");
67087
+ }
67088
+ /** Normalize a raw user-provided source string into its origin key. */
67089
+ function sourceKeyFromRawSource(source) {
67090
+ const trimmed = source.trim();
67091
+ const github = trimmed.match(/github\.com[/]([^/]+)[/]([^/#?\s]+)/);
67092
+ if (github !== null) return `github:${github[1]}/${github[2]}`;
67093
+ return trimmed;
67094
+ }
67095
+ /** Origin key for an installed record's persisted fields. */
67096
+ function sourceKeyFromFields(fields) {
67097
+ if (fields.github !== void 0) return `github:${fields.github.owner}/${fields.github.repo}`;
67098
+ return fields.originalSource ?? "";
67099
+ }
67100
+ async function readQuarantine(screamHomeDir) {
67101
+ try {
67102
+ const entries = JSON.parse(await readFile(quarantinePath(screamHomeDir), "utf8")).entries;
67103
+ return Array.isArray(entries) ? entries : [];
67104
+ } catch {
67105
+ return [];
67106
+ }
67107
+ }
67108
+ async function appendQuarantine(screamHomeDir, entry) {
67109
+ const entries = await readQuarantine(screamHomeDir);
67110
+ entries.push(entry);
67111
+ const file = {
67112
+ version: 1,
67113
+ entries: entries.slice(-200)
67114
+ };
67115
+ await mkdir(path.dirname(quarantinePath(screamHomeDir)), { recursive: true });
67116
+ await writeFile(quarantinePath(screamHomeDir), `${JSON.stringify(file, null, 2)}\n`, "utf8");
67117
+ }
67118
+ /** Mark the newest unacknowledged entry for this key; no-op when absent. */
67119
+ async function acknowledgeQuarantine(screamHomeDir, sourceKey) {
67120
+ const entries = await readQuarantine(screamHomeDir);
67121
+ const now = (/* @__PURE__ */ new Date()).toISOString();
67122
+ let changed = false;
67123
+ for (let index = 0; index < entries.length; index += 1) {
67124
+ const candidate = entries[index];
67125
+ if (candidate === void 0) continue;
67126
+ if (candidate.sourceKey === sourceKey && candidate.acknowledgedAt === void 0) {
67127
+ entries[index] = {
67128
+ ...candidate,
67129
+ acknowledgedAt: now
67130
+ };
67131
+ changed = true;
67132
+ }
67133
+ }
67134
+ if (!changed) return;
67135
+ const file = {
67136
+ version: 1,
67137
+ entries
67138
+ };
67139
+ await writeFile(quarantinePath(screamHomeDir), `${JSON.stringify(file, null, 2)}\n`, "utf8");
67140
+ }
67141
+ /** Newest unacknowledged entry for a normalized origin key, if any. */
67142
+ function matchQuarantine(entries, sourceKey) {
67143
+ for (let index = entries.length - 1; index >= 0; index -= 1) {
67144
+ const candidate = entries[index];
67145
+ if (candidate === void 0) continue;
67146
+ if (candidate.sourceKey === sourceKey && candidate.acknowledgedAt === void 0) return candidate;
67147
+ }
67148
+ }
67149
+ //#endregion
67150
+ //#region ../../packages/agent-core/src/plugin/stats.ts
67151
+ const STATS_DEBOUNCE_MS = 5e3;
67152
+ const caches = /* @__PURE__ */ new Map();
67153
+ function statsPath(screamHomeDir) {
67154
+ return path.join(screamHomeDir, "plugins", "stats.json");
67155
+ }
67156
+ function cacheFor(screamHomeDir) {
67157
+ let cache = caches.get(screamHomeDir);
67158
+ if (cache === void 0) {
67159
+ cache = {
67160
+ loaded: false,
67161
+ base: /* @__PURE__ */ new Map(),
67162
+ deltas: /* @__PURE__ */ new Map(),
67163
+ timer: void 0
67164
+ };
67165
+ caches.set(screamHomeDir, cache);
67166
+ }
67167
+ return cache;
67168
+ }
67169
+ async function loadIntoCache(screamHomeDir) {
67170
+ const cache = cacheFor(screamHomeDir);
67171
+ if (cache.loaded) return cache.base;
67172
+ try {
67173
+ const byId = JSON.parse(await readFile(statsPath(screamHomeDir), "utf8")).byId ?? {};
67174
+ for (const [id, entry] of Object.entries(byId)) if (typeof entry.calls === "number" && typeof entry.okCalls === "number") cache.base.set(id, {
67175
+ calls: entry.calls,
67176
+ okCalls: entry.okCalls,
67177
+ lastUsedAt: entry.lastUsedAt
67178
+ });
67179
+ } catch {}
67180
+ cache.loaded = true;
67181
+ return cache.base;
67182
+ }
67183
+ function mergeEntry(entry, delta) {
67184
+ entry.calls += delta.calls;
67185
+ entry.okCalls += delta.okCalls;
67186
+ entry.lastUsedAt = delta.lastUsedAt;
67187
+ }
67188
+ /** Effective view = base ⊕ current deltas (deltas survive independently of load timing). */
67189
+ async function effectiveEntries(screamHomeDir) {
67190
+ const cache = cacheFor(screamHomeDir);
67191
+ const base = await loadIntoCache(screamHomeDir);
67192
+ if (cache.deltas.size === 0) return base;
67193
+ const merged = new Map(base);
67194
+ for (const [id, delta] of cache.deltas) {
67195
+ const entry = { ...merged.get(id) ?? {
67196
+ calls: 0,
67197
+ okCalls: 0,
67198
+ lastUsedAt: void 0
67199
+ } };
67200
+ mergeEntry(entry, delta);
67201
+ merged.set(id, entry);
67202
+ }
67203
+ return merged;
67204
+ }
67205
+ /** Force-write base⊕deltas for one home; safe to call repeatedly. */
67206
+ async function flushStats(screamHomeDir) {
67207
+ const cache = cacheFor(screamHomeDir);
67208
+ if (cache.timer !== void 0) {
67209
+ clearTimeout(cache.timer);
67210
+ cache.timer = void 0;
67211
+ }
67212
+ const base = await loadIntoCache(screamHomeDir);
67213
+ for (const [id, delta] of cache.deltas) {
67214
+ const entry = base.get(id) ?? {
67215
+ calls: 0,
67216
+ okCalls: 0,
67217
+ lastUsedAt: void 0
67218
+ };
67219
+ mergeEntry(entry, delta);
67220
+ base.set(id, entry);
67221
+ }
67222
+ cache.deltas.clear();
67223
+ const byId = {};
67224
+ for (const [id, entry] of base) byId[id] = { ...entry };
67225
+ await mkdir(path.dirname(statsPath(screamHomeDir)), { recursive: true });
67226
+ const file = {
67227
+ version: 1,
67228
+ byId
67229
+ };
67230
+ await writeFile(statsPath(screamHomeDir), `${JSON.stringify(file, null, 2)}\n`, "utf8");
67231
+ }
67232
+ /** Arm the debounced writer at most once per home (first-schedule semantics). */
67233
+ function ensureFlushScheduled(screamHomeDir) {
67234
+ const cache = cacheFor(screamHomeDir);
67235
+ if (cache.timer !== void 0) return;
67236
+ cache.timer = setTimeout(() => {
67237
+ cache.timer = void 0;
67238
+ flushStats(screamHomeDir).catch(() => {});
67239
+ }, STATS_DEBOUNCE_MS);
67240
+ }
67241
+ /** Synchronous counter bump into the delta layer; disk write is debounced. */
67242
+ function recordUsageInMemory(screamHomeDir, pluginId, ok) {
67243
+ const cache = cacheFor(screamHomeDir);
67244
+ const existing = cache.deltas.get(pluginId) ?? {
67245
+ calls: 0,
67246
+ okCalls: 0,
67247
+ lastUsedAt: (/* @__PURE__ */ new Date()).toISOString()
67248
+ };
67249
+ cache.deltas.set(pluginId, {
67250
+ calls: existing.calls + 1,
67251
+ okCalls: existing.okCalls + (ok ? 1 : 0),
67252
+ lastUsedAt: (/* @__PURE__ */ new Date()).toISOString()
67253
+ });
67254
+ loadIntoCache(screamHomeDir);
67255
+ ensureFlushScheduled(screamHomeDir);
67256
+ }
67257
+ /** Drop counters for a removed/uninstalled plugin id (base and deltas).
67258
+ * Best-effort: the in-memory delete always happens; a failed disk flush must
67259
+ * never fail the removal whose caller depends on it. */
67260
+ async function forgetStats(screamHomeDir, pluginId) {
67261
+ cacheFor(screamHomeDir).deltas.delete(pluginId);
67262
+ (await loadIntoCache(screamHomeDir)).delete(pluginId);
67263
+ try {
67264
+ await flushStats(screamHomeDir);
67265
+ } catch {}
67266
+ }
67267
+ /** Read-through snapshot used by tool surfaces (check/list views). */
67268
+ async function getUsage(screamHomeDir, pluginId) {
67269
+ return (await effectiveEntries(screamHomeDir)).get(pluginId) ?? {
67270
+ calls: 0,
67271
+ okCalls: 0,
67272
+ lastUsedAt: void 0
67273
+ };
67274
+ }
67275
+ //#endregion
67039
67276
  //#region ../../packages/agent-core/src/plugin/manager.ts
67040
67277
  const SCREAM_NODE_FALLBACK_SUBCOMMAND = "__plugin_run_node";
67041
67278
  var PluginManager = class {
@@ -67162,6 +67399,243 @@ var PluginManager = class {
67162
67399
  await this.persist();
67163
67400
  return record;
67164
67401
  }
67402
+ /**
67403
+ * Replace an already-installed plugin's files from `source`, keeping a
67404
+ * single-slot backup of the previous contents under
67405
+ * `plugins/backups/<id>/`. Unlike a plain reinstall this records the
67406
+ * version transition and refuses to run for unknown ids, so it can always
67407
+ * be paired with `rollback`.
67408
+ */
67409
+ async upgrade(source) {
67410
+ const resolved = resolveInstallSource(source);
67411
+ const isLocal = resolved.kind === "local-path";
67412
+ let stagedRoot;
67413
+ let originalSource;
67414
+ let sourceType;
67415
+ let parsed;
67416
+ let github;
67417
+ let remoteTmpRoot;
67418
+ if (resolved.kind === "local-path") {
67419
+ stagedRoot = await normalizeInstallRoot(resolved.path);
67420
+ originalSource = resolved.path;
67421
+ sourceType = "local-path";
67422
+ parsed = await parseManifest(stagedRoot);
67423
+ if (parsed.manifest === void 0) {
67424
+ const msg = parsed.diagnostics.find((d) => d.severity === "error")?.message ?? "no manifest";
67425
+ throw new Error(`Cannot upgrade plugin at ${stagedRoot}: ${msg}`);
67426
+ }
67427
+ } else {
67428
+ let zipUrl;
67429
+ const tmpDir = await mkdtemp(path.join(tmpdir(), "scream-plugin-upgrade-"));
67430
+ stagedRoot = tmpDir;
67431
+ remoteTmpRoot = tmpDir;
67432
+ try {
67433
+ if (resolved.kind === "github") {
67434
+ const githubResolution = await resolveGithubSource(resolved);
67435
+ zipUrl = githubResolution.tarballUrl;
67436
+ originalSource = source.trim();
67437
+ sourceType = "github";
67438
+ github = {
67439
+ owner: resolved.owner,
67440
+ repo: resolved.repo,
67441
+ ref: githubResolution.ref
67442
+ };
67443
+ } else {
67444
+ zipUrl = resolved.path;
67445
+ originalSource = resolved.path;
67446
+ sourceType = "zip-url";
67447
+ }
67448
+ stagedRoot = await extractZip(await downloadZip(zipUrl), tmpDir);
67449
+ parsed = await parseManifest(stagedRoot);
67450
+ if (parsed.manifest === void 0) {
67451
+ const msg = parsed.diagnostics.find((d) => d.severity === "error")?.message ?? "no manifest";
67452
+ throw new Error(`Cannot upgrade plugin from ${originalSource}: ${msg}`);
67453
+ }
67454
+ } catch (error) {
67455
+ await rm(tmpDir, {
67456
+ recursive: true,
67457
+ force: true
67458
+ });
67459
+ throw error;
67460
+ }
67461
+ }
67462
+ const disposeStaged = async () => {
67463
+ if (!isLocal) {
67464
+ await rm(stagedRoot, {
67465
+ recursive: true,
67466
+ force: true
67467
+ });
67468
+ if (remoteTmpRoot !== void 0 && remoteTmpRoot !== stagedRoot) await rm(remoteTmpRoot, {
67469
+ recursive: true,
67470
+ force: true
67471
+ });
67472
+ }
67473
+ };
67474
+ try {
67475
+ const id = normalizePluginId(parsed.manifest.name);
67476
+ const existing = this.records.get(id);
67477
+ if (existing === void 0) throw new Error(`Plugin "${id}" is not installed; upgrade requires an existing installation`);
67478
+ const oldVersion = existing.manifest?.version;
67479
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replaceAll(/[:.]/g, "-");
67480
+ const backupsForId = path.join(this.screamHomeDir, "plugins", "backups", id);
67481
+ const backupPath = path.join(backupsForId, `${stamp}-${(oldVersion ?? "unknown").replaceAll(/[:.]/g, "-")}`);
67482
+ await mkdir(backupPath, { recursive: true });
67483
+ await cp(existing.root, backupPath, { recursive: true });
67484
+ try {
67485
+ await writeFile(path.join(backupPath, ".scream-rollback.json"), JSON.stringify({
67486
+ originalSource: existing.originalSource,
67487
+ source: existing.source,
67488
+ ...existing.github !== void 0 ? { github: existing.github } : {}
67489
+ }), "utf8");
67490
+ } catch {}
67491
+ await keepNewestBackupOnly(backupsForId);
67492
+ const comparison = compareManifestVersions(oldVersion, parsed.manifest.version);
67493
+ const warn = comparison === void 0 ? "unknown" : comparison === 0 ? "same" : comparison === 1 ? "downgrade" : void 0;
67494
+ const promotedRoot = await copyPluginToManagedRoot(this.screamHomeDir, id, stagedRoot);
67495
+ const promoted = await parseManifest(promotedRoot);
67496
+ const baseRecord = await recordFrom({
67497
+ id,
67498
+ root: promotedRoot,
67499
+ enabled: existing.enabled,
67500
+ installedAt: existing.installedAt,
67501
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
67502
+ originalSource,
67503
+ source: sourceType,
67504
+ capabilities: existing.capabilities,
67505
+ github,
67506
+ parsed: promoted
67507
+ });
67508
+ const note = `upgraded ${oldVersion ?? "unknown"} -> ${promoted.manifest?.version ?? "unknown"}`;
67509
+ const record = {
67510
+ ...baseRecord,
67511
+ diagnostics: [...baseRecord.diagnostics, {
67512
+ severity: "info",
67513
+ message: note
67514
+ }]
67515
+ };
67516
+ this.records.set(id, record);
67517
+ await this.persist();
67518
+ return {
67519
+ record,
67520
+ from: oldVersion,
67521
+ to: promoted.manifest?.version,
67522
+ ...warn !== void 0 ? { warn } : {},
67523
+ backupPath
67524
+ };
67525
+ } finally {
67526
+ await disposeStaged();
67527
+ }
67528
+ }
67529
+ /**
67530
+ * Restore a plugin's files from its newest backup (created by `upgrade`),
67531
+ * then reload the table. The backup manifest is validated BEFORE anything
67532
+ * under plugins/managed is touched.
67533
+ */
67534
+ async rollback(id) {
67535
+ const key = normalizePluginId(id);
67536
+ const existing = this.records.get(key);
67537
+ if (existing === void 0) throw new Error(`Plugin "${id}" is not installed`);
67538
+ const backupsForId = path.join(this.screamHomeDir, "plugins", "backups", key);
67539
+ let newest;
67540
+ try {
67541
+ newest = (await readdir(backupsForId)).toSorted().at(-1);
67542
+ } catch {
67543
+ newest = void 0;
67544
+ }
67545
+ if (newest === void 0) throw new Error(`No backup available for "${key}"`);
67546
+ const backupRoot = path.join(backupsForId, newest);
67547
+ if ((await parseManifest(backupRoot)).manifest === void 0) throw new Error(`Backup at ${backupRoot} has no readable manifest`);
67548
+ let preUpgrade = {};
67549
+ try {
67550
+ preUpgrade = JSON.parse(await readFile(path.join(backupRoot, ".scream-rollback.json"), "utf8"));
67551
+ } catch {}
67552
+ const promotedRoot = await copyPluginToManagedRoot(this.screamHomeDir, key, backupRoot);
67553
+ await rm(path.join(promotedRoot, ".scream-rollback.json"), {
67554
+ recursive: true,
67555
+ force: true
67556
+ });
67557
+ const promoted = await parseManifest(promotedRoot);
67558
+ const baseRecord = await recordFrom({
67559
+ id: key,
67560
+ root: promotedRoot,
67561
+ enabled: existing.enabled,
67562
+ installedAt: existing.installedAt,
67563
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
67564
+ originalSource: preUpgrade.originalSource ?? existing.originalSource,
67565
+ source: preUpgrade.source ?? existing.source,
67566
+ capabilities: existing.capabilities,
67567
+ github: preUpgrade.github ?? existing.github,
67568
+ parsed: promoted
67569
+ });
67570
+ const record = {
67571
+ ...baseRecord,
67572
+ diagnostics: [...baseRecord.diagnostics, {
67573
+ severity: "info",
67574
+ message: `rolled back to backup ${newest}`
67575
+ }]
67576
+ };
67577
+ this.records.set(key, record);
67578
+ await this.persist();
67579
+ return {
67580
+ record,
67581
+ restoredFrom: backupRoot
67582
+ };
67583
+ }
67584
+ /**
67585
+ * Immune memory: remember the ORIGIN of a circuit-tripped plugin so future
67586
+ * sessions can warn before installing from the same repository again.
67587
+ * Best-effort bookkeeping; unknown/local-less records are silently skipped.
67588
+ */
67589
+ async appendQuarantine(pluginId, reason) {
67590
+ const key = normalizePluginId(pluginId);
67591
+ const record = this.records.get(key);
67592
+ if (record === void 0) return void 0;
67593
+ const sourceKey = sourceKeyFromFields({
67594
+ github: record.github,
67595
+ originalSource: record.originalSource
67596
+ });
67597
+ if (sourceKey === "") return void 0;
67598
+ const entry = {
67599
+ at: (/* @__PURE__ */ new Date()).toISOString(),
67600
+ pluginId: key,
67601
+ ...record.manifest?.name !== void 0 ? { name: record.manifest.name } : {},
67602
+ sourceKey,
67603
+ reason
67604
+ };
67605
+ await appendQuarantine(this.screamHomeDir, entry);
67606
+ return entry;
67607
+ }
67608
+ /** Newest unacknowledged quarantine hit for a raw source string, if any.
67609
+ * Both the verbatim and the realpath-resolved forms are tried, because
67610
+ * temp-dir fixtures and user paths can differ by symlinks (macOS /var).
67611
+ */
67612
+ async matchQuarantineForSource(source) {
67613
+ const entries = await readQuarantine(this.screamHomeDir);
67614
+ for (const key of await this.quarantineKeysFor(source)) {
67615
+ const hit = matchQuarantine(entries, key);
67616
+ if (hit !== void 0) return hit;
67617
+ }
67618
+ }
67619
+ /** Clear the pending warning for a source once an approved install landed. */
67620
+ async acknowledgeQuarantineForSource(source) {
67621
+ for (const key of await this.quarantineKeysFor(source)) await acknowledgeQuarantine(this.screamHomeDir, key);
67622
+ }
67623
+ /** Advisory usage counters (see plugin/stats.ts); unknown ids are ignored. */
67624
+ recordUsage(pluginId, ok) {
67625
+ const key = normalizePluginId(pluginId);
67626
+ if (!this.records.has(key)) return;
67627
+ recordUsageInMemory(this.screamHomeDir, key, ok);
67628
+ }
67629
+ async getUsage(pluginId) {
67630
+ return getUsage(this.screamHomeDir, normalizePluginId(pluginId));
67631
+ }
67632
+ async quarantineKeysFor(source) {
67633
+ const keys = [sourceKeyFromRawSource(source)];
67634
+ try {
67635
+ keys.push(sourceKeyFromRawSource(await realpath(source)));
67636
+ } catch {}
67637
+ return [...new Set(keys.filter((key) => key !== ""))];
67638
+ }
67165
67639
  async setEnabled(id, enabled) {
67166
67640
  const key = normalizePluginId(id);
67167
67641
  const current = this.records.get(key);
@@ -67195,9 +67669,37 @@ var PluginManager = class {
67195
67669
  });
67196
67670
  await this.persist();
67197
67671
  }
67672
+ /**
67673
+ * Flag a plugin as failed at runtime (typically its code entry point threw
67674
+ * while activating). The record keeps the reason as an error diagnostic so
67675
+ * `/plugin info` and listings show why the plugin is not usable.
67676
+ */
67677
+ async markError(id, message) {
67678
+ const key = normalizePluginId(id);
67679
+ const current = this.records.get(key);
67680
+ if (current === void 0) {
67681
+ log.warn("plugin markError for an unknown plugin", {
67682
+ pluginId: id,
67683
+ message
67684
+ });
67685
+ return;
67686
+ }
67687
+ const diagnostic = {
67688
+ severity: "error",
67689
+ message
67690
+ };
67691
+ this.records.set(key, {
67692
+ ...current,
67693
+ state: "error",
67694
+ diagnostics: [...current.diagnostics, diagnostic],
67695
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
67696
+ });
67697
+ await this.persist();
67698
+ }
67198
67699
  async remove(id) {
67199
67700
  const key = normalizePluginId(id);
67200
67701
  if (!this.records.delete(key)) throw new Error(`Plugin "${id}" is not installed`);
67702
+ await forgetStats(this.screamHomeDir, key);
67201
67703
  await this.persist();
67202
67704
  }
67203
67705
  async reload() {
@@ -67286,6 +67788,9 @@ var PluginManager = class {
67286
67788
  version: 1,
67287
67789
  plugins: installed
67288
67790
  });
67791
+ try {
67792
+ await flushStats(this.screamHomeDir);
67793
+ } catch {}
67289
67794
  }
67290
67795
  async materialize(entry) {
67291
67796
  const parsed = await parseManifest(entry.root);
@@ -67336,6 +67841,25 @@ async function copyPluginToManagedRoot(screamHomeDir, id, sourceRoot) {
67336
67841
  }
67337
67842
  return realpath(managedRoot);
67338
67843
  }
67844
+ /**
67845
+ * Keep only the newest backup slot per plugin id: upgrade creates a fresh
67846
+ * timestamped directory each run, so older siblings are removed here to keep
67847
+ * backups bounded. Names sort chronologically because they embed an
67848
+ * ISO-derived stamp.
67849
+ */
67850
+ async function keepNewestBackupOnly(backupsForId) {
67851
+ let names;
67852
+ try {
67853
+ names = await readdir(backupsForId);
67854
+ } catch {
67855
+ return;
67856
+ }
67857
+ const ordered = names.toSorted();
67858
+ for (const name of ordered.slice(0, Math.max(0, ordered.length - 1))) await rm(path.join(backupsForId, name), {
67859
+ recursive: true,
67860
+ force: true
67861
+ });
67862
+ }
67339
67863
  async function recordFrom(input) {
67340
67864
  const { parsed } = input;
67341
67865
  const hasError = parsed.diagnostics.some((d) => d.severity === "error");
@@ -67440,7 +67964,26 @@ function withMcpServerEnabled(config, enabled) {
67440
67964
  };
67441
67965
  }
67442
67966
  function pluginMcpRuntimeName(pluginId, serverName) {
67443
- return `plugin-${pluginId}:${serverName}`;
67967
+ return `${PLUGIN_MCP_RUNTIME_PREFIX}${pluginId}:${serverName}`;
67968
+ }
67969
+ /** Runtime-name prefix that marks an MCP server as contributed by a plugin. */
67970
+ const PLUGIN_MCP_RUNTIME_PREFIX = "plugin-";
67971
+ /**
67972
+ * True when an MCP runtime name belongs to a plugin. Anything else is
67973
+ * user-configured and must never be touched by the plugin hot-apply pass.
67974
+ */
67975
+ function isPluginMcpRuntimeName(name) {
67976
+ return name.startsWith(PLUGIN_MCP_RUNTIME_PREFIX);
67977
+ }
67978
+ /**
67979
+ * The plugin id embedded in a plugin MCP runtime name, or `undefined` for a
67980
+ * user-configured server or a name that is not well-formed.
67981
+ */
67982
+ function pluginIdFromMcpRuntimeName(name) {
67983
+ if (!isPluginMcpRuntimeName(name)) return void 0;
67984
+ const separator = name.indexOf(":");
67985
+ if (separator <= 7) return void 0;
67986
+ return name.slice(7, separator);
67444
67987
  }
67445
67988
  function withPluginMcpRuntime(config, pluginRoot, screamHomeDir) {
67446
67989
  if (config.transport === "http") return config;
@@ -67475,7 +68018,7 @@ function isSafeRelativePath(filePath) {
67475
68018
  return !normalized.startsWith("/");
67476
68019
  }
67477
68020
  async function writePluginSkillPackage(options) {
67478
- const { jian, screamHomeDir, package: pkg } = options;
68021
+ const { jian, screamHomeDir, manager, package: pkg } = options;
67479
68022
  const name = sanitizeSkillName(pkg.name);
67480
68023
  const targetDir = join$1(screamHomeDir, "plugins", "managed", name);
67481
68024
  if (await pathExists$1(jian, targetDir)) throw new ScreamError(ErrorCodes.SKILL_ALREADY_EXISTS, `Skill "${name}" already exists at ${targetDir}. Choose a different name or delete the existing skill first.`);
@@ -67512,14 +68055,21 @@ async function writePluginSkillPackage(options) {
67512
68055
  if (error instanceof ScreamError) throw error;
67513
68056
  throw new ScreamError(ErrorCodes.SKILL_INSTALL_FAILED, `Failed to write skill files to ${targetDir}: ${error instanceof Error ? error.message : String(error)}`);
67514
68057
  }
68058
+ let registered;
67515
68059
  try {
67516
- const manager = new PluginManager({ screamHomeDir });
67517
- await manager.load();
67518
- await manager.registerGenerated(targetDir);
68060
+ if (manager !== void 0) registered = await manager.registerGenerated(targetDir);
68061
+ else {
68062
+ const owned = new PluginManager({ screamHomeDir });
68063
+ await owned.load();
68064
+ registered = await owned.registerGenerated(targetDir);
68065
+ }
67519
68066
  } catch (error) {
67520
68067
  throw new ScreamError(ErrorCodes.SKILL_INSTALL_FAILED, `Failed to register skill in plugin center: ${error instanceof Error ? error.message : String(error)}`);
67521
68068
  }
67522
- return { targetDir };
68069
+ return {
68070
+ targetDir,
68071
+ pluginId: registered.id
68072
+ };
67523
68073
  }
67524
68074
  async function pathExists$1(jian, filePath) {
67525
68075
  try {
@@ -67569,11 +68119,19 @@ var MakeSkillApplyTool = class {
67569
68119
  content: args.content,
67570
68120
  files: args.files ?? []
67571
68121
  };
67572
- return { output: `Skill installed to ${(await writePluginSkillPackage({
68122
+ const result = await writePluginSkillPackage({
67573
68123
  jian: this.agent.jian,
67574
68124
  screamHomeDir,
68125
+ manager: this.agent.toolServices?.plugins,
67575
68126
  package: pkg
67576
- })).targetDir}\n\nYou can manage it in the Skill Center with /plugin, and invoke it with /${sanitizeSkillName(args.name)} in a new session.` };
68127
+ });
68128
+ const sync = this.agent.toolServices?.pluginSync;
68129
+ let hotApplied = false;
68130
+ if (sync !== void 0) try {
68131
+ const report = await sync([result.pluginId], { skipMcpAdd: true });
68132
+ hotApplied = report.ok || report.applied.length > 0;
68133
+ } catch {}
68134
+ return { output: `Skill installed to ${result.targetDir}\n\nYou can manage it in the Skill Center with /plugin, and invoke it with /${sanitizeSkillName(args.name)}` + (hotApplied ? " right away — it was hot-applied to this session." : " in a new session.") };
67577
68135
  } catch (error) {
67578
68136
  if (error instanceof ScreamError) return {
67579
68137
  isError: true,
@@ -71847,10 +72405,10 @@ async function findExistingRg(shareDir) {
71847
72405
  source: "share-bin-cached"
71848
72406
  };
71849
72407
  }
71850
- let downloadPromise;
72408
+ let downloadPromise$1;
71851
72409
  async function downloadRgWithLock(shareDir) {
71852
- if (downloadPromise !== void 0) return downloadPromise;
71853
- downloadPromise = (async () => {
72410
+ if (downloadPromise$1 !== void 0) return downloadPromise$1;
72411
+ downloadPromise$1 = (async () => {
71854
72412
  try {
71855
72413
  const existing = await findExistingRg(shareDir);
71856
72414
  if (existing) return existing;
@@ -71859,10 +72417,10 @@ async function downloadRgWithLock(shareDir) {
71859
72417
  source: "share-bin-downloaded"
71860
72418
  };
71861
72419
  } finally {
71862
- downloadPromise = void 0;
72420
+ downloadPromise$1 = void 0;
71863
72421
  }
71864
72422
  })();
71865
- return downloadPromise;
72423
+ return downloadPromise$1;
71866
72424
  }
71867
72425
  function rgBinaryName() {
71868
72426
  return process.platform === "win32" ? "rg.exe" : "rg";
@@ -74686,6 +75244,796 @@ function truncateUtf8$1(input, maxBytes) {
74686
75244
  return `${input.slice(0, low)}${suffix}`;
74687
75245
  }
74688
75246
  //#endregion
75247
+ //#region ../../packages/agent-core/src/plugin/marketplace.ts
75248
+ /**
75249
+ * Read-only plugin marketplace catalog.
75250
+ *
75251
+ * This module mirrors the shape the TUI consumes so a main agent can browse the
75252
+ * same catalog from inside a session. It intentionally does NOT download or
75253
+ * install anything: it only reads a JSON document and returns display fields.
75254
+ * Nothing here feeds the permission chain — a marketplace `tier` is a label, and
75255
+ * `install` always goes through its own approval gate regardless of it.
75256
+ */
75257
+ /** Env var that overrides the catalog location. Shared name with the TUI copy. */
75258
+ const SCREAM_CODE_PLUGIN_MARKETPLACE_URL_ENV$1 = "SCREAM_CODE_PLUGIN_MARKETPLACE_URL";
75259
+ /** Network budget for a catalog read. The catalog is small; fail fast. */
75260
+ const MARKETPLACE_FETCH_TIMEOUT_MS = 15e3;
75261
+ const PLUGIN_MARKETPLACE_TIERS$1 = ["official", "curated"];
75262
+ /**
75263
+ * Load and parse the marketplace catalog.
75264
+ *
75265
+ * Throws on an unreadable or malformed catalog; callers surface the failure as a
75266
+ * tool error with an actionable `next`.
75267
+ */
75268
+ async function loadPluginMarketplace$1(options = {}) {
75269
+ const location = resolveMarketplaceLocation$1(options.source ?? process.env["SCREAM_CODE_PLUGIN_MARKETPLACE_URL"] ?? "https://raw.githubusercontent.com/LIUTod/scream-code/main/plugins/marketplace.json", options.workDir);
75270
+ return parsePluginMarketplace$1(await readMarketplaceText$1(location, options.fetchImpl ?? fetch), location);
75271
+ }
75272
+ /**
75273
+ * Case-insensitive substring filter over the searchable fields.
75274
+ *
75275
+ * An empty/omitted query returns every entry, so callers can pass the raw
75276
+ * argument straight through.
75277
+ */
75278
+ function filterMarketplaceEntries(entries, query) {
75279
+ const needle = query?.trim().toLowerCase();
75280
+ if (needle === void 0 || needle.length === 0) return entries;
75281
+ return entries.filter((entry) => {
75282
+ return [
75283
+ entry.id,
75284
+ entry.displayName,
75285
+ entry.description ?? "",
75286
+ ...entry.keywords ?? []
75287
+ ].join(" ").toLowerCase().includes(needle);
75288
+ });
75289
+ }
75290
+ function resolveMarketplaceLocation$1(source, workDir) {
75291
+ const trimmed = source.trim();
75292
+ if (trimmed.length === 0) throw new Error(`Marketplace source is empty (${SCREAM_CODE_PLUGIN_MARKETPLACE_URL_ENV$1} must not be blank)`);
75293
+ if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) return {
75294
+ raw: trimmed,
75295
+ kind: "remote",
75296
+ resolved: trimmed
75297
+ };
75298
+ if (trimmed.startsWith("file://")) return {
75299
+ raw: trimmed,
75300
+ kind: "local",
75301
+ resolved: fileURLToPath(trimmed)
75302
+ };
75303
+ return {
75304
+ raw: trimmed,
75305
+ kind: "local",
75306
+ resolved: resolveLocalPath$1(trimmed, workDir)
75307
+ };
75308
+ }
75309
+ async function readMarketplaceText$1(location, fetchImpl) {
75310
+ if (location.kind === "local") try {
75311
+ return await readFile(location.resolved, "utf8");
75312
+ } catch (error) {
75313
+ throw new Error(`Cannot read the marketplace file at ${location.resolved}: ${messageOf(error)}`, { cause: error });
75314
+ }
75315
+ const response = await fetchImpl(location.resolved, { signal: AbortSignal.timeout(MARKETPLACE_FETCH_TIMEOUT_MS) });
75316
+ if (!response.ok) throw new Error(`The marketplace at ${location.resolved} returned HTTP ${String(response.status)}`);
75317
+ return response.text();
75318
+ }
75319
+ /**
75320
+ * Parse the catalog document.
75321
+ *
75322
+ * The array key is tolerated as either `entries` or `plugins` because both
75323
+ * shapes are in the wild. Per-entry fields are optional except `source`: without
75324
+ * one there is nothing to install. `id` falls back to the manifest name so a
75325
+ * hand-written catalog without ids still browses.
75326
+ */
75327
+ function parsePluginMarketplace$1(raw, location) {
75328
+ let parsed;
75329
+ try {
75330
+ parsed = JSON.parse(raw);
75331
+ } catch (error) {
75332
+ throw new Error(`The marketplace at ${location.resolved} is not valid JSON: ${messageOf(error)}`, { cause: error });
75333
+ }
75334
+ if (!isRecord$3(parsed)) throw new TypeError(`The marketplace at ${location.resolved} must be a JSON object.`);
75335
+ const rawEntries = rawEntriesOf(parsed);
75336
+ if (rawEntries === void 0) throw new TypeError(`The marketplace at ${location.resolved} must contain an "entries" (or "plugins") array.`);
75337
+ const entries = [];
75338
+ rawEntries.forEach((value, index) => {
75339
+ const entry = parseMarketplaceEntry$1(value, index, location);
75340
+ if (entry !== void 0) entries.push(entry);
75341
+ });
75342
+ return {
75343
+ source: location.resolved,
75344
+ version: stringField$1(parsed, "version"),
75345
+ entries
75346
+ };
75347
+ }
75348
+ function rawEntriesOf(parsed) {
75349
+ for (const key of ["entries", "plugins"]) {
75350
+ const value = parsed[key];
75351
+ if (Array.isArray(value)) return value;
75352
+ }
75353
+ }
75354
+ function parseMarketplaceEntry$1(value, index, location) {
75355
+ if (!isRecord$3(value)) return void 0;
75356
+ const source = stringField$1(value, "source") ?? stringField$1(value, "url") ?? stringField$1(value, "downloadUrl");
75357
+ if (source === void 0) return;
75358
+ const id = stringField$1(value, "id") ?? stringField$1(value, "name") ?? `entry-${String(index + 1)}`;
75359
+ return {
75360
+ id,
75361
+ displayName: stringField$1(value, "displayName") ?? stringField$1(value, "name") ?? id,
75362
+ source: resolveEntrySource$1(source, location),
75363
+ tier: parseMarketplaceTier$1(value),
75364
+ version: stringField$1(value, "version"),
75365
+ description: stringField$1(value, "description") ?? stringField$1(value, "shortDescription"),
75366
+ homepage: stringField$1(value, "homepage") ?? stringField$1(value, "websiteURL"),
75367
+ keywords: stringArrayField$1(value, "keywords") ?? stringArrayField$1(value, "tags")
75368
+ };
75369
+ }
75370
+ function parseMarketplaceTier$1(value) {
75371
+ const raw = value["tier"];
75372
+ if (typeof raw !== "string") return void 0;
75373
+ const tier = raw.trim();
75374
+ return PLUGIN_MARKETPLACE_TIERS$1.includes(tier) ? tier : void 0;
75375
+ }
75376
+ function resolveEntrySource$1(source, location) {
75377
+ const trimmed = source.trim();
75378
+ if (trimmed.startsWith("http://") || trimmed.startsWith("https://") || trimmed.startsWith("~/") || trimmed === "~" || isAbsolute(trimmed)) return trimmed;
75379
+ if (trimmed.startsWith("file://")) return fileURLToPath(trimmed);
75380
+ if (location.kind === "remote") return new URL(trimmed, location.resolved).toString();
75381
+ return resolve(dirname$1(location.resolved), trimmed);
75382
+ }
75383
+ function resolveLocalPath$1(input, workDir) {
75384
+ if (input === "~") return homedir();
75385
+ if (input.startsWith("~/")) return join(homedir(), input.slice(2));
75386
+ return isAbsolute(input) ? input : resolve(workDir ?? process.cwd(), input);
75387
+ }
75388
+ function stringField$1(value, field) {
75389
+ const raw = value[field];
75390
+ if (typeof raw !== "string") return void 0;
75391
+ const trimmed = raw.trim();
75392
+ return trimmed.length > 0 ? trimmed : void 0;
75393
+ }
75394
+ function stringArrayField$1(value, field) {
75395
+ const raw = value[field];
75396
+ if (!Array.isArray(raw)) return void 0;
75397
+ const out = raw.filter((item) => typeof item === "string").map((item) => item.trim()).filter((item) => item.length > 0);
75398
+ return out.length > 0 ? out : void 0;
75399
+ }
75400
+ function isRecord$3(value) {
75401
+ return typeof value === "object" && value !== null && !Array.isArray(value);
75402
+ }
75403
+ function messageOf(error) {
75404
+ return error instanceof Error ? error.message : String(error);
75405
+ }
75406
+ //#endregion
75407
+ //#region ../../packages/agent-core/src/tools/builtin/plugin/manage-plugin.md
75408
+ 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";
75409
+ //#endregion
75410
+ //#region ../../packages/agent-core/src/tools/builtin/plugin/manage-plugin.ts
75411
+ /** Every action this tool understands. */
75412
+ const MANAGE_PLUGIN_ACTIONS = [
75413
+ "list",
75414
+ "info",
75415
+ "check",
75416
+ "marketplace",
75417
+ "precheck",
75418
+ "install",
75419
+ "register_generated",
75420
+ "enable",
75421
+ "disable",
75422
+ "set_mcp_enabled",
75423
+ "activate",
75424
+ "deactivate",
75425
+ "remove",
75426
+ "reset",
75427
+ "rollback",
75428
+ "reload"
75429
+ ];
75430
+ /**
75431
+ * Actions that only read plugin state. The permission policy auto-approves
75432
+ * exactly this set; nothing here may write state or run plugin code.
75433
+ */
75434
+ const MANAGE_PLUGIN_READ_ONLY_ACTIONS = [
75435
+ "list",
75436
+ "info",
75437
+ "check",
75438
+ "marketplace",
75439
+ "precheck"
75440
+ ];
75441
+ const ManagePluginInputSchema = z.object({
75442
+ action: z.enum(MANAGE_PLUGIN_ACTIONS).describe("Which plugin operation to run."),
75443
+ id: z.string().optional().describe("Plugin id. Required for info, check, enable, disable, set_mcp_enabled, activate, deactivate, remove, reset."),
75444
+ source: z.string().optional().describe("install: absolute path / GitHub URL / zip URL. register_generated: the generated plugin directory. marketplace: optional catalog URL or local JSON path."),
75445
+ upgrade: z.boolean().optional().describe("install onto an existing plugin id: back up its current files, replace with the incoming ones, and reload. Without it, installing over an existing id silently overwrites."),
75446
+ server: z.string().optional().describe("MCP server name, for set_mcp_enabled."),
75447
+ enabled: z.boolean().optional().describe("Target state, for set_mcp_enabled."),
75448
+ query: z.string().optional().describe("Case-insensitive substring filter, for marketplace.")
75449
+ });
75450
+ /**
75451
+ * The two actions that bring third-party code in. Their approval identity
75452
+ * includes the source, so a "approve for session" grant cannot be replayed
75453
+ * against a different URL later in the same session.
75454
+ */
75455
+ const SOURCE_SCOPED_ACTIONS = new Set(["install", "register_generated"]);
75456
+ /**
75457
+ * Actions that cannot be answered without naming a plugin. `check` is not here:
75458
+ * without an id it reports the health of the whole table.
75459
+ */
75460
+ const NEEDS_ID = new Set([
75461
+ "info",
75462
+ "enable",
75463
+ "disable",
75464
+ "set_mcp_enabled",
75465
+ "activate",
75466
+ "deactivate",
75467
+ "remove",
75468
+ "reset",
75469
+ "rollback"
75470
+ ]);
75471
+ function isKnownAction(action) {
75472
+ return MANAGE_PLUGIN_ACTIONS.includes(action);
75473
+ }
75474
+ function isReadOnlyAction(action) {
75475
+ return MANAGE_PLUGIN_READ_ONLY_ACTIONS.includes(action);
75476
+ }
75477
+ function isSourceScoped(action) {
75478
+ return SOURCE_SCOPED_ACTIONS.has(action);
75479
+ }
75480
+ function fail$1(code, message, next) {
75481
+ return { error: {
75482
+ code,
75483
+ message,
75484
+ next
75485
+ } };
75486
+ }
75487
+ function errorMessage$3(error) {
75488
+ return error instanceof Error ? error.message : String(error);
75489
+ }
75490
+ function result(payload, isError = false) {
75491
+ return isError ? {
75492
+ output: JSON.stringify(payload),
75493
+ isError: true
75494
+ } : { output: JSON.stringify(payload) };
75495
+ }
75496
+ /**
75497
+ * Same payload as {@link result} but typed as the error variant, which is what
75498
+ * `resolveExecution` may return to reject a call before it reaches approval.
75499
+ */
75500
+ function preflightError(envelope) {
75501
+ return {
75502
+ output: JSON.stringify(envelope),
75503
+ isError: true
75504
+ };
75505
+ }
75506
+ var ManagePluginTool = class {
75507
+ agent;
75508
+ name = "ManagePlugin";
75509
+ description = manage_plugin_default;
75510
+ parameters = toInputJsonSchema(ManagePluginInputSchema);
75511
+ constructor(agent) {
75512
+ this.agent = agent;
75513
+ }
75514
+ resolveExecution(args) {
75515
+ if (!isKnownAction(args.action)) return preflightError(fail$1("unknown_action", `Unknown ManagePlugin action "${String(args.action)}".`, `Pick one of: ${MANAGE_PLUGIN_ACTIONS.join(", ")}.`));
75516
+ const missing = missingRequiredArgs(args);
75517
+ if (missing !== void 0) return preflightError(missing);
75518
+ const subject = ruleSubject(args);
75519
+ return {
75520
+ accesses: isReadOnlyAction(args.action) ? ToolAccesses.none() : ToolAccesses.all(),
75521
+ description: approvalDescription(args),
75522
+ approvalRule: literalRulePattern(this.name, subject),
75523
+ matchesRule: (ruleArgs) => matchesGlobRuleSubject(ruleArgs, subject),
75524
+ execute: () => this.run(args)
75525
+ };
75526
+ }
75527
+ /**
75528
+ * Single funnel for every action. Contract: JSON in `output`, failures as
75529
+ * `isError` with `{error:{code,message,next}}`, and no exception ever
75530
+ * escapes — a plugin-center bug must not surface as a tool crash.
75531
+ */
75532
+ async run(args) {
75533
+ const manager = this.agent.toolServices?.plugins;
75534
+ if (manager === void 0) return result(fail$1("plugin_center_unavailable", "The shared plugin manager is not available in this runtime.", "Report this so the host can expose its PluginManager, or use the /plugin command."), true);
75535
+ try {
75536
+ switch (args.action) {
75537
+ case "list": return result({
75538
+ action: "list",
75539
+ count: manager.list().length,
75540
+ plugins: manager.list().map(listView),
75541
+ note: "Enabled plugins contribute skills and MCP servers; a code plugin only runs after a separate activate."
75542
+ });
75543
+ case "info": return this.info(manager, args.id);
75544
+ case "check": return await this.check(manager, args.id);
75545
+ case "precheck": return await this.precheck(args.source);
75546
+ case "marketplace": return await this.marketplace(args.source, args.query);
75547
+ case "install": return await this.install(manager, args.source, args.upgrade === true);
75548
+ case "register_generated": return await this.registerGenerated(manager, args.source);
75549
+ case "enable":
75550
+ case "disable": {
75551
+ const record = manager.get(args.id);
75552
+ if (record === void 0) return result(notFound(args.id, manager), true);
75553
+ const enabled = args.action === "enable";
75554
+ await manager.setEnabled(record.id, enabled);
75555
+ const sync = await this.sync([record.id]);
75556
+ return result({
75557
+ action: args.action,
75558
+ id: record.id,
75559
+ enabled,
75560
+ record: listView(manager.get(record.id)),
75561
+ ...sync !== void 0 ? { sync } : {},
75562
+ note: enabled ? "Enabled; skills and MCP servers hot-applied when sync reports them. Code still needs a separate activate." : "Disabled; its skills, MCP servers, and plugin tools were pulled from live sessions."
75563
+ });
75564
+ }
75565
+ case "set_mcp_enabled": {
75566
+ const record = manager.get(args.id);
75567
+ if (record === void 0) return result(notFound(args.id, manager), true);
75568
+ await manager.setMcpServerEnabled(record.id, args.server, args.enabled);
75569
+ const info = manager.info(record.id);
75570
+ const sync = await this.sync([record.id]);
75571
+ return result({
75572
+ action: args.action,
75573
+ id: record.id,
75574
+ server: args.server,
75575
+ enabled: args.enabled,
75576
+ mcpServers: info?.mcpServers ?? [],
75577
+ ...sync !== void 0 ? { sync } : {},
75578
+ note: "MCP server state changed; live sessions were reconciled when sync reports mcp.add/mcp.remove."
75579
+ });
75580
+ }
75581
+ case "activate": return await this.activate(manager, args.id);
75582
+ case "deactivate": return await this.deactivate(manager, args.id);
75583
+ case "remove": return await this.remove(manager, args.id);
75584
+ case "reset": return await this.reset(manager, args.id);
75585
+ case "rollback": return await this.rollbackPlugin(manager, args.id);
75586
+ case "reload": {
75587
+ const summary = await manager.reload();
75588
+ const sync = await this.sync();
75589
+ return result({
75590
+ action: "reload",
75591
+ ...summary,
75592
+ ...sync !== void 0 ? { sync } : {}
75593
+ });
75594
+ }
75595
+ default: return result(fail$1("unknown_action", `Unknown ManagePlugin action "${String(args.action)}".`, `Pick one of: ${MANAGE_PLUGIN_ACTIONS.join(", ")}.`), true);
75596
+ }
75597
+ } catch (error) {
75598
+ return result(fail$1("plugin_operation_failed", errorMessage$3(error), nextForError(args, error)), true);
75599
+ }
75600
+ }
75601
+ info(manager, id) {
75602
+ const info = manager.info(id);
75603
+ if (info === void 0) return result(notFound(id, manager), true);
75604
+ return result({
75605
+ action: "info",
75606
+ plugin: projectInfo(info),
75607
+ active: this.runtime?.isActive(info.id) === true
75608
+ });
75609
+ }
75610
+ /**
75611
+ * Health view built from the diagnostics each record already carries. There
75612
+ * is no persistent reload log, so none is invented: `reload` is what returns
75613
+ * a fresh summary after re-reading the table from disk.
75614
+ */
75615
+ async check(manager, id) {
75616
+ if (id !== void 0 && manager.get(id) === void 0) return result(notFound(id, manager), true);
75617
+ const records = id === void 0 ? manager.list() : [manager.get(id)];
75618
+ const plugins = await Promise.all(records.map(async (record) => ({
75619
+ ...checkView(record, this.runtime?.isActive(record.id) === true),
75620
+ circuit: this.circuitView(record.id),
75621
+ usage: await this.usageView(record.id)
75622
+ })));
75623
+ const unhealthy = plugins.filter((plugin) => plugin.errors.length > 0 || plugin.state === "error");
75624
+ return result({
75625
+ action: "check",
75626
+ checked: plugins.length,
75627
+ healthy: unhealthy.length === 0,
75628
+ unhealthy: unhealthy.map((plugin) => plugin.id),
75629
+ plugins,
75630
+ note: "Diagnostics are the ones stored on each record from the last load or reload. Run action:\"reload\" to re-read the plugin table from disk and get a fresh summary."
75631
+ });
75632
+ }
75633
+ async marketplace(source, query) {
75634
+ try {
75635
+ const catalog = await loadPluginMarketplace$1({
75636
+ source,
75637
+ workDir: this.agent.config.cwd
75638
+ });
75639
+ const filtered = filterMarketplaceEntries(catalog.entries, query);
75640
+ const quarantinedIds = /* @__PURE__ */ new Set();
75641
+ for (const entry of filtered) if (await this.matchQuarantine(entry.source) !== void 0) quarantinedIds.add(entry.id);
75642
+ const entries = filtered.map((entry) => ({
75643
+ ...entry,
75644
+ ...quarantinedIds.has(entry.id) ? { quarantined: true } : {}
75645
+ }));
75646
+ return result({
75647
+ action: "marketplace",
75648
+ catalog: catalog.source,
75649
+ version: catalog.version,
75650
+ query: query ?? null,
75651
+ count: entries.length,
75652
+ entries,
75653
+ note: "Catalog metadata only. \"tier\" is a display label and never skips the approval prompt; nothing was downloaded or installed here."
75654
+ });
75655
+ } catch (error) {
75656
+ return result(fail$1("marketplace_unavailable", `Could not load the plugin marketplace: ${errorMessage$3(error)}`, "Check the URL or file path, or pass one explicitly: {action:\"marketplace\", source:\"<https url or local json path>\"}."), true);
75657
+ }
75658
+ }
75659
+ /**
75660
+ * Install only lands files and registers the record. Nothing is imported or
75661
+ * run: activation stays a separate, separately-approved action.
75662
+ */
75663
+ async precheck(source) {
75664
+ const entry = await this.agent.toolServices?.plugins?.matchQuarantineForSource?.(source) ?? void 0;
75665
+ return result({
75666
+ action: "precheck",
75667
+ source,
75668
+ quarantined: entry !== void 0,
75669
+ ...entry !== void 0 ? { entry: {
75670
+ at: entry.at,
75671
+ pluginId: entry.pluginId,
75672
+ reason: entry.reason
75673
+ } } : {},
75674
+ note: entry === void 0 ? "No unacknowledged incident on record for this origin." : "This origin previously produced a circuit-tripped plugin; proceed only with explicit user consent."
75675
+ });
75676
+ }
75677
+ /** Quarantine lookup helper tolerant of hosts without the ledger. */
75678
+ async matchQuarantine(source) {
75679
+ return await this.agent.toolServices?.plugins?.matchQuarantineForSource?.(source) ?? void 0;
75680
+ }
75681
+ /** Acknowledge the quarantine warning after an approved install landed. */
75682
+ async acknowledgeQuarantine(source) {
75683
+ try {
75684
+ await this.agent.toolServices?.plugins?.acknowledgeQuarantineForSource?.(source);
75685
+ } catch {}
75686
+ }
75687
+ async install(manager, source, upgrade = false) {
75688
+ const quarantineHit = await this.agent.toolServices?.plugins?.matchQuarantineForSource?.(source) ?? void 0;
75689
+ const quarantineNote = quarantineHit !== void 0 ? { quarantine: {
75690
+ at: quarantineHit.at,
75691
+ reason: quarantineHit.reason
75692
+ } } : {};
75693
+ if (upgrade) {
75694
+ const upgraded = await manager.upgrade(source);
75695
+ const sync = await this.sync([upgraded.record.id], { skipMcpAdd: true });
75696
+ await this.acknowledgeQuarantine(source);
75697
+ return result({
75698
+ action: "install",
75699
+ id: upgraded.record.id,
75700
+ installed: true,
75701
+ upgraded: true,
75702
+ from: upgraded.from,
75703
+ to: upgraded.to,
75704
+ ...upgraded.warn !== void 0 ? { warn: upgraded.warn } : {},
75705
+ backupPath: upgraded.backupPath,
75706
+ ...quarantineNote,
75707
+ codeExecuted: false,
75708
+ hasCodeEntryPoint: upgraded.record.manifest?.entryPoint !== void 0,
75709
+ record: listView(upgraded.record),
75710
+ ...sync !== void 0 ? { sync } : {},
75711
+ message: "files replaced from backup-safe upgrade; previous contents restorable via {action:\"rollback\"}; skills live now, MCP servers start after an approved enable/activate" + (quarantineHit !== void 0 ? ` \u26A0 origin previously tripped (${quarantineHit.reason})` : "")
75712
+ });
75713
+ }
75714
+ const record = await manager.install(source);
75715
+ const sync = await this.sync([record.id], { skipMcpAdd: true });
75716
+ await this.acknowledgeQuarantine(source);
75717
+ return result({
75718
+ action: "install",
75719
+ id: record.id,
75720
+ installed: true,
75721
+ activated: false,
75722
+ codeExecuted: false,
75723
+ hasCodeEntryPoint: record.manifest?.entryPoint !== void 0,
75724
+ record: listView(record),
75725
+ ...quarantineNote,
75726
+ ...sync !== void 0 ? { sync } : {},
75727
+ message: "installed, code not executed yet — its skills are live now, MCP servers start after an approved activate/enable; run activate to enable its code tools" + (quarantineHit !== void 0 ? ` \u26A0 origin previously tripped (${quarantineHit.reason})` : "")
75728
+ });
75729
+ }
75730
+ /**
75731
+ * Register a locally generated plugin directory. A manifest with an
75732
+ * `entryPoint` is refused: the generated-skill flow is auto-approved, so it
75733
+ * must never become a channel for running unapproved code.
75734
+ */
75735
+ async registerGenerated(manager, source) {
75736
+ const parsed = await parseManifest(source);
75737
+ if (parsed.manifest === void 0) return result(fail$1("manifest_invalid", `Cannot register "${source}" as a generated plugin: ${parsed.diagnostics.find((diagnostic) => diagnostic.severity === "error")?.message ?? "no plugin manifest found"}`, "Write a valid plugin manifest in that directory first (see the MakeSkillPlan / MakeSkillApply flow)."), true);
75738
+ if (parsed.manifest.entryPoint !== void 0) return result(fail$1("code_entry_point_not_allowed", `Refusing to register "${parsed.manifest.name}": its manifest declares an entryPoint, which would run code through a path that is normally auto-approved.`, "Install it from its source instead, then activate it as a separate approved step: {action:\"install\", source:\"<path or url>\"} followed by {action:\"activate\", id:\"<plugin id>\"}."), true);
75739
+ const record = await manager.registerGenerated(source);
75740
+ const sync = await this.sync([record.id], { skipMcpAdd: true });
75741
+ return result({
75742
+ action: "register_generated",
75743
+ id: record.id,
75744
+ registered: true,
75745
+ enabled: record.enabled,
75746
+ hasCodeEntryPoint: false,
75747
+ record: listView(record),
75748
+ ...sync !== void 0 ? { sync } : {},
75749
+ note: "Registered in place; its skills are hot-applied to this session when sync reports skills.inject."
75750
+ });
75751
+ }
75752
+ async activate(manager, id) {
75753
+ const record = manager.get(id);
75754
+ if (record === void 0) return result(notFound(id, manager), true);
75755
+ const runtime = this.runtime;
75756
+ if (runtime === void 0) return result(fail$1("extension_runtime_unavailable", "This runtime has no extension manager, so plugin code cannot be activated.", "The plugin stays installed; use the /plugin command or report this so the host can expose an ExtensionRuntime."), true);
75757
+ if (runtime.isActive(record.id)) return result({
75758
+ action: "activate",
75759
+ id: record.id,
75760
+ active: true,
75761
+ alreadyActive: true,
75762
+ note: "Nothing ran again — the plugin is already live in this process."
75763
+ });
75764
+ const [extension] = runtime.discover([record]);
75765
+ if (extension === void 0) return result(fail$1("no_code_entry_point", `Plugin "${record.id}" has no manifest entryPoint, so there is no code to activate.`, `Enable it instead: {action:"enable", id:"${record.id}"}.`), true);
75766
+ try {
75767
+ await runtime.activate(this.agent, extension);
75768
+ } catch (error) {
75769
+ await manager.markError(record.id, errorMessage$3(error)).catch(() => void 0);
75770
+ const upgradedRecently = record.diagnostics.some((d) => d.message.startsWith("upgraded "));
75771
+ return result(fail$1("activation_failed", `Failed to activate "${record.id}": ${errorMessage$3(error)}`, `Inspect {action:"info", id:"${record.id}"} for the stored diagnostics; fix it, or ${upgradedRecently ? `{action:"rollback", id:"${record.id}"} to restore the pre-upgrade files, or ` : ""}{action:"remove", id:"${record.id}"}.`), true);
75772
+ }
75773
+ const sync = await this.sync([record.id]);
75774
+ return result({
75775
+ action: "activate",
75776
+ id: record.id,
75777
+ active: true,
75778
+ entryPoint: extension.entryPoint,
75779
+ enabled: record.enabled,
75780
+ ...sync !== void 0 ? { sync } : {},
75781
+ ...record.enabled ? {} : { warning: "The plugin record is disabled; enable it so its capabilities survive the next reload." }
75782
+ });
75783
+ }
75784
+ async deactivate(manager, id) {
75785
+ const record = manager.get(id);
75786
+ if (record === void 0) return result(notFound(id, manager), true);
75787
+ const runtime = this.runtime;
75788
+ if (runtime === void 0) return result(fail$1("extension_runtime_unavailable", "This runtime has no extension manager to deactivate from.", "Use the /plugin command or report this so the host can expose an ExtensionRuntime."), true);
75789
+ const wasActive = runtime.isActive(record.id);
75790
+ await runtime.deactivate(record.id);
75791
+ const toolsUnregistered = this.agent.services?.tools?.unregisterToolsByOwner?.(record.id) ?? 0;
75792
+ return result({
75793
+ action: "deactivate",
75794
+ id: record.id,
75795
+ active: false,
75796
+ wasActive,
75797
+ toolsUnregistered,
75798
+ ...wasActive ? {} : { note: "The plugin was not active; nothing to unwind." }
75799
+ });
75800
+ }
75801
+ async remove(manager, id) {
75802
+ const record = manager.get(id);
75803
+ if (record === void 0) return result(notFound(id, manager), true);
75804
+ const runtime = this.runtime;
75805
+ const wasActive = runtime?.isActive(record.id) === true;
75806
+ if (wasActive && runtime !== void 0) await runtime.deactivate(record.id);
75807
+ await manager.remove(record.id);
75808
+ const sync = await this.sync([record.id]);
75809
+ return result({
75810
+ action: "remove",
75811
+ id: record.id,
75812
+ removed: true,
75813
+ deactivatedFirst: wasActive,
75814
+ ...sync !== void 0 ? { sync } : {},
75815
+ note: "The plugin record is gone; files on disk were not deleted."
75816
+ });
75817
+ }
75818
+ get runtime() {
75819
+ return this.agent.toolServices?.extensionRuntime;
75820
+ }
75821
+ /** Live circuit-breaker state for one plugin, read from the agent ledger. */
75822
+ circuitView(id) {
75823
+ const tools = this.agent.services?.tools;
75824
+ return {
75825
+ failures: tools?.getCircuitFailures?.(id) ?? 0,
75826
+ tripped: tools?.isCircuitTripped?.(id) ?? false
75827
+ };
75828
+ }
75829
+ /** Advisory usage counters from the host sidecar, formatted for the model. */
75830
+ async usageView(id) {
75831
+ const usage = await this.agent.toolServices?.plugins?.getUsage?.(id) ?? {
75832
+ calls: 0,
75833
+ okCalls: 0,
75834
+ lastUsedAt: void 0
75835
+ };
75836
+ const okRate = usage.calls === 0 ? null : Math.round(usage.okCalls / usage.calls * 100);
75837
+ return {
75838
+ calls: usage.calls,
75839
+ okCalls: usage.okCalls,
75840
+ okRate,
75841
+ lastUsedAt: usage.lastUsedAt
75842
+ };
75843
+ }
75844
+ /**
75845
+ * Recover a circuit-tripped (or otherwise disabled) plugin: clear the
75846
+ * failure ledger, re-derive the record state from disk, enable it, and hot
75847
+ * -apply its capabilities. A code entry point still needs a separate,
75848
+ * approved `activate` — reset never runs plugin code.
75849
+ */
75850
+ async reset(manager, id) {
75851
+ const record = manager.get(id);
75852
+ if (record === void 0) return result(notFound(id, manager), true);
75853
+ const recordId = record.id;
75854
+ this.agent.services?.tools?.resetCircuit?.(recordId);
75855
+ await manager.reload();
75856
+ const after = manager.get(recordId);
75857
+ if (after === void 0) return result(fail$1("plugin_not_found", `Plugin "${recordId}" disappeared when reset re-read the table.`, "call action:'list' to see what is installed now"), true);
75858
+ await manager.setEnabled(recordId, true);
75859
+ const sync = await this.sync([recordId]);
75860
+ return result({
75861
+ action: "reset",
75862
+ id: recordId,
75863
+ enabled: true,
75864
+ state: after.state,
75865
+ circuit: { cleared: true },
75866
+ ...sync !== void 0 ? { sync } : {},
75867
+ note: after.state === "error" ? "Circuit cleared, but the record still reports errors after re-reading the manifest — see {action:\"info\"}." : "Circuit cleared and capabilities hot-applied. Code entry points still need a separate approved activate."
75868
+ });
75869
+ }
75870
+ /**
75871
+ * Restore a plugin's files from the single-slot backup written by
75872
+ * `upgrade` (install with upgrade:true). Reads-only until success; any
75873
+ * failure is reported as an ordinary tool error.
75874
+ */
75875
+ async rollbackPlugin(manager, id) {
75876
+ const record = manager.get(id);
75877
+ if (record === void 0) return result(notFound(id, manager), true);
75878
+ try {
75879
+ const restored = await manager.rollback(record.id);
75880
+ const sync = await this.sync([record.id]);
75881
+ return result({
75882
+ action: "rollback",
75883
+ id: record.id,
75884
+ rolledBack: true,
75885
+ restoredFrom: restored.restoredFrom,
75886
+ state: restored.record.state,
75887
+ version: restored.record.manifest?.version,
75888
+ ...sync !== void 0 ? { sync } : {},
75889
+ note: "Previous files restored and the table reloaded; code entry points still need a separate approved activate."
75890
+ });
75891
+ } catch (error) {
75892
+ return result(fail$1("rollback_unavailable", error instanceof Error ? error.message : String(error), "call action:'info' to confirm the plugin is installed"), true);
75893
+ }
75894
+ }
75895
+ /**
75896
+ * Hot-apply a successful mutation into live sessions through the host-owned
75897
+ * sync entry point. Advisory by design: the plugin change is already durable
75898
+ * when this runs, so the report is surfaced but never flips the action's
75899
+ * outcome, and a missing entry point (older hosts) degrades to `undefined`.
75900
+ */
75901
+ async sync(changedIds, options) {
75902
+ const pluginSync = this.agent.toolServices?.pluginSync;
75903
+ if (pluginSync === void 0) return void 0;
75904
+ try {
75905
+ return summarizePluginSync(await pluginSync(changedIds, options));
75906
+ } catch (error) {
75907
+ return {
75908
+ ok: false,
75909
+ applied: [],
75910
+ failed: [{
75911
+ step: "mcp.add",
75912
+ message: error instanceof Error ? error.message : String(error)
75913
+ }]
75914
+ };
75915
+ }
75916
+ }
75917
+ };
75918
+ /**
75919
+ * The string that identifies what is being approved. For the source-scoped
75920
+ * actions the source is part of the identity, so the recorded session rule
75921
+ * reads `ManagePlugin(install <source>)` and only replays for that exact source.
75922
+ */
75923
+ function ruleSubject(args) {
75924
+ if (isSourceScoped(args.action) && args.source !== void 0) return `${args.action} ${args.source}`;
75925
+ return args.action;
75926
+ }
75927
+ /** Human-readable approval line. Source-scoped actions must show the full source. */
75928
+ function approvalDescription(args) {
75929
+ switch (args.action) {
75930
+ case "list": return "Listing installed plugins";
75931
+ case "info": return `Reading plugin "${args.id}"`;
75932
+ case "check": return args.id === void 0 ? "Checking all plugins" : `Checking plugin "${args.id}"`;
75933
+ case "marketplace": return `Browsing the plugin marketplace${args.source === void 0 ? "" : ` at ${args.source}`}`;
75934
+ case "precheck": return `Prechecking install origin "${args.source}"`;
75935
+ case "install": return `Install plugin from source: ${args.source} (downloads code; it is not executed until a separate activate)`;
75936
+ case "register_generated": return `Register generated plugin from directory: ${args.source}`;
75937
+ case "enable": return `Enable plugin "${args.id}"`;
75938
+ case "disable": return `Disable plugin "${args.id}"`;
75939
+ case "set_mcp_enabled": return `${args.enabled ? "Enable" : "Disable"} MCP server "${args.server}" of plugin "${args.id}"`;
75940
+ case "activate": return `Activate plugin "${args.id}" — this runs its code entry point in this process`;
75941
+ case "deactivate": return `Deactivate plugin "${args.id}"`;
75942
+ case "remove": return `Remove plugin "${args.id}" from the plugin center`;
75943
+ case "reset": return `Reset plugin "${args.id}" — clear its circuit breaker and re-enable it`;
75944
+ case "rollback": return `Roll back plugin "${args.id}" to its pre-upgrade backup`;
75945
+ case "reload": return "Reload the plugin table from disk";
75946
+ default: return "Managing plugins";
75947
+ }
75948
+ }
75949
+ function missingRequiredArgs(args) {
75950
+ if (NEEDS_ID.has(args.action) && args.id === void 0) return fail$1("missing_id", `Action "${args.action}" needs a plugin id.`, "call action:'list' to see installed plugin ids");
75951
+ if (isSourceScoped(args.action) && (args.source === void 0 || args.source.trim().length === 0)) return fail$1("missing_source", `Action "${args.action}" needs a source.`, "pass source:\"<absolute path | GitHub url | zip url>\", or browse first with action:\"marketplace\"");
75952
+ if (args.action === "set_mcp_enabled") {
75953
+ if (args.server === void 0) return fail$1("missing_server", "Action \"set_mcp_enabled\" needs the MCP server name.", `call {action:"info", id:"${String(args.id)}"} and read its mcpServers list`);
75954
+ if (typeof args.enabled !== "boolean") return fail$1("missing_enabled", "Action \"set_mcp_enabled\" needs enabled:true or enabled:false.", "pass the desired boolean state");
75955
+ }
75956
+ }
75957
+ function notFound(id, manager) {
75958
+ return fail$1("plugin_not_found", `Plugin "${id}" is not installed.`, `call action:'list' to see installed plugin ids (${String(manager.list().length)} installed)`);
75959
+ }
75960
+ /** Turn a manager-thrown message into the next step the model can act on. */
75961
+ function nextForError(args, error) {
75962
+ const message = errorMessage$3(error);
75963
+ if (/already exists/i.test(message)) return `use {action:"enable", id:"${args.id ?? args.source ?? ""}"} or install under a different name`;
75964
+ if (/not installed/i.test(message)) return "call action:'list' to see installed plugin ids";
75965
+ if (/does not declare MCP server/i.test(message)) return `call {action:"info", id:"${String(args.id)}"} and pick one of its listed servers`;
75966
+ if (/absolute path|does not exist|not a directory/i.test(message)) return "pass an absolute directory path, a https GitHub url, or a https zip url";
75967
+ return "inspect {action:\"list\"} and retry; if a record is broken, {action:\"remove\", id:\"<id>\"} it";
75968
+ }
75969
+ function listView(record) {
75970
+ return {
75971
+ id: record.id,
75972
+ name: record.manifest?.name ?? record.id,
75973
+ displayName: record.manifest?.interface?.displayName ?? record.id,
75974
+ version: record.manifest?.version,
75975
+ state: record.state,
75976
+ enabled: record.enabled,
75977
+ source: record.source,
75978
+ originalSource: record.originalSource,
75979
+ path: record.root,
75980
+ keywords: record.manifest?.keywords ?? [],
75981
+ skillCount: record.skillCount,
75982
+ mcpServerCount: Object.keys(record.manifest?.mcpServers ?? {}).length,
75983
+ hasCodeEntryPoint: record.manifest?.entryPoint !== void 0,
75984
+ hasErrors: record.diagnostics.some((diagnostic) => diagnostic.severity === "error")
75985
+ };
75986
+ }
75987
+ function checkView(record, active) {
75988
+ return {
75989
+ id: record.id,
75990
+ state: record.state,
75991
+ enabled: record.enabled,
75992
+ active,
75993
+ hasCodeEntryPoint: record.manifest?.entryPoint !== void 0,
75994
+ errors: record.diagnostics.filter((diagnostic) => diagnostic.severity === "error").map((diagnostic) => diagnostic.message),
75995
+ warnings: record.diagnostics.filter((diagnostic) => diagnostic.severity === "warn").map((diagnostic) => diagnostic.message)
75996
+ };
75997
+ }
75998
+ /**
75999
+ * Full detail for one plugin, minus the raw manifest blob: `mcpServers`,
76000
+ * `skillRoots` and `diagnostics` are already projected by the manager.
76001
+ */
76002
+ function projectInfo(info) {
76003
+ return {
76004
+ id: info.id,
76005
+ name: info.manifest?.name ?? info.displayName,
76006
+ displayName: info.displayName,
76007
+ version: info.version,
76008
+ description: info.manifest?.description,
76009
+ state: info.state,
76010
+ enabled: info.enabled,
76011
+ source: info.source,
76012
+ originalSource: info.originalSource,
76013
+ path: info.root,
76014
+ keywords: info.manifest?.keywords ?? [],
76015
+ skillCount: info.skillCount,
76016
+ skills: info.skills,
76017
+ skillRoots: info.manifest?.skills ?? [],
76018
+ skillInstructions: info.manifest?.skillInstructions,
76019
+ mcpServerCount: info.mcpServerCount,
76020
+ enabledMcpServerCount: info.enabledMcpServerCount,
76021
+ mcpServers: info.mcpServers,
76022
+ hasCodeEntryPoint: info.manifest?.entryPoint !== void 0,
76023
+ entryPoint: info.manifest?.entryPoint,
76024
+ hooks: (info.manifest?.hooks ?? []).map((hook) => hook.event),
76025
+ hasErrors: info.hasErrors,
76026
+ diagnostics: info.diagnostics,
76027
+ installedAt: info.installedAt,
76028
+ updatedAt: info.updatedAt,
76029
+ manifestKind: info.manifestKind,
76030
+ manifestPath: info.manifestPath,
76031
+ shadowedManifestPath: info.shadowedManifestPath,
76032
+ github: info.github,
76033
+ homepage: info.manifest?.homepage
76034
+ };
76035
+ }
76036
+ //#endregion
74689
76037
  //#region ../../packages/agent-core/src/tools/builtin/python/python.ts
74690
76038
  const PY_DONE_MARKER = "__SCREAM_PY_DONE__";
74691
76039
  /** Head/tail output cap for python tool results — mirrors the generic tool
@@ -75794,7 +77142,7 @@ function describeError(error) {
75794
77142
  }
75795
77143
  //#endregion
75796
77144
  //#region ../../packages/agent-core/src/tools/builtin/state/inspect-own-assets.md
75797
- 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";
77145
+ 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\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. 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";
75798
77146
  const InspectOwnAssetsInputSchema = z.object({ scope: z.enum([
75799
77147
  "all",
75800
77148
  "skills",
@@ -75887,17 +77235,20 @@ async function listSkills(dir) {
75887
77235
  if (!await isSkillDir(join$1(dir, entry.name))) continue;
75888
77236
  const skillMd = join$1(dir, entry.name, "SKILL.md");
75889
77237
  const fm = await checkFrontmatter(skillMd);
77238
+ const skillName = await readSkillName(skillMd) ?? entry.name;
75890
77239
  out.push({
75891
- name: entry.name,
77240
+ name: skillName,
75892
77241
  path: skillMd,
75893
77242
  kind: "dir",
75894
77243
  frontmatter: fm
75895
77244
  });
75896
77245
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
75897
77246
  if (DOCUMENTATION_MARKDOWN_LOWER.has(entry.name.toLowerCase())) continue;
77247
+ const path = join$1(dir, entry.name);
77248
+ const skillName = await readSkillName(path) ?? entry.name.slice(0, -3);
75898
77249
  out.push({
75899
- name: entry.name.slice(0, -3),
75900
- path: join$1(dir, entry.name),
77250
+ name: skillName,
77251
+ path,
75901
77252
  kind: "flat",
75902
77253
  frontmatter: "ok"
75903
77254
  });
@@ -75905,8 +77256,12 @@ async function listSkills(dir) {
75905
77256
  }
75906
77257
  return out.toSorted((a, b) => a.name.localeCompare(b.name));
75907
77258
  }
75908
- function formatSkillEntry(entry) {
75909
- return `- ${entry.name} — ${entry.kind === "dir" ? "dir" : "flat"} — ${entry.frontmatter} \`${entry.path}\``;
77259
+ function buildInvocableNameSet(agent) {
77260
+ return new Set((agent.skills?.registry.listInvocableSkills() ?? []).map((s) => s.name));
77261
+ }
77262
+ function formatSkillEntry(entry, invocable) {
77263
+ const origin = entry.plugin === void 0 ? "" : ` — plugin: ${entry.plugin}`;
77264
+ return `- ${entry.name} — ${entry.kind === "dir" ? "dir" : "flat"} — ${entry.frontmatter} — ${invocable ? "invocable" : "not invocable"}${origin} — \`${entry.path}\``;
75910
77265
  }
75911
77266
  async function inspectConfig(home, userHome) {
75912
77267
  const items = [
@@ -75922,27 +77277,66 @@ async function inspectConfig(home, userHome) {
75922
77277
  }
75923
77278
  return lines.join("\n");
75924
77279
  }
75925
- async function inspectSkills(home, userHome, cwd) {
77280
+ async function inspectSkills(home, userHome, cwd, agent) {
75926
77281
  const { userDir, projectDir } = await resolveSkillInstallPaths({
75927
77282
  userHomeDir: userHome,
75928
77283
  workDir: cwd
75929
77284
  });
77285
+ const invocableNames = buildInvocableNameSet(agent);
75930
77286
  const sections = ["## Skills", ""];
77287
+ let invocableCount = 0;
77288
+ let totalCount = 0;
77289
+ const isInvocable = (entry) => invocableNames.has(entry.name) || entry.plugin !== void 0 && invocableNames.has(`${entry.plugin}:${entry.name}`);
77290
+ const track = (entry, invocable) => {
77291
+ totalCount++;
77292
+ if (invocable) invocableCount++;
77293
+ return formatSkillEntry(entry, invocable);
77294
+ };
75931
77295
  const userEntries = await listSkills(userDir);
75932
77296
  sections.push(`User skills (${userDir}): ${userEntries.length === 0 ? "none" : ""}`);
75933
- sections.push(...userEntries.length > 0 ? userEntries.map(formatSkillEntry) : []);
77297
+ sections.push(...userEntries.map((e) => track(e, isInvocable(e))));
75934
77298
  const extraDir = join$1(home, "plugins", "managed");
75935
77299
  const extraEntries = await listManagedSkills(extraDir);
75936
77300
  sections.push("");
75937
77301
  sections.push(`Plugin-managed skills (${extraDir}): ${extraEntries.length === 0 ? "none" : ""}`);
75938
- sections.push(...extraEntries.length > 0 ? extraEntries.map(formatSkillEntry) : []);
77302
+ sections.push(...extraEntries.map((e) => track(e, isInvocable(e))));
75939
77303
  const projectEntries = await listSkills(projectDir);
75940
77304
  sections.push("");
75941
77305
  sections.push(`Project skills (${projectDir}): ${projectEntries.length === 0 ? "none" : ""}`);
75942
- sections.push(...projectEntries.length > 0 ? projectEntries.map(formatSkillEntry) : []);
77306
+ sections.push(...projectEntries.map((e) => track(e, isInvocable(e))));
77307
+ sections.push("");
77308
+ sections.push(`Invocable now: ${invocableCount}/${totalCount} (against the live skill registry; entries marked "not invocable" exist on disk but are disabled, shadowed, renamed, or broken)`);
75943
77309
  return sections.join("\n");
75944
77310
  }
75945
77311
  /**
77312
+ * Extracts the skill name from SKILL.md frontmatter (best effort) so the
77313
+ * inventory name matches what the skill registry holds; returns null when
77314
+ * frontmatter is missing or has no name line.
77315
+ */
77316
+ async function readSkillName(path) {
77317
+ let handle;
77318
+ try {
77319
+ handle = await open(path, "r");
77320
+ const buffer = Buffer.alloc(FRONTMATTER_READ_LIMIT);
77321
+ const { bytesRead } = await handle.read(buffer, 0, FRONTMATTER_READ_LIMIT, 0);
77322
+ const head = buffer.subarray(0, bytesRead).toString("utf-8").split("\n").slice(0, 25);
77323
+ if (head[0]?.trim() !== "---") return null;
77324
+ for (const line of head.slice(1)) {
77325
+ if (line.trim() === "---") break;
77326
+ const m = /^name\s*:\s*(.+)$/.exec(line);
77327
+ if (m !== null) {
77328
+ const value = m[1].trim().replaceAll(/^["']|["']$/g, "");
77329
+ return value.length > 0 ? value : null;
77330
+ }
77331
+ }
77332
+ return null;
77333
+ } catch {
77334
+ return null;
77335
+ } finally {
77336
+ await handle?.close().catch(() => {});
77337
+ }
77338
+ }
77339
+ /**
75946
77340
  * List plugin-managed skills: each `<dir>/SKILL.md` under a managed plugin
75947
77341
  * directory is a skill entry (Extra source, mirroring plugin/manager.ts).
75948
77342
  */
@@ -75959,11 +77353,13 @@ async function listManagedSkills(managedDir) {
75959
77353
  const skillMd = join$1(managedDir, plugin.name, "SKILL.md");
75960
77354
  if (!await isSkillDir(join$1(managedDir, plugin.name))) continue;
75961
77355
  const fm = await checkFrontmatter(skillMd);
77356
+ const skillName = await readSkillName(skillMd) ?? plugin.name;
75962
77357
  out.push({
75963
- name: plugin.name,
77358
+ name: skillName,
75964
77359
  path: skillMd,
75965
77360
  kind: "dir",
75966
- frontmatter: fm
77361
+ frontmatter: fm,
77362
+ plugin: plugin.name
75967
77363
  });
75968
77364
  }
75969
77365
  return out.toSorted((a, b) => a.name.localeCompare(b.name));
@@ -76059,7 +77455,7 @@ var InspectOwnAssetsTool = class {
76059
77455
  const scope = args.scope ?? "all";
76060
77456
  const sections = [];
76061
77457
  if (scope === "all" || scope === "config") sections.push(await inspectConfig(home, userHome));
76062
- if (scope === "all" || scope === "skills") sections.push(await inspectSkills(home, userHome, cwd));
77458
+ if (scope === "all" || scope === "skills") sections.push(await inspectSkills(home, userHome, cwd, this.agent));
76063
77459
  if (scope === "all" || scope === "mcp") sections.push(await inspectMcp(home, cwd));
76064
77460
  if (scope === "all" || scope === "memory") sections.push(await inspectMemory(home));
76065
77461
  if (scope === "all" || scope === "knowledge") sections.push(await inspectKnowledge(home));
@@ -76615,19 +78011,23 @@ var EventSubscriptionBus = class {
76615
78011
  this.wildcard.clear();
76616
78012
  }
76617
78013
  /**
76618
- * Deliver an event to matching handlers. A handler that throws is isolated
76619
- * so a subscriber bug can never break the agent's event loop.
78014
+ * Deliver an event to matching handlers. A handler that throws synchronously
78015
+ * is caught here, and a handler that returns a rejected promise has that
78016
+ * rejection swallowed, so a subscriber bug can never break the agent's event
78017
+ * loop nor crash the process through `unhandledRejection`.
76620
78018
  */
76621
78019
  dispatch(event) {
76622
78020
  const typed = this.byType.get(event.type);
76623
78021
  if (typed !== void 0) for (const handler of typed) try {
76624
- handler(event);
78022
+ Promise.resolve(handler(event)).catch(noop$1);
76625
78023
  } catch {}
76626
78024
  if (this.wildcard.size > 0) for (const handler of this.wildcard) try {
76627
- handler(event);
78025
+ Promise.resolve(handler(event)).catch(noop$1);
76628
78026
  } catch {}
76629
78027
  }
76630
78028
  };
78029
+ /** Rejection sink for async subscribers: their failures are deliberately dropped. */
78030
+ function noop$1() {}
76631
78031
  //#endregion
76632
78032
  //#region ../../packages/agent-core/src/agent/context/projector.ts
76633
78033
  /** Synthetic error text used when a tool result is missing and must be
@@ -81652,6 +83052,46 @@ var GitCwdWriteApprovePermissionPolicy = class {
81652
83052
  }
81653
83053
  };
81654
83054
  //#endregion
83055
+ //#region ../../packages/agent-core/src/agent/permission/policies/manage-plugin-read-only-approve.ts
83056
+ const READ_ONLY_ACTIONS = new Set(MANAGE_PLUGIN_READ_ONLY_ACTIONS);
83057
+ /**
83058
+ * Auto-approves the read-only faces of `ManagePlugin`.
83059
+ *
83060
+ * Listing and inspecting the plugin table has no side effects, so gating it
83061
+ * would only train the model to skip the "look before you add" step the tool
83062
+ * exists to enforce. Anything that writes state or runs plugin code decides
83063
+ * nothing here and falls through the chain to the ordinary approval prompt.
83064
+ *
83065
+ * The action is read off the invocation arguments. A missing or unparseable
83066
+ * payload decides nothing on purpose, so the fail-open direction is always
83067
+ * "ask the user".
83068
+ */
83069
+ var ManagePluginReadOnlyApprovePermissionPolicy = class {
83070
+ name = "manage-plugin-read-only-approve";
83071
+ evaluate(context) {
83072
+ if (context.toolCall.name !== "ManagePlugin") return;
83073
+ const action = readAction(context);
83074
+ if (action === void 0 || !READ_ONLY_ACTIONS.has(action)) return;
83075
+ return {
83076
+ kind: "approve",
83077
+ reason: { manage_plugin_action: action }
83078
+ };
83079
+ }
83080
+ };
83081
+ /** Read `action` from the resolved args, falling back to the raw tool-call JSON. */
83082
+ function readAction(context) {
83083
+ const fromArgs = context.args?.action;
83084
+ if (typeof fromArgs === "string") return fromArgs;
83085
+ const rawArguments = context.toolCall.arguments;
83086
+ if (typeof rawArguments !== "string") return void 0;
83087
+ try {
83088
+ const raw = JSON.parse(rawArguments);
83089
+ return typeof raw?.action === "string" ? raw.action : void 0;
83090
+ } catch {
83091
+ return;
83092
+ }
83093
+ }
83094
+ //#endregion
81655
83095
  //#region ../../packages/agent-core/src/agent/permission/policies/plan-mode-guard-deny.ts
81656
83096
  var PlanModeGuardDenyPermissionPolicy = class {
81657
83097
  agent;
@@ -81924,6 +83364,7 @@ function createPermissionDecisionPolicies(agent) {
81924
83364
  new YoloModeApprovePermissionPolicy(agent),
81925
83365
  new WolfPackModeApprovePermissionPolicy(agent),
81926
83366
  new DefaultToolApprovePermissionPolicy(),
83367
+ new ManagePluginReadOnlyApprovePermissionPolicy(),
81927
83368
  new GitCwdWriteApprovePermissionPolicy(agent),
81928
83369
  new FallbackAskPermissionPolicy()
81929
83370
  ];
@@ -83243,6 +84684,43 @@ function compareWireVersions(a, b) {
83243
84684
  }
83244
84685
  //#endregion
83245
84686
  //#region ../../packages/agent-core/src/agent/records/persistence.ts
84687
+ /**
84688
+ * Record types whose state is fully captured by a `context.snapshot` record.
84689
+ * Canonical definition — records/index.ts imports it for the restore
84690
+ * fast-path. It also drives the parse-skipping fast path in `read()` below:
84691
+ * serialized lines of these types that predate the last snapshot are never
84692
+ * JSON.parse'd.
84693
+ */
84694
+ const SNAPSHOT_FOLDED_CONTEXT_TYPES = new Set([
84695
+ "context.append_message",
84696
+ "context.append_loop_event",
84697
+ "context.apply_compaction",
84698
+ "micro_compaction.apply",
84699
+ "full_compaction.complete"
84700
+ ]);
84701
+ /**
84702
+ * Serialized-line prefix of a context.snapshot record. Persisted records
84703
+ * always serialize "type" as the first key (locked by test), so a startsWith
84704
+ * probe is exact — a false positive would require an entire line to start
84705
+ * with this JSON prefix, which no other record (or any message content, whose
84706
+ * line starts with its own record "type") can produce.
84707
+ *
84708
+ * Probes run on raw UTF-8 bytes (see SNAPSHOT_RECORD_LINE_PREFIX_BYTES): all
84709
+ * characters in these prefixes are ASCII, so byte-level matching is exact.
84710
+ */
84711
+ const SNAPSHOT_RECORD_LINE_PREFIX = "{\"type\":\"context.snapshot\"";
84712
+ /** Line prefixes of the folded record types, derived from the set above. */
84713
+ const SNAPSHOT_FOLDED_LINE_PREFIXES = [...SNAPSHOT_FOLDED_CONTEXT_TYPES].map((type) => `{"type":"${type}"`);
84714
+ /** UTF-8 bytes of {@link SNAPSHOT_RECORD_LINE_PREFIX}. */
84715
+ const SNAPSHOT_RECORD_LINE_PREFIX_BYTES = Buffer.from(SNAPSHOT_RECORD_LINE_PREFIX, "utf8");
84716
+ /** UTF-8 bytes of each entry of {@link SNAPSHOT_FOLDED_LINE_PREFIXES}. */
84717
+ const SNAPSHOT_FOLDED_LINE_PREFIX_BYTES = SNAPSHOT_FOLDED_LINE_PREFIXES.map((prefix) => Buffer.from(prefix, "utf8"));
84718
+ function startsWithPrefix(data, prefix) {
84719
+ return data.length >= prefix.length && data.subarray(0, prefix.length).equals(prefix);
84720
+ }
84721
+ function startsWithAnyPrefix(data, prefixes) {
84722
+ return prefixes.some((prefix) => startsWithPrefix(data, prefix));
84723
+ }
83246
84724
  var FileSystemAgentRecordPersistence = class {
83247
84725
  filePath;
83248
84726
  options;
@@ -83257,29 +84735,53 @@ var FileSystemAgentRecordPersistence = class {
83257
84735
  }
83258
84736
  async *read() {
83259
84737
  await this.flush();
83260
- let line = "";
84738
+ const lines = [];
84739
+ let lastSnapshotLineNumber = -1;
83261
84740
  let lineNumber = 0;
83262
- const stream = createReadStream(this.filePath, { encoding: "utf8" });
84741
+ let openChunks = [];
84742
+ const stream = createReadStream(this.filePath);
83263
84743
  try {
83264
84744
  for await (const chunk of stream) {
83265
- line += chunk;
83266
- let newlineIndex = line.indexOf("\n");
83267
- while (newlineIndex !== -1) {
83268
- const rawLine = line.slice(0, newlineIndex);
83269
- line = line.slice(newlineIndex + 1);
84745
+ let searchFrom = 0;
84746
+ for (;;) {
84747
+ const newlineIndex = chunk.indexOf(10, searchFrom);
84748
+ if (newlineIndex === -1) break;
84749
+ const parts = openChunks.length > 0 ? [...openChunks, chunk.subarray(searchFrom, newlineIndex)] : [chunk.subarray(searchFrom, newlineIndex)];
84750
+ let lineData = Buffer.concat(parts);
84751
+ if (lineData.length > 0 && lineData.at(-1) === 13) lineData = lineData.subarray(0, lineData.length - 1);
83270
84752
  lineNumber++;
83271
- const record = parseRecordLine(rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine, lineNumber, this.filePath, false);
83272
- if (record !== void 0) yield record;
83273
- newlineIndex = line.indexOf("\n");
84753
+ if (startsWithPrefix(lineData, SNAPSHOT_RECORD_LINE_PREFIX_BYTES)) lastSnapshotLineNumber = lineNumber;
84754
+ lines.push({
84755
+ data: lineData,
84756
+ lineNumber,
84757
+ allowTruncated: false
84758
+ });
84759
+ openChunks = [];
84760
+ searchFrom = newlineIndex + 1;
83274
84761
  }
84762
+ if (searchFrom < chunk.length) openChunks.push(chunk.subarray(searchFrom));
83275
84763
  }
83276
84764
  } catch (error) {
83277
84765
  if (error.code === "ENOENT") return;
83278
84766
  throw error;
83279
84767
  }
83280
- if (line.length > 0) {
84768
+ if (openChunks.length > 0) {
83281
84769
  lineNumber++;
83282
- const record = parseRecordLine(line, lineNumber, this.filePath, true);
84770
+ lines.push({
84771
+ data: Buffer.concat(openChunks),
84772
+ lineNumber,
84773
+ allowTruncated: true
84774
+ });
84775
+ }
84776
+ let skipFoldedBeforeSnapshot = false;
84777
+ if (lines.length > 0) try {
84778
+ skipFoldedBeforeSnapshot = JSON.parse(lines[0].data.toString("utf8")).protocol_version === "1.4";
84779
+ } catch {
84780
+ skipFoldedBeforeSnapshot = false;
84781
+ }
84782
+ for (const entry of lines) {
84783
+ if (skipFoldedBeforeSnapshot && entry.lineNumber < lastSnapshotLineNumber && startsWithAnyPrefix(entry.data, SNAPSHOT_FOLDED_LINE_PREFIX_BYTES)) continue;
84784
+ const record = parseRecordLine(entry.data.toString("utf8"), entry.lineNumber, this.filePath, entry.allowTruncated);
83283
84785
  if (record !== void 0) yield record;
83284
84786
  }
83285
84787
  }
@@ -83603,14 +85105,10 @@ function asMediaContainer(value) {
83603
85105
  * pushing onto `compactedHistory` — a debug trail rendered from the pre-fold
83604
85106
  * history. Snapshot replay skips that pre-fold history, so the trail would be
83605
85107
  * re-rendered as empty; the snapshot carries the trail itself instead.
85108
+ *
85109
+ * The canonical definition lives in persistence.ts (it also drives the
85110
+ * parse-skipping fast path during read); imported from there.
83606
85111
  */
83607
- const SNAPSHOT_FOLDED_CONTEXT_TYPES = new Set([
83608
- "context.append_message",
83609
- "context.append_loop_event",
83610
- "context.apply_compaction",
83611
- "micro_compaction.apply",
83612
- "full_compaction.complete"
83613
- ]);
83614
85112
  function isSnapshotFoldedContextRecord(type) {
83615
85113
  return SNAPSHOT_FOLDED_CONTEXT_TYPES.has(type);
83616
85114
  }
@@ -83809,16 +85307,37 @@ var AgentRecords = class {
83809
85307
  await this.persistence?.flush();
83810
85308
  }
83811
85309
  };
83812
- //#endregion
83813
- //#region ../../packages/agent-core/src/agent/replay/index.ts
85310
+ /** Mirrors the TUI's isReplayUserTurnRecord predicate (message-replay.ts):
85311
+ * a replay record starts a user turn when it is a user-role message from the
85312
+ * user (or a user-slash skill activation). */
85313
+ function isUserTurnStartRecord(record) {
85314
+ if (record.type !== "message") return false;
85315
+ const { message } = record;
85316
+ if (message.role !== "user") return false;
85317
+ switch (message.origin?.kind) {
85318
+ case void 0:
85319
+ case "user": return true;
85320
+ case "skill_activation": return message.origin.trigger === "user-slash";
85321
+ default: return false;
85322
+ }
85323
+ }
83814
85324
  var ReplayBuilder = class {
83815
85325
  agent;
83816
85326
  records = [];
85327
+ /** Indices (into `records`) of user-turn-start records, oldest first. */
85328
+ userTurnStarts = [];
83817
85329
  constructor(agent) {
83818
85330
  this.agent = agent;
83819
85331
  }
83820
85332
  push(record) {
83821
- if (this.agent.records.restoring) this.records.push(record);
85333
+ if (!this.agent.records.restoring) return;
85334
+ this.records.push(record);
85335
+ if (!isUserTurnStartRecord(record)) return;
85336
+ this.userTurnStarts.push(this.records.length - 1);
85337
+ if (this.userTurnStarts.length <= 10) return;
85338
+ const dropBefore = this.userTurnStarts[this.userTurnStarts.length - 10];
85339
+ this.records.splice(0, dropBefore);
85340
+ this.userTurnStarts = this.userTurnStarts.slice(-10).map((i) => i - dropBefore);
83822
85341
  }
83823
85342
  buildResult() {
83824
85343
  return this.records;
@@ -91661,7 +93180,7 @@ function parseToolCallArguments(raw) {
91661
93180
  } catch {
91662
93181
  return {
91663
93182
  success: false,
91664
- error: errorMessage$4(error)
93183
+ error: errorMessage$5(error)
91665
93184
  };
91666
93185
  }
91667
93186
  }
@@ -91755,7 +93274,7 @@ async function prepareToolCall(step, call) {
91755
93274
  toolCallId: call.toolCall.id,
91756
93275
  error
91757
93276
  });
91758
- return settleError(effectiveArgs, error instanceof PathSecurityError ? error.message : `Tool "${call.toolName}" failed to resolve execution: ${errorMessage$4(error)}`);
93277
+ return settleError(effectiveArgs, error instanceof PathSecurityError ? error.message : `Tool "${call.toolName}" failed to resolve execution: ${errorMessage$5(error)}`);
91759
93278
  }
91760
93279
  const displayFields = toolCallDisplayFieldsFromExecution(execution);
91761
93280
  const settleAborted = () => settleError(effectiveArgs, abortedToolOutput(call.toolName, step.signal), displayFields);
@@ -91814,7 +93333,7 @@ async function runPrepareToolExecutionHook(step, call) {
91814
93333
  return {
91815
93334
  kind: "hookFailed",
91816
93335
  args,
91817
- output: `prepareToolExecution hook failed for "${call.toolName}": ${errorMessage$4(error)}`
93336
+ output: `prepareToolExecution hook failed for "${call.toolName}": ${errorMessage$5(error)}`
91818
93337
  };
91819
93338
  }
91820
93339
  const effectiveArgs = hookResult?.updatedArgs ?? args;
@@ -91855,7 +93374,7 @@ async function runAuthorizeToolExecutionHook(step, call, args, execution) {
91855
93374
  };
91856
93375
  return {
91857
93376
  block: true,
91858
- reason: `authorizeToolExecution hook failed for "${call.toolName}": ${errorMessage$4(error)}`
93377
+ reason: `authorizeToolExecution hook failed for "${call.toolName}": ${errorMessage$5(error)}`
91859
93378
  };
91860
93379
  }
91861
93380
  }
@@ -91882,7 +93401,7 @@ async function runRunnableToolCall(step, call, effectiveArgs, metadata, executio
91882
93401
  toolCallId: toolCall.id,
91883
93402
  error
91884
93403
  });
91885
- return makeErrorToolResult(call, effectiveArgs, aborted ? abortedToolOutput(toolName, signal) : `Tool "${toolName}" failed: ${errorMessage$4(error)}`);
93404
+ return makeErrorToolResult(call, effectiveArgs, aborted ? abortedToolOutput(toolName, signal) : `Tool "${toolName}" failed: ${errorMessage$5(error)}`);
91886
93405
  }
91887
93406
  return makeToolResult(call, effectiveArgs, toolResult);
91888
93407
  }
@@ -91914,7 +93433,7 @@ async function finalizePendingToolResult(step, pendingResult) {
91914
93433
  toolCallId: pendingResult.toolCall.id,
91915
93434
  error
91916
93435
  });
91917
- const output = aborted ? `Tool "${pendingResult.toolName}" aborted during finalizeToolResult hook.` : `finalizeToolResult hook failed for "${pendingResult.toolName}": ${errorMessage$4(error)}`;
93436
+ const output = aborted ? `Tool "${pendingResult.toolName}" aborted during finalizeToolResult hook.` : `finalizeToolResult hook failed for "${pendingResult.toolName}": ${errorMessage$5(error)}`;
91918
93437
  return {
91919
93438
  ...pendingResult,
91920
93439
  stopTurn: pendingResult.stopTurn,
@@ -92427,7 +93946,7 @@ async function runTurn(input) {
92427
93946
  usage
92428
93947
  };
92429
93948
  }
92430
- dispatchEvent(makeInterruptedEvent(isMaxStepsExceededError(error) ? "max_steps" : "error", steps, activeStep, errorMessage$4(error)));
93949
+ dispatchEvent(makeInterruptedEvent(isMaxStepsExceededError(error) ? "max_steps" : "error", steps, activeStep, errorMessage$5(error)));
92431
93950
  throw error;
92432
93951
  }
92433
93952
  return {
@@ -96373,7 +97892,7 @@ function buildSelfMap(options) {
96373
97892
  "",
96374
97893
  "Data:",
96375
97894
  `- skills/ — user skills: \`${userSkillsDir}\` (project skills are listed with their \`Path\` under Available skills above)`,
96376
- `- plugins/ — managed plugins and plugin-managed skills: \`${pluginsDir}\` (installed.json + managed/<name>/)`,
97895
+ `- plugins/ — managed plugins and plugin-managed skills: \`${pluginsDir}\` (installed.json + managed/<name>/); manage them with the \`ManagePlugin\` builtin (read-only actions are free, mutations ask for approval) — never edit installed.json by hand`,
96377
97896
  `- memory/ — persistent cross-session memory: \`${memoryDir}\` (memos.sqlite + entries.jsonl)`,
96378
97897
  `- knowledge/ — local knowledge base (via the KnowledgeLookup tool): \`${knowledgeDir}\` (knowledge.db)`,
96379
97898
  "",
@@ -96567,7 +98086,7 @@ function normalizeSourcePath(path) {
96567
98086
  }
96568
98087
  //#endregion
96569
98088
  //#region ../../packages/agent-core/src/profile/default/agent.yaml
96570
- var agent_default = "name: agent\ndescription: Default Scream Code agent\n\nsystemPromptPath: ./system.md\npromptVars:\n roleAdditional: ''\n\ntools:\n - Read\n - Write\n - Edit\n - Grep\n - Glob\n - Bash\n - TaskList\n - TaskOutput\n - TaskStop\n - CronCreate\n - CronList\n - CronDelete\n - CreateGoal\n - GetGoal\n - SetGoalBudget\n - UpdateGoal\n - ReadMediaFile\n - TodoList\n - MemoryLookup\n - MemoryConsolidatePlan\n - MemoryConsolidateApply\n - MemoryWrite\n - KnowledgeLookup\n - InspectOwnAssets\n - Skill\n - MakeSkillPlan\n - MakeSkillApply\n - WebSearch\n - Agent\n - WolfPack\n\n - FetchURL\n - AskUserQuestion\n - EnterPlanMode\n - FusionPlan\n - ExitPlanMode\n - mcp__*\n\nsubagents:\n coder:\n description: Good at general software engineering tasks.\n explore:\n description: Fast codebase exploration with prompt-enforced read-only behavior.\n plan:\n description: Read-only implementation planning and architecture design.\n verify:\n description: Verification specialist. Runs build, test, and lint commands to validate code changes.\n reviewer:\n description: Code review specialist. Identifies bugs and API contract violations before merge.\n oracle:\n description: Deep debugging, architecture decisions, and second opinions.\n worker:\n description: Office and document automation worker. Performs format conversion, batch file processing, file organization, and document transformation; never modifies code and does not write content.\n writer:\n description: Professional writing and document specialist. Researches, drafts, rewrites, edits, translates, summarizes, and uses available workspace-local toolchains to produce or revise Markdown, text, HTML, PDF/Office-compatible, spreadsheet-style, and presentation-oriented artifacts.\n";
98089
+ var agent_default = "name: agent\ndescription: Default Scream Code agent\n\nsystemPromptPath: ./system.md\npromptVars:\n roleAdditional: ''\n\ntools:\n - Read\n - Write\n - Edit\n - Grep\n - Glob\n - Bash\n - TaskList\n - TaskOutput\n - TaskStop\n - CronCreate\n - CronList\n - CronDelete\n - CreateGoal\n - GetGoal\n - SetGoalBudget\n - UpdateGoal\n - ReadMediaFile\n - TodoList\n - MemoryLookup\n - MemoryConsolidatePlan\n - MemoryConsolidateApply\n - MemoryWrite\n - KnowledgeLookup\n - InspectOwnAssets\n - Skill\n - MakeSkillPlan\n - MakeSkillApply\n # Main-agent capability management. Subagent profiles inherit this list, but\n # the tool is only registered for main agents, so it never reaches their\n # model-visible tool set.\n - ManagePlugin\n - WebSearch\n - Agent\n - WolfPack\n\n - FetchURL\n - AskUserQuestion\n - EnterPlanMode\n - FusionPlan\n - ExitPlanMode\n - mcp__*\n\nsubagents:\n coder:\n description: Good at general software engineering tasks.\n explore:\n description: Fast codebase exploration with prompt-enforced read-only behavior.\n plan:\n description: Read-only implementation planning and architecture design.\n verify:\n description: Verification specialist. Runs build, test, and lint commands to validate code changes.\n reviewer:\n description: Code review specialist. Identifies bugs and API contract violations before merge.\n oracle:\n description: Deep debugging, architecture decisions, and second opinions.\n worker:\n description: Office and document automation worker. Performs format conversion, batch file processing, file organization, and document transformation; never modifies code and does not write content.\n writer:\n description: Professional writing and document specialist. Researches, drafts, rewrites, edits, translates, summarizes, and uses available workspace-local toolchains to produce or revise Markdown, text, HTML, PDF/Office-compatible, spreadsheet-style, and presentation-oriented artifacts.\n";
96571
98090
  //#endregion
96572
98091
  //#region ../../packages/agent-core/src/profile/default/coder.yaml
96573
98092
  var coder_default = "extends: agent\nname: coder\npromptVars:\n roleAdditional: |\n You are now running as a subagent. All the `user` messages are sent by the main agent. The main agent cannot see your context, it can only see your last message when you finish the task. You must treat the parent agent as your caller. Do not directly ask the end user questions. If something is unclear, explain the ambiguity in your final summary to the parent agent.\nwhenToUse: |\n Use this agent for non-trivial software engineering work that may require reading files, editing code, running commands, and returning a compact but technically complete summary to the parent agent.\ntools:\n - Bash\n - Read\n - ReadMediaFile\n - Glob\n - Grep\n - Write\n - Edit\n - WebSearch\n - FetchURL\n - MemoryLookup\n - MemoryConsolidatePlan\n - MemoryConsolidateApply\n - mcp__*\n";
@@ -96728,6 +98247,20 @@ function cloneTodos(todos) {
96728
98247
  phase: todo.phase
96729
98248
  }));
96730
98249
  }
98250
+ /** Parse `plugin-<id>:<server>` back to the owning plugin id (undefined otherwise). */
98251
+ function pluginOwnerFromMcpServerName(serverName) {
98252
+ if (!serverName.startsWith("plugin-")) return void 0;
98253
+ const colon = serverName.indexOf(":");
98254
+ return colon > 7 ? serverName.slice(7, colon) : void 0;
98255
+ }
98256
+ /** Advisory appended to a tool result when its plugin just tripped the circuit. */
98257
+ function circuitTripNote(pluginId) {
98258
+ return `[circuit] Plugin "${pluginId}" failed ${String(3)} tool calls in a row and was disabled; its tools, MCP servers, skills, and hooks were pulled from this session. Inspect it with ManagePlugin {action:"check", id:"${pluginId}"}; recover with {action:"reset", id:"${pluginId}"} or abandon it with {action:"remove", id:"${pluginId}"}.`;
98259
+ }
98260
+ /** Render any tool payload for the advisory appendage without '[object Object]'. */
98261
+ function describeToolOutput(output) {
98262
+ return typeof output === "string" ? output : JSON.stringify(output);
98263
+ }
96731
98264
  const CRITERIA_SYSTEM_PROMPT = [
96732
98265
  "You generate concrete, verifiable acceptance criteria for a given objective.",
96733
98266
  "Criteria must be specific and testable — state what should work end-to-end, not just what should exist.",
@@ -96948,6 +98481,11 @@ var ToolManager = class {
96948
98481
  agent;
96949
98482
  builtinTools = /* @__PURE__ */ new Map();
96950
98483
  userTools = /* @__PURE__ */ new Map();
98484
+ /** tool name → owning plugin id; the index `unregisterToolsByOwner` reads. */
98485
+ toolOwners = /* @__PURE__ */ new Map();
98486
+ /** plugin id → consecutive capability failures; a success resets to zero. */
98487
+ circuitFailures = /* @__PURE__ */ new Map();
98488
+ trippedPlugins = /* @__PURE__ */ new Set();
96951
98489
  mcpTools = /* @__PURE__ */ new Map();
96952
98490
  /** server name → list of qualified tool names registered for that server. */
96953
98491
  mcpToolsByServer = /* @__PURE__ */ new Map();
@@ -97005,11 +98543,14 @@ var ToolManager = class {
97005
98543
  * this method takes a hand-written registration.
97006
98544
  */
97007
98545
  registerUserTool(input) {
98546
+ const { name, description, parameters, ownerPluginId, execute } = input;
97008
98547
  this.agent.records.logRecord({
97009
98548
  type: "tools.register_user_tool",
97010
- ...input
98549
+ name,
98550
+ description,
98551
+ parameters,
98552
+ ...ownerPluginId !== void 0 ? { ownerPluginId } : {}
97011
98553
  });
97012
- const { name, description, parameters } = input;
97013
98554
  const tool = {
97014
98555
  name,
97015
98556
  description,
@@ -97019,17 +98560,48 @@ var ToolManager = class {
97019
98560
  description,
97020
98561
  approvalRule: name,
97021
98562
  execute: async (context) => {
97022
- return this.agent.rpc.toolCall({
98563
+ const noteOutcome = (outcome) => {
98564
+ if (ownerPluginId === void 0) return outcome;
98565
+ if (this.reportOwnerOutcome(ownerPluginId, outcome.isError !== true) !== "tripped") return outcome;
98566
+ return {
98567
+ ...outcome,
98568
+ output: `${describeToolOutput(outcome.output)}\n\n${circuitTripNote(ownerPluginId)}`
98569
+ };
98570
+ };
98571
+ if (execute !== void 0) try {
98572
+ return noteOutcome(await execute(args ?? {}, context) ?? {
98573
+ output: `Tool "${name}" returned no result.`,
98574
+ isError: true
98575
+ });
98576
+ } catch (error) {
98577
+ return noteOutcome({
98578
+ output: `Tool "${name}" failed: ${error instanceof Error ? error.message : String(error)}`,
98579
+ isError: true
98580
+ });
98581
+ }
98582
+ const rpc = this.agent.rpc;
98583
+ if (rpc?.toolCall === void 0) return {
98584
+ output: `User-registered tool "${name}" cannot execute: host does not support in-band tool callbacks.`,
98585
+ isError: true
98586
+ };
98587
+ return noteOutcome(await rpc.toolCall({
97023
98588
  turnId: Number(context.turnId),
97024
98589
  toolCallId: context.toolCallId,
97025
98590
  args
97026
- }, { signal: context.signal });
98591
+ }, { signal: context.signal }));
97027
98592
  }
97028
98593
  };
97029
98594
  }
97030
98595
  };
97031
98596
  this.userTools.set(name, tool);
97032
98597
  this.enabledTools.add(name);
98598
+ if (ownerPluginId !== void 0) this.toolOwners.set(name, ownerPluginId);
98599
+ else this.toolOwners.delete(name);
98600
+ this.agent.emitEvent({
98601
+ type: "tool.list.updated",
98602
+ reason: "user.registered",
98603
+ toolName: name
98604
+ });
97033
98605
  }
97034
98606
  unregisterUserTool(name) {
97035
98607
  this.agent.records.logRecord({
@@ -97038,6 +98610,80 @@ var ToolManager = class {
97038
98610
  });
97039
98611
  this.userTools.delete(name);
97040
98612
  this.enabledTools.delete(name);
98613
+ this.toolOwners.delete(name);
98614
+ this.agent.emitEvent({
98615
+ type: "tool.list.updated",
98616
+ reason: "user.unregistered",
98617
+ toolName: name
98618
+ });
98619
+ }
98620
+ /**
98621
+ * Drop every user tool owned by `ownerPluginId` (a plugin being removed,
98622
+ * disabled, or circuit-tripped). Returns the removed count. The loop rebuilds
98623
+ * its tool list from `loopTools` on the next step, so no extra refresh is
98624
+ * needed beyond the per-tool events emitted here.
98625
+ */
98626
+ unregisterToolsByOwner(ownerPluginId) {
98627
+ let removed = 0;
98628
+ for (const [toolName, owner] of this.toolOwners) {
98629
+ if (owner !== ownerPluginId) continue;
98630
+ this.unregisterUserTool(toolName);
98631
+ removed += 1;
98632
+ }
98633
+ return removed;
98634
+ }
98635
+ /**
98636
+ * Circuit breaker bookkeeping (see `PLUGIN_CIRCUIT_TRIP_THRESHOLD`): every
98637
+ * plugin-owned capability outcome flows here. `ok` clears the streak; a
98638
+ * failure streak reaching the threshold trips the plugin exactly once and
98639
+ * starts its asynchronous teardown. Returns 'tripped' on the call that
98640
+ * crossed the line, so the caller can attach the advisory to its result.
98641
+ */
98642
+ reportOwnerOutcome(pluginId, ok) {
98643
+ this.agent.toolServices?.plugins?.recordUsage?.(pluginId, ok);
98644
+ if (this.trippedPlugins.has(pluginId)) return void 0;
98645
+ if (ok) {
98646
+ this.circuitFailures.delete(pluginId);
98647
+ return;
98648
+ }
98649
+ const failures = (this.circuitFailures.get(pluginId) ?? 0) + 1;
98650
+ this.circuitFailures.set(pluginId, failures);
98651
+ if (failures < 3) return void 0;
98652
+ this.trippedPlugins.add(pluginId);
98653
+ this.tripPlugin(pluginId, failures);
98654
+ return "tripped";
98655
+ }
98656
+ getCircuitFailures(pluginId) {
98657
+ return this.circuitFailures.get(pluginId) ?? 0;
98658
+ }
98659
+ isCircuitTripped(pluginId) {
98660
+ return this.trippedPlugins.has(pluginId);
98661
+ }
98662
+ /** Clear the breaker for a plugin (ManagePlugin `reset`); it starts clean. */
98663
+ resetCircuit(pluginId) {
98664
+ this.circuitFailures.delete(pluginId);
98665
+ this.trippedPlugins.delete(pluginId);
98666
+ }
98667
+ /**
98668
+ * Take a tripped plugin out of service: record why, persist it disabled,
98669
+ * then let the host's session-sync pass tear its code, tools, MCP servers,
98670
+ * and skills out of every live session. Best-effort: a teardown surprise
98671
+ * must never propagate back into the tool loop that detected the failures.
98672
+ */
98673
+ async tripPlugin(pluginId, failures) {
98674
+ const services = this.agent.toolServices;
98675
+ try {
98676
+ if (services?.plugins !== void 0) {
98677
+ await services.plugins.markError(pluginId, `circuit tripped after ${String(failures)} consecutive tool failures`);
98678
+ await services.plugins.setEnabled(pluginId, false);
98679
+ }
98680
+ } catch {}
98681
+ try {
98682
+ await services?.pluginSync?.([pluginId]);
98683
+ } catch {}
98684
+ try {
98685
+ await services?.plugins?.appendQuarantine(pluginId, `circuit tripped after ${String(failures)} consecutive tool failures`);
98686
+ } catch {}
97041
98687
  }
97042
98688
  /**
97043
98689
  * Register an MCP server (and its tools). Public entry point for third-party
@@ -97085,38 +98731,48 @@ var ToolManager = class {
97085
98731
  description: tool.description,
97086
98732
  approvalRule: qualified,
97087
98733
  execute: async (context) => {
97088
- const runCall = async (targetClient) => {
97089
- return mcpResultToExecutableOutput(await targetClient.callTool(tool.name, args ?? {}, context.signal), qualified);
97090
- };
97091
- try {
97092
- return await runCall(client);
97093
- } catch (error) {
97094
- const mcp = options?.mcp;
97095
- if (mcp === void 0 || context.signal.aborted || !(isRetriableMcpCallError(error) || mcp.get(serverName)?.status !== "connected")) return {
97096
- isError: true,
97097
- output: `MCP tool "${tool.name}" failed: ` + (error instanceof Error ? error.message : String(error))
97098
- };
97099
- try {
97100
- await mcp.reconnect(serverName);
97101
- } catch {}
97102
- if (context.signal.aborted) return {
97103
- isError: true,
97104
- output: `MCP tool "${tool.name}" aborted during reconnect.`
97105
- };
97106
- const resolved = mcp.resolved(serverName);
97107
- if (resolved === void 0) return {
97108
- isError: true,
97109
- output: `MCP tool "${tool.name}" failed: ` + (error instanceof Error ? error.message : String(error))
98734
+ const invoke = async () => {
98735
+ const runCall = async (targetClient) => {
98736
+ return mcpResultToExecutableOutput(await targetClient.callTool(tool.name, args ?? {}, context.signal), qualified);
97110
98737
  };
97111
98738
  try {
97112
- return await runCall(resolved.client);
97113
- } catch (retryError) {
97114
- return {
98739
+ return await runCall(client);
98740
+ } catch (error) {
98741
+ const mcp = options?.mcp;
98742
+ if (mcp === void 0 || context.signal.aborted || !(isRetriableMcpCallError(error) || mcp.get(serverName)?.status !== "connected")) return {
98743
+ isError: true,
98744
+ output: `MCP tool "${tool.name}" failed: ` + (error instanceof Error ? error.message : String(error))
98745
+ };
98746
+ try {
98747
+ await mcp.reconnect(serverName);
98748
+ } catch {}
98749
+ if (context.signal.aborted) return {
98750
+ isError: true,
98751
+ output: `MCP tool "${tool.name}" aborted during reconnect.`
98752
+ };
98753
+ const resolved = mcp.resolved(serverName);
98754
+ if (resolved === void 0) return {
97115
98755
  isError: true,
97116
- output: `MCP tool "${tool.name}" failed after reconnect: ` + (retryError instanceof Error ? retryError.message : String(retryError))
98756
+ output: `MCP tool "${tool.name}" failed: ` + (error instanceof Error ? error.message : String(error))
97117
98757
  };
98758
+ try {
98759
+ return await runCall(resolved.client);
98760
+ } catch (retryError) {
98761
+ return {
98762
+ isError: true,
98763
+ output: `MCP tool "${tool.name}" failed after reconnect: ` + (retryError instanceof Error ? retryError.message : String(retryError))
98764
+ };
98765
+ }
97118
98766
  }
97119
- }
98767
+ };
98768
+ const outcome = await invoke();
98769
+ const owner = pluginOwnerFromMcpServerName(serverName);
98770
+ if (owner === void 0) return outcome;
98771
+ if (this.reportOwnerOutcome(owner, outcome.isError !== true) !== "tripped") return outcome;
98772
+ return {
98773
+ ...outcome,
98774
+ output: `${describeToolOutput(outcome.output)}\n\n${circuitTripNote(owner)}`
98775
+ };
97120
98776
  }
97121
98777
  };
97122
98778
  }
@@ -97229,6 +98885,26 @@ var ToolManager = class {
97229
98885
  isMcpToolEnabled(name) {
97230
98886
  return this.mcpAccessPatterns.some((pattern) => import_picomatch.default.isMatch(name, pattern));
97231
98887
  }
98888
+ /** The enabled-name filter a turn freezes at its start. */
98889
+ snapshotEnabledTools() {
98890
+ return {
98891
+ names: new Set(this.enabledTools),
98892
+ mcpPatterns: [...this.mcpAccessPatterns]
98893
+ };
98894
+ }
98895
+ /**
98896
+ * Build the offered tool table against an explicit enabled-name filter
98897
+ * while the registration maps (user tools, MCP servers) stay live. A turn
98898
+ * uses this with its frozen filter: a mid-turn `setActiveTools` then
98899
+ * applies to the NEXT turn by design (turn config stability), while a
98900
+ * plugin tool registered — or reclaimed by the breaker — mid-turn still
98901
+ * shows up on the very next step.
98902
+ */
98903
+ loopToolsFor(filter) {
98904
+ const mcpNames = [...this.mcpTools.keys()].filter((name) => filter.mcpPatterns.some((pattern) => import_picomatch.default.isMatch(name, pattern)));
98905
+ const hideGoalMutationTools = this.agent.goal.getGoal().goal === null;
98906
+ return uniq([...filter.names, ...mcpNames]).toSorted((a, b) => a.localeCompare(b)).filter((name) => !(hideGoalMutationTools && (name === "SetGoalBudget" || name === "UpdateGoal" || name === "WriteGoalNote"))).map((name) => this.userTools.get(name) ?? this.mcpTools.get(name)?.tool ?? this.builtinTools.get(name)).filter((tool) => !!tool);
98907
+ }
97232
98908
  *toolInfos() {
97233
98909
  for (const tool of this.builtinTools.values()) yield {
97234
98910
  name: tool.name,
@@ -97306,6 +98982,7 @@ var ToolManager = class {
97306
98982
  this.agent.skills?.registry.listInvocableSkills().length && new SkillTool(this.agent),
97307
98983
  this.agent.type === "main" && new MakeSkillPlanTool(this.agent),
97308
98984
  this.agent.type === "main" && new MakeSkillApplyTool(this.agent),
98985
+ this.agent.type === "main" && new ManagePluginTool(this.agent),
97309
98986
  canSpawn && new AgentTool(this.agent.subagentHost, background, visibleSubagents, {
97310
98987
  allowBackground,
97311
98988
  log: this.agent.log,
@@ -97331,9 +99008,10 @@ var ToolManager = class {
97331
99008
  return (input) => withAuth((auth) => uploadVideo(input, { auth }));
97332
99009
  }
97333
99010
  get loopTools() {
97334
- const mcpNames = [...this.mcpTools.keys()].filter((name) => this.isMcpToolEnabled(name));
97335
- const hideGoalMutationTools = this.agent.goal.getGoal().goal === null;
97336
- return uniq([...this.enabledTools, ...mcpNames]).toSorted((a, b) => a.localeCompare(b)).filter((name) => !(hideGoalMutationTools && (name === "SetGoalBudget" || name === "UpdateGoal" || name === "WriteGoalNote"))).map((name) => this.userTools.get(name) ?? this.mcpTools.get(name)?.tool ?? this.builtinTools.get(name)).filter((tool) => !!tool);
99011
+ return this.loopToolsFor({
99012
+ names: this.enabledTools,
99013
+ mcpPatterns: this.mcpAccessPatterns
99014
+ });
97337
99015
  }
97338
99016
  };
97339
99017
  //#endregion
@@ -98217,6 +99895,7 @@ var TurnFlow = class {
98217
99895
  async runTurn(turnId, signal) {
98218
99896
  let stopHookContinuationUsed = false;
98219
99897
  const deduper = new ToolCallDeduplicator();
99898
+ const turnToolFilter = this.agent.tools.snapshotEnabledTools();
98220
99899
  await Promise.race([this.agent.mcp?.waitForInitialLoad(signal) ?? Promise.resolve(), new Promise((resolve) => setTimeout(resolve, MCP_WAIT_TIMEOUT_MS))]);
98221
99900
  while (true) {
98222
99901
  signal.throwIfAborted();
@@ -98230,6 +99909,7 @@ var TurnFlow = class {
98230
99909
  buildMessages: () => this.agent.context.messagesForLLM(),
98231
99910
  dispatchEvent: this.buildDispatchEvent(turnId),
98232
99911
  tools: this.agent.tools.loopTools,
99912
+ buildTools: () => this.agent.tools.loopToolsFor(turnToolFilter),
98233
99913
  log: this.agent.log,
98234
99914
  maxSteps: loopControl?.maxStepsPerTurn,
98235
99915
  maxRetryAttempts: loopControl?.maxRetriesPerStep,
@@ -99683,6 +101363,7 @@ function applyEnvModelConfig(config, env = process.env) {
99683
101363
  const displayName = trimmed(env["SCREAM_MODEL_DISPLAY_NAME"]);
99684
101364
  const reasoningKey = trimmed(env["SCREAM_MODEL_REASONING_KEY"]);
99685
101365
  const adaptiveThinking = parseBooleanVar(env["SCREAM_MODEL_ADAPTIVE_THINKING"], "SCREAM_MODEL_ADAPTIVE_THINKING");
101366
+ const forceThinking = parseBooleanVar(env["SCREAM_MODEL_FORCE_THINKING"], "SCREAM_MODEL_FORCE_THINKING");
99686
101367
  const alias = {
99687
101368
  provider: ENV_MODEL_PROVIDER_KEY,
99688
101369
  model,
@@ -99691,7 +101372,8 @@ function applyEnvModelConfig(config, env = process.env) {
99691
101372
  ...displayName !== void 0 ? { displayName } : {},
99692
101373
  ...maxOutputSize !== void 0 ? { maxOutputSize } : {},
99693
101374
  ...reasoningKey !== void 0 ? { reasoningKey } : {},
99694
- ...adaptiveThinking !== void 0 ? { adaptiveThinking } : {}
101375
+ ...adaptiveThinking !== void 0 ? { adaptiveThinking } : {},
101376
+ ...forceThinking !== void 0 ? { forceThinking } : {}
99695
101377
  };
99696
101378
  const thinkingMode = trimmed(env["SCREAM_MODEL_THINKING_MODE"]);
99697
101379
  const thinkingEffort = trimmed(env["SCREAM_MODEL_THINKING_EFFORT"]);
@@ -104535,9 +106217,10 @@ function createRPC() {
104535
106217
  * - `load()` — dynamic-import the entry point (cached per path)
104536
106218
  * - `activate()` — inject declared manifest hooks into the agent's HookEngine,
104537
106219
  * then call the plugin's `activate(context)`; tools are registered by the
104538
- * plugin itself via `context.services.tools.registerUserTool`
104539
- * - `deactivate()` symmetric removal (hooks + deactivate hook), isolated so
104540
- * a failing plugin never breaks the agent
106220
+ * plugin itself via `context.services.tools.registerUserTool`. Hooks and
106221
+ * event subscriptions made during a failed activation are rolled back
106222
+ * - `deactivate()` symmetric removal (hooks + event subscriptions +
106223
+ * deactivate hook), isolated so a failing plugin never breaks the agent
104541
106224
  *
104542
106225
  * Activation is deliberately lazy (a `/plugin activate` command or explicit
104543
106226
  * config opt-in), so merely installing a plugin never executes its code.
@@ -104572,9 +106255,15 @@ var ExtensionRuntime = class {
104572
106255
  const module = await this.load(extension.entryPoint);
104573
106256
  let hooksUnregister;
104574
106257
  if (extension.manifest.hooks !== void 0 && extension.manifest.hooks.length > 0) hooksUnregister = agent.hooks?.registerAll(extension.manifest.hooks);
106258
+ const subscriptions = [];
106259
+ let handlerFaults = 0;
106260
+ const onHandlerFault = () => {
106261
+ handlerFaults += 1;
106262
+ if (handlerFaults >= 3 && this.isActive(extension.pluginId)) this.deactivate(extension.pluginId);
106263
+ };
104575
106264
  const context = {
104576
- services: agent.services,
104577
- events: agent.eventBus,
106265
+ services: withPluginToolOwnership(agent.services, extension.pluginId),
106266
+ events: createTrackedEventBus(agent.eventBus, subscriptions, onHandlerFault),
104578
106267
  config: extension.manifest.config,
104579
106268
  pluginId: extension.pluginId
104580
106269
  };
@@ -104582,20 +106271,42 @@ var ExtensionRuntime = class {
104582
106271
  await module.activate(context);
104583
106272
  } catch (error) {
104584
106273
  hooksUnregister?.();
106274
+ releaseSubscriptions(subscriptions);
104585
106275
  throw error;
104586
106276
  }
104587
106277
  this.activations.set(extension.pluginId, {
104588
106278
  module,
104589
- hooksUnregister
106279
+ hooksUnregister,
106280
+ subscriptions,
106281
+ log: agent.log,
106282
+ agent
104590
106283
  });
104591
106284
  }
104592
- /** Deactivate a plugin: remove its hooks and call its deactivate hook. */
106285
+ /** Deactivate a plugin: drop its hooks/subscriptions, then call its deactivate. */
104593
106286
  async deactivate(pluginId) {
104594
106287
  const activation = this.activations.get(pluginId);
104595
106288
  if (activation === void 0) return;
104596
106289
  activation.hooksUnregister?.();
106290
+ releaseSubscriptions(activation.subscriptions);
104597
106291
  this.activations.delete(pluginId);
104598
- await activation.module.deactivate?.();
106292
+ try {
106293
+ activation.agent.services.tools?.unregisterToolsByOwner?.(pluginId);
106294
+ } catch {}
106295
+ try {
106296
+ await activation.module.deactivate?.();
106297
+ } catch (error) {
106298
+ activation.log?.warn("plugin deactivate failed", {
106299
+ pluginId,
106300
+ error: error instanceof Error ? error.message : String(error)
106301
+ });
106302
+ }
106303
+ }
106304
+ /**
106305
+ * Deactivate every live plugin. Provided so a host can tear down all code
106306
+ * plugins in one call; each plugin's failure is isolated from the others.
106307
+ */
106308
+ async deactivateAll() {
106309
+ for (const pluginId of Array.from(this.activations.keys())) await this.deactivate(pluginId);
104599
106310
  }
104600
106311
  isActive(pluginId) {
104601
106312
  return this.activations.has(pluginId);
@@ -104609,6 +106320,67 @@ function normalizeExtensionModule(mod) {
104609
106320
  if (candidate === void 0 || typeof candidate.activate !== "function") throw new Error("Plugin entry point must export an activate(context) function");
104610
106321
  return candidate;
104611
106322
  }
106323
+ /**
106324
+ * Wrap the agent's bus in a per-activation view that records every unsubscribe
106325
+ * handle the plugin gets, so the runtime can release them if activation fails.
106326
+ */
106327
+ function createTrackedEventBus(bus, subscriptions, onFault) {
106328
+ return {
106329
+ subscribe: (type, handler) => {
106330
+ const guarded = (event) => {
106331
+ try {
106332
+ const out = handler(event);
106333
+ if (out !== null && typeof out.then === "function") out.catch(() => {
106334
+ onFault?.();
106335
+ });
106336
+ return out;
106337
+ } catch (error) {
106338
+ onFault?.();
106339
+ throw error;
106340
+ }
106341
+ };
106342
+ const unsubscribe = bus.subscribe(type, guarded);
106343
+ subscriptions.push(unsubscribe);
106344
+ return unsubscribe;
106345
+ },
106346
+ clear: () => {
106347
+ releaseSubscriptions(subscriptions);
106348
+ }
106349
+ };
106350
+ }
106351
+ /** Release every subscription recorded for one activation, isolating failures. */
106352
+ function releaseSubscriptions(subscriptions) {
106353
+ while (subscriptions.length > 0) {
106354
+ const unsubscribe = subscriptions.pop();
106355
+ try {
106356
+ unsubscribe?.();
106357
+ } catch {}
106358
+ }
106359
+ }
106360
+ /**
106361
+ * A services view handed to one activated plugin. It forwards everything to
106362
+ * the agent's real manifest except `tools.registerUserTool`, where it stamps
106363
+ * `ownerPluginId` with this plugin's id (an explicit id from the plugin wins).
106364
+ * Ownership is what lets remove/disable/circuit-trip reclaim the plugin's
106365
+ * tools from live agents without trusting the plugin to clean up after itself.
106366
+ */
106367
+ function withPluginToolOwnership(services, pluginId) {
106368
+ const tools = services.tools;
106369
+ if (typeof tools !== "object" || tools === null) return services;
106370
+ const ownedTools = new Proxy(tools, { get(target, prop, receiver) {
106371
+ if (prop === "registerUserTool") return (input) => {
106372
+ target.registerUserTool({
106373
+ ...input,
106374
+ ownerPluginId: input.ownerPluginId ?? pluginId
106375
+ });
106376
+ };
106377
+ return Reflect.get(target, prop, receiver);
106378
+ } });
106379
+ return {
106380
+ ...services,
106381
+ tools: ownedTools
106382
+ };
106383
+ }
104612
106384
  //#endregion
104613
106385
  //#region ../../packages/agent-core/src/tools/providers/fetch-cache.ts
104614
106386
  /**
@@ -119096,7 +120868,7 @@ var ProviderManager = class {
119096
120868
  const providerConfig = this.config.providers[providerName];
119097
120869
  if (providerConfig === void 0) throw new ScreamError(ErrorCodes.CONFIG_INVALID, `Provider "${providerName}" for model "${model}" is not configured.`);
119098
120870
  if (!Number.isInteger(alias.maxContextSize) || alias.maxContextSize <= 0) throw new ScreamError(ErrorCodes.CONFIG_INVALID, `Model "${model}" must define a positive max_context_size in config.toml.`);
119099
- const provider = toLtodProviderConfig(providerConfig, alias.model, this.options.screamRequestHeaders, alias.maxOutputSize, alias.reasoningKey, this.options.promptCacheKey, alias.adaptiveThinking);
120871
+ const provider = toLtodProviderConfig(providerConfig, alias.model, this.options.screamRequestHeaders, alias.maxOutputSize, alias.reasoningKey, this.options.promptCacheKey, alias.adaptiveThinking, alias.forceThinking);
119100
120872
  return {
119101
120873
  providerName,
119102
120874
  provider,
@@ -119158,7 +120930,7 @@ function resolveModelCapabilities(alias, provider) {
119158
120930
  max_context_tokens: alias.maxContextSize
119159
120931
  };
119160
120932
  }
119161
- function toLtodProviderConfig(provider, model, screamRequestHeaders, maxOutputSize, reasoningKey, promptCacheKey, adaptiveThinking) {
120933
+ function toLtodProviderConfig(provider, model, screamRequestHeaders, maxOutputSize, reasoningKey, promptCacheKey, adaptiveThinking, forceThinking) {
119162
120934
  switch (provider.type) {
119163
120935
  case "anthropic": return {
119164
120936
  type: "anthropic",
@@ -119175,6 +120947,7 @@ function toLtodProviderConfig(provider, model, screamRequestHeaders, maxOutputSi
119175
120947
  baseUrl: providerValue(provider.baseUrl, provider.env, "OPENAI_BASE_URL"),
119176
120948
  apiKey: providerApiKey(provider),
119177
120949
  reasoningKey,
120950
+ ...forceThinking !== void 0 ? { forceThinking } : {},
119178
120951
  ...defaultHeadersField(provider.customHeaders)
119179
120952
  };
119180
120953
  case "scream": return {
@@ -119183,6 +120956,7 @@ function toLtodProviderConfig(provider, model, screamRequestHeaders, maxOutputSi
119183
120956
  baseUrl: providerValue(provider.baseUrl, provider.env, "SCREAM_BASE_URL"),
119184
120957
  apiKey: providerApiKey(provider),
119185
120958
  generationKwargs: { prompt_cache_key: promptCacheKey },
120959
+ ...forceThinking !== void 0 ? { forceThinking } : {},
119186
120960
  ...defaultHeadersField({
119187
120961
  ...screamRequestHeaders,
119188
120962
  ...provider.customHeaders
@@ -121244,12 +123018,36 @@ var ScreamCore = class {
121244
123018
  if (plugin === void 0) throw new ScreamError(ErrorCodes.PLUGIN_NOT_FOUND, `Plugin "${pluginId}" was not found`, { details: { pluginId } });
121245
123019
  const [extension] = this.extensionRuntime.discover([plugin]);
121246
123020
  if (extension === void 0) throw new ScreamError(ErrorCodes.PLUGIN_NOT_FOUND, `Plugin "${pluginId}" has no code entry point`, { details: { pluginId } });
121247
- await this.extensionRuntime.activate(agent, extension);
123021
+ try {
123022
+ await this.extensionRuntime.activate(agent, extension);
123023
+ } catch (error) {
123024
+ await this.markPluginError(pluginId, error);
123025
+ throw error;
123026
+ }
123027
+ await this.syncSessionsBestEffort([pluginId]);
123028
+ }
123029
+ /**
123030
+ * Best-effort bookkeeping for a failed activation: a `markError` failure must
123031
+ * not replace the activation error the caller is about to see.
123032
+ */
123033
+ async markPluginError(pluginId, error) {
123034
+ const message = error instanceof Error ? error.message : String(error);
123035
+ try {
123036
+ await this.plugins.markError(pluginId, message);
123037
+ } catch (markError) {
123038
+ log.warn("failed to record plugin activation error", {
123039
+ pluginId,
123040
+ reason: markError instanceof Error ? markError.message : String(markError)
123041
+ });
123042
+ }
121248
123043
  }
121249
123044
  /** Deactivate a code plugin (removes its hooks and runs its deactivate). */
121250
123045
  async deactivatePlugin({ pluginId }) {
121251
123046
  await this.pluginsReady;
121252
123047
  await this.extensionRuntime.deactivate(pluginId);
123048
+ for (const session of this.sessions.values()) for (const agent of session.agents.values()) try {
123049
+ agent.tools.unregisterToolsByOwner(pluginId);
123050
+ } catch {}
121253
123051
  }
121254
123052
  /** Code plugins the runtime can load, with their activation state. */
121255
123053
  async pluginExtensionStatus() {
@@ -121397,13 +123195,202 @@ var ScreamCore = class {
121397
123195
  removeMcpServer({ sessionId, ...payload }) {
121398
123196
  return this.sessionApi(sessionId).removeMcpServer(payload);
121399
123197
  }
123198
+ /**
123199
+ * Push the current plugin table into every live session: plugin MCP servers,
123200
+ * plugin skills, and in-process code/tool teardown for plugins that stopped
123201
+ * being live. This is the hot-apply path behind `ToolServices.pluginSync`
123202
+ * and the plugin write RPCs.
123203
+ *
123204
+ * Contract:
123205
+ * - MCP edits are confined to plugin-owned runtime names (`plugin-<id>:<server>`);
123206
+ * user-configured servers are never touched.
123207
+ * - `skipMcpAdd` honors "install never executes": skill data hot-applies, but
123208
+ * no plugin MCP process is started until an explicit enable/activate/reload.
123209
+ * - A changed id whose record is gone, disabled, or errored gets its code
123210
+ * deactivated and its owned user tools dropped before its capabilities are
123211
+ * pulled from sessions. A live id is never activated here — running code
123212
+ * still requires an explicit, separately approved `activate`.
123213
+ * - Every sub-action is isolated: failures accumulate in `failed[]` and this
123214
+ * returns a report; it never throws back to a caller whose plugin mutation
123215
+ * already succeeded.
123216
+ */
123217
+ async applyPluginChangesToSessions(changedIds, options) {
123218
+ await this.pluginsReady;
123219
+ const applied = [];
123220
+ const failed = [];
123221
+ const messageOf = (error) => error instanceof Error ? error.message : String(error);
123222
+ let desired = {};
123223
+ let desiredUnknown = false;
123224
+ try {
123225
+ desired = this.plugins.enabledMcpServers();
123226
+ } catch (error) {
123227
+ desiredUnknown = true;
123228
+ failed.push({
123229
+ step: "mcp.add",
123230
+ message: messageOf(error)
123231
+ });
123232
+ }
123233
+ const targets = new Set(changedIds ?? []);
123234
+ if (changedIds === void 0) {
123235
+ try {
123236
+ for (const record of this.plugins.list()) targets.add(record.id);
123237
+ } catch (error) {
123238
+ failed.push({
123239
+ step: "mcp.add",
123240
+ message: messageOf(error)
123241
+ });
123242
+ }
123243
+ for (const [sessionId, session] of this.sessions) try {
123244
+ for (const entry of session.mcp.list()) {
123245
+ if (!isPluginMcpRuntimeName(entry.name)) continue;
123246
+ const id = pluginIdFromMcpRuntimeName(entry.name);
123247
+ if (id !== void 0) targets.add(id);
123248
+ }
123249
+ } catch (error) {
123250
+ failed.push({
123251
+ step: "mcp.remove",
123252
+ message: `session ${sessionId}: ${messageOf(error)}`
123253
+ });
123254
+ }
123255
+ }
123256
+ const isLive = (id) => {
123257
+ const record = this.plugins.get(id);
123258
+ return record !== void 0 && record.enabled && record.state === "ok";
123259
+ };
123260
+ for (const id of targets) {
123261
+ if (isLive(id)) continue;
123262
+ if (this.extensionRuntime.isActive(id)) try {
123263
+ await this.extensionRuntime.deactivate(id);
123264
+ applied.push({
123265
+ kind: "plugin.deactivate",
123266
+ name: id,
123267
+ session: "host"
123268
+ });
123269
+ } catch (error) {
123270
+ failed.push({
123271
+ step: "plugin.deactivate",
123272
+ pluginId: id,
123273
+ message: messageOf(error)
123274
+ });
123275
+ }
123276
+ for (const [sessionId, session] of this.sessions) for (const agent of session.agents.values()) try {
123277
+ if (agent.tools.unregisterToolsByOwner(id) > 0) applied.push({
123278
+ kind: "tools.remove",
123279
+ name: id,
123280
+ session: sessionId
123281
+ });
123282
+ } catch (error) {
123283
+ failed.push({
123284
+ step: "tools.remove",
123285
+ pluginId: id,
123286
+ message: messageOf(error)
123287
+ });
123288
+ }
123289
+ }
123290
+ for (const [sessionId, session] of this.sessions) {
123291
+ let actualNames = /* @__PURE__ */ new Set();
123292
+ try {
123293
+ actualNames = new Set(session.mcp.list().map((entry) => entry.name));
123294
+ } catch (error) {
123295
+ failed.push({
123296
+ step: "mcp.add",
123297
+ message: messageOf(error)
123298
+ });
123299
+ }
123300
+ for (const id of targets) {
123301
+ const live = isLive(id);
123302
+ if (!desiredUnknown) {
123303
+ for (const name of actualNames) {
123304
+ if (!isPluginMcpRuntimeName(name)) continue;
123305
+ if (pluginIdFromMcpRuntimeName(name) !== id) continue;
123306
+ if (live && desired[name] !== void 0) continue;
123307
+ try {
123308
+ await session.mcp.removeServer(name);
123309
+ applied.push({
123310
+ kind: "mcp.remove",
123311
+ name,
123312
+ session: sessionId
123313
+ });
123314
+ actualNames.delete(name);
123315
+ } catch (error) {
123316
+ failed.push({
123317
+ step: "mcp.remove",
123318
+ pluginId: id,
123319
+ message: messageOf(error)
123320
+ });
123321
+ }
123322
+ }
123323
+ if (live && options?.skipMcpAdd !== true) for (const [name, config] of Object.entries(desired)) {
123324
+ if (pluginIdFromMcpRuntimeName(name) !== id) continue;
123325
+ if (actualNames.has(name)) continue;
123326
+ try {
123327
+ await session.mcp.addServer(name, config);
123328
+ applied.push({
123329
+ kind: "mcp.add",
123330
+ name,
123331
+ session: sessionId
123332
+ });
123333
+ actualNames.add(name);
123334
+ } catch (error) {
123335
+ failed.push({
123336
+ step: "mcp.add",
123337
+ pluginId: id,
123338
+ message: messageOf(error)
123339
+ });
123340
+ }
123341
+ }
123342
+ }
123343
+ try {
123344
+ session.ejectPlugin(id);
123345
+ applied.push({
123346
+ kind: "skills.eject",
123347
+ name: id,
123348
+ session: sessionId
123349
+ });
123350
+ if (live) {
123351
+ const roots = this.plugins.pluginSkillRoots().filter((root) => root.plugin?.id === id);
123352
+ if (roots.length > 0) {
123353
+ await session.injectSkillRoots(roots);
123354
+ applied.push({
123355
+ kind: "skills.inject",
123356
+ name: id,
123357
+ session: sessionId
123358
+ });
123359
+ }
123360
+ }
123361
+ } catch (error) {
123362
+ failed.push({
123363
+ step: live ? "skills.inject" : "skills.eject",
123364
+ pluginId: id,
123365
+ message: messageOf(error)
123366
+ });
123367
+ }
123368
+ }
123369
+ }
123370
+ return {
123371
+ ok: failed.length === 0,
123372
+ sessions: this.sessions.size,
123373
+ applied,
123374
+ failed
123375
+ };
123376
+ }
123377
+ /**
123378
+ * Best-effort wrapper for hot-apply after a successful plugin write: the
123379
+ * mutation already landed, so a sync surprise must never fail the RPC or
123380
+ * the tool call that triggered it.
123381
+ */
123382
+ async syncSessionsBestEffort(changedIds, options) {
123383
+ try {
123384
+ await this.applyPluginChangesToSessions(changedIds, options);
123385
+ } catch (error) {
123386
+ log.warn("plugin hot-apply failed", { reason: error instanceof Error ? error.message : String(error) });
123387
+ }
123388
+ }
121400
123389
  async removePlugin({ id }) {
121401
123390
  await this.pluginsReady;
121402
123391
  this.assertPluginsLoaded();
121403
123392
  await this.plugins.remove(id);
121404
- for (const session of this.sessions.values()) try {
121405
- session.ejectPlugin(id);
121406
- } catch {}
123393
+ await this.applyPluginChangesToSessions([id]);
121407
123394
  }
121408
123395
  generateAgentsMd({ sessionId, ...payload }) {
121409
123396
  return this.sessionApi(sessionId).generateAgentsMd(payload);
@@ -121412,6 +123399,7 @@ var ScreamCore = class {
121412
123399
  await this.pluginsReady;
121413
123400
  this.assertPluginsLoaded();
121414
123401
  const record = await this.plugins.install(payload.source);
123402
+ await this.syncSessionsBestEffort([record.id], { skipMcpAdd: true });
121415
123403
  return this.plugins.summaries().find((s) => s.id === record.id);
121416
123404
  }
121417
123405
  async listPlugins(_) {
@@ -121423,16 +123411,19 @@ var ScreamCore = class {
121423
123411
  await this.pluginsReady;
121424
123412
  this.assertPluginsLoaded();
121425
123413
  await this.plugins.setEnabled(id, enabled);
123414
+ await this.syncSessionsBestEffort([id]);
121426
123415
  }
121427
123416
  async setPluginMcpServerEnabled({ id, server, enabled }) {
121428
123417
  await this.pluginsReady;
121429
123418
  this.assertPluginsLoaded();
121430
123419
  await this.plugins.setMcpServerEnabled(id, server, enabled);
123420
+ await this.syncSessionsBestEffort([id]);
121431
123421
  }
121432
123422
  async reloadPlugins(_) {
121433
123423
  try {
121434
123424
  const summary = await this.plugins.reload();
121435
123425
  this.pluginsLoadError = void 0;
123426
+ await this.syncSessionsBestEffort();
121436
123427
  return summary;
121437
123428
  } catch (error) {
121438
123429
  this.pluginsLoadError = error instanceof Error ? error : new Error(String(error));
@@ -121457,10 +123448,14 @@ var ScreamCore = class {
121457
123448
  });
121458
123449
  }
121459
123450
  async resolveRuntime(config) {
121460
- if (this.runtime !== void 0) return this.runtime;
121461
- const runtime = await createRuntimeConfig({ config });
121462
- this.runtime = runtime;
121463
- return runtime;
123451
+ const base = this.runtime ?? await createRuntimeConfig({ config });
123452
+ this.runtime = base;
123453
+ return {
123454
+ ...base,
123455
+ plugins: this.plugins,
123456
+ extensionRuntime: this.extensionRuntime,
123457
+ pluginSync: (changedIds, syncOptions) => this.applyPluginChangesToSessions(changedIds, syncOptions)
123458
+ };
121464
123459
  }
121465
123460
  resolveSessionSkillConfig(config) {
121466
123461
  const explicitDirs = this.skillDirs.length > 0 ? this.skillDirs : void 0;
@@ -123445,6 +125440,92 @@ const PULSE_WAVE_FRAMES = [
123445
125440
  }
123446
125441
  ];
123447
125442
  //#endregion
125443
+ //#region src/tui/commands/knowledge-store.ts
125444
+ let knowledgeStoreInstance;
125445
+ let embeddingEngineInstance;
125446
+ let embeddingStatus = "idle";
125447
+ function getEmbeddingCacheDir() {
125448
+ const dir = join(getDataDir(), "cache", "fastembed");
125449
+ mkdirSync(dir, { recursive: true });
125450
+ return dir;
125451
+ }
125452
+ /** Subdirectory fastembed uses for the BGESmallZH model inside the cache dir. */
125453
+ const EMBEDDING_MODEL_DIR = "fast-bge-small-zh-v1.5";
125454
+ /**
125455
+ * Whether the embedding model has been downloaded to the local cache.
125456
+ * Checks the ONNX weights plus the config and tokenizer sidecars — all
125457
+ * required for FlagEmbedding.init to load without network access (a missing
125458
+ * tokenizer would trigger a network fetch and violate the warm-up's
125459
+ * offline-load guarantee).
125460
+ */
125461
+ function isEmbeddingModelCached() {
125462
+ const modelDir = join(getEmbeddingCacheDir(), EMBEDDING_MODEL_DIR);
125463
+ return existsSync(join(modelDir, "model_optimized.onnx")) && existsSync(join(modelDir, "config.json")) && existsSync(join(modelDir, "tokenizer.json"));
125464
+ }
125465
+ async function getKnowledgeStore() {
125466
+ if (knowledgeStoreInstance === void 0) {
125467
+ knowledgeStoreInstance = new KnowledgeStore(getDataDir());
125468
+ await knowledgeStoreInstance.init();
125469
+ embeddingEngineInstance = createFastEmbedEngine(getEmbeddingCacheDir());
125470
+ knowledgeStoreInstance.setEmbeddingEngine(embeddingEngineInstance);
125471
+ }
125472
+ return knowledgeStoreInstance;
125473
+ }
125474
+ function getEmbeddingStatus() {
125475
+ return embeddingStatus;
125476
+ }
125477
+ /**
125478
+ * Manually trigger the embedding model download/load.
125479
+ * Only mutates embeddingStatus; the actual download is delegated to
125480
+ * EmbeddingEngine.ensureReady() and saves the model to the shared cache dir.
125481
+ * Returns { ok: true } on success, or { ok: false, error } on failure.
125482
+ * Concurrent calls join the in-flight download and share its result instead
125483
+ * of failing — the startup warm-up and a user-initiated download must never
125484
+ * race into a spurious "download already in progress" error.
125485
+ */
125486
+ async function startManualEmbeddingDownload() {
125487
+ if (embeddingEngineInstance === void 0) return {
125488
+ ok: false,
125489
+ error: "embedding engine not initialized"
125490
+ };
125491
+ if (embeddingEngineInstance.available) {
125492
+ embeddingStatus = "ready";
125493
+ return {
125494
+ ok: true,
125495
+ alreadyReady: true
125496
+ };
125497
+ }
125498
+ if (downloadPromise !== void 0) return downloadPromise;
125499
+ embeddingStatus = "downloading";
125500
+ downloadPromise = performDownload().finally(() => {
125501
+ downloadPromise = void 0;
125502
+ });
125503
+ return downloadPromise;
125504
+ }
125505
+ let downloadPromise;
125506
+ async function performDownload() {
125507
+ try {
125508
+ let ok = await embeddingEngineInstance.ensureReady();
125509
+ let error = ok ? void 0 : embeddingEngineInstance.lastError;
125510
+ if (!ok) {
125511
+ clearEmbeddingModelCache(getEmbeddingCacheDir());
125512
+ ok = await embeddingEngineInstance.ensureReady();
125513
+ error = ok ? void 0 : embeddingEngineInstance.lastError;
125514
+ }
125515
+ embeddingStatus = ok ? "ready" : "failed";
125516
+ return {
125517
+ ok,
125518
+ error
125519
+ };
125520
+ } catch (error) {
125521
+ embeddingStatus = "failed";
125522
+ return {
125523
+ ok: false,
125524
+ error: error instanceof Error ? error.message : String(error)
125525
+ };
125526
+ }
125527
+ }
125528
+ //#endregion
123448
125529
  //#region src/tui/commands/experimental-flags.ts
123449
125530
  let snapshot = {};
123450
125531
  /** Replace the cached flag snapshot. Call once after fetching via `harness.getExperimentalFlags()`. */
@@ -124754,31 +126835,16 @@ function getWireTypeOptions() {
124754
126835
  }
124755
126836
  function getThinkingOptions() {
124756
126837
  return [
124757
- {
124758
- value: "off",
124759
- label: t("prompts.thinking_off")
124760
- },
124761
- {
124762
- value: "low",
124763
- label: t("prompts.thinking_low")
124764
- },
124765
- {
124766
- value: "medium",
124767
- label: t("prompts.thinking_medium")
124768
- },
124769
- {
124770
- value: "high",
124771
- label: t("prompts.thinking_high")
124772
- },
124773
- {
124774
- value: "xhigh",
124775
- label: t("prompts.thinking_xhigh")
124776
- },
124777
- {
124778
- value: "max",
124779
- label: t("prompts.thinking_max")
124780
- }
124781
- ];
126838
+ "off",
126839
+ "low",
126840
+ "medium",
126841
+ "high",
126842
+ "xhigh",
126843
+ "max"
126844
+ ].map((level) => ({
126845
+ value: level,
126846
+ label: level
126847
+ }));
124782
126848
  }
124783
126849
  function getImageOptions() {
124784
126850
  return [{
@@ -127301,7 +129367,7 @@ function isStreaming(state) {
127301
129367
  /** True when the session is busy and should reject state-changing operations.
127302
129368
  * Covers both active streaming and context compaction. */
127303
129369
  function isBusy(state) {
127304
- return isStreaming(state) || state.isCompacting;
129370
+ return isStreaming(state) || state.isCompacting || state.isSwitchingSession;
127305
129371
  }
127306
129372
  //#endregion
127307
129373
  //#region src/tui/constant/terminal.ts
@@ -129525,7 +131591,7 @@ async function guidedGoalSetup(host) {
129525
131591
  host.showNotice(t("goal.storm_breaker"), t("goal.conflict_loop"));
129526
131592
  return;
129527
131593
  }
129528
- const { TextInputDialogComponent } = await import("./text-input-dialog-B9uwX1Uy.mjs");
131594
+ const { TextInputDialogComponent } = await import("./text-input-dialog-Dr42qLMU.mjs");
129529
131595
  const initialDesc = await promptText(host, TextInputDialogComponent, {
129530
131596
  title: t("goal.setup_title_initial"),
129531
131597
  subtitle: t("goal.setup_desc_hint"),
@@ -129546,7 +131612,7 @@ async function guidedGoalSetup(host) {
129546
131612
  await showGoalConfigWizard(host, session, confirmed.trim() || objective, false);
129547
131613
  }
129548
131614
  async function showGoalConfigWizard(host, session, objective, replace) {
129549
- const { TextInputDialogComponent } = await import("./text-input-dialog-B9uwX1Uy.mjs");
131615
+ const { TextInputDialogComponent } = await import("./text-input-dialog-Dr42qLMU.mjs");
129550
131616
  const turnInput = await promptNumber(host, TextInputDialogComponent, {
129551
131617
  title: t("goal.wizard_title", { objective }),
129552
131618
  subtitle: t("goal.budget_turns_hint"),
@@ -136352,7 +138418,7 @@ function buildRoleAdditionalText(prefs) {
136352
138418
  if (items.length === 0 && (doNot === void 0 || doNot.length === 0) && !hasToolPriority) return "";
136353
138419
  lines.push("", ...items);
136354
138420
  if (doNot !== void 0 && doNot.length > 0) lines.push("", "## Do NOT (explicit prohibitions — NEVER do these)", doNot);
136355
- 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.");
138421
+ 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.");
136356
138422
  lines.push("", t("like.priority"));
136357
138423
  return lines.join("\n");
136358
138424
  }
@@ -136482,65 +138548,6 @@ function openUrl(url) {
136482
138548
  execFile(command[0], command[1], () => {});
136483
138549
  }
136484
138550
  //#endregion
136485
- //#region src/tui/commands/knowledge-store.ts
136486
- let knowledgeStoreInstance;
136487
- let embeddingEngineInstance;
136488
- let embeddingStatus = "idle";
136489
- function getEmbeddingCacheDir() {
136490
- const dir = join(getDataDir(), "cache", "fastembed");
136491
- mkdirSync(dir, { recursive: true });
136492
- return dir;
136493
- }
136494
- async function getKnowledgeStore() {
136495
- if (knowledgeStoreInstance === void 0) {
136496
- knowledgeStoreInstance = new KnowledgeStore(getDataDir());
136497
- await knowledgeStoreInstance.init();
136498
- embeddingEngineInstance = createFastEmbedEngine(getEmbeddingCacheDir());
136499
- knowledgeStoreInstance.setEmbeddingEngine(embeddingEngineInstance);
136500
- }
136501
- return knowledgeStoreInstance;
136502
- }
136503
- function getEmbeddingStatus() {
136504
- return embeddingStatus;
136505
- }
136506
- /**
136507
- * Manually trigger the embedding model download/load.
136508
- * Only mutates embeddingStatus; the actual download is delegated to
136509
- * EmbeddingEngine.ensureReady() and saves the model to the shared cache dir.
136510
- * Returns { ok: true } on success, or { ok: false, error } on failure.
136511
- * Concurrent calls while a download is already in flight are ignored.
136512
- */
136513
- async function startManualEmbeddingDownload() {
136514
- if (embeddingEngineInstance === void 0) return {
136515
- ok: false,
136516
- error: "embedding engine not initialized"
136517
- };
136518
- if (embeddingStatus === "downloading") return {
136519
- ok: false,
136520
- error: "download already in progress"
136521
- };
136522
- if (embeddingEngineInstance.available) {
136523
- embeddingStatus = "ready";
136524
- return {
136525
- ok: true,
136526
- alreadyReady: true
136527
- };
136528
- }
136529
- embeddingStatus = "downloading";
136530
- let ok = await embeddingEngineInstance.ensureReady();
136531
- let error = ok ? void 0 : embeddingEngineInstance.lastError;
136532
- if (!ok) {
136533
- clearEmbeddingModelCache(getEmbeddingCacheDir());
136534
- ok = await embeddingEngineInstance.ensureReady();
136535
- error = ok ? void 0 : embeddingEngineInstance.lastError;
136536
- }
136537
- embeddingStatus = ok ? "ready" : "failed";
136538
- return {
136539
- ok,
136540
- error
136541
- };
136542
- }
136543
- //#endregion
136544
138551
  //#region src/tui/commands/knowledge-web.ts
136545
138552
  /**
136546
138553
  * /knowledge web — 知识图谱可视化
@@ -138230,6 +140237,7 @@ async function handleIngest(host) {
138230
140237
  }
138231
140238
  const store = await getKnowledgeStore();
138232
140239
  const llm = makeLlmCaller(host);
140240
+ if (!await ensureEmbeddingReadyInteractive(host)) return;
138233
140241
  const spinner = host.showProgressSpinner(t("knowledge.ingesting"));
138234
140242
  try {
138235
140243
  if (stats.isDirectory()) {
@@ -138240,21 +140248,35 @@ async function handleIngest(host) {
138240
140248
  ok: result.failed === 0,
138241
140249
  label: t("knowledge.batch_done")
138242
140250
  });
140251
+ const coverage = await store.embeddingCoverage();
140252
+ const syncLine = t("knowledge.sync_counts", {
140253
+ created: String(result.created),
140254
+ updated: String(result.updated),
140255
+ unchanged: String(result.unchanged)
140256
+ });
138243
140257
  if (result.failed > 0) {
138244
140258
  const summary = [
138245
140259
  t("knowledge.succeeded", { count: String(result.succeeded) }),
140260
+ syncLine,
138246
140261
  t("knowledge.failed", { count: String(result.failed) }),
138247
140262
  t("knowledge.ingest_summary", {
138248
140263
  chunks: String(result.totalChunks),
138249
140264
  events: String(result.totalEvents),
138250
140265
  entities: String(result.totalEntities)
138251
140266
  }),
140267
+ t("knowledge.vector_coverage", {
140268
+ embedded: String(coverage.embedded),
140269
+ total: String(coverage.total)
140270
+ }),
138252
140271
  "",
138253
140272
  t("knowledge.failed_files"),
138254
140273
  ...result.errors.map((e) => ` • ${basename(e.filePath)}: ${e.message}`)
138255
140274
  ].join("\n");
138256
140275
  host.showNotice(t("knowledge.batch_partial"), summary);
138257
- } else host.showNotice(t("knowledge.batch_done"), `${t("knowledge.succeeded", { count: String(result.succeeded) })}\n${result.totalChunks} chunks, ${result.totalEvents} events, ${result.totalEntities} entities`);
140276
+ } else host.showNotice(t("knowledge.batch_done"), `${syncLine}\n${result.totalChunks} chunks, ${result.totalEvents} events, ${result.totalEntities} entities\n${t("knowledge.vector_coverage", {
140277
+ embedded: String(coverage.embedded),
140278
+ total: String(coverage.total)
140279
+ })}`);
138258
140280
  } else {
138259
140281
  if (!isSupportedFile(filePath)) {
138260
140282
  spinner.stop({
@@ -138267,11 +140289,19 @@ async function handleIngest(host) {
138267
140289
  const result = await ingestFile(store, llm, filePath, (progress) => {
138268
140290
  spinner.setLabel(formatProgress(progress));
138269
140291
  });
140292
+ const outcomeLabel = result.outcome === "unchanged" ? t("knowledge.ingest_unchanged") : result.outcome === "updated" ? t("knowledge.ingest_updated") : t("knowledge.ingest_done");
138270
140293
  spinner.stop({
138271
140294
  ok: true,
138272
- label: t("knowledge.ingest_done")
140295
+ label: outcomeLabel
138273
140296
  });
138274
- host.showNotice(t("knowledge.ingest_done"), `${t("knowledge.file_label")}: ${basename(filePath)}\nchunks: ${result.chunkCount}\nevents: ${result.eventCount}\nentities: ${result.entityCount}`);
140297
+ if (result.outcome === "unchanged") host.showNotice(outcomeLabel, `${t("knowledge.file_label")}: ${basename(filePath)}`);
140298
+ else {
140299
+ const coverage = await store.embeddingCoverage();
140300
+ host.showNotice(outcomeLabel, `${t("knowledge.file_label")}: ${basename(filePath)}\nchunks: ${result.chunkCount}\nevents: ${result.eventCount}\nentities: ${result.entityCount}\n${t("knowledge.vector_coverage", {
140301
+ embedded: String(coverage.embedded),
140302
+ total: String(coverage.total)
140303
+ })}`);
140304
+ }
138275
140305
  }
138276
140306
  } catch (error) {
138277
140307
  spinner.stop({
@@ -138281,6 +140311,31 @@ async function handleIngest(host) {
138281
140311
  throw error;
138282
140312
  }
138283
140313
  }
140314
+ /**
140315
+ * Ensure the shared embedding engine is ready before a vector-dependent
140316
+ * operation. Returns true when ready; otherwise shows an error (missing
140317
+ * download) or attempts a cached-model load and reports the outcome.
140318
+ */
140319
+ async function ensureEmbeddingReadyInteractive(host) {
140320
+ const engine = (await getKnowledgeStore()).getEmbeddingEngine();
140321
+ if (engine !== void 0 && engine.available) return true;
140322
+ if (!isEmbeddingModelCached()) {
140323
+ host.showError(t("knowledge.model_missing"));
140324
+ return false;
140325
+ }
140326
+ const spinner = host.showProgressSpinner(t("knowledge.loading_model"));
140327
+ const { ok, error } = await startManualEmbeddingDownload();
140328
+ spinner.stop({
140329
+ ok,
140330
+ label: ok ? t("kw.embedding_ready") : t("kw.embedding_failed")
140331
+ });
140332
+ if (!ok) {
140333
+ const detail = [t("knowledge.download_model_retry_hint"), error].filter(Boolean).join("\n");
140334
+ host.showNotice(t("knowledge.model_load_failed"), detail);
140335
+ return false;
140336
+ }
140337
+ return true;
140338
+ }
138284
140339
  async function handleList(host) {
138285
140340
  const store = await getKnowledgeStore();
138286
140341
  const docs = await store.listDocuments();
@@ -138430,6 +140485,97 @@ async function handleDelete(host) {
138430
140485
  if (await store.deleteSource(sourceId)) host.showNotice(t("knowledge.deleted"), t("knowledge.doc_removed"));
138431
140486
  else host.showError(t("knowledge.delete_fail_not_found"));
138432
140487
  }
140488
+ function pickSourceToReembed(host, sources) {
140489
+ const { promise, resolve } = Promise.withResolvers();
140490
+ const options = sources.map((s) => ({
140491
+ value: s.id,
140492
+ label: s.name,
140493
+ description: s.filePath ?? void 0
140494
+ }));
140495
+ const picker = new ChoicePickerComponent({
140496
+ title: t("knowledge.reembed_pick"),
140497
+ options,
140498
+ colors: host.state.theme.colors,
140499
+ onSelect: (value) => {
140500
+ host.restoreEditor();
140501
+ resolve(value);
140502
+ },
140503
+ onCancel: () => {
140504
+ host.restoreEditor();
140505
+ resolve(void 0);
140506
+ }
140507
+ });
140508
+ host.mountEditorReplacement(picker);
140509
+ return promise;
140510
+ }
140511
+ function confirmReembedSource(host, source) {
140512
+ const { promise, resolve } = Promise.withResolvers();
140513
+ const options = [{
140514
+ value: "cancel",
140515
+ label: t("common.cancel")
140516
+ }, {
140517
+ value: "confirm",
140518
+ label: t("knowledge.reembed"),
140519
+ description: t("knowledge.reembed_confirm_desc")
140520
+ }];
140521
+ const picker = new ChoicePickerComponent({
140522
+ title: t("knowledge.reembed_confirm_name", { name: source.name }),
140523
+ options,
140524
+ colors: host.state.theme.colors,
140525
+ onSelect: (value) => {
140526
+ host.restoreEditor();
140527
+ resolve(value === "confirm");
140528
+ },
140529
+ onCancel: () => {
140530
+ host.restoreEditor();
140531
+ resolve(false);
140532
+ }
140533
+ });
140534
+ host.mountEditorReplacement(picker);
140535
+ return promise;
140536
+ }
140537
+ async function handleReembed(host) {
140538
+ const store = await getKnowledgeStore();
140539
+ const sources = await store.listSources();
140540
+ if (sources.length === 0) {
140541
+ host.showNotice(t("knowledge.empty"), t("knowledge.reembed_none"));
140542
+ return;
140543
+ }
140544
+ const sourceId = await pickSourceToReembed(host, sources);
140545
+ if (sourceId === void 0) return;
140546
+ const source = sources.find((s) => s.id === sourceId);
140547
+ if (source === void 0) return;
140548
+ if (!await confirmReembedSource(host, source)) {
140549
+ host.showNotice(t("knowledge.cancelled"));
140550
+ return;
140551
+ }
140552
+ if (!await ensureEmbeddingReadyInteractive(host)) return;
140553
+ const engine = store.getEmbeddingEngine();
140554
+ if (engine === void 0) {
140555
+ host.showError(t("knowledge.model_missing"));
140556
+ return;
140557
+ }
140558
+ const spinner = host.showProgressSpinner(t("knowledge.reembed"));
140559
+ try {
140560
+ const counts = await store.reembedSource(sourceId, engine);
140561
+ spinner.stop({
140562
+ ok: true,
140563
+ label: t("knowledge.reembed_done")
140564
+ });
140565
+ host.showNotice(t("knowledge.reembed_done"), t("knowledge.reembed_summary", {
140566
+ chunks: String(counts.chunks),
140567
+ events: String(counts.events),
140568
+ entities: String(counts.entities),
140569
+ relations: String(counts.relations)
140570
+ }));
140571
+ } catch (error) {
140572
+ spinner.stop({
140573
+ ok: false,
140574
+ label: t("knowledge.reembed")
140575
+ });
140576
+ throw error;
140577
+ }
140578
+ }
138433
140579
  async function handleDownloadModel(host) {
138434
140580
  await getKnowledgeStore();
138435
140581
  const spinner = host.showProgressSpinner(t("kw.embedding_downloading"));
@@ -138511,6 +140657,11 @@ async function handleKnowledgeCommand(host, _args) {
138511
140657
  description: t("knowledge.delete_desc"),
138512
140658
  tone: "danger"
138513
140659
  },
140660
+ {
140661
+ value: "reembed",
140662
+ label: "🔁 " + t("knowledge.reembed"),
140663
+ description: t("knowledge.reembed_desc")
140664
+ },
138514
140665
  {
138515
140666
  value: "stats",
138516
140667
  label: "📊 " + t("knowledge.stats"),
@@ -138536,6 +140687,7 @@ async function handleKnowledgeCommand(host, _args) {
138536
140687
  else if (value === "list") await handleList(host);
138537
140688
  else if (value === "search") await handleSearch(host);
138538
140689
  else if (value === "delete") await handleDelete(host);
140690
+ else if (value === "reembed") await handleReembed(host);
138539
140691
  else if (value === "stats") await handleStats(host);
138540
140692
  else if (value === "web") await handleWeb(host);
138541
140693
  } catch (error) {
@@ -139183,4 +141335,4 @@ async function handleBuiltInSlashCommand(host, name, args) {
139183
141335
  }
139184
141336
  }
139185
141337
  //#endregion
139186
- 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 };
141338
+ 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 };