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.
- package/README.md +3 -0
- package/dist/{app-D_n0flAk.mjs → app-2Dd57a5d.mjs} +190 -31
- package/dist/{dispatch-MWaAkj2l.mjs → dispatch-1ojfKMwf.mjs} +2430 -278
- package/dist/{dispatch-CmY89ob7.mjs → dispatch-CJPpI4gm.mjs} +1 -1
- package/dist/main.mjs +1 -1
- package/dist/{src-DCp4eCi5.mjs → src-h6ZExh4A.mjs} +1 -1
- package/dist/{src-BH9W5k24.mjs → src-tDEINaMV.mjs} +196 -17
- package/dist/{text-input-dialog-B0yHChBj.mjs → text-input-dialog-BUXAKycx.mjs} +40 -14
- package/dist/{text-input-dialog-B9uwX1Uy.mjs → text-input-dialog-Dr42qLMU.mjs} +1 -1
- package/package.json +1 -1
- package/dist/{src-C8lOjRbK.mjs → src-C3oCiVNb.mjs} +1 -1
|
@@ -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-
|
|
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-
|
|
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$
|
|
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$
|
|
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$
|
|
1844
|
+
function noop$4() {}
|
|
1845
1845
|
function makeLogFn$2(fnLevel, logger, logLevel) {
|
|
1846
|
-
if (!logger || levelNumbers$2[fnLevel] > levelNumbers$2[logLevel]) return noop$
|
|
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$
|
|
1851
|
-
warn: noop$
|
|
1852
|
-
info: noop$
|
|
1853
|
-
debug: noop$
|
|
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-
|
|
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$
|
|
31850
|
+
function noop$3() {}
|
|
31851
31851
|
function makeLogFn$1(fnLevel, logger, logLevel) {
|
|
31852
|
-
if (!logger || levelNumbers$1[fnLevel] > levelNumbers$1[logLevel]) return noop$
|
|
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$
|
|
31857
|
-
warn: noop$
|
|
31858
|
-
info: noop$
|
|
31859
|
-
debug: noop$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
35788
|
+
function noop$2() {}
|
|
35789
35789
|
function makeLogFn(fnLevel, logger, logLevel) {
|
|
35790
|
-
if (!logger || levelNumbers[fnLevel] > levelNumbers[logLevel]) return noop$
|
|
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$
|
|
35795
|
-
warn: noop$
|
|
35796
|
-
info: noop$
|
|
35797
|
-
debug: noop$
|
|
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 "
|
|
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 = "
|
|
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
|
-
|
|
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 =
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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 '
|
|
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]]),
|
|
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-
|
|
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$
|
|
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$
|
|
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(
|
|
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$
|
|
66511
|
-
description: stringField$
|
|
66512
|
-
keywords: stringArrayField$
|
|
66513
|
-
homepage: stringField$
|
|
66514
|
-
license: stringField$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
66778
|
-
const email = stringField$
|
|
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$
|
|
66789
|
-
shortDescription: stringField$
|
|
66790
|
-
longDescription: stringField$
|
|
66791
|
-
developerName: stringField$
|
|
66792
|
-
websiteURL: stringField$
|
|
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$
|
|
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$
|
|
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
|
|
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
|
-
|
|
67517
|
-
|
|
67518
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
-
})
|
|
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:
|
|
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:
|
|
75900
|
-
path
|
|
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
|
|
75909
|
-
return
|
|
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.
|
|
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.
|
|
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.
|
|
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:
|
|
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
|
|
76619
|
-
*
|
|
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
|
-
|
|
84738
|
+
const lines = [];
|
|
84739
|
+
let lastSnapshotLineNumber = -1;
|
|
83261
84740
|
let lineNumber = 0;
|
|
83262
|
-
|
|
84741
|
+
let openChunks = [];
|
|
84742
|
+
const stream = createReadStream(this.filePath);
|
|
83263
84743
|
try {
|
|
83264
84744
|
for await (const chunk of stream) {
|
|
83265
|
-
|
|
83266
|
-
|
|
83267
|
-
|
|
83268
|
-
|
|
83269
|
-
|
|
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
|
-
|
|
83272
|
-
|
|
83273
|
-
|
|
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 (
|
|
84768
|
+
if (openChunks.length > 0) {
|
|
83281
84769
|
lineNumber++;
|
|
83282
|
-
|
|
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
|
-
|
|
83813
|
-
|
|
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)
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
97089
|
-
|
|
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(
|
|
97113
|
-
} catch (
|
|
97114
|
-
|
|
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
|
|
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
|
-
|
|
97335
|
-
|
|
97336
|
-
|
|
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
|
-
*
|
|
104540
|
-
*
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
121461
|
-
|
|
121462
|
-
|
|
121463
|
-
|
|
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
|
-
|
|
124759
|
-
|
|
124760
|
-
|
|
124761
|
-
|
|
124762
|
-
|
|
124763
|
-
|
|
124764
|
-
|
|
124765
|
-
|
|
124766
|
-
|
|
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-
|
|
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-
|
|
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.
|
|
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"), `${
|
|
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:
|
|
140295
|
+
label: outcomeLabel
|
|
138273
140296
|
});
|
|
138274
|
-
host.showNotice(
|
|
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,
|
|
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 };
|