openmausbot 0.1.86 → 0.1.87

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 (166) hide show
  1. package/README.md +2 -2
  2. package/dist/assets/{abnfDiagram-O67JEVCF-DpUZNnFD.js → abnfDiagram-O67JEVCF-BTiuL79F.js} +1 -1
  3. package/dist/assets/{arc-DW7mrI4p.js → arc-C88Blc2v.js} +1 -1
  4. package/dist/assets/{architectureDiagram-NJMV4G6O-CyeZ5WLZ.js → architectureDiagram-NJMV4G6O-BZsJIZrb.js} +1 -1
  5. package/dist/assets/{blockDiagram-BEXU5L5S-CBVaNjO7.js → blockDiagram-BEXU5L5S-CVeMe9Ah.js} +1 -1
  6. package/dist/assets/{c4Diagram-YGBWAQC7-sf8d41uT.js → c4Diagram-YGBWAQC7-Cedk3Bhx.js} +1 -1
  7. package/dist/assets/channel-taCNRlUU.js +1 -0
  8. package/dist/assets/{chunk-2Q5K7J3B-BOGvy7Y_.js → chunk-2Q5K7J3B-D-VRqzTB.js} +1 -1
  9. package/dist/assets/{chunk-3FUC2YCW-BiF9zyiz.js → chunk-3FUC2YCW-BChov01Y.js} +2 -2
  10. package/dist/assets/{chunk-5VM5RSS4-EFuZ1SBZ.js → chunk-5VM5RSS4-C-TfwvHy.js} +1 -1
  11. package/dist/assets/{chunk-GTNCS2PH-pJLNSAT7.js → chunk-GTNCS2PH-Db7CGj5Y.js} +1 -1
  12. package/dist/assets/{chunk-GWA4HPMP-Cb-3_3IT.js → chunk-GWA4HPMP-CNvIfqKT.js} +1 -1
  13. package/dist/assets/{chunk-JWPE2WC7-DAf5aogn.js → chunk-JWPE2WC7-C_bEp5tq.js} +1 -1
  14. package/dist/assets/{chunk-NETBCI7D-DR3kiwZz.js → chunk-NETBCI7D-Coi4BOls.js} +1 -1
  15. package/dist/assets/{chunk-WEXAMYUT-BmU8PeyM.js → chunk-WEXAMYUT-CzylEuL3.js} +1 -1
  16. package/dist/assets/{chunk-XXDRQBXY-Buh8g8OT.js → chunk-XXDRQBXY-m7p7ztaD.js} +1 -1
  17. package/dist/assets/{classDiagram-v2-NBCMYWYE-BurfmMWM.js → classDiagram-v2-NBCMYWYE-BMy6o9x2.js} +1 -1
  18. package/dist/assets/{cose-bilkent-JH36ORCC-KkGZUEkU.js → cose-bilkent-JH36ORCC-BDQ__0_J.js} +1 -1
  19. package/dist/assets/{cynefin-OW5HDTMX-C3PpV96d.js → cynefin-OW5HDTMX-Djv4ghUb.js} +1 -1
  20. package/dist/assets/{cynefinDiagram-VND7K2PF-Dt8JwJaI.js → cynefinDiagram-VND7K2PF-BULrlGYH.js} +1 -1
  21. package/dist/assets/{dagre-6A5THRUB-DkjN8266.js → dagre-6A5THRUB-D-jrT_hU.js} +1 -1
  22. package/dist/assets/{diagram-22UHCM2B-DUAJS0JV.js → diagram-22UHCM2B-p2UhQmfL.js} +1 -1
  23. package/dist/assets/{diagram-3UASUU5V-Dw4KhF1a.js → diagram-3UASUU5V-DHvGWZ7q.js} +1 -1
  24. package/dist/assets/{diagram-ATOU4E4O-Dd1gmQqf.js → diagram-ATOU4E4O-ChhKDot-.js} +1 -1
  25. package/dist/assets/{diagram-CDSNMT55-CjwGs0Yw.js → diagram-CDSNMT55-B-T7A43R.js} +1 -1
  26. package/dist/assets/{diagram-MLGK6HIB-BL-69Zrq.js → diagram-MLGK6HIB-CJm8ENNY.js} +1 -1
  27. package/dist/assets/{diagram-MPIPVDR6-5Zi2N7wo.js → diagram-MPIPVDR6-510tlza8.js} +1 -1
  28. package/dist/assets/{ebnfDiagram-ZINNZB2B-D9NekWUX.js → ebnfDiagram-ZINNZB2B-PkvRPHel.js} +1 -1
  29. package/dist/assets/{elk-276RUBZZ-CkaSQjxw.js → elk-276RUBZZ-BVBVPoX3.js} +1 -1
  30. package/dist/assets/{erDiagram-OPXOYQCR-CdAVBV8-.js → erDiagram-OPXOYQCR-BgYMVm7L.js} +1 -1
  31. package/dist/assets/{flowDiagram-KWPJA3E3-Bm_lWWH7.js → flowDiagram-KWPJA3E3-BGqsnc_3.js} +1 -1
  32. package/dist/assets/{ganttDiagram-FUAMR5RP-FZ8ejYWJ.js → ganttDiagram-FUAMR5RP-D_-UeReH.js} +1 -1
  33. package/dist/assets/{gitGraphDiagram-X574FWY7-Dlv9rizZ.js → gitGraphDiagram-X574FWY7-D0uooONx.js} +1 -1
  34. package/dist/assets/index-BwdMAQ6p.css +1 -0
  35. package/dist/assets/index-ByJennr7.js +581 -0
  36. package/dist/assets/{index-h9FFNwTu.js → index-zRsJERGJ.js} +1 -1
  37. package/dist/assets/{infoDiagram-VRGFBTTK-BdQ_gW5B.js → infoDiagram-VRGFBTTK-B9e1z77f.js} +1 -1
  38. package/dist/assets/{ishikawaDiagram-OU5B5YK6-EwssEp2X.js → ishikawaDiagram-OU5B5YK6-BB4vNwvD.js} +1 -1
  39. package/dist/assets/{journeyDiagram-ZHPQQLJL-BX1G1vOd.js → journeyDiagram-ZHPQQLJL-DpkosOTB.js} +1 -1
  40. package/dist/assets/{kanban-definition-PNTS6WVX-BB4kAsyC.js → kanban-definition-PNTS6WVX-DbDP4kUu.js} +1 -1
  41. package/dist/assets/{linear-3su2TG93.js → linear-DBJGPmjT.js} +1 -1
  42. package/dist/assets/{mermaid.core-CDD1E0s9.js → mermaid.core-DWPRi-ue.js} +5 -5
  43. package/dist/assets/{mindmap-definition-NLK3R4M7-Ba50MsNK.js → mindmap-definition-NLK3R4M7-nszBbJmx.js} +1 -1
  44. package/dist/assets/{pegDiagram-GJSIUBJH-DZLkkZlM.js → pegDiagram-GJSIUBJH-CWykJWu_.js} +1 -1
  45. package/dist/assets/{pieDiagram-5QR66LMP-DaTz77Uq.js → pieDiagram-5QR66LMP-g3Da-Vnv.js} +1 -1
  46. package/dist/assets/{quadrantDiagram-O4NWA36T-CpaiidpR.js → quadrantDiagram-O4NWA36T-Wzrlpzgh.js} +1 -1
  47. package/dist/assets/{railroadDiagram-XR7U4H2S-mN4JrMeI.js → railroadDiagram-XR7U4H2S-moHTFMC0.js} +1 -1
  48. package/dist/assets/{requirementDiagram-PLB6GJNP-BSNDRpg7.js → requirementDiagram-PLB6GJNP-CwfXdFH8.js} +1 -1
  49. package/dist/assets/{sankeyDiagram-IPEJSGJF-FngzGNug.js → sankeyDiagram-IPEJSGJF-3ZROYvnq.js} +1 -1
  50. package/dist/assets/{sequenceDiagram-PO4LG4MO-CM4pCC3L.js → sequenceDiagram-PO4LG4MO-E1TBebP8.js} +1 -1
  51. package/dist/assets/{sizeCapture-INFHLROL-CRICHXkw.js → sizeCapture-INFHLROL-DHKEU5pu.js} +1 -1
  52. package/dist/assets/{stateDiagram-v2-GCMORJYK-oyTWpxH2.js → stateDiagram-v2-GCMORJYK-BguEj09D.js} +1 -1
  53. package/dist/assets/{swimlanes-2SLR337P-D9h9mPBq.js → swimlanes-2SLR337P-C7IDYnhd.js} +1 -1
  54. package/dist/assets/swimlanesDiagram-TC7HE7FX-BpEdoB2e.js +8 -0
  55. package/dist/assets/{timeline-definition-EJHVYXUP-BJUlN9bW.js → timeline-definition-EJHVYXUP-DCW-F5PW.js} +1 -1
  56. package/dist/assets/{usecaseDiagram-POWQR4AR-3uBNkbAb.js → usecaseDiagram-POWQR4AR-xBMFtWYV.js} +1 -1
  57. package/dist/assets/{vennDiagram-UO4OBE2U-D2odalzT.js → vennDiagram-UO4OBE2U-B5nIkuH8.js} +1 -1
  58. package/dist/assets/{wardleyDiagram-VNRHLVJA-DHgFaB9S.js → wardleyDiagram-VNRHLVJA-DYFAhdbr.js} +1 -1
  59. package/dist/assets/{xychartDiagram-PMCCYNJV-w5GYM2h0.js → xychartDiagram-PMCCYNJV-DeXwX6Cz.js} +1 -1
  60. package/dist/index.html +2 -2
  61. package/dist-server/browser-proxy.js +2 -1
  62. package/dist-server/container-mcp.js +1029 -164
  63. package/dist-server/drivers/agents-proxy.js +64 -10
  64. package/dist-server/drivers/phone-proxy.js +33 -2
  65. package/dist-server/index.js +14577 -9001
  66. package/dist-server/local-computer-proxy.js +3 -3
  67. package/dist-server/local-computer.js +53 -17
  68. package/dist-server/openmausbot.js +3981 -1165
  69. package/dist-server/pair-cli.js +3981 -1165
  70. package/dist-server/permission-proxy.js +29 -9
  71. package/dist-server/server/attachments.js +55 -0
  72. package/dist-server/server/auto-approve.js +4 -3
  73. package/dist-server/server/auto-vm-claims.js +1 -0
  74. package/dist-server/server/bot-package.js +18 -292
  75. package/dist-server/server/bot-profile.js +5 -17
  76. package/dist-server/server/box-create-idempotency.js +3 -3
  77. package/dist-server/server/box.js +57 -55
  78. package/dist-server/server/browser-proxy.js +3 -2
  79. package/dist-server/server/browser-runtime.js +37 -1
  80. package/dist-server/server/cli.js +2 -2
  81. package/dist-server/server/command-allowlist.js +154 -0
  82. package/dist-server/server/computer-wait.js +22 -9
  83. package/dist-server/server/config.js +59 -9
  84. package/dist-server/server/connector-verdict.js +141 -0
  85. package/dist-server/server/container-computer.js +1 -1
  86. package/dist-server/server/default-model-selection.js +9 -0
  87. package/dist-server/server/delegations.js +85 -34
  88. package/dist-server/server/drivers/acp/core.js +57 -15
  89. package/dist-server/server/drivers/agents-call.js +42 -3
  90. package/dist-server/server/drivers/agents-catalog.js +34 -4
  91. package/dist-server/server/drivers/boxagent.js +174 -36
  92. package/dist-server/server/drivers/builtIn.js +2 -0
  93. package/dist-server/server/drivers/chat-box-tools.js +90 -0
  94. package/dist-server/server/drivers/chat-images.js +54 -0
  95. package/dist-server/server/drivers/chat-mcp-tools.js +38 -8
  96. package/dist-server/server/drivers/chat-tool-approval.js +70 -18
  97. package/dist-server/server/drivers/claude.js +7 -2
  98. package/dist-server/server/drivers/codex-instructions.js +48 -6
  99. package/dist-server/server/drivers/codex-tool-surface.js +20 -0
  100. package/dist-server/server/drivers/codex.js +95 -30
  101. package/dist-server/server/drivers/mistral.js +115 -0
  102. package/dist-server/server/drivers/openai-chat.js +155 -32
  103. package/dist-server/server/drivers/openai-compat.js +1 -0
  104. package/dist-server/server/drivers/permission-command.js +35 -0
  105. package/dist-server/server/drivers/phone-proxy.js +47 -2
  106. package/dist-server/server/drivers/pi.js +75 -4
  107. package/dist-server/server/drivers/prompt-split.js +87 -0
  108. package/dist-server/server/fleet.js +1 -1
  109. package/dist-server/server/group-thread-usage.js +103 -0
  110. package/dist-server/server/harness/registry.js +1 -0
  111. package/dist-server/server/hosted-models.js +1 -1
  112. package/dist-server/server/index.js +1385 -322
  113. package/dist-server/server/local-computer.js +72 -23
  114. package/dist-server/server/managed-desktop.js +1 -1
  115. package/dist-server/server/mcp-bridge.js +6 -6
  116. package/dist-server/server/new-bot-defaults.js +115 -0
  117. package/dist-server/server/org-library.js +1051 -0
  118. package/dist-server/server/package-export.js +627 -39
  119. package/dist-server/server/package-import.js +458 -0
  120. package/dist-server/server/package-parts.js +87 -0
  121. package/dist-server/server/peer-approval.js +16 -7
  122. package/dist-server/server/peer-roster.js +10 -0
  123. package/dist-server/server/presets.js +492 -0
  124. package/dist-server/server/provider-key-check.js +3 -2
  125. package/dist-server/server/published-teams.js +80 -0
  126. package/dist-server/server/room-handoffs.js +19 -3
  127. package/dist-server/server/routes/bot-presets.js +25 -0
  128. package/dist-server/server/routine-requests.js +6 -3
  129. package/dist-server/server/routines.js +41 -1
  130. package/dist-server/server/section-context.js +10 -0
  131. package/dist-server/server/skills.js +34 -61
  132. package/dist-server/server/steer-queue.js +3 -2
  133. package/dist-server/server/store.js +259 -15
  134. package/dist-server/server/surface.js +10 -4
  135. package/dist-server/server/system-prompt.js +12 -4
  136. package/dist-server/server/team-backup.js +6 -2
  137. package/dist-server/server/team-computers.js +10 -0
  138. package/dist-server/server/team-library.js +6 -2
  139. package/dist-server/server/team-manifest.js +4 -3
  140. package/dist-server/server/team-setup-requests.js +16 -5
  141. package/dist-server/server/thread-events.js +17 -1
  142. package/dist-server/server/turn-context.js +2 -0
  143. package/dist-server/server/turn-watchdog.js +12 -3
  144. package/dist-server/server/usage-ledger.js +12 -1
  145. package/dist-server/server/vps-computer.js +139 -51
  146. package/dist-server/server/vps-container-mcp.js +7 -0
  147. package/dist-server/server/vps-ssh.js +36 -7
  148. package/dist-server/server/workspace-backup-policy.js +12 -0
  149. package/dist-server/server/workspace-backup.js +2 -2
  150. package/dist-server/server/workspace.js +1 -1
  151. package/dist-server/shared/approval-mode.js +1 -1
  152. package/dist-server/shared/ask-question.js +188 -12
  153. package/dist-server/shared/bot-profile.js +15 -0
  154. package/dist-server/shared/command-allowlist.js +1 -0
  155. package/dist-server/shared/credential-request.js +1 -1
  156. package/dist-server/shared/package-format.js +967 -0
  157. package/dist-server/shared/skill-md.js +64 -0
  158. package/dist-server/shared/team-backup.js +7 -0
  159. package/dist-server/shared/wire.js +4 -0
  160. package/dist-server/vps-container-mcp.js +1129 -170
  161. package/enterprise/README.md +1 -1
  162. package/package.json +1 -1
  163. package/dist/assets/channel-BXFkgus9.js +0 -1
  164. package/dist/assets/index-BGgqlWPJ.css +0 -1
  165. package/dist/assets/index-BtHo8ZKl.js +0 -577
  166. package/dist/assets/swimlanesDiagram-TC7HE7FX-BWrvB2ak.js +0 -8
