scream-code 0.14.9 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{app-2Dd57a5d.mjs → app-CPu6R7nd.mjs} +1138 -50
- package/dist/{dispatch-CJPpI4gm.mjs → dispatch-BPcn43Vw.mjs} +1 -1
- package/dist/{dispatch-1ojfKMwf.mjs → dispatch-D8ovLbmr.mjs} +259 -142
- package/dist/main.mjs +1 -1
- package/dist/public/assets/index-BfVIY6UW.css +1 -0
- package/dist/public/assets/index-CYmwuyd8.js +93 -0
- package/dist/public/assets/index-CYmwuyd8.js.map +1 -0
- package/dist/public/assets/logo-v2-ChmPAIbI.svg +1 -0
- package/dist/public/gateway.html +156 -0
- package/dist/public/index.html +2 -2
- package/package.json +8 -2
- package/dist/public/assets/index-CopRDlgZ.js +0 -90
- package/dist/public/assets/index-CopRDlgZ.js.map +0 -1
- package/dist/public/assets/index-CyGAkekh.css +0 -1
|
@@ -3,5 +3,5 @@ import { fileURLToPath as __cjsShimFileURLToPath } from 'node:url';
|
|
|
3
3
|
import { dirname as __cjsShimDirname } from 'node:path';
|
|
4
4
|
const __filename = __cjsShimFileURLToPath(import.meta.url);
|
|
5
5
|
const __dirname = __cjsShimDirname(__filename);
|
|
6
|
-
import { Kt as handleConnectCommand } from "./dispatch-
|
|
6
|
+
import { Kt as handleConnectCommand } from "./dispatch-D8ovLbmr.mjs";
|
|
7
7
|
export { handleConnectCommand };
|
|
@@ -575,7 +575,7 @@ function createToolMessage(toolCallId, output) {
|
|
|
575
575
|
}
|
|
576
576
|
//#endregion
|
|
577
577
|
//#region ../../packages/ltod/src/rate-limit-utils.ts
|
|
578
|
-
const QUOTA_EXHAUSTED_BACKOFF_MS =
|
|
578
|
+
const QUOTA_EXHAUSTED_BACKOFF_MS = 30 * 1e3;
|
|
579
579
|
const RATE_LIMIT_EXCEEDED_BACKOFF_MS = 30 * 1e3;
|
|
580
580
|
const MODEL_CAPACITY_BASE_MS = 45 * 1e3;
|
|
581
581
|
const MODEL_CAPACITY_JITTER_MS = 30 * 1e3;
|
|
@@ -605,9 +605,9 @@ function parseRateLimitReason(errorMessage) {
|
|
|
605
605
|
/**
|
|
606
606
|
* Backoff in ms for a given reason. MODEL_CAPACITY gets jitter to avoid
|
|
607
607
|
* thundering herd. UNKNOWN (a classified 429 with no recognized reason)
|
|
608
|
-
* uses the short 20s SERVER_ERROR backoff — a
|
|
609
|
-
*
|
|
610
|
-
* quota exhaustion.
|
|
608
|
+
* uses the short 20s SERVER_ERROR backoff — a long quota backoff on an
|
|
609
|
+
* unrecoverable error would hang a turn for far longer than the value of
|
|
610
|
+
* retrying, and UNKNOWN is not evidence of quota exhaustion.
|
|
611
611
|
*/
|
|
612
612
|
function calculateRateLimitBackoffMs(reason) {
|
|
613
613
|
switch (reason) {
|
|
@@ -765,14 +765,9 @@ var APIEmptyResponseError = class extends ChatProviderError {
|
|
|
765
765
|
function isRetryableGenerateError(error) {
|
|
766
766
|
if (error instanceof APIConnectionError$3 || error instanceof APITimeoutError) return true;
|
|
767
767
|
if (error instanceof APIEmptyResponseError) return true;
|
|
768
|
-
if (error instanceof APIProviderRateLimitError) return
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
500,
|
|
772
|
-
502,
|
|
773
|
-
503,
|
|
774
|
-
504
|
|
775
|
-
].includes(error.statusCode);
|
|
768
|
+
if (error instanceof APIProviderRateLimitError) return true;
|
|
769
|
+
if (error instanceof APIStatusError) return error.statusCode === 429 || error.statusCode === 408 || error.statusCode >= 500 && error.statusCode < 600;
|
|
770
|
+
return false;
|
|
776
771
|
}
|
|
777
772
|
const CONTEXT_OVERFLOW_MESSAGE_PATTERNS = [
|
|
778
773
|
/context[ _-]?length/,
|
|
@@ -47489,12 +47484,9 @@ async function chatWithRetry(input) {
|
|
|
47489
47484
|
logRequestFailure(input, error, attempt, maxAttempts);
|
|
47490
47485
|
throw error;
|
|
47491
47486
|
}
|
|
47492
|
-
|
|
47493
|
-
|
|
47494
|
-
|
|
47495
|
-
}
|
|
47496
|
-
if (attempt >= maxAttempts || !input.llm.isRetryableError(error)) {
|
|
47497
|
-
logRequestFailure(input, error, attempt, maxAttempts);
|
|
47487
|
+
const attemptLimit = error instanceof APIProviderRateLimitError && error.reason === "QUOTA_EXHAUSTED" ? Math.min(3, maxAttempts) : maxAttempts;
|
|
47488
|
+
if (attempt >= attemptLimit || !input.llm.isRetryableError(error)) {
|
|
47489
|
+
logRequestFailure(input, error, attempt, attemptLimit);
|
|
47498
47490
|
throw error;
|
|
47499
47491
|
}
|
|
47500
47492
|
const delayMs = computeDelayMs(error, delays, attempt);
|
|
@@ -47506,7 +47498,7 @@ async function chatWithRetry(input) {
|
|
|
47506
47498
|
stepUuid: input.stepUuid,
|
|
47507
47499
|
failedAttempt: attempt,
|
|
47508
47500
|
nextAttempt: attempt + 1,
|
|
47509
|
-
maxAttempts,
|
|
47501
|
+
maxAttempts: attemptLimit,
|
|
47510
47502
|
delayMs,
|
|
47511
47503
|
...retryErrorFields(error)
|
|
47512
47504
|
});
|
|
@@ -65283,7 +65275,7 @@ var import_yauzl = (/* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
65283
65275
|
var EventEmitter$3 = __require("events").EventEmitter;
|
|
65284
65276
|
var Transform$1 = __require("stream").Transform;
|
|
65285
65277
|
var PassThrough$2 = __require("stream").PassThrough;
|
|
65286
|
-
var Writable = __require("stream").Writable;
|
|
65278
|
+
var Writable$1 = __require("stream").Writable;
|
|
65287
65279
|
exports.fromBuffer = fromBuffer;
|
|
65288
65280
|
function fromBuffer(buffer, options, callback) {
|
|
65289
65281
|
if (typeof options === "function") {
|
|
@@ -65807,7 +65799,7 @@ var import_yauzl = (/* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
65807
65799
|
start: position,
|
|
65808
65800
|
end: position + length
|
|
65809
65801
|
});
|
|
65810
|
-
var writeStream = new Writable();
|
|
65802
|
+
var writeStream = new Writable$1();
|
|
65811
65803
|
var written = 0;
|
|
65812
65804
|
writeStream._write = function(chunk, encoding, cb) {
|
|
65813
65805
|
chunk.copy(buffer, offset + written, 0, chunk.length);
|
|
@@ -65976,9 +65968,9 @@ async function hasManifest(dir) {
|
|
|
65976
65968
|
const dirManifest = path.join(dir, ".scream-plugin", "plugin.json");
|
|
65977
65969
|
const claudeDirManifest = path.join(dir, ".claude-plugin", "plugin.json");
|
|
65978
65970
|
const skillMd = path.join(dir, "SKILL.md");
|
|
65979
|
-
return await isFile$
|
|
65971
|
+
return await isFile$3(rootManifest) || await isFile$3(dirManifest) || await isFile$3(claudeDirManifest) || await isFile$3(skillMd);
|
|
65980
65972
|
}
|
|
65981
|
-
async function isFile$
|
|
65973
|
+
async function isFile$3(p) {
|
|
65982
65974
|
try {
|
|
65983
65975
|
return (await stat(p)).isFile();
|
|
65984
65976
|
} catch {
|
|
@@ -66441,12 +66433,12 @@ async function parseManifest(pluginRoot) {
|
|
|
66441
66433
|
const rootJsonPath = path.join(pluginRoot, SCREAM_PLUGIN_ROOT_PATH);
|
|
66442
66434
|
const dirJsonPath = path.join(pluginRoot, SCREAM_PLUGIN_DIR_PATH);
|
|
66443
66435
|
const claudeDirJsonPath = path.join(pluginRoot, CLAUDE_PLUGIN_DIR_PATH);
|
|
66444
|
-
const rootJsonExists = await isFile$
|
|
66445
|
-
const dirJsonExists = await isFile$
|
|
66446
|
-
const claudeDirJsonExists = await isFile$
|
|
66436
|
+
const rootJsonExists = await isFile$2(rootJsonPath);
|
|
66437
|
+
const dirJsonExists = await isFile$2(dirJsonPath);
|
|
66438
|
+
const claudeDirJsonExists = await isFile$2(claudeDirJsonPath);
|
|
66447
66439
|
if (!rootJsonExists && !dirJsonExists && !claudeDirJsonExists) {
|
|
66448
66440
|
const skillMdPath = path.join(pluginRoot, BARE_SKILL_PATH);
|
|
66449
|
-
if (await isFile$
|
|
66441
|
+
if (await isFile$2(skillMdPath)) return {
|
|
66450
66442
|
manifest: {
|
|
66451
66443
|
name: path.basename(pluginRoot),
|
|
66452
66444
|
skills: [pluginRoot]
|
|
@@ -66524,7 +66516,7 @@ async function parseManifest(pluginRoot) {
|
|
|
66524
66516
|
}
|
|
66525
66517
|
let skills = await resolveSkillsField(pluginRoot, raw["skills"], diagnostics);
|
|
66526
66518
|
if (raw["skills"] === void 0) {
|
|
66527
|
-
if (await isFile$
|
|
66519
|
+
if (await isFile$2(path.join(pluginRoot, "SKILL.md"))) skills = [pluginRoot];
|
|
66528
66520
|
}
|
|
66529
66521
|
const skillInstructions = typeof raw["skillInstructions"] === "string" ? raw["skillInstructions"] : void 0;
|
|
66530
66522
|
const config = typeof raw["config"] === "object" && raw["config"] !== null && !Array.isArray(raw["config"]) ? raw["config"] : void 0;
|
|
@@ -66856,7 +66848,7 @@ async function discoverSkillDirs(root, maxDepth) {
|
|
|
66856
66848
|
for (const entry of entries) {
|
|
66857
66849
|
if (!entry.isDirectory()) continue;
|
|
66858
66850
|
const child = path.join(dir, entry.name);
|
|
66859
|
-
if (await isFile$
|
|
66851
|
+
if (await isFile$2(path.join(child, BARE_SKILL_PATH))) found.push(child);
|
|
66860
66852
|
await walk(child, depth + 1);
|
|
66861
66853
|
}
|
|
66862
66854
|
}
|
|
@@ -66866,7 +66858,7 @@ async function discoverSkillDirs(root, maxDepth) {
|
|
|
66866
66858
|
for (const dir of sorted) if (!result.some((parent) => dir.startsWith(parent + path.sep))) result.push(dir);
|
|
66867
66859
|
return result;
|
|
66868
66860
|
}
|
|
66869
|
-
async function isFile$
|
|
66861
|
+
async function isFile$2(p) {
|
|
66870
66862
|
try {
|
|
66871
66863
|
return (await stat(p)).isFile();
|
|
66872
66864
|
} catch {
|
|
@@ -67863,7 +67855,10 @@ async function keepNewestBackupOnly(backupsForId) {
|
|
|
67863
67855
|
async function recordFrom(input) {
|
|
67864
67856
|
const { parsed } = input;
|
|
67865
67857
|
const hasError = parsed.diagnostics.some((d) => d.severity === "error");
|
|
67866
|
-
const
|
|
67858
|
+
const discovery = hasError || parsed.manifest === void 0 ? {
|
|
67859
|
+
skills: [],
|
|
67860
|
+
diagnostics: []
|
|
67861
|
+
} : await discoverPluginSkills(input.id, parsed.manifest);
|
|
67867
67862
|
return {
|
|
67868
67863
|
id: input.id,
|
|
67869
67864
|
root: input.root,
|
|
@@ -67875,13 +67870,13 @@ async function recordFrom(input) {
|
|
|
67875
67870
|
originalSource: input.originalSource,
|
|
67876
67871
|
capabilities: input.capabilities,
|
|
67877
67872
|
github: input.github,
|
|
67878
|
-
skills,
|
|
67879
|
-
skillCount: skills.length,
|
|
67873
|
+
skills: discovery.skills,
|
|
67874
|
+
skillCount: discovery.skills.length,
|
|
67880
67875
|
manifest: parsed.manifest,
|
|
67881
67876
|
manifestKind: parsed.manifestKind,
|
|
67882
67877
|
manifestPath: parsed.manifestPath,
|
|
67883
67878
|
shadowedManifestPath: parsed.shadowedManifestPath,
|
|
67884
|
-
diagnostics: parsed.diagnostics,
|
|
67879
|
+
diagnostics: [...parsed.diagnostics, ...discovery.diagnostics],
|
|
67885
67880
|
skillInstructions: parsed.manifest?.skillInstructions
|
|
67886
67881
|
};
|
|
67887
67882
|
}
|
|
@@ -67911,11 +67906,26 @@ async function discoverPluginSkills(pluginId, manifest) {
|
|
|
67911
67906
|
instructions: manifest?.skillInstructions
|
|
67912
67907
|
}
|
|
67913
67908
|
}));
|
|
67914
|
-
if (roots.length === 0) return
|
|
67915
|
-
|
|
67916
|
-
|
|
67917
|
-
|
|
67918
|
-
|
|
67909
|
+
if (roots.length === 0) return {
|
|
67910
|
+
skills: [],
|
|
67911
|
+
diagnostics: []
|
|
67912
|
+
};
|
|
67913
|
+
const diagnostics = [];
|
|
67914
|
+
return {
|
|
67915
|
+
skills: (await discoverSkills({
|
|
67916
|
+
roots,
|
|
67917
|
+
onWarning: (message) => {
|
|
67918
|
+
diagnostics.push({
|
|
67919
|
+
severity: "warn",
|
|
67920
|
+
message
|
|
67921
|
+
});
|
|
67922
|
+
}
|
|
67923
|
+
})).map((skill) => ({
|
|
67924
|
+
name: skill.name,
|
|
67925
|
+
description: skill.description
|
|
67926
|
+
})),
|
|
67927
|
+
diagnostics
|
|
67928
|
+
};
|
|
67919
67929
|
}
|
|
67920
67930
|
function recordToInfo(record) {
|
|
67921
67931
|
return {
|
|
@@ -77142,7 +77152,7 @@ function describeError(error) {
|
|
|
77142
77152
|
}
|
|
77143
77153
|
//#endregion
|
|
77144
77154
|
//#region ../../packages/agent-core/src/tools/builtin/state/inspect-own-assets.md
|
|
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,
|
|
77155
|
+
var inspect_own_assets_default = "Use this tool to inspect the agent's own persistent assets: skills, MCP server declarations, configuration files, the memory store, and the knowledge base. It reports what exists, where it lives, and whether it looks valid.\n\n**When to use:**\n- The user asks \"what skills do you have?\", \"show me your mcp config\", \"how is your memory set up?\", \"where is your knowledge base?\", \"检查技能/插件状态\"\n- Auditing your own configuration and data (e.g. checking whether mcp.json parses, why a skill is not usable, which plugin directories are unregistered)\n\n**Inventory questions — this tool is the single answering view.** When the user asks what you have, in ANY wording (skills / plugins / capabilities / MCP / tools / 技能 / 插件 / 能力), treat it as one intent and answer from this inventory: it reports the skill packages in the three standard skill scopes (user, plugin-managed, project) with live `invocable` status from the skill registry, plus MCP declarations. Every \"not invocable\" entry carries its concrete reason (registered but not invocable / broken frontmatter with the parse message / unregistered), and plugin-managed entries show whether their directory is registered in the plugin table, with unregistered directories listed separately — so a status question is answered by this single call, no need to consult ManagePlugin for the same facts. Skills discovered from non-standard locations (nested directories, `.agents/skills`) may not appear here. Pair it with the system-prompt skills section for the \"callable right now\" view. Division of labor: **InspectOwnAssets = inventory (read-only)**, **ManagePlugin = changes (install/enable/remove)**, **Skill tool = invocation**.\n\nYou must NOT modify any of these assets unless the user explicitly asks you to — this tool is strictly read-only.\n\n**When NOT to use:**\n- Reading the user's workspace files — use `read` / `glob` / `grep` instead\n- Writing or editing anything — this tool is strictly read-only\n\n**How to use:**\n- Call with no arguments (or `scope: \"all\"`) to inspect everything\n- Narrow with `scope: \"skills\"` / `\"mcp\"` / `\"config\"` / `\"memory\"` / `\"knowledge\"` to inspect a single category\n\nThis tool never writes, creates, or modifies any file.\n";
|
|
77146
77156
|
const InspectOwnAssetsInputSchema = z.object({ scope: z.enum([
|
|
77147
77157
|
"all",
|
|
77148
77158
|
"skills",
|
|
@@ -77151,8 +77161,14 @@ const InspectOwnAssetsInputSchema = z.object({ scope: z.enum([
|
|
|
77151
77161
|
"memory",
|
|
77152
77162
|
"knowledge"
|
|
77153
77163
|
]).optional().describe("Which self-assets to inspect: 'all' (default) reports everything; narrow to 'skills', 'mcp', 'config', 'memory', or 'knowledge'.") });
|
|
77154
|
-
/**
|
|
77155
|
-
|
|
77164
|
+
/**
|
|
77165
|
+
* Skills larger than this are reported as broken rather than parsed. Real
|
|
77166
|
+
* skill bundles are a few KB; the cap exists only to guard against reading a
|
|
77167
|
+
* pathological file (e.g. a multi-GB binary misnamed SKILL.md) in one shot.
|
|
77168
|
+
*/
|
|
77169
|
+
const SKILL_FILE_READ_LIMIT = 4 * 1024 * 1024;
|
|
77170
|
+
/** Recursion bound for counting nested SKILL.md files inside unregistered dirs. */
|
|
77171
|
+
const ORPHAN_SCAN_DEPTH = 4;
|
|
77156
77172
|
/**
|
|
77157
77173
|
* Common documentation files shipped inside skill/plugin bundles are not
|
|
77158
77174
|
* skills; matched case-insensitively against top-level flat `.md` entries
|
|
@@ -77192,34 +77208,73 @@ function describeFile(info) {
|
|
|
77192
77208
|
if (!info.exists) return "missing";
|
|
77193
77209
|
return `${info.size} bytes`;
|
|
77194
77210
|
}
|
|
77195
|
-
|
|
77196
|
-
async function checkFrontmatter(path) {
|
|
77197
|
-
let handle;
|
|
77211
|
+
async function isFile$1(path) {
|
|
77198
77212
|
try {
|
|
77199
|
-
|
|
77200
|
-
const buffer = Buffer.alloc(FRONTMATTER_READ_LIMIT);
|
|
77201
|
-
const { bytesRead } = await handle.read(buffer, 0, FRONTMATTER_READ_LIMIT, 0);
|
|
77202
|
-
const head = buffer.subarray(0, bytesRead).toString("utf-8").split("\n").slice(0, 25);
|
|
77203
|
-
if (head[0]?.trim() !== "---") return "missing";
|
|
77204
|
-
return head.some((line) => /^name\s*:/.test(line)) ? "ok" : "broken";
|
|
77213
|
+
return (await stat(path)).isFile();
|
|
77205
77214
|
} catch {
|
|
77206
|
-
return
|
|
77207
|
-
} finally {
|
|
77208
|
-
await handle?.close().catch(() => {});
|
|
77215
|
+
return false;
|
|
77209
77216
|
}
|
|
77210
77217
|
}
|
|
77211
|
-
/**
|
|
77212
|
-
|
|
77218
|
+
/**
|
|
77219
|
+
* Parse a skill file the same way the registry does (`skill/parser`), so the
|
|
77220
|
+
* inventory's status and names agree with what is actually invocable: a file
|
|
77221
|
+
* whose frontmatter fails YAML parsing is `broken` with the real message, not
|
|
77222
|
+
* a heuristic "ok" based on the presence of a `name:` line.
|
|
77223
|
+
*/
|
|
77224
|
+
async function parseSkillEntry(path, fallbackName, requireFrontmatter) {
|
|
77225
|
+
let info;
|
|
77213
77226
|
try {
|
|
77214
|
-
|
|
77227
|
+
info = await stat(path);
|
|
77215
77228
|
} catch {
|
|
77216
|
-
return
|
|
77229
|
+
return {
|
|
77230
|
+
name: fallbackName,
|
|
77231
|
+
frontmatter: "missing"
|
|
77232
|
+
};
|
|
77233
|
+
}
|
|
77234
|
+
if (info.size > SKILL_FILE_READ_LIMIT) return {
|
|
77235
|
+
name: fallbackName,
|
|
77236
|
+
frontmatter: "ok"
|
|
77237
|
+
};
|
|
77238
|
+
let text;
|
|
77239
|
+
try {
|
|
77240
|
+
text = await readFile(path, "utf8");
|
|
77241
|
+
} catch {
|
|
77242
|
+
return {
|
|
77243
|
+
name: fallbackName,
|
|
77244
|
+
frontmatter: "missing"
|
|
77245
|
+
};
|
|
77246
|
+
}
|
|
77247
|
+
if (text.split(/\r?\n/, 1)[0]?.trim() !== "---") {
|
|
77248
|
+
if (requireFrontmatter) return {
|
|
77249
|
+
name: fallbackName,
|
|
77250
|
+
frontmatter: "missing"
|
|
77251
|
+
};
|
|
77252
|
+
return {
|
|
77253
|
+
name: fallbackName,
|
|
77254
|
+
frontmatter: "ok"
|
|
77255
|
+
};
|
|
77256
|
+
}
|
|
77257
|
+
try {
|
|
77258
|
+
return {
|
|
77259
|
+
name: (await parseSkillFromFile({
|
|
77260
|
+
skillMdPath: path,
|
|
77261
|
+
skillDirName: fallbackName,
|
|
77262
|
+
source: "user"
|
|
77263
|
+
})).name,
|
|
77264
|
+
frontmatter: "ok"
|
|
77265
|
+
};
|
|
77266
|
+
} catch (error) {
|
|
77267
|
+
return {
|
|
77268
|
+
name: fallbackName,
|
|
77269
|
+
frontmatter: "broken",
|
|
77270
|
+
reason: error instanceof SkillParseError ? error.message : error instanceof Error ? error.message : String(error)
|
|
77271
|
+
};
|
|
77217
77272
|
}
|
|
77218
77273
|
}
|
|
77219
77274
|
/**
|
|
77220
|
-
* List skill entries under a
|
|
77221
|
-
*
|
|
77222
|
-
*
|
|
77275
|
+
* List skill entries under a skills directory, mirroring the loader's rules:
|
|
77276
|
+
* skip dot-entries, node_modules and README.md; directory skills must contain
|
|
77277
|
+
* SKILL.md; flat skills are non-README `.md` files (frontmatter optional).
|
|
77223
77278
|
*/
|
|
77224
77279
|
async function listSkills(dir) {
|
|
77225
77280
|
let entries;
|
|
@@ -77232,36 +77287,141 @@ async function listSkills(dir) {
|
|
|
77232
77287
|
for (const entry of entries) {
|
|
77233
77288
|
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
77234
77289
|
if (entry.isDirectory()) {
|
|
77235
|
-
if (!await isSkillDir(join$1(dir, entry.name))) continue;
|
|
77236
77290
|
const skillMd = join$1(dir, entry.name, "SKILL.md");
|
|
77237
|
-
|
|
77238
|
-
const
|
|
77291
|
+
if (!await isFile$1(skillMd)) continue;
|
|
77292
|
+
const parsed = await parseSkillEntry(skillMd, entry.name, true);
|
|
77239
77293
|
out.push({
|
|
77240
|
-
|
|
77294
|
+
...parsed,
|
|
77241
77295
|
path: skillMd,
|
|
77242
|
-
kind: "dir"
|
|
77243
|
-
frontmatter: fm
|
|
77296
|
+
kind: "dir"
|
|
77244
77297
|
});
|
|
77245
77298
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
77246
77299
|
if (DOCUMENTATION_MARKDOWN_LOWER.has(entry.name.toLowerCase())) continue;
|
|
77247
77300
|
const path = join$1(dir, entry.name);
|
|
77248
|
-
const
|
|
77301
|
+
const parsed = await parseSkillEntry(path, entry.name.slice(0, -3), false);
|
|
77249
77302
|
out.push({
|
|
77250
|
-
|
|
77303
|
+
...parsed,
|
|
77251
77304
|
path,
|
|
77252
|
-
kind: "flat"
|
|
77253
|
-
frontmatter: "ok"
|
|
77305
|
+
kind: "flat"
|
|
77254
77306
|
});
|
|
77255
77307
|
}
|
|
77256
77308
|
}
|
|
77257
77309
|
return out.toSorted((a, b) => a.name.localeCompare(b.name));
|
|
77258
77310
|
}
|
|
77259
|
-
|
|
77260
|
-
|
|
77311
|
+
/** Count every SKILL.md reachable under `dirPath` (inclusive), depth-limited. */
|
|
77312
|
+
async function countNestedSkillFiles(dirPath, depth) {
|
|
77313
|
+
if (depth > ORPHAN_SCAN_DEPTH) return 0;
|
|
77314
|
+
let entries;
|
|
77315
|
+
try {
|
|
77316
|
+
entries = await readdir(dirPath, { withFileTypes: true });
|
|
77317
|
+
} catch {
|
|
77318
|
+
return 0;
|
|
77319
|
+
}
|
|
77320
|
+
let count = 0;
|
|
77321
|
+
for (const entry of entries) {
|
|
77322
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
77323
|
+
if (entry.isDirectory()) count += await countNestedSkillFiles(join$1(dirPath, entry.name), depth + 1);
|
|
77324
|
+
else if (entry.name === "SKILL.md") count++;
|
|
77325
|
+
}
|
|
77326
|
+
return count;
|
|
77261
77327
|
}
|
|
77262
|
-
|
|
77328
|
+
/**
|
|
77329
|
+
* Describe an unregistered directory under the managed plugins root so the
|
|
77330
|
+
* inventory can surface things the plugin table cannot see: orphan skill
|
|
77331
|
+
* bundles, nested skill trees, and dormant code plugins (entryPoint) that
|
|
77332
|
+
* would run arbitrary code on install+activate.
|
|
77333
|
+
*/
|
|
77334
|
+
async function describeUnregisteredDir(dirPath) {
|
|
77335
|
+
const parts = [];
|
|
77336
|
+
const topLevelSkillMd = await isFile$1(join$1(dirPath, "SKILL.md"));
|
|
77337
|
+
const totalSkills = await countNestedSkillFiles(dirPath, 0);
|
|
77338
|
+
if (topLevelSkillMd) parts.push(totalSkills > 1 ? `skill bundle (${totalSkills} SKILL.md)` : "skill bundle");
|
|
77339
|
+
else if (totalSkills > 0) parts.push(`${totalSkills} nested skills (not registered)`);
|
|
77340
|
+
try {
|
|
77341
|
+
const manifestText = await readFile(join$1(dirPath, "scream.plugin.json"), "utf8");
|
|
77342
|
+
let hasEntryPoint = false;
|
|
77343
|
+
try {
|
|
77344
|
+
const manifest = JSON.parse(manifestText);
|
|
77345
|
+
hasEntryPoint = typeof manifest.entryPoint === "string" && manifest.entryPoint.length > 0;
|
|
77346
|
+
} catch {
|
|
77347
|
+
parts.push("scream.plugin.json (unparseable)");
|
|
77348
|
+
return parts.join("; ");
|
|
77349
|
+
}
|
|
77350
|
+
parts.push(hasEntryPoint ? "code plugin (entryPoint)" : "manifest but not registered");
|
|
77351
|
+
} catch {}
|
|
77352
|
+
if (parts.length === 0) parts.push("no manifest or skill files");
|
|
77353
|
+
return parts.join("; ");
|
|
77354
|
+
}
|
|
77355
|
+
/**
|
|
77356
|
+
* Plugin-managed skills live under `<managedDir>/<plugin>/SKILL.md`. When the
|
|
77357
|
+
* plugin table (via `agent.toolServices.plugins`) is reachable, entries carry
|
|
77358
|
+
* their registration status and unregistered directories are reported in a
|
|
77359
|
+
* dedicated section; without it, the listing falls back to a pure file scan.
|
|
77360
|
+
*/
|
|
77361
|
+
async function listManagedSkills(managedDir, records) {
|
|
77362
|
+
let plugins;
|
|
77363
|
+
try {
|
|
77364
|
+
plugins = await readdir(managedDir, { withFileTypes: true });
|
|
77365
|
+
} catch {
|
|
77366
|
+
return {
|
|
77367
|
+
entries: [],
|
|
77368
|
+
orphanLines: []
|
|
77369
|
+
};
|
|
77370
|
+
}
|
|
77371
|
+
const entries = [];
|
|
77372
|
+
const orphanLines = [];
|
|
77373
|
+
for (const plugin of plugins) {
|
|
77374
|
+
if (!plugin.isDirectory() || plugin.name.startsWith(".")) continue;
|
|
77375
|
+
const dirPath = join$1(managedDir, plugin.name);
|
|
77376
|
+
const skillMd = join$1(dirPath, "SKILL.md");
|
|
77377
|
+
const record = records?.get(plugin.name);
|
|
77378
|
+
if (records === void 0 || record === void 0) {
|
|
77379
|
+
if (records === void 0) {
|
|
77380
|
+
if (!await isFile$1(skillMd)) continue;
|
|
77381
|
+
const parsed = await parseSkillEntry(skillMd, plugin.name, true);
|
|
77382
|
+
entries.push({
|
|
77383
|
+
...parsed,
|
|
77384
|
+
path: skillMd,
|
|
77385
|
+
kind: "dir",
|
|
77386
|
+
plugin: plugin.name
|
|
77387
|
+
});
|
|
77388
|
+
} else if (await isFile$1(skillMd)) orphanLines.push(`- ${plugin.name} — ${await describeUnregisteredDir(dirPath)} — \`${dirPath}\``);
|
|
77389
|
+
else if (await isFile$1(join$1(dirPath, "scream.plugin.json"))) orphanLines.push(`- ${plugin.name} — ${await describeUnregisteredDir(dirPath)} — \`${dirPath}\``);
|
|
77390
|
+
else if (await countNestedSkillFiles(dirPath, 0) > 0) orphanLines.push(`- ${plugin.name} — ${await describeUnregisteredDir(dirPath)} — \`${dirPath}\``);
|
|
77391
|
+
continue;
|
|
77392
|
+
}
|
|
77393
|
+
if (await isFile$1(skillMd)) {
|
|
77394
|
+
const parsed = await parseSkillEntry(skillMd, plugin.name, true);
|
|
77395
|
+
entries.push({
|
|
77396
|
+
...parsed,
|
|
77397
|
+
path: skillMd,
|
|
77398
|
+
kind: "dir",
|
|
77399
|
+
plugin: plugin.name,
|
|
77400
|
+
registered: true
|
|
77401
|
+
});
|
|
77402
|
+
}
|
|
77403
|
+
}
|
|
77404
|
+
return {
|
|
77405
|
+
entries,
|
|
77406
|
+
orphanLines
|
|
77407
|
+
};
|
|
77408
|
+
}
|
|
77409
|
+
/**
|
|
77410
|
+
* Traffic-light status for a skill entry. The registry is the single source of
|
|
77411
|
+
* truth for what is callable; everything else is a disk-side explanation.
|
|
77412
|
+
*/
|
|
77413
|
+
function classify(entry, invocableNames, registeredNames) {
|
|
77414
|
+
const variants = [entry.name];
|
|
77415
|
+
if (entry.plugin !== void 0) variants.push(`${entry.plugin}:${entry.name}`);
|
|
77416
|
+
if (variants.some((v) => invocableNames.has(v))) return "invocable";
|
|
77417
|
+
if (variants.some((v) => registeredNames.has(v))) return "not invocable — registered but not invocable";
|
|
77418
|
+
if (entry.frontmatter === "broken") return `not invocable — broken: ${entry.reason ?? "frontmatter failed to parse"}`;
|
|
77419
|
+
return "not invocable — unregistered";
|
|
77420
|
+
}
|
|
77421
|
+
function formatSkillEntry(entry, status) {
|
|
77263
77422
|
const origin = entry.plugin === void 0 ? "" : ` — plugin: ${entry.plugin}`;
|
|
77264
|
-
|
|
77423
|
+
const registration = entry.registered === true ? " — registered" : "";
|
|
77424
|
+
return `- ${entry.name} — ${entry.kind} — ${entry.frontmatter} — ${status}${origin}${registration} — \`${entry.path}\``;
|
|
77265
77425
|
}
|
|
77266
77426
|
async function inspectConfig(home, userHome) {
|
|
77267
77427
|
const items = [
|
|
@@ -77282,88 +77442,45 @@ async function inspectSkills(home, userHome, cwd, agent) {
|
|
|
77282
77442
|
userHomeDir: userHome,
|
|
77283
77443
|
workDir: cwd
|
|
77284
77444
|
});
|
|
77285
|
-
const
|
|
77445
|
+
const registry = agent.skills?.registry;
|
|
77446
|
+
const invocableNames = new Set((registry?.listInvocableSkills() ?? []).map((s) => s.name));
|
|
77447
|
+
const registeredNames = new Set((registry?.listSkills?.() ?? []).map((s) => s.name));
|
|
77448
|
+
const manager = agent.toolServices?.plugins;
|
|
77449
|
+
const pluginRecords = manager === void 0 ? void 0 : new Map(manager.list().map((r) => [r.id, r]));
|
|
77286
77450
|
const sections = ["## Skills", ""];
|
|
77287
77451
|
let invocableCount = 0;
|
|
77288
77452
|
let totalCount = 0;
|
|
77289
|
-
const
|
|
77290
|
-
const track = (entry, invocable) => {
|
|
77453
|
+
const track = (entry) => {
|
|
77291
77454
|
totalCount++;
|
|
77292
|
-
if (invocable) invocableCount++;
|
|
77293
|
-
return formatSkillEntry(entry,
|
|
77455
|
+
if (classify(entry, invocableNames, registeredNames) === "invocable") invocableCount++;
|
|
77456
|
+
return formatSkillEntry(entry, classify(entry, invocableNames, registeredNames));
|
|
77294
77457
|
};
|
|
77295
77458
|
const userEntries = await listSkills(userDir);
|
|
77296
77459
|
sections.push(`User skills (${userDir}): ${userEntries.length === 0 ? "none" : ""}`);
|
|
77297
|
-
sections.push(...userEntries.map(
|
|
77460
|
+
sections.push(...userEntries.map(track));
|
|
77298
77461
|
const extraDir = join$1(home, "plugins", "managed");
|
|
77299
|
-
const
|
|
77462
|
+
const managed = await listManagedSkills(extraDir, pluginRecords);
|
|
77300
77463
|
sections.push("");
|
|
77301
|
-
sections.push(`Plugin-managed skills (${extraDir}): ${
|
|
77302
|
-
sections.push(...
|
|
77464
|
+
sections.push(`Plugin-managed skills (${extraDir}): ${managed.entries.length === 0 ? "none" : ""}`);
|
|
77465
|
+
sections.push(...managed.entries.map(track));
|
|
77466
|
+
if (pluginRecords !== void 0) for (const record of pluginRecords.values()) {
|
|
77467
|
+
const warns = record.diagnostics.filter((d) => d.severity === "warn");
|
|
77468
|
+
if (warns.length === 0) continue;
|
|
77469
|
+
sections.push(`plugin ${record.id}: warnings: [${warns.map((d) => d.message).join(" | ")}]`);
|
|
77470
|
+
}
|
|
77471
|
+
if (managed.orphanLines.length > 0) {
|
|
77472
|
+
sections.push("");
|
|
77473
|
+
sections.push(`Unregistered plugin dirs (${extraDir}):`);
|
|
77474
|
+
sections.push(...managed.orphanLines);
|
|
77475
|
+
}
|
|
77303
77476
|
const projectEntries = await listSkills(projectDir);
|
|
77304
77477
|
sections.push("");
|
|
77305
77478
|
sections.push(`Project skills (${projectDir}): ${projectEntries.length === 0 ? "none" : ""}`);
|
|
77306
|
-
sections.push(...projectEntries.map((e) => track(e
|
|
77479
|
+
sections.push(...projectEntries.map((e) => track(e)));
|
|
77307
77480
|
sections.push("");
|
|
77308
|
-
sections.push(`Invocable now: ${invocableCount}/${totalCount} (against the live skill registry; entries marked "not invocable"
|
|
77481
|
+
sections.push(`Invocable now: ${invocableCount}/${totalCount} (against the live skill registry; entries marked "not invocable" carry their reason — registered-but-not, broken frontmatter, or unregistered)`);
|
|
77309
77482
|
return sections.join("\n");
|
|
77310
77483
|
}
|
|
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
|
-
/**
|
|
77340
|
-
* List plugin-managed skills: each `<dir>/SKILL.md` under a managed plugin
|
|
77341
|
-
* directory is a skill entry (Extra source, mirroring plugin/manager.ts).
|
|
77342
|
-
*/
|
|
77343
|
-
async function listManagedSkills(managedDir) {
|
|
77344
|
-
let plugins;
|
|
77345
|
-
try {
|
|
77346
|
-
plugins = await readdir(managedDir, { withFileTypes: true });
|
|
77347
|
-
} catch {
|
|
77348
|
-
return [];
|
|
77349
|
-
}
|
|
77350
|
-
const out = [];
|
|
77351
|
-
for (const plugin of plugins) {
|
|
77352
|
-
if (!plugin.isDirectory() || plugin.name.startsWith(".")) continue;
|
|
77353
|
-
const skillMd = join$1(managedDir, plugin.name, "SKILL.md");
|
|
77354
|
-
if (!await isSkillDir(join$1(managedDir, plugin.name))) continue;
|
|
77355
|
-
const fm = await checkFrontmatter(skillMd);
|
|
77356
|
-
const skillName = await readSkillName(skillMd) ?? plugin.name;
|
|
77357
|
-
out.push({
|
|
77358
|
-
name: skillName,
|
|
77359
|
-
path: skillMd,
|
|
77360
|
-
kind: "dir",
|
|
77361
|
-
frontmatter: fm,
|
|
77362
|
-
plugin: plugin.name
|
|
77363
|
-
});
|
|
77364
|
-
}
|
|
77365
|
-
return out.toSorted((a, b) => a.name.localeCompare(b.name));
|
|
77366
|
-
}
|
|
77367
77484
|
/** mcp.json files larger than this are reported as oversize and not parsed. */
|
|
77368
77485
|
const MCP_CONFIG_SIZE_LIMIT = 1024 * 1024;
|
|
77369
77486
|
async function inspectMcp(home, cwd) {
|
package/dist/main.mjs
CHANGED
|
@@ -6,7 +6,7 @@ const __dirname = __cjsShimDirname(__filename);
|
|
|
6
6
|
import "./suppress-sqlite-warning-C2VB0doZ.mjs";
|
|
7
7
|
//#region src/main.ts
|
|
8
8
|
try {
|
|
9
|
-
(await import("./app-
|
|
9
|
+
(await import("./app-CPu6R7nd.mjs")).main();
|
|
10
10
|
} catch (error) {
|
|
11
11
|
process.stderr.write(`${error instanceof Error ? error.stack ?? error.message : String(error)}\n`);
|
|
12
12
|
process.exit(1);
|