openmausbot 0.1.86 → 0.1.88
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 +4 -3
- package/dist/assets/{abnfDiagram-O67JEVCF-DpUZNnFD.js → abnfDiagram-O67JEVCF-qd9VD3GV.js} +1 -1
- package/dist/assets/{arc-DW7mrI4p.js → arc-3RwkUjRe.js} +1 -1
- package/dist/assets/{architectureDiagram-NJMV4G6O-CyeZ5WLZ.js → architectureDiagram-NJMV4G6O-CFKSJS7a.js} +1 -1
- package/dist/assets/{blockDiagram-BEXU5L5S-CBVaNjO7.js → blockDiagram-BEXU5L5S-CG1rEfWD.js} +1 -1
- package/dist/assets/{c4Diagram-YGBWAQC7-sf8d41uT.js → c4Diagram-YGBWAQC7-CBW9N664.js} +1 -1
- package/dist/assets/channel-CnRnO3tF.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-BOGvy7Y_.js → chunk-2Q5K7J3B-2mRfTksB.js} +1 -1
- package/dist/assets/{chunk-3FUC2YCW-BiF9zyiz.js → chunk-3FUC2YCW-Cv1dpErj.js} +2 -2
- package/dist/assets/{chunk-5VM5RSS4-EFuZ1SBZ.js → chunk-5VM5RSS4-B7IACJp-.js} +1 -1
- package/dist/assets/{chunk-GTNCS2PH-pJLNSAT7.js → chunk-GTNCS2PH-An0hZtYa.js} +1 -1
- package/dist/assets/{chunk-GWA4HPMP-Cb-3_3IT.js → chunk-GWA4HPMP-BcJB2nEm.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-DAf5aogn.js → chunk-JWPE2WC7-CV_W7TN7.js} +1 -1
- package/dist/assets/{chunk-NETBCI7D-DR3kiwZz.js → chunk-NETBCI7D-Bcs_kwnA.js} +1 -1
- package/dist/assets/{chunk-WEXAMYUT-BmU8PeyM.js → chunk-WEXAMYUT-lrx_DxFP.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-Buh8g8OT.js → chunk-XXDRQBXY-DabHlLGM.js} +1 -1
- package/dist/assets/{classDiagram-v2-NBCMYWYE-BurfmMWM.js → classDiagram-v2-NBCMYWYE-BnHj6P7d.js} +1 -1
- package/dist/assets/{cose-bilkent-JH36ORCC-KkGZUEkU.js → cose-bilkent-JH36ORCC-CfLp6UDi.js} +1 -1
- package/dist/assets/{cynefin-OW5HDTMX-C3PpV96d.js → cynefin-OW5HDTMX-Dm4dbVA1.js} +1 -1
- package/dist/assets/{cynefinDiagram-VND7K2PF-Dt8JwJaI.js → cynefinDiagram-VND7K2PF-B9mH-PID.js} +1 -1
- package/dist/assets/{dagre-6A5THRUB-DkjN8266.js → dagre-6A5THRUB-DDc_NVQJ.js} +1 -1
- package/dist/assets/{diagram-22UHCM2B-DUAJS0JV.js → diagram-22UHCM2B-lwr8Ip8-.js} +1 -1
- package/dist/assets/{diagram-3UASUU5V-Dw4KhF1a.js → diagram-3UASUU5V-BLLe6dmI.js} +1 -1
- package/dist/assets/{diagram-ATOU4E4O-Dd1gmQqf.js → diagram-ATOU4E4O-6eUIq4lz.js} +1 -1
- package/dist/assets/{diagram-CDSNMT55-CjwGs0Yw.js → diagram-CDSNMT55-O-pRN7VQ.js} +1 -1
- package/dist/assets/{diagram-MLGK6HIB-BL-69Zrq.js → diagram-MLGK6HIB-BietvGAI.js} +1 -1
- package/dist/assets/{diagram-MPIPVDR6-5Zi2N7wo.js → diagram-MPIPVDR6-Bd2Cl62X.js} +1 -1
- package/dist/assets/{ebnfDiagram-ZINNZB2B-D9NekWUX.js → ebnfDiagram-ZINNZB2B-B6iDy69W.js} +1 -1
- package/dist/assets/{elk-276RUBZZ-CkaSQjxw.js → elk-276RUBZZ-DQVHfKe0.js} +1 -1
- package/dist/assets/{erDiagram-OPXOYQCR-CdAVBV8-.js → erDiagram-OPXOYQCR-BWdVp2tQ.js} +1 -1
- package/dist/assets/{flowDiagram-KWPJA3E3-Bm_lWWH7.js → flowDiagram-KWPJA3E3-WLE6rKB4.js} +1 -1
- package/dist/assets/{ganttDiagram-FUAMR5RP-FZ8ejYWJ.js → ganttDiagram-FUAMR5RP-BbjS-4gn.js} +1 -1
- package/dist/assets/{gitGraphDiagram-X574FWY7-Dlv9rizZ.js → gitGraphDiagram-X574FWY7-Dpy2QSGq.js} +1 -1
- package/dist/assets/index-BIMdIbaw.css +1 -0
- package/dist/assets/index-CHnODCr2.js +583 -0
- package/dist/assets/{index-h9FFNwTu.js → index-CIaHn5NS.js} +1 -1
- package/dist/assets/{infoDiagram-VRGFBTTK-BdQ_gW5B.js → infoDiagram-VRGFBTTK-D_jKWlKe.js} +1 -1
- package/dist/assets/{ishikawaDiagram-OU5B5YK6-EwssEp2X.js → ishikawaDiagram-OU5B5YK6-DLJohSuK.js} +1 -1
- package/dist/assets/{journeyDiagram-ZHPQQLJL-BX1G1vOd.js → journeyDiagram-ZHPQQLJL-DPOFj0qN.js} +1 -1
- package/dist/assets/{kanban-definition-PNTS6WVX-BB4kAsyC.js → kanban-definition-PNTS6WVX-BI7v-xpK.js} +1 -1
- package/dist/assets/{linear-3su2TG93.js → linear-C2pWPSxO.js} +1 -1
- package/dist/assets/{mermaid.core-CDD1E0s9.js → mermaid.core-CR-j5WQU.js} +5 -5
- package/dist/assets/{mindmap-definition-NLK3R4M7-Ba50MsNK.js → mindmap-definition-NLK3R4M7-lz9GvXRh.js} +1 -1
- package/dist/assets/{pegDiagram-GJSIUBJH-DZLkkZlM.js → pegDiagram-GJSIUBJH-Dl4_ia61.js} +1 -1
- package/dist/assets/{pieDiagram-5QR66LMP-DaTz77Uq.js → pieDiagram-5QR66LMP-DWuT3n88.js} +1 -1
- package/dist/assets/{quadrantDiagram-O4NWA36T-CpaiidpR.js → quadrantDiagram-O4NWA36T-DXWe9Jx7.js} +1 -1
- package/dist/assets/{railroadDiagram-XR7U4H2S-mN4JrMeI.js → railroadDiagram-XR7U4H2S-COY1IBVY.js} +1 -1
- package/dist/assets/{requirementDiagram-PLB6GJNP-BSNDRpg7.js → requirementDiagram-PLB6GJNP-DnyVuhsQ.js} +1 -1
- package/dist/assets/{sankeyDiagram-IPEJSGJF-FngzGNug.js → sankeyDiagram-IPEJSGJF-BSc4NdGK.js} +1 -1
- package/dist/assets/{sequenceDiagram-PO4LG4MO-CM4pCC3L.js → sequenceDiagram-PO4LG4MO-kqEU2EGq.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-CRICHXkw.js → sizeCapture-INFHLROL-BvB4bkWD.js} +1 -1
- package/dist/assets/{stateDiagram-v2-GCMORJYK-oyTWpxH2.js → stateDiagram-v2-GCMORJYK-BIrqNv7y.js} +1 -1
- package/dist/assets/{swimlanes-2SLR337P-D9h9mPBq.js → swimlanes-2SLR337P-CFnalQUE.js} +1 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-C4a8qNGj.js +8 -0
- package/dist/assets/{timeline-definition-EJHVYXUP-BJUlN9bW.js → timeline-definition-EJHVYXUP-CsSWJnSQ.js} +1 -1
- package/dist/assets/{usecaseDiagram-POWQR4AR-3uBNkbAb.js → usecaseDiagram-POWQR4AR-DkAmJ1EE.js} +1 -1
- package/dist/assets/{vennDiagram-UO4OBE2U-D2odalzT.js → vennDiagram-UO4OBE2U-C3vd9drL.js} +1 -1
- package/dist/assets/{wardleyDiagram-VNRHLVJA-DHgFaB9S.js → wardleyDiagram-VNRHLVJA-j26CM-pi.js} +1 -1
- package/dist/assets/{xychartDiagram-PMCCYNJV-w5GYM2h0.js → xychartDiagram-PMCCYNJV-BYu1Q_at.js} +1 -1
- package/dist/index.html +2 -2
- package/dist-server/browser-proxy.js +2 -1
- package/dist-server/companion/src/routes.js +8 -1
- package/dist-server/connector-proxy.js +105 -1
- package/dist-server/container-mcp.js +1032 -165
- package/dist-server/drivers/agents-proxy.js +338 -29
- package/dist-server/drivers/phone-proxy.js +33 -2
- package/dist-server/index.js +40590 -31811
- package/dist-server/local-computer-proxy.js +5 -3
- package/dist-server/local-computer.js +53 -17
- package/dist-server/mcp-gate.js +2 -0
- package/dist-server/mcp-server.js +5 -3
- package/dist-server/message-search.worker.js +52 -0
- package/dist-server/openmausbot.js +4457 -1399
- package/dist-server/pair-cli.js +4457 -1399
- package/dist-server/permission-proxy.js +29 -9
- package/dist-server/server/admission.js +81 -0
- package/dist-server/server/aside-queue.js +294 -0
- package/dist-server/server/attachments.js +98 -1
- package/dist-server/server/auto-approve.js +4 -3
- package/dist-server/server/auto-vm-claims.js +1 -0
- package/dist-server/server/bot-attachment.js +107 -0
- package/dist-server/server/bot-overview.js +19 -0
- package/dist-server/server/bot-package.js +18 -292
- package/dist-server/server/bot-profile.js +5 -17
- package/dist-server/server/box-create-idempotency.js +3 -3
- package/dist-server/server/box.js +57 -55
- package/dist-server/server/browser-proxy.js +3 -2
- package/dist-server/server/browser-runtime.js +37 -1
- package/dist-server/server/cli-api-setup.js +1 -0
- package/dist-server/server/cli.js +2 -2
- package/dist-server/server/command-allowlist.js +154 -0
- package/dist-server/server/composio.js +297 -1
- package/dist-server/server/computer-wait.js +22 -9
- package/dist-server/server/config.js +84 -12
- package/dist-server/server/connector-advertisement.js +165 -0
- package/dist-server/server/connector-proxy.js +14 -2
- package/dist-server/server/connector-verdict.js +168 -0
- package/dist-server/server/container-computer.js +61 -1
- package/dist-server/server/contracts.js +22 -0
- package/dist-server/server/default-model-selection.js +9 -0
- package/dist-server/server/delegations.js +85 -34
- package/dist-server/server/drivers/acp/core.js +80 -20
- package/dist-server/server/drivers/acp/cursor.js +3 -7
- package/dist-server/server/drivers/acp/droid.js +4 -7
- package/dist-server/server/drivers/acp/gemini.js +4 -4
- package/dist-server/server/drivers/acp/grok.js +4 -9
- package/dist-server/server/drivers/acp/hermes.js +3 -5
- package/dist-server/server/drivers/acp/opencode-go.js +2 -5
- package/dist-server/server/drivers/acp/qwen.js +9 -15
- package/dist-server/server/drivers/agents-call.js +179 -13
- package/dist-server/server/drivers/agents-catalog.js +134 -10
- package/dist-server/server/drivers/boxagent.js +160 -36
- package/dist-server/server/drivers/builtIn.js +2 -0
- package/dist-server/server/drivers/chat-box-tools.js +90 -0
- package/dist-server/server/drivers/chat-images.js +54 -0
- package/dist-server/server/drivers/chat-mcp-tools.js +38 -8
- package/dist-server/server/drivers/chat-tool-approval.js +70 -18
- package/dist-server/server/drivers/claude.js +38 -19
- package/dist-server/server/drivers/codex-catalog.js +14 -6
- package/dist-server/server/drivers/codex-identity.js +2 -1
- package/dist-server/server/drivers/codex-instructions.js +48 -6
- package/dist-server/server/drivers/codex-release.js +48 -0
- package/dist-server/server/drivers/codex-tool-surface.js +20 -0
- package/dist-server/server/drivers/codex.js +102 -55
- package/dist-server/server/drivers/mistral.js +115 -0
- package/dist-server/server/drivers/openai-chat.js +180 -33
- package/dist-server/server/drivers/openai-compat.js +1 -0
- package/dist-server/server/drivers/permission-command.js +35 -0
- package/dist-server/server/drivers/phone-proxy.js +47 -2
- package/dist-server/server/drivers/pi.js +306 -28
- package/dist-server/server/drivers/prompt-split.js +110 -0
- package/dist-server/server/drivers/session-idle.js +29 -0
- package/dist-server/server/env-path.js +15 -0
- package/dist-server/server/fleet.js +1 -1
- package/dist-server/server/group-thread-usage.js +103 -0
- package/dist-server/server/guarded-requests.js +2 -2
- package/dist-server/server/harness/registry.js +1 -0
- package/dist-server/server/harness-capabilities.js +46 -0
- package/dist-server/server/hosted-models.js +1 -1
- package/dist-server/server/index.js +2396 -505
- package/dist-server/server/local-computer.js +72 -23
- package/dist-server/server/managed-desktop.js +1 -1
- package/dist-server/server/mcp-bridge.js +6 -6
- package/dist-server/server/message-db.js +22 -42
- package/dist-server/server/message-file.js +9 -1
- package/dist-server/server/message-search-query.js +43 -0
- package/dist-server/server/message-search-worker.js +80 -0
- package/dist-server/server/message-search.worker.js +21 -0
- package/dist-server/server/model-requests.js +248 -0
- package/dist-server/server/new-bot-defaults.js +115 -0
- package/dist-server/server/options-card.js +21 -0
- package/dist-server/server/org-library.js +1065 -0
- package/dist-server/server/package-export.js +627 -39
- package/dist-server/server/package-import.js +458 -0
- package/dist-server/server/package-parts.js +87 -0
- package/dist-server/server/peer-approval.js +16 -7
- package/dist-server/server/peer-delivery.js +60 -0
- package/dist-server/server/peer-roster.js +67 -0
- package/dist-server/server/presets.js +492 -0
- package/dist-server/server/profile-requests.js +66 -39
- package/dist-server/server/profile-revision.js +9 -1
- package/dist-server/server/profile-versions.js +6 -3
- package/dist-server/server/provider-key-check.js +3 -2
- package/dist-server/server/published-teams.js +80 -0
- package/dist-server/server/room-handoffs.js +19 -3
- package/dist-server/server/routes/bot-presets.js +25 -0
- package/dist-server/server/routine-requests.js +35 -10
- package/dist-server/server/routines.js +41 -1
- package/dist-server/server/section-context.js +10 -0
- package/dist-server/server/skill-fetch.js +72 -14
- package/dist-server/server/skills.js +35 -61
- package/dist-server/server/steer-queue.js +3 -2
- package/dist-server/server/store.js +291 -17
- package/dist-server/server/surface.js +10 -4
- package/dist-server/server/system-prompt.js +38 -6
- package/dist-server/server/team-backup.js +6 -2
- package/dist-server/server/team-computers.js +10 -0
- package/dist-server/server/team-library.js +6 -2
- package/dist-server/server/team-manifest.js +4 -3
- package/dist-server/server/team-setup-requests.js +139 -27
- package/dist-server/server/thread-events.js +20 -2
- package/dist-server/server/tightening-requests.js +405 -0
- package/dist-server/server/tts/chatterbox.js +27 -5
- package/dist-server/server/turn-context.js +2 -0
- package/dist-server/server/turn-watchdog.js +12 -3
- package/dist-server/server/usage-ledger.js +19 -2
- package/dist-server/server/vps-computer.js +139 -51
- package/dist-server/server/vps-container-mcp.js +7 -0
- package/dist-server/server/vps-ssh.js +36 -7
- package/dist-server/server/workspace-backup-policy.js +12 -0
- package/dist-server/server/workspace-backup.js +42 -5
- package/dist-server/server/workspace-backup.worker.js +10 -0
- package/dist-server/server/workspace.js +1 -1
- package/dist-server/shared/approval-mode.js +1 -1
- package/dist-server/shared/ask-question.js +207 -12
- package/dist-server/shared/bot-profile.js +15 -0
- package/dist-server/shared/command-allowlist.js +1 -0
- package/dist-server/shared/credential-request.js +6 -2
- package/dist-server/shared/line-diff.js +23 -0
- package/dist-server/shared/model-request.js +1 -0
- package/dist-server/shared/options-card.js +51 -0
- package/dist-server/shared/package-format.js +967 -0
- package/dist-server/shared/profile-request.js +5 -4
- package/dist-server/shared/skill-md.js +64 -0
- package/dist-server/shared/team-backup.js +7 -0
- package/dist-server/shared/tightening-request.js +87 -0
- package/dist-server/shared/wire.js +4 -0
- package/dist-server/src/lib/composer-attachments.js +25 -3
- package/dist-server/vps-container-mcp.js +1132 -171
- package/dist-server/workspace-backup.worker.js +19474 -0
- package/enterprise/README.md +1 -1
- package/package.json +1 -1
- package/dist/assets/channel-BXFkgus9.js +0 -1
- package/dist/assets/index-BGgqlWPJ.css +0 -1
- package/dist/assets/index-BtHo8ZKl.js +0 -577
- package/dist/assets/swimlanesDiagram-TC7HE7FX-BWrvB2ak.js +0 -8
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Fetch a skill's files from where users actually keep skills: a GitHub
|
|
2
|
-
// repo, a folder inside one,
|
|
2
|
+
// repo, a folder inside one, a direct SKILL.md, or a skills.sh page that
|
|
3
|
+
// points at one. Network in, plain
|
|
3
4
|
// {path, content} list out — validation, scanning, and storage live in
|
|
4
5
|
// skills.ts, so this file owns exactly one concern and its tests can hand
|
|
5
6
|
// it a fake fetch.
|
|
@@ -53,8 +54,9 @@ function boundedImportFetch(fetcher) {
|
|
|
53
54
|
return new Response(Buffer.concat(chunks), { status: response.status, headers: response.headers });
|
|
54
55
|
};
|
|
55
56
|
}
|
|
56
|
-
/** owner/repo, github.com/owner/repo[/tree/<ref>/<path>],
|
|
57
|
-
* straight to a SKILL.md
|
|
57
|
+
/** owner/repo, github.com/owner/repo[/tree/<ref>/<path>], a raw/blob URL
|
|
58
|
+
* straight to a SKILL.md, or a skills.sh/owner/repo[/skill] page. Anything
|
|
59
|
+
* else is refused, loudly. */
|
|
58
60
|
export function parseSkillSource(input) {
|
|
59
61
|
const text = input.trim();
|
|
60
62
|
if (!text)
|
|
@@ -69,10 +71,21 @@ export function parseSkillSource(input) {
|
|
|
69
71
|
if (tree) {
|
|
70
72
|
return { owner: tree[1], repo: tree[2], ref: tree[3], path: tree[4] ?? "" };
|
|
71
73
|
}
|
|
74
|
+
const registry = text.match(/^https?:\/\/skills\.sh\/([\w.-]+)\/([\w.-]+)(?:\/([\w.-]+))?\/?$/i);
|
|
75
|
+
if (registry) {
|
|
76
|
+
if ([registry[1], registry[2]].some((part) => part === "." || part === "..") ||
|
|
77
|
+
(registry[3] && !/^[a-z0-9]+(?:-[a-z0-9]+)*$/i.test(registry[3]))) {
|
|
78
|
+
return { error: "that does not look like a skills.sh repository or skill URL" };
|
|
79
|
+
}
|
|
80
|
+
// skills.sh is a registry over GitHub: each page installs from
|
|
81
|
+
// github.com/<owner>/<repo> filtered to the named skill, so resolve to
|
|
82
|
+
// the repo and remember the slug to filter discovery on.
|
|
83
|
+
return { owner: registry[1], repo: registry[2], path: "", skill: registry[3] };
|
|
84
|
+
}
|
|
72
85
|
const shorthand = text.match(/^([\w.-]+)\/([\w.-]+)$/);
|
|
73
86
|
if (shorthand)
|
|
74
87
|
return { owner: shorthand[1], repo: shorthand[2], path: "" };
|
|
75
|
-
return { error: "that does not look like a GitHub repository, folder, or SKILL.md URL" };
|
|
88
|
+
return { error: "that does not look like a GitHub or skills.sh repository, folder, or SKILL.md URL" };
|
|
76
89
|
}
|
|
77
90
|
const CONTENT_ENTRY = z.object({
|
|
78
91
|
type: z.string(),
|
|
@@ -114,21 +127,55 @@ async function listDir(target, path, fetcher) {
|
|
|
114
127
|
export const MAX_SKILLS_PER_IMPORT = 30;
|
|
115
128
|
const MAX_FOLDERS_WALKED = 24;
|
|
116
129
|
const MAX_CHILDREN_PER_FOLDER = 60;
|
|
130
|
+
/** Convert a skill name to a URL-safe slug, mirroring the skills.sh
|
|
131
|
+
* registry's own toSkillSlug: page slugs derive from each SKILL.md's name
|
|
132
|
+
* field, which can differ from the folder it lives in. */
|
|
133
|
+
const toSkillSlug = (name) => name.toLowerCase().replace(/[\s_]+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
134
|
+
/** The name field from a skill folder's SKILL.md frontmatter, or undefined
|
|
135
|
+
* when it cannot be read. Budget and timeout errors still propagate. */
|
|
136
|
+
async function skillNameFrom(entries, fetcher) {
|
|
137
|
+
const skillMd = entries.find((entry) => entry.type === "file" && entry.name === "SKILL.md" && entry.download_url);
|
|
138
|
+
if (!skillMd)
|
|
139
|
+
return undefined;
|
|
140
|
+
try {
|
|
141
|
+
const text = await fetchText(skillMd.download_url, fetcher);
|
|
142
|
+
return text.match(/^---\r?\n([\s\S]*?)\r?\n---/)?.[1]?.match(/^name:\s*(.+)$/m)?.[1]?.trim();
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
if (error instanceof ImportLimitError || (error instanceof Error && error.name === "TimeoutError"))
|
|
146
|
+
throw error;
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
117
150
|
/** Where SKILL.md folders live in real repos, per the registry's own
|
|
118
151
|
* discovery order: the pasted path itself, then skills/, then .claude/skills/
|
|
119
|
-
* and .agents/skills/, then one level of direct children.
|
|
152
|
+
* and .agents/skills/, then one level of direct children. A requested
|
|
153
|
+
* skills.sh slug matches a folder whose name slugifies to it, else a skill
|
|
154
|
+
* whose SKILL.md name does, like the registry's own installer. */
|
|
120
155
|
export async function discoverSkillDirs(target, fetcher) {
|
|
121
156
|
const root = await listDir(target, target.path, fetcher);
|
|
122
|
-
|
|
157
|
+
const wanted = target.skill ? toSkillSlug(target.skill) || undefined : undefined;
|
|
158
|
+
const dirMatches = (dir) => !!wanted && toSkillSlug(dir.split("/").at(-1)) === wanted;
|
|
159
|
+
const isWanted = async (dir, entries) => {
|
|
160
|
+
if (!wanted)
|
|
161
|
+
return true;
|
|
162
|
+
if (dirMatches(dir))
|
|
163
|
+
return true;
|
|
164
|
+
const name = await skillNameFrom(entries, fetcher);
|
|
165
|
+
return !!name && toSkillSlug(name) === wanted;
|
|
166
|
+
};
|
|
167
|
+
if (root.some((entry) => entry.type === "file" && entry.name === "SKILL.md") && (await isWanted(target.path, root))) {
|
|
123
168
|
return [target.path];
|
|
124
169
|
}
|
|
125
170
|
const dirs = root.filter((entry) => entry.type === "dir");
|
|
126
171
|
const found = [];
|
|
127
172
|
const preferred = ["skills", ".claude", ".agents"];
|
|
128
|
-
const
|
|
173
|
+
const rank = (name) => (wanted && toSkillSlug(name) === wanted ? 0 : preferred.includes(name) ? 1 : 2);
|
|
174
|
+
const ordered = [...dirs].sort((a, b) => rank(a.name) - rank(b.name));
|
|
175
|
+
const enough = () => found.length >= (wanted ? 1 : MAX_SKILLS_PER_IMPORT);
|
|
129
176
|
// The shared fetch budget caps the whole walk, not each nested loop.
|
|
130
177
|
for (const dir of ordered.slice(0, MAX_FOLDERS_WALKED)) {
|
|
131
|
-
if (
|
|
178
|
+
if (enough())
|
|
132
179
|
break;
|
|
133
180
|
const base = dir.name === ".claude" || dir.name === ".agents" ? `${dir.path}/skills` : dir.path;
|
|
134
181
|
let children;
|
|
@@ -141,15 +188,21 @@ export async function discoverSkillDirs(target, fetcher) {
|
|
|
141
188
|
continue;
|
|
142
189
|
}
|
|
143
190
|
if (children.some((entry) => entry.type === "file" && entry.name === "SKILL.md")) {
|
|
144
|
-
|
|
191
|
+
if (await isWanted(base, children))
|
|
192
|
+
found.push(base);
|
|
145
193
|
continue;
|
|
146
194
|
}
|
|
147
|
-
|
|
148
|
-
|
|
195
|
+
const childDirs = children.filter((entry) => entry.type === "dir");
|
|
196
|
+
const exact = wanted ? childDirs.find((child) => dirMatches(child.path)) : undefined;
|
|
197
|
+
const candidates = exact
|
|
198
|
+
? [exact, ...childDirs.filter((child) => child !== exact).slice(0, MAX_CHILDREN_PER_FOLDER - 1)]
|
|
199
|
+
: childDirs.slice(0, MAX_CHILDREN_PER_FOLDER);
|
|
200
|
+
for (const child of candidates) {
|
|
201
|
+
if (enough())
|
|
149
202
|
break;
|
|
150
203
|
try {
|
|
151
204
|
const inner = await listDir(target, child.path, fetcher);
|
|
152
|
-
if (inner.some((entry) => entry.type === "file" && entry.name === "SKILL.md"))
|
|
205
|
+
if (inner.some((entry) => entry.type === "file" && entry.name === "SKILL.md") && (await isWanted(child.path, inner)))
|
|
153
206
|
found.push(child.path);
|
|
154
207
|
}
|
|
155
208
|
catch (error) {
|
|
@@ -191,8 +244,13 @@ export async function fetchSkillFromSource(input, fetcher = fetch) {
|
|
|
191
244
|
return { skills: [{ source: parsed.rawUrl, files: [{ path: "SKILL.md", content }] }] };
|
|
192
245
|
}
|
|
193
246
|
const dirs = await discoverSkillDirs(parsed, fetcher);
|
|
194
|
-
if (!dirs.length)
|
|
195
|
-
return {
|
|
247
|
+
if (!dirs.length) {
|
|
248
|
+
return {
|
|
249
|
+
error: parsed.skill
|
|
250
|
+
? `no skill named "${parsed.skill}" found there — check the exact name on the skills.sh page`
|
|
251
|
+
: "no SKILL.md found there — paste a skill folder or a repo with a skills/ directory",
|
|
252
|
+
};
|
|
253
|
+
}
|
|
196
254
|
const skills = [];
|
|
197
255
|
for (const dir of dirs)
|
|
198
256
|
skills.push(await fetchSkillDir(parsed, dir, fetcher));
|
|
@@ -34,14 +34,11 @@ import { DATA_DIR } from "./config.js";
|
|
|
34
34
|
import { redactSecretsInText } from "./redact.js";
|
|
35
35
|
import { LEARN_SOURCE_PREFIX } from "./skill-learn.js";
|
|
36
36
|
import { workspaceDir } from "./workspace.js";
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
export
|
|
42
|
-
export const DESCRIPTION_MAX = 1024;
|
|
43
|
-
/** One SKILL.md may be at most this large; the spec recommends <5k tokens. */
|
|
44
|
-
export const SKILL_FILE_MAX_BYTES = 256 * 1024;
|
|
37
|
+
import { isSkillName, parseSkillMd, scanSkillText, SKILL_FILE_MAX_BYTES } from "../shared/skill-md.js";
|
|
38
|
+
// SKILL.md parsing and the static scan live in shared/ so the package
|
|
39
|
+
// format (Admin and the renderer included) validates skills with the exact
|
|
40
|
+
// same rules; re-exported here under their historical path.
|
|
41
|
+
export { DESCRIPTION_MAX, isSkillName, parseSkillMd, scanSkillText, SKILL_FILE_MAX_BYTES, SKILL_NAME_MAX, } from "../shared/skill-md.js";
|
|
45
42
|
/** Index budget: name+description lines only, ~100 tokens per skill. */
|
|
46
43
|
export const INDEX_MAX_SKILLS = 30;
|
|
47
44
|
export const INDEX_MAX_BYTES = 4_000;
|
|
@@ -51,58 +48,6 @@ export const STAGED_GIST_MAX = 240;
|
|
|
51
48
|
/** Learned skills are duplicated onto their durable review card. Keep that
|
|
52
49
|
* exact review payload bounded while leaving fetched skill imports unchanged. */
|
|
53
50
|
export const STAGED_SKILL_FILE_MAX_BYTES = 32 * 1024;
|
|
54
|
-
export function isSkillName(name) {
|
|
55
|
-
return name.length >= 1 && name.length <= SKILL_NAME_MAX && SKILL_NAME.test(name);
|
|
56
|
-
}
|
|
57
|
-
/** Minimal frontmatter reader for the two required keys plus the two we
|
|
58
|
-
* display. Deliberately not a YAML engine: values are single-line strings in
|
|
59
|
-
* every skill the spec's own examples show, and a parser that cannot
|
|
60
|
-
* evaluate anchors or tags cannot be surprised by them. */
|
|
61
|
-
export function parseSkillMd(raw) {
|
|
62
|
-
const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
|
|
63
|
-
if (!match)
|
|
64
|
-
return { error: "SKILL.md has no YAML frontmatter (--- block) at the top" };
|
|
65
|
-
const fields = {};
|
|
66
|
-
for (const line of match[1].split(/\r?\n/)) {
|
|
67
|
-
const kv = line.match(/^([A-Za-z][\w-]*):\s*(.*)$/);
|
|
68
|
-
if (!kv)
|
|
69
|
-
continue;
|
|
70
|
-
fields[kv[1].toLowerCase()] = kv[2].replace(/^["']|["']$/g, "").trim();
|
|
71
|
-
}
|
|
72
|
-
const name = fields.name ?? "";
|
|
73
|
-
const description = fields.description ?? "";
|
|
74
|
-
if (!isSkillName(name)) {
|
|
75
|
-
return { error: `frontmatter name ${JSON.stringify(name)} is not a valid skill name (lowercase, hyphens, max ${SKILL_NAME_MAX})` };
|
|
76
|
-
}
|
|
77
|
-
if (!description || description.length > DESCRIPTION_MAX) {
|
|
78
|
-
return { error: `frontmatter description is required and must be at most ${DESCRIPTION_MAX} characters` };
|
|
79
|
-
}
|
|
80
|
-
return {
|
|
81
|
-
name,
|
|
82
|
-
description,
|
|
83
|
-
license: fields.license || undefined,
|
|
84
|
-
compatibility: fields.compatibility || undefined,
|
|
85
|
-
body: match[2] ?? "",
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
/** Static red flags before a human review. Presence is a warning shown in
|
|
89
|
-
* the review screen, never a silent rejection — the reviewer decides. These
|
|
90
|
-
* are the three patterns the public registry audits actually caught. */
|
|
91
|
-
export function scanSkillText(raw) {
|
|
92
|
-
const warnings = [];
|
|
93
|
-
if (/[A-Za-z0-9+/]{120,}={0,2}/.test(raw)) {
|
|
94
|
-
warnings.push("contains a long base64-looking blob — a common wrapper for hidden instructions or payloads");
|
|
95
|
-
}
|
|
96
|
-
if (/\b(curl|wget)\b[^\n]{0,200}\|\s*(ba|z|da)?sh\b/.test(raw)) {
|
|
97
|
-
warnings.push("pipes a download straight into a shell (curl|sh) — never enable without understanding why");
|
|
98
|
-
}
|
|
99
|
-
// zero-width and bidi-control characters hide text from the reviewer while
|
|
100
|
-
// the model still reads it — the invisible-instruction trick
|
|
101
|
-
if (/[\u200B-\u200F\u202A-\u202E\u2060-\u2064\uFEFF]/.test(raw)) {
|
|
102
|
-
warnings.push("contains invisible Unicode characters (zero-width or bidi controls) — text you cannot see");
|
|
103
|
-
}
|
|
104
|
-
return warnings;
|
|
105
|
-
}
|
|
106
51
|
const skillManifestEntrySchema = z.object({
|
|
107
52
|
description: z.string(),
|
|
108
53
|
enabled: z.boolean(),
|
|
@@ -115,6 +60,14 @@ const skillManifestEntrySchema = z.object({
|
|
|
115
60
|
skippedFiles: z.array(z.string()),
|
|
116
61
|
appliedStageId: z.string().optional(),
|
|
117
62
|
storageRevision: z.string().regex(/^[a-f0-9]{64}$/).optional(),
|
|
63
|
+
package: z.object({
|
|
64
|
+
installId: z.string().regex(/^[a-f0-9]{32}$/),
|
|
65
|
+
key: z.string().min(1).max(64),
|
|
66
|
+
release: z.string().min(1).max(40),
|
|
67
|
+
r: z.string().regex(/^[a-f0-9]{64}$/),
|
|
68
|
+
w: z.string().regex(/^[a-f0-9]{64}$/),
|
|
69
|
+
via: z.literal("preset").optional(),
|
|
70
|
+
}).optional(),
|
|
118
71
|
});
|
|
119
72
|
const skillManifestSchema = z.record(z.string(), skillManifestEntrySchema);
|
|
120
73
|
const managedLinksSchema = z.array(z.string());
|
|
@@ -498,7 +451,7 @@ function skillContentMatches(botId, name, entry) {
|
|
|
498
451
|
}
|
|
499
452
|
}
|
|
500
453
|
function skillListing(botId, name, entry) {
|
|
501
|
-
const { appliedStageId, storageRevision: _storageRevision, ...visible } = entry;
|
|
454
|
+
const { appliedStageId, storageRevision: _storageRevision, package: _package, ...visible } = entry;
|
|
502
455
|
const intact = skillContentMatches(botId, name, entry);
|
|
503
456
|
return {
|
|
504
457
|
name,
|
|
@@ -563,6 +516,26 @@ export function installSkill(botId, source, files) {
|
|
|
563
516
|
return prepared;
|
|
564
517
|
return installPreparedSkill(botId, source, prepared, { enabled: false });
|
|
565
518
|
}
|
|
519
|
+
/** The organization library's path: a skill the organization's Admin
|
|
520
|
+
* published arrives switched ON (the Admin is the review), with the install
|
|
521
|
+
* stamp that lets the library find it again. `source` is chosen by the
|
|
522
|
+
* caller (`org:<ref>@<release>`), never read from the package. */
|
|
523
|
+
export function installOrgSkill(botId, source, skillMd, stamp) {
|
|
524
|
+
if (!source.startsWith("org:"))
|
|
525
|
+
return { error: "organization skills need an org: source" };
|
|
526
|
+
const prepared = preparedSkillFiles([{ path: "SKILL.md", content: skillMd }]);
|
|
527
|
+
if ("error" in prepared)
|
|
528
|
+
return prepared;
|
|
529
|
+
if (prepared.parsed.name !== stamp.key)
|
|
530
|
+
return { error: `the skill is named "${prepared.parsed.name}", not "${stamp.key}"` };
|
|
531
|
+
return installPreparedSkill(botId, source, prepared, { enabled: true, package: { ...stamp } });
|
|
532
|
+
}
|
|
533
|
+
/** A bot's skills that an organization install added, with their stamps. */
|
|
534
|
+
export function skillPackageStamps(botId) {
|
|
535
|
+
return Object.entries(readManifest(botId)).flatMap(([name, entry]) => entry.package
|
|
536
|
+
? [{ name, enabled: entry.enabled, stamp: { ...entry.package } }]
|
|
537
|
+
: []);
|
|
538
|
+
}
|
|
566
539
|
export function setSkillEnabled(botId, name, enabled) {
|
|
567
540
|
if (!isSkillName(name))
|
|
568
541
|
return { error: "invalid skill name" };
|
|
@@ -936,6 +909,7 @@ function installPreparedSkill(botId, source, prepared, options) {
|
|
|
936
909
|
warnings: prepared.warnings,
|
|
937
910
|
skippedFiles: prepared.skippedFiles,
|
|
938
911
|
appliedStageId: options.appliedStageId,
|
|
912
|
+
...(options.package ? { package: options.package } : {}),
|
|
939
913
|
};
|
|
940
914
|
const root = ensureSkillsRoot(botId);
|
|
941
915
|
if (!root)
|
|
@@ -81,14 +81,15 @@ export function queueSteeredMessage(botId, threadId, text, options = {}) {
|
|
|
81
81
|
changed();
|
|
82
82
|
return { id };
|
|
83
83
|
}
|
|
84
|
-
/** Where a thread stands among this bot's threads waiting for
|
|
84
|
+
/** Where a thread stands among this bot's threads waiting for the bot to
|
|
85
|
+
* become available (a full slot list or an active room turn):
|
|
85
86
|
* 1 for the next to start. The drain visits queues in insertion order, so
|
|
86
87
|
* insertion order is the line. Null when nothing of this bot's is waiting
|
|
87
88
|
* on that thread. */
|
|
88
89
|
export function queuedThreadPosition(botId, threadId) {
|
|
89
90
|
let position = 0;
|
|
90
91
|
for (const [candidate, entry] of queues) {
|
|
91
|
-
if (entry.botId !== botId || !entry.items.some((item) => item.reason === "capacity"))
|
|
92
|
+
if (entry.botId !== botId || !entry.items.some((item) => item.reason === "capacity" || item.reason === "group-turn"))
|
|
92
93
|
continue;
|
|
93
94
|
position += 1;
|
|
94
95
|
if (candidate === threadId)
|