@@ -0,0 +1,25 @@
1
+ // Preset bots for New bot (server/presets.ts has the store and the rules).
2
+ // GET lists what New bot offers: the organization's presets first, then
3
+ // imported files', never one from a withdrawn release. DELETE removes an
4
+ // imported preset (the person's own click; no confirm step). Organization
5
+ // presets are Admin's to remove. Both are admin-scoped by default in
6
+ // server/request-auth.ts, like the New bot defaults: a preset adds skills
7
+ // and starter notes to the bot made from it.
8
+ import { listBotPresets, ORG_PRESET_REMOVE_MESSAGE, PRESET_UNAVAILABLE_MESSAGE } from "../presets.js";
9
+ import { PASS } from "./table.js";
10
+ export function createBotPresetRoutes(deps) {
11
+ return async ({ res, path, method, json }) => {
12
+ if (path === "/api/bot-presets" && method === "GET") {
13
+ return json(res, 200, { presets: listBotPresets(deps.presets, deps.orgStatuses()) });
14
+ }
15
+ const m = path.match(/^\/api\/bot-presets\/([\w-]+)$/);
16
+ if (!m || method !== "DELETE")
17
+ return PASS;
18
+ const removed = deps.presets.removeFilePreset(m[1]);
19
+ if (removed === "not_found")
20
+ return json(res, 404, { error: PRESET_UNAVAILABLE_MESSAGE });
21
+ if (removed === "organization")
22
+ return json(res, 409, { error: ORG_PRESET_REMOVE_MESSAGE });
23
+ return json(res, 200, { ok: true });
24
+ };
25
+ }
@@ -923,7 +923,7 @@ export class RoutineRequestService {
923
923
  if (refusal)
924
924
  throw new RoutineRequestError(refusal, 403);
925
925
  }
