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.
Files changed (216) hide show
  1. package/README.md +4 -3
  2. package/dist/assets/{abnfDiagram-O67JEVCF-DpUZNnFD.js → abnfDiagram-O67JEVCF-qd9VD3GV.js} +1 -1
  3. package/dist/assets/{arc-DW7mrI4p.js → arc-3RwkUjRe.js} +1 -1
  4. package/dist/assets/{architectureDiagram-NJMV4G6O-CyeZ5WLZ.js → architectureDiagram-NJMV4G6O-CFKSJS7a.js} +1 -1
  5. package/dist/assets/{blockDiagram-BEXU5L5S-CBVaNjO7.js → blockDiagram-BEXU5L5S-CG1rEfWD.js} +1 -1
  6. package/dist/assets/{c4Diagram-YGBWAQC7-sf8d41uT.js → c4Diagram-YGBWAQC7-CBW9N664.js} +1 -1
  7. package/dist/assets/channel-CnRnO3tF.js +1 -0
  8. package/dist/assets/{chunk-2Q5K7J3B-BOGvy7Y_.js → chunk-2Q5K7J3B-2mRfTksB.js} +1 -1
  9. package/dist/assets/{chunk-3FUC2YCW-BiF9zyiz.js → chunk-3FUC2YCW-Cv1dpErj.js} +2 -2
  10. package/dist/assets/{chunk-5VM5RSS4-EFuZ1SBZ.js → chunk-5VM5RSS4-B7IACJp-.js} +1 -1
  11. package/dist/assets/{chunk-GTNCS2PH-pJLNSAT7.js → chunk-GTNCS2PH-An0hZtYa.js} +1 -1
  12. package/dist/assets/{chunk-GWA4HPMP-Cb-3_3IT.js → chunk-GWA4HPMP-BcJB2nEm.js} +1 -1
  13. package/dist/assets/{chunk-JWPE2WC7-DAf5aogn.js → chunk-JWPE2WC7-CV_W7TN7.js} +1 -1
  14. package/dist/assets/{chunk-NETBCI7D-DR3kiwZz.js → chunk-NETBCI7D-Bcs_kwnA.js} +1 -1
  15. package/dist/assets/{chunk-WEXAMYUT-BmU8PeyM.js → chunk-WEXAMYUT-lrx_DxFP.js} +1 -1
  16. package/dist/assets/{chunk-XXDRQBXY-Buh8g8OT.js → chunk-XXDRQBXY-DabHlLGM.js} +1 -1
  17. package/dist/assets/{classDiagram-v2-NBCMYWYE-BurfmMWM.js → classDiagram-v2-NBCMYWYE-BnHj6P7d.js} +1 -1
  18. package/dist/assets/{cose-bilkent-JH36ORCC-KkGZUEkU.js → cose-bilkent-JH36ORCC-CfLp6UDi.js} +1 -1
  19. package/dist/assets/{cynefin-OW5HDTMX-C3PpV96d.js → cynefin-OW5HDTMX-Dm4dbVA1.js} +1 -1
  20. package/dist/assets/{cynefinDiagram-VND7K2PF-Dt8JwJaI.js → cynefinDiagram-VND7K2PF-B9mH-PID.js} +1 -1
  21. package/dist/assets/{dagre-6A5THRUB-DkjN8266.js → dagre-6A5THRUB-DDc_NVQJ.js} +1 -1
  22. package/dist/assets/{diagram-22UHCM2B-DUAJS0JV.js → diagram-22UHCM2B-lwr8Ip8-.js} +1 -1
  23. package/dist/assets/{diagram-3UASUU5V-Dw4KhF1a.js → diagram-3UASUU5V-BLLe6dmI.js} +1 -1
  24. package/dist/assets/{diagram-ATOU4E4O-Dd1gmQqf.js → diagram-ATOU4E4O-6eUIq4lz.js} +1 -1
  25. package/dist/assets/{diagram-CDSNMT55-CjwGs0Yw.js → diagram-CDSNMT55-O-pRN7VQ.js} +1 -1
  26. package/dist/assets/{diagram-MLGK6HIB-BL-69Zrq.js → diagram-MLGK6HIB-BietvGAI.js} +1 -1
  27. package/dist/assets/{diagram-MPIPVDR6-5Zi2N7wo.js → diagram-MPIPVDR6-Bd2Cl62X.js} +1 -1
  28. package/dist/assets/{ebnfDiagram-ZINNZB2B-D9NekWUX.js → ebnfDiagram-ZINNZB2B-B6iDy69W.js} +1 -1
  29. package/dist/assets/{elk-276RUBZZ-CkaSQjxw.js → elk-276RUBZZ-DQVHfKe0.js} +1 -1
  30. package/dist/assets/{erDiagram-OPXOYQCR-CdAVBV8-.js → erDiagram-OPXOYQCR-BWdVp2tQ.js} +1 -1
  31. package/dist/assets/{flowDiagram-KWPJA3E3-Bm_lWWH7.js → flowDiagram-KWPJA3E3-WLE6rKB4.js} +1 -1
  32. package/dist/assets/{ganttDiagram-FUAMR5RP-FZ8ejYWJ.js → ganttDiagram-FUAMR5RP-BbjS-4gn.js} +1 -1
  33. package/dist/assets/{gitGraphDiagram-X574FWY7-Dlv9rizZ.js → gitGraphDiagram-X574FWY7-Dpy2QSGq.js} +1 -1
  34. package/dist/assets/index-BIMdIbaw.css +1 -0
  35. package/dist/assets/index-CHnODCr2.js +583 -0
  36. package/dist/assets/{index-h9FFNwTu.js → index-CIaHn5NS.js} +1 -1
  37. package/dist/assets/{infoDiagram-VRGFBTTK-BdQ_gW5B.js → infoDiagram-VRGFBTTK-D_jKWlKe.js} +1 -1
  38. package/dist/assets/{ishikawaDiagram-OU5B5YK6-EwssEp2X.js → ishikawaDiagram-OU5B5YK6-DLJohSuK.js} +1 -1
  39. package/dist/assets/{journeyDiagram-ZHPQQLJL-BX1G1vOd.js → journeyDiagram-ZHPQQLJL-DPOFj0qN.js} +1 -1
  40. package/dist/assets/{kanban-definition-PNTS6WVX-BB4kAsyC.js → kanban-definition-PNTS6WVX-BI7v-xpK.js} +1 -1
  41. package/dist/assets/{linear-3su2TG93.js → linear-C2pWPSxO.js} +1 -1
  42. package/dist/assets/{mermaid.core-CDD1E0s9.js → mermaid.core-CR-j5WQU.js} +5 -5
  43. package/dist/assets/{mindmap-definition-NLK3R4M7-Ba50MsNK.js → mindmap-definition-NLK3R4M7-lz9GvXRh.js} +1 -1
  44. package/dist/assets/{pegDiagram-GJSIUBJH-DZLkkZlM.js → pegDiagram-GJSIUBJH-Dl4_ia61.js} +1 -1
  45. package/dist/assets/{pieDiagram-5QR66LMP-DaTz77Uq.js → pieDiagram-5QR66LMP-DWuT3n88.js} +1 -1
  46. package/dist/assets/{quadrantDiagram-O4NWA36T-CpaiidpR.js → quadrantDiagram-O4NWA36T-DXWe9Jx7.js} +1 -1
  47. package/dist/assets/{railroadDiagram-XR7U4H2S-mN4JrMeI.js → railroadDiagram-XR7U4H2S-COY1IBVY.js} +1 -1
  48. package/dist/assets/{requirementDiagram-PLB6GJNP-BSNDRpg7.js → requirementDiagram-PLB6GJNP-DnyVuhsQ.js} +1 -1
  49. package/dist/assets/{sankeyDiagram-IPEJSGJF-FngzGNug.js → sankeyDiagram-IPEJSGJF-BSc4NdGK.js} +1 -1
  50. package/dist/assets/{sequenceDiagram-PO4LG4MO-CM4pCC3L.js → sequenceDiagram-PO4LG4MO-kqEU2EGq.js} +1 -1
  51. package/dist/assets/{sizeCapture-INFHLROL-CRICHXkw.js → sizeCapture-INFHLROL-BvB4bkWD.js} +1 -1
  52. package/dist/assets/{stateDiagram-v2-GCMORJYK-oyTWpxH2.js → stateDiagram-v2-GCMORJYK-BIrqNv7y.js} +1 -1
  53. package/dist/assets/{swimlanes-2SLR337P-D9h9mPBq.js → swimlanes-2SLR337P-CFnalQUE.js} +1 -1
  54. package/dist/assets/swimlanesDiagram-TC7HE7FX-C4a8qNGj.js +8 -0
  55. package/dist/assets/{timeline-definition-EJHVYXUP-BJUlN9bW.js → timeline-definition-EJHVYXUP-CsSWJnSQ.js} +1 -1
  56. package/dist/assets/{usecaseDiagram-POWQR4AR-3uBNkbAb.js → usecaseDiagram-POWQR4AR-DkAmJ1EE.js} +1 -1
  57. package/dist/assets/{vennDiagram-UO4OBE2U-D2odalzT.js → vennDiagram-UO4OBE2U-C3vd9drL.js} +1 -1
  58. package/dist/assets/{wardleyDiagram-VNRHLVJA-DHgFaB9S.js → wardleyDiagram-VNRHLVJA-j26CM-pi.js} +1 -1
  59. package/dist/assets/{xychartDiagram-PMCCYNJV-w5GYM2h0.js → xychartDiagram-PMCCYNJV-BYu1Q_at.js} +1 -1
  60. package/dist/index.html +2 -2
  61. package/dist-server/browser-proxy.js +2 -1
  62. package/dist-server/companion/src/routes.js +8 -1
  63. package/dist-server/connector-proxy.js +105 -1
  64. package/dist-server/container-mcp.js +1032 -165
  65. package/dist-server/drivers/agents-proxy.js +338 -29
  66. package/dist-server/drivers/phone-proxy.js +33 -2
  67. package/dist-server/index.js +40590 -31811
  68. package/dist-server/local-computer-proxy.js +5 -3
  69. package/dist-server/local-computer.js +53 -17
  70. package/dist-server/mcp-gate.js +2 -0
  71. package/dist-server/mcp-server.js +5 -3
  72. package/dist-server/message-search.worker.js +52 -0
  73. package/dist-server/openmausbot.js +4457 -1399
  74. package/dist-server/pair-cli.js +4457 -1399
  75. package/dist-server/permission-proxy.js +29 -9
  76. package/dist-server/server/admission.js +81 -0
  77. package/dist-server/server/aside-queue.js +294 -0
  78. package/dist-server/server/attachments.js +98 -1
  79. package/dist-server/server/auto-approve.js +4 -3
  80. package/dist-server/server/auto-vm-claims.js +1 -0
  81. package/dist-server/server/bot-attachment.js +107 -0
  82. package/dist-server/server/bot-overview.js +19 -0
  83. package/dist-server/server/bot-package.js +18 -292
  84. package/dist-server/server/bot-profile.js +5 -17
  85. package/dist-server/server/box-create-idempotency.js +3 -3
  86. package/dist-server/server/box.js +57 -55
  87. package/dist-server/server/browser-proxy.js +3 -2
  88. package/dist-server/server/browser-runtime.js +37 -1
  89. package/dist-server/server/cli-api-setup.js +1 -0
  90. package/dist-server/server/cli.js +2 -2
  91. package/dist-server/server/command-allowlist.js +154 -0
  92. package/dist-server/server/composio.js +297 -1
  93. package/dist-server/server/computer-wait.js +22 -9
  94. package/dist-server/server/config.js +84 -12
  95. package/dist-server/server/connector-advertisement.js +165 -0
  96. package/dist-server/server/connector-proxy.js +14 -2
  97. package/dist-server/server/connector-verdict.js +168 -0
  98. package/dist-server/server/container-computer.js +61 -1
  99. package/dist-server/server/contracts.js +22 -0
  100. package/dist-server/server/default-model-selection.js +9 -0
  101. package/dist-server/server/delegations.js +85 -34
  102. package/dist-server/server/drivers/acp/core.js +80 -20
  103. package/dist-server/server/drivers/acp/cursor.js +3 -7
  104. package/dist-server/server/drivers/acp/droid.js +4 -7
  105. package/dist-server/server/drivers/acp/gemini.js +4 -4
  106. package/dist-server/server/drivers/acp/grok.js +4 -9
  107. package/dist-server/server/drivers/acp/hermes.js +3 -5
  108. package/dist-server/server/drivers/acp/opencode-go.js +2 -5
  109. package/dist-server/server/drivers/acp/qwen.js +9 -15
  110. package/dist-server/server/drivers/agents-call.js +179 -13
  111. package/dist-server/server/drivers/agents-catalog.js +134 -10
  112. package/dist-server/server/drivers/boxagent.js +160 -36
  113. package/dist-server/server/drivers/builtIn.js +2 -0
  114. package/dist-server/server/drivers/chat-box-tools.js +90 -0
  115. package/dist-server/server/drivers/chat-images.js +54 -0
  116. package/dist-server/server/drivers/chat-mcp-tools.js +38 -8
  117. package/dist-server/server/drivers/chat-tool-approval.js +70 -18
  118. package/dist-server/server/drivers/claude.js +38 -19
  119. package/dist-server/server/drivers/codex-catalog.js +14 -6
  120. package/dist-server/server/drivers/codex-identity.js +2 -1
  121. package/dist-server/server/drivers/codex-instructions.js +48 -6
  122. package/dist-server/server/drivers/codex-release.js +48 -0
  123. package/dist-server/server/drivers/codex-tool-surface.js +20 -0
  124. package/dist-server/server/drivers/codex.js +102 -55
  125. package/dist-server/server/drivers/mistral.js +115 -0
  126. package/dist-server/server/drivers/openai-chat.js +180 -33
  127. package/dist-server/server/drivers/openai-compat.js +1 -0
  128. package/dist-server/server/drivers/permission-command.js +35 -0
  129. package/dist-server/server/drivers/phone-proxy.js +47 -2
  130. package/dist-server/server/drivers/pi.js +306 -28
  131. package/dist-server/server/drivers/prompt-split.js +110 -0
  132. package/dist-server/server/drivers/session-idle.js +29 -0
  133. package/dist-server/server/env-path.js +15 -0
  134. package/dist-server/server/fleet.js +1 -1
  135. package/dist-server/server/group-thread-usage.js +103 -0
  136. package/dist-server/server/guarded-requests.js +2 -2
  137. package/dist-server/server/harness/registry.js +1 -0
  138. package/dist-server/server/harness-capabilities.js +46 -0
  139. package/dist-server/server/hosted-models.js +1 -1
  140. package/dist-server/server/index.js +2396 -505
  141. package/dist-server/server/local-computer.js +72 -23
  142. package/dist-server/server/managed-desktop.js +1 -1
  143. package/dist-server/server/mcp-bridge.js +6 -6
  144. package/dist-server/server/message-db.js +22 -42
  145. package/dist-server/server/message-file.js +9 -1
  146. package/dist-server/server/message-search-query.js +43 -0
  147. package/dist-server/server/message-search-worker.js +80 -0
  148. package/dist-server/server/message-search.worker.js +21 -0
  149. package/dist-server/server/model-requests.js +248 -0
  150. package/dist-server/server/new-bot-defaults.js +115 -0
  151. package/dist-server/server/options-card.js +21 -0
  152. package/dist-server/server/org-library.js +1065 -0
  153. package/dist-server/server/package-export.js +627 -39
  154. package/dist-server/server/package-import.js +458 -0
  155. package/dist-server/server/package-parts.js +87 -0
  156. package/dist-server/server/peer-approval.js +16 -7
  157. package/dist-server/server/peer-delivery.js +60 -0
  158. package/dist-server/server/peer-roster.js +67 -0
  159. package/dist-server/server/presets.js +492 -0
  160. package/dist-server/server/profile-requests.js +66 -39
  161. package/dist-server/server/profile-revision.js +9 -1
  162. package/dist-server/server/profile-versions.js +6 -3
  163. package/dist-server/server/provider-key-check.js +3 -2
  164. package/dist-server/server/published-teams.js +80 -0
  165. package/dist-server/server/room-handoffs.js +19 -3
  166. package/dist-server/server/routes/bot-presets.js +25 -0
  167. package/dist-server/server/routine-requests.js +35 -10
  168. package/dist-server/server/routines.js +41 -1
  169. package/dist-server/server/section-context.js +10 -0
  170. package/dist-server/server/skill-fetch.js +72 -14
  171. package/dist-server/server/skills.js +35 -61
  172. package/dist-server/server/steer-queue.js +3 -2
  173. package/dist-server/server/store.js +291 -17
  174. package/dist-server/server/surface.js +10 -4
  175. package/dist-server/server/system-prompt.js +38 -6
  176. package/dist-server/server/team-backup.js +6 -2
  177. package/dist-server/server/team-computers.js +10 -0
  178. package/dist-server/server/team-library.js +6 -2
  179. package/dist-server/server/team-manifest.js +4 -3
  180. package/dist-server/server/team-setup-requests.js +139 -27
  181. package/dist-server/server/thread-events.js +20 -2
  182. package/dist-server/server/tightening-requests.js +405 -0
  183. package/dist-server/server/tts/chatterbox.js +27 -5
  184. package/dist-server/server/turn-context.js +2 -0
  185. package/dist-server/server/turn-watchdog.js +12 -3
  186. package/dist-server/server/usage-ledger.js +19 -2
  187. package/dist-server/server/vps-computer.js +139 -51
  188. package/dist-server/server/vps-container-mcp.js +7 -0
  189. package/dist-server/server/vps-ssh.js +36 -7
  190. package/dist-server/server/workspace-backup-policy.js +12 -0
  191. package/dist-server/server/workspace-backup.js +42 -5
  192. package/dist-server/server/workspace-backup.worker.js +10 -0
  193. package/dist-server/server/workspace.js +1 -1
  194. package/dist-server/shared/approval-mode.js +1 -1
  195. package/dist-server/shared/ask-question.js +207 -12
  196. package/dist-server/shared/bot-profile.js +15 -0
  197. package/dist-server/shared/command-allowlist.js +1 -0
  198. package/dist-server/shared/credential-request.js +6 -2
  199. package/dist-server/shared/line-diff.js +23 -0
  200. package/dist-server/shared/model-request.js +1 -0
  201. package/dist-server/shared/options-card.js +51 -0
  202. package/dist-server/shared/package-format.js +967 -0
  203. package/dist-server/shared/profile-request.js +5 -4
  204. package/dist-server/shared/skill-md.js +64 -0
  205. package/dist-server/shared/team-backup.js +7 -0
  206. package/dist-server/shared/tightening-request.js +87 -0
  207. package/dist-server/shared/wire.js +4 -0
  208. package/dist-server/src/lib/composer-attachments.js +25 -3
  209. package/dist-server/vps-container-mcp.js +1132 -171
  210. package/dist-server/workspace-backup.worker.js +19474 -0
  211. package/enterprise/README.md +1 -1
  212. package/package.json +1 -1
  213. package/dist/assets/channel-BXFkgus9.js +0 -1
  214. package/dist/assets/index-BGgqlWPJ.css +0 -1
  215. package/dist/assets/index-BtHo8ZKl.js +0 -577
  216. 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, or a direct SKILL.md. Network in, plain
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>], or a raw/blob URL
57
- * straight to a SKILL.md. Anything else is refused, loudly. */
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
- if (root.some((entry) => entry.type === "file" && entry.name === "SKILL.md")) {
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 ordered = [...dirs].sort((a, b) => (preferred.includes(a.name) ? 0 : 1) - (preferred.includes(b.name) ? 0 : 1));
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 (found.length >= MAX_SKILLS_PER_IMPORT)
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
- found.push(base);
191
+ if (await isWanted(base, children))
192
+ found.push(base);
145
193
  continue;
146
194
  }
147
- for (const child of children.filter((entry) => entry.type === "dir").slice(0, MAX_CHILDREN_PER_FOLDER)) {
148
- if (found.length >= MAX_SKILLS_PER_IMPORT)
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 { error: "no SKILL.md found there — paste a skill folder or a repo with a skills/ directory" };
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
- /** Spec rule: lowercase alphanumerics with single hyphens, 1-64 chars,
38
- * folder name must equal it. The regex IS the traversal gate — no dots, no
39
- * slashes, no way to name a skill "..". */
40
- const SKILL_NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
41
- export const SKILL_NAME_MAX = 64;
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 a free slot:
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)