926
- await this.requireCloudReadiness(operation);
926
+ await this.requireCloudReadiness(operation, botId);
927
927
  // The readiness probe is asynchronous. Another request can edit or
928
928
  // delete the routine while it is in flight, so re-check the captured
929
929
  // revision before rendering and persisting the confirmation snapshot.
@@ -1006,7 +1006,7 @@ export class RoutineRequestService {
1006
1006
  }
1007
1007
  throw new RoutineRequestError(result.state === "invalid" ? result.error : "The routine change could not be applied", result.state === "invalid" ? result.status : 409);
1008
1008
  }
1009
- async requireCloudReadiness(operation) {
1009
+ async requireCloudReadiness(operation, proposerBotId) {
1010
1010
  if (!this.cloudReady || operation.action === "pause" || operation.action === "delete")
1011
1011
  return;
1012
1012
  const definition = effectiveDefinition(operation, this.routines);
@@ -1014,7 +1014,10 @@ export class RoutineRequestService {
1014
1014
  return;
1015
1015
  let readiness;
1016
1016
  try {
1017
- readiness = await this.cloudReady();
1017
+ const targetBotId = operation.action === "create"
1018
+ ? operation.forBot?.botId ?? proposerBotId
1019
+ : this.routines.listRoutines().find(routine => routine.id === operation.routineId)?.botId ?? proposerBotId;
1020
+ readiness = await this.cloudReady(targetBotId);
1018
1021
  }
1019
1022
  catch (error) {
1020
1023
  const detail = error instanceof Error ? error.message : String(error);
@@ -7,7 +7,24 @@ import { writeFileAtomic } from "./atomic.js";
7
7
  import { redactSecretsInText } from "./redact.js";
8
8
  import { normalizeCronSchedule, nextCronRuns } from "../shared/routine-schedule.js";
9
9
  import { isRoutineProblemRun } from "../shared/routines.js";
10
+ import { ROUTINE_PARTS } from "./package-parts.js";
10
11
  const persistedSourceThreadId = z.string().trim().min(1).optional().catch(undefined);
12
+ const HASH = /^[a-f0-9]{64}$/;
13
+ function loadInstalledPackage(value) {
14
+ if (!value || typeof value !== "object")
15
+ return undefined;
16
+ const stamp = value;
17
+ if (typeof stamp.installId !== "string" || !/^[a-f0-9]{32}$/.test(stamp.installId) || typeof stamp.key !== "string" || !stamp.key || stamp.key.length > 80)
18
+ return undefined;
19
+ const parts = stamp.parts;
20
+ if (!parts || typeof parts !== "object" || !ROUTINE_PARTS.every((part) => HASH.test(String(parts[part]?.r)) && HASH.test(String(parts[part]?.w))))
21
+ return undefined;
22
+ return {
23
+ installId: stamp.installId,
24
+ key: stamp.key,
25
+ parts: Object.fromEntries(ROUTINE_PARTS.map((part) => [part, { r: parts[part].r, w: parts[part].w }])),
26
+ };
27
+ }
11
28
  const webhookRunReceiptSchema = z.object({
12
29
  webhookId: z.string().min(1).max(200),
13
30
  deliveryId: z.string().min(1).max(200),
@@ -490,9 +507,12 @@ export class RoutineManager {
490
507
  overlap: routine.overlap === "queue" ? "queue" : undefined,
491
508
  skippedRuns: Number.isSafeInteger(routine.skippedRuns) && routine.skippedRuns > 0 ? routine.skippedRuns : undefined,
492
509
  lastSkippedAt: Number.isSafeInteger(routine.lastSkippedAt) && routine.lastSkippedAt >= 0 && routine.lastSkippedAt <= MAX_DATE_MS ? routine.lastSkippedAt : undefined,
510
+ installedPackage: loadInstalledPackage(routine.installedPackage),
493
511
  };
494
512
  if (loaded.timeoutMinutes === undefined)
495
513
  delete loaded.timeoutMinutes;
514
+ if (loaded.installedPackage === undefined)
515
+ delete loaded.installedPackage;
496
516
  delete loaded.failureStreak;
497
517
  return [loaded];
498
518
  })
@@ -582,7 +602,27 @@ export class RoutineManager {
582
602
  .sort((a, b) => (b.finishedAt ?? b.createdAt) - (a.finishedAt ?? a.createdAt) || b.createdAt - a.createdAt);
583
603
  const success = outcomes.findIndex(run => run.status === "completed");
584
604
  const failures = success < 0 ? outcomes.length : success;
585
- return { ...cloneRoutine(routine), ...(failures ? { failureStreak: failures } : {}) };
605
+ const { installedPackage: _installedPackage, ...visible } = cloneRoutine(routine);
606
+ return { ...visible, ...(failures ? { failureStreak: failures } : {}) };
607
+ }
608
+ /** Routines added from the organization's library, with their stamps. */
609
+ packageStamps() {
610
+ return this.routines.flatMap((routine) => routine.installedPackage
611
+ ? [{ routineId: routine.id, botId: routine.botId, enabled: routine.enabled, stamp: structuredClone(routine.installedPackage) }]
612
+ : []);
613
+ }
614
+ /** Record which organization install a just-created routine belongs to. */
615
+ stampInstalledPackage(id, stamp) {
616
+ const clean = loadInstalledPackage(stamp);
617
+ if (!clean)
618
+ throw new Error("Invalid routine package stamp");
619
+ if (!this.routines.some((routine) => routine.id === id))
620
+ return false;
621
+ this.commitMutation(() => {
622
+ const routine = this.routines.find((candidate) => candidate.id === id);
623
+ routine.installedPackage = clean;
624
+ });
625
+ return true;
586
626
  }
587
627
  /** Whether this computer should stay awake for routines: a run is in
588
628
  * flight, or an enabled routine is due within the horizon. The schedule
@@ -9,6 +9,7 @@ import { join } from "node:path";
9
9
  import { z } from "zod";
10
10
  import { writeFileAtomic } from "./atomic.js";
11
11
  import { DATA_DIR } from "./config.js";
12
+ import { renamePublishedTeam } from "./published-teams.js";
12
13
  export const SECTION_CONTEXT_MAX_BYTES = 24_000;
13
14
  export const SECTION_CONTEXTS_FILE = join(DATA_DIR, "section-contexts.json");
14
15
  const sectionContextRecordSchema = z.object({ text: z.string(), updatedAt: z.number().finite() });
@@ -94,6 +95,15 @@ export function changeEmptySection(name, nextName) {
94
95
  if (nextName !== null && context)
95
96
  data.contexts[nextName] = context;
96
97
  saveFile(data);
98
+ // Every team rename and removal funnels through here, so the team's
99
+ // published package id and keys follow it (published-teams.ts). The team
100
+ // itself is already renamed; a failure here costs only key stability.
101
+ try {
102
+ renamePublishedTeam(name, nextName);
103
+ }
104
+ catch (error) {
105
+ console.warn(`[teams] Could not move the shared-package record for this team: ${error.message}`);
106
+ }
97
107
  }
98
108
  export function readSectionContext(section) {
99
109
  const record = loadFile().contexts[sectionContextKey(section)];
@@ -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,13 @@ 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
+ }).optional(),
118
70
  });
119
71
  const skillManifestSchema = z.record(z.string(), skillManifestEntrySchema);
120
72
  const managedLinksSchema = z.array(z.string());
@@ -498,7 +450,7 @@ function skillContentMatches(botId, name, entry) {
498
450
  }
499
451
  }
500
452
  function skillListing(botId, name, entry) {
501
- const { appliedStageId, storageRevision: _storageRevision, ...visible } = entry;
453
+ const { appliedStageId, storageRevision: _storageRevision, package: _package, ...visible } = entry;
502
454
  const intact = skillContentMatches(botId, name, entry);
503
455
  return {
504
456
  name,
@@ -563,6 +515,26 @@ export function installSkill(botId, source, files) {
563
515
  return prepared;
564
516
  return installPreparedSkill(botId, source, prepared, { enabled: false });
565
517
  }
518
+ /** The organization library's path: a skill the organization's Admin
519
+ * published arrives switched ON (the Admin is the review), with the install
520
+ * stamp that lets the library find it again. `source` is chosen by the
521
+ * caller (`org:<ref>@<release>`), never read from the package. */
522
+ export function installOrgSkill(botId, source, skillMd, stamp) {
523
+ if (!source.startsWith("org:"))
524
+ return { error: "organization skills need an org: source" };
525
+ const prepared = preparedSkillFiles([{ path: "SKILL.md", content: skillMd }]);
526
+ if ("error" in prepared)
527
+ return prepared;
528
+ if (prepared.parsed.name !== stamp.key)
529
+ return { error: `the skill is named "${prepared.parsed.name}", not "${stamp.key}"` };
530
+ return installPreparedSkill(botId, source, prepared, { enabled: true, package: { ...stamp } });
531
+ }
532
+ /** A bot's skills that an organization install added, with their stamps. */
533
+ export function skillPackageStamps(botId) {
534
+ return Object.entries(readManifest(botId)).flatMap(([name, entry]) => entry.package
535
+ ? [{ name, enabled: entry.enabled, stamp: { ...entry.package } }]
536
+ : []);
537
+ }
566
538
  export function setSkillEnabled(botId, name, enabled) {
567
539
  if (!isSkillName(name))
568
540
  return { error: "invalid skill name" };
@@ -936,6 +908,7 @@ function installPreparedSkill(botId, source, prepared, options) {
936
908
  warnings: prepared.warnings,
937
909
  skippedFiles: prepared.skippedFiles,
938
910
  appliedStageId: options.appliedStageId,
911
+ ...(options.package ? { package: options.package } : {}),
939
912
  };
940
913
  const root = ensureSkillsRoot(botId);
941
914
  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)