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
@@ -92,6 +92,16 @@ export class TeamComputers {
92
92
  setProblem(id, problem) {
93
93
  return this.patch(id, { problem: problem?.slice(0, 500) });
94
94
  }
95
+ /** Relabel an existing team's assignment; this never changes its computer. */
96
+ renameSection(from, to) {
97
+ const entry = this.forSection(from);
98
+ if (!entry || from === to)
99
+ return false;
100
+ if (this.forSection(to))
101
+ throw failure("This team already has a computer");
102
+ this.patch(entry.id, { section: to });
103
+ return true;
104
+ }
95
105
  patch(id, patch) {
96
106
  const entries = this.list();
97
107
  const entry = entries.find(candidate => candidate.id === id);
@@ -1,5 +1,5 @@
1
1
  import { parseJson } from "./schema.js";
2
- import { isBotPackage, parseBotPackage } from "./bot-package.js";
2
+ import { isBotPackage, parseBotPackage, parsePackageDocument } from "./bot-package.js";
3
3
  import { parseTeamManifest } from "./team-manifest.js";
4
4
  export const TEAM_LIBRARY_REPOSITORY = "https://github.com/milind-soni/openmausbot-teams";
5
5
  export const TEAM_LIBRARY_RAW_ROOT = "https://raw.githubusercontent.com/milind-soni/openmausbot-teams/main";
@@ -127,7 +127,11 @@ export async function fetchTeamCatalog(fetcher = fetch) {
127
127
  function parseShareable(value) {
128
128
  if (typeof value === "string")
129
129
  return parseBotPackage(value);
130
- return isBotPackage(value) ? parseBotPackage(value) : parseTeamManifest(value);
130
+ if (!isBotPackage(value))
131
+ return parseTeamManifest(value);
132
+ // A shared team (v2) is read as a file: it cannot claim a publisher.
133
+ // Version 1 keeps its original shape for older callers.
134
+ return value.version === 1 ? parseBotPackage(value) : parsePackageDocument(value, { trust: "file" });
131
135
  }
132
136
  async function fetchShareable(url, fetcher) {
133
137
  return url.endsWith(".md")
@@ -140,13 +140,14 @@ export function parseTeamManifest(value) {
140
140
  * 1. Allowlist, not blocklist. The returned object is built field by field
141
141
  * from the parsed member, so every privilege-bearing BotRecord field —
142
142
  * approvalMode, autoApprove, alwaysAllow, chiefOfStaff, approvePeerComms, composio,
143
- * computer, cloudBackend, cwd — is structurally absent, whatever the
143
+ * connectorTools, computer, cloudBackend, cwd — is structurally absent, whatever the
144
144
  * file claimed. parseTeamManifest already drops unknown member keys;
145
145
  * this keeps the guarantee even if the schema grows a field later,
146
146
  * because nothing new can reach a bot record without someone
147
147
  * consciously widening this return type. The caller must still force
148
- * composio: false on the created record — that is the one privilege
149
- * where *absence* means allowed, so leaving it unset is not safe.
148
+ * composio: false (and connectorTools: {}) on the created record —
149
+ * composio is the one privilege where *absence* means allowed, so
150
+ * leaving it unset is not safe.
150
151
  *
151
152
  * 2. No name captures. Display names are identity wherever bots address
152
153
  * each other — @mention resolution in rooms, the Chief of Staff roster,
@@ -1,15 +1,27 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { z } from "zod";
3
+ import { soulDiffLines } from "../shared/line-diff.js";
3
4
  import { newId } from "./contracts.js";
4
5
  import { fitsOnOneLine, parseBotProfilePatch } from "./bot-profile.js";
5
6
  import { profileSnapshot } from "./profile-revision.js";
7
+ import { validateBotCwd } from "./bot-cwd.js";
8
+ import { harnessCapabilityLines } from "./harness-capabilities.js";
6
9
  import { redactSecretsInText } from "./redact.js";
7
10
  const section = (value) => value?.trim() || "";
8
11
  const teamName = z.string().trim().min(1).max(60).refine(fitsOnOneLine).refine((value) => redactSecretsInText(value) === value, "Team names cannot contain credentials");
12
+ const FIELD_LABELS = { name: "Name", title: "Title", description: "Description", section: "Section", cwd: "Working folder" };
13
+ // The variant rides ModelSelection, and a proposal that omits it removes it,
14
+ // so the review text says both states outright: set on the current
15
+ // selection, explicitly absent on the proposed one. Otherwise a bot with a
16
+ // configured variant shows a before value that hides the setting changing
17
+ // under it.
18
+ const modelSelectionText = (selection) => `${selection.instanceId}/${selection.model}${selection.variant ? ` (variant ${selection.variant})` : " (no variant)"}${selection.effort ? ` (effort ${selection.effort})` : ""}`;
9
19
  const fieldsSchema = z.object({
20
+ chiefOfStaff: z.boolean().optional(),
10
21
  name: z.string().optional(), title: z.string().optional(), description: z.string().optional(), soul: z.string().optional(),
22
+ cwd: z.string().optional(),
11
23
  section: z.string().trim().max(60).refine(fitsOnOneLine).refine((value) => redactSecretsInText(value) === value, "Team names cannot contain credentials").optional(),
12
- modelSelection: z.object({ instanceId: z.string().trim().min(1), model: z.string().trim().min(1), effort: z.string().optional() }).strict().optional(),
24
+ modelSelection: z.object({ instanceId: z.string().trim().min(1), model: z.string().trim().min(1), effort: z.string().optional(), variant: z.string().optional() }).strict().optional(),
13
25
  }).strict();
14
26
  const planSchema = z.object({
15
27
  reason: z.string().trim().min(1).max(500),
@@ -23,15 +35,44 @@ export class TeamSetupError extends Error {
23
35
  status;
24
36
  constructor(message, status = 400) { super(message); this.status = status; }
25
37
  }
26
- /** Settings relevant to approval, including the previous durable receipt. */
27
- export function teamSetupRevision(bot) {
28
- return createHash("sha256").update(JSON.stringify({
29
- ...profileSnapshot(bot), section: section(bot.section), modelSelection: bot.modelSelection,
30
- hidden: Boolean(bot.hidden), chiefOfStaff: Boolean(bot.chiefOfStaff), peers: bot.peers,
31
- managedSections: bot.managedSections,
32
- approvalMode: bot.approvalMode, autoApprove: bot.autoApprove, approvalGrant: bot.approvalGrant,
33
- receipt: bot.lastTeamSetupReceipt?.requestId,
34
- })).digest("hex");
38
+ /**
39
+ * The revision a team-setup card pins to. Without `fields` it hashes the
40
+ * bot's whole state — any drift cancels — which the destructive deletion
41
+ * path keeps on purpose. With `fields` (setup plans) it pins exactly the
42
+ * before-values the card displayed for the fields the plan touches;
43
+ * authority, team scope, peer scope, visibility, busyness, destination, and
44
+ * name collisions are re-checked fresh at confirm time, so drift on
45
+ * untouched fields no longer cancels a plan that never claimed to touch
46
+ * them. Every update card renders a section line, so section stays pinned
47
+ * even when the plan only carries it through unchanged.
48
+ */
49
+ export function teamSetupRevision(bot, fields) {
50
+ if (fields === undefined) {
51
+ return createHash("sha256").update(JSON.stringify({
52
+ ...profileSnapshot(bot), section: section(bot.section), modelSelection: bot.modelSelection,
53
+ hidden: Boolean(bot.hidden), chiefOfStaff: Boolean(bot.chiefOfStaff), peers: bot.peers,
54
+ managedSections: bot.managedSections,
55
+ approvalMode: bot.approvalMode, autoApprove: bot.autoApprove, approvalGrant: bot.approvalGrant,
56
+ receipt: bot.lastTeamSetupReceipt?.requestId,
57
+ })).digest("hex");
58
+ }
59
+ const snapshot = profileSnapshot(bot);
60
+ const scope = {};
61
+ if (fields.name !== undefined)
62
+ scope.name = snapshot.name;
63
+ if (fields.title !== undefined)
64
+ scope.title = snapshot.title;
65
+ if (fields.description !== undefined)
66
+ scope.description = snapshot.description;
67
+ if (fields.soul !== undefined)
68
+ scope.soul = snapshot.soul;
69
+ if (fields.section !== undefined)
70
+ scope.section = section(bot.section);
71
+ if (fields.modelSelection !== undefined)
72
+ scope.modelSelection = bot.modelSelection;
73
+ if (fields.chiefOfStaff !== undefined)
74
+ scope.chiefOfStaff = Boolean(bot.chiefOfStaff);
75
+ return createHash("sha256").update(JSON.stringify(scope)).digest("hex");
35
76
  }
36
77
  export class TeamSetupRequestService {
37
78
  resolving = new Set();
@@ -43,8 +84,23 @@ export class TeamSetupRequestService {
43
84
  throw new TeamSetupError("Only an active Chief of Staff can propose team setup", 403);
44
85
  return chief;
45
86
  }
87
+ /**
88
+ * The Chief's own pinned state: the fields this plan's operations display
89
+ * about the Chief, or the whole state for deletions. A plan that never
90
+ * touches the Chief pins nothing of the Chief's — its authority is
91
+ * re-checked fresh at confirm time either way.
92
+ */
93
+ requesterScope(request) {
94
+ if (request.deletion)
95
+ return undefined;
96
+ const scope = {};
97
+ for (const operation of request.operations)
98
+ if (operation.botId === request.botId)
99
+ Object.assign(scope, operation.fields);
100
+ return scope;
101
+ }
46
102
  fields(input, current) {
47
- const { section: targetSection, modelSelection, ...profile } = input;
103
+ const { section: targetSection, modelSelection, chiefOfStaff, cwd: rawCwd, ...profile } = input;
48
104
  const safe = Object.fromEntries(Object.entries(profile).map(([key, value]) => [key, redactSecretsInText(value)]));
49
105
  const parsed = parseBotProfilePatch(safe, true);
50
106
  if (!parsed.ok)
@@ -52,6 +108,19 @@ export class TeamSetupRequestService {
52
108
  const result = { ...parsed.patch };
53
109
  if (targetSection !== undefined)
54
110
  result.section = targetSection;
111
+ if (chiefOfStaff !== undefined)
112
+ result.chiefOfStaff = chiefOfStaff;
113
+ if (rawCwd !== undefined) {
114
+ // Create-only, and the exact check the profile path runs: absolute,
115
+ // exists, is a folder. An existing bot's folder keeps going through
116
+ // propose_profile, which re-checks the folder at confirm time.
117
+ if (current)
118
+ throw new TeamSetupError("A working folder can only be chosen when creating a bot; propose_profile changes it later");
119
+ const checked = validateBotCwd(rawCwd);
120
+ if (!checked.ok)
121
+ throw new TeamSetupError(checked.error);
122
+ result.cwd = checked.cwd ?? "";
123
+ }
55
124
  if (modelSelection) {
56
125
  const selection = modelSelection;
57
126
  const error = this.options.validateModel(selection, current);
@@ -69,7 +138,7 @@ export class TeamSetupRequestService {
69
138
  throw new TeamSetupError("Full Access is no longer enabled for this conversation", 409);
70
139
  if (confirming && !immediate && !this.options.store.messagesFor(request.threadId).some((message) => message.card?.requestId === request.requestId && !message.card.answered && !message.card.dismissed))
71
140
  throw new TeamSetupError("This setup card is no longer pending", 409);
72
- if (confirming && teamSetupRevision(chief) !== request.requesterRevision)
141
+ if (confirming && teamSetupRevision(chief, this.requesterScope(request)) !== request.requesterRevision)
73
142
  throw new TeamSetupError("The Chief's settings changed. This setup was cancelled; review a new proposal.", 409);
74
143
  const existingTeams = new Set(this.options.teams().map(section));
75
144
  if (new Set([...(chief.managedSections ?? []), ...request.newTeams]).size > 100)
@@ -94,7 +163,7 @@ export class TeamSetupRequestService {
94
163
  else {
95
164
  if (!target || target.hidden || !this.options.canAccessTeam(chief, target.section) || (target.id !== chief.id && Array.isArray(chief.peers) && !chief.peers.includes(target.id)))
96
165
  throw new TeamSetupError("A target bot is outside this Chief's authorized team and peer scope", 403);
97
- if (teamSetupRevision(target) !== operation.expectedRevision)
166
+ if (teamSetupRevision(target, operation.fields) !== operation.expectedRevision)
98
167
  throw new TeamSetupError(`@${target.name} changed. This setup was cancelled; review a new proposal.`, 409);
99
168
  const sourceThreadId = immediate && target.id === chief.id ? request.threadId : undefined;
100
169
  if (this.options.targetBusy(target.id, sourceThreadId) && (confirming || target.id !== chief.id))
@@ -108,7 +177,7 @@ export class TeamSetupRequestService {
108
177
  throw new TeamSetupError("The destination team is outside this Chief's authorized scope", 403);
109
178
  if (!request.newTeams.includes(section(destination)) && !existingTeams.has(section(destination)))
110
179
  throw new TeamSetupError("The destination team no longer exists", 409);
111
- const next = { id: operation.botId, name: fields.name ?? target.name, section: section(destination), chiefOfStaff: target?.chiefOfStaff, hidden: false };
180
+ const next = { id: operation.botId, name: fields.name ?? target.name, section: section(destination), chiefOfStaff: fields.chiefOfStaff ?? target?.chiefOfStaff, hidden: false };
112
181
  const at = projected.findIndex((bot) => bot.id === operation.botId);
113
182
  if (at < 0)
114
183
  projected.push(next);
@@ -125,7 +194,7 @@ export class TeamSetupRequestService {
125
194
  if (projected.some((bot) => bot.id !== candidate.id && !bot.hidden && bot.section === candidate.section && bot.name.trim().toLowerCase() === candidate.name.trim().toLowerCase()))
126
195
  throw new TeamSetupError(`@${candidate.name} already exists in that team`, 409);
127
196
  if (candidate.chiefOfStaff && projected.some((bot) => bot.id !== candidate.id && bot.chiefOfStaff && bot.section === candidate.section))
128
- throw new TeamSetupError("That move would put two Chiefs in one team", 409);
197
+ throw new TeamSetupError("Each team can have one Chief. Include the current Chief's demotion in this plan.", 409);
129
198
  }
130
199
  if (request.deletion) {
131
200
  const target = this.options.store.bot(request.deletion.botId);
@@ -150,14 +219,19 @@ export class TeamSetupRequestService {
150
219
  throw new TeamSetupError("Review at most eight bots in one setup");
151
220
  const operations = [...combined.values()].map((operation) => {
152
221
  const target = operation.action === "update" ? this.options.store.bot(operation.botId) : undefined;
222
+ const fields = this.fields({ ...operation.fields, section: operation.fields.section ?? target?.section ?? chief.section ?? "" }, target ?? undefined);
153
223
  return { action: operation.action, botId: operation.action === "create" ? newId() : operation.botId,
154
224
  ...(operation.action === "create" ? { threadId: newId() } : {}),
155
- fields: this.fields({ ...operation.fields, section: operation.fields.section ?? target?.section ?? chief.section ?? "" }, target ?? undefined),
156
- ...(target ? { expectedRevision: teamSetupRevision(target) } : {}),
225
+ fields,
226
+ ...(target ? { expectedRevision: teamSetupRevision(target, fields) } : {}),
157
227
  };
158
228
  });
229
+ const requesterScope = {};
230
+ for (const operation of operations)
231
+ if (operation.botId === chief.id)
232
+ Object.assign(requesterScope, operation.fields);
159
233
  const request = { version: 1, requestId: newId(), botId: args.botId, threadId: args.threadId,
160
- reason: redactSecretsInText(parsed.data.reason), createdAt: Date.now(), requesterRevision: teamSetupRevision(chief),
234
+ reason: redactSecretsInText(parsed.data.reason), createdAt: Date.now(), requesterRevision: teamSetupRevision(chief, requesterScope),
161
235
  newTeams: [...new Set(parsed.data.newTeams)], operations };
162
236
  this.validate(request, false);
163
237
  return request;
@@ -195,18 +269,52 @@ export class TeamSetupRequestService {
195
269
  const lines = [`Why: ${request.reason}`];
196
270
  if (request.deletion)
197
271
  lines.push(`Delete @${request.deletion.name} (${request.deletion.botId}).`, "Permanently removes this bot, all its conversations, memory, instructions, skills, and any computer owned only by it. Generated project files and shared team computers remain. Active work or an unavailable provider can block deletion safely.");
198
- if (request.newTeams.length)
199
- lines.push(`Create teams: ${request.newTeams.map((name) => JSON.stringify(name)).join(", ")}.`, `Authorize @${chief.name} to coordinate and propose setup changes in these new teams.`);
272
+ if (request.newTeams.length) {
273
+ lines.push(`Create teams: ${request.newTeams.map((name) => JSON.stringify(name)).join(", ")}.`);
274
+ if (!request.operations.some(op => op.botId === chief.id && op.fields.chiefOfStaff === false)) {
275
+ lines.push(`Authorize @${chief.name} to coordinate and propose setup changes in these new teams.`);
276
+ }
277
+ }
200
278
  for (const operation of request.operations) {
201
279
  const current = this.options.store.bot(operation.botId);
202
280
  lines.push(`\n${operation.action === "create" ? "Create" : "Update"} @${operation.fields.name ?? current?.name} (${operation.action === "create" ? "new bot" : operation.botId})`);
203
281
  for (const [key, value] of Object.entries(operation.fields)) {
204
- const before = current ? (key === "section" ? current.section || "General" : current[key]) : undefined;
205
- lines.push(`${key === "modelSelection" ? "Default engine/model" : key}: ${current ? `${JSON.stringify(before ?? "")} → ` : ""}${key === "section" ? JSON.stringify(value || "General") : JSON.stringify(value)}`);
282
+ if (key === "chiefOfStaff") {
283
+ lines.push(`Chief of Staff: ${current?.chiefOfStaff ? "Yes" : "No"} → ${value ? "Yes" : "No"}.${value ? " May coordinate and configure bots in this team." : " Additional managed-team access is removed."}`);
284
+ continue;
285
+ }
286
+ // The same review shape the profile card uses: labeled before/after
287
+ // lines, and SOUL.md as a line diff with the shared large-change
288
+ // fallback — never a JSON.stringify blob of the instructions.
289
+ if (key === "soul") {
290
+ lines.push(...soulDiffLines(current?.soul ?? "", value));
291
+ }
292
+ else if (key === "modelSelection") {
293
+ lines.push(`Default engine/model: ${current ? `${modelSelectionText(current.modelSelection)} → ` : ""}${modelSelectionText(value)}`);
294
+ // An engine switch changes what the bot can do, not just its label:
295
+ // show what the destination driver gains and loses. Same-instance
296
+ // model or effort changes render nothing extra.
297
+ if (current && typeof value === "object" && value !== null &&
298
+ value.instanceId !== current.modelSelection.instanceId && this.options.driverCapabilities) {
299
+ lines.push(...harnessCapabilityLines(this.options.driverCapabilities(current.modelSelection.instanceId), this.options.driverCapabilities(value.instanceId)));
300
+ }
301
+ }
302
+ else {
303
+ const before = current ? (key === "section" ? current.section || "General" : String(current[key] ?? "")) : undefined;
304
+ const after = key === "section" ? value || "General" : String(value);
305
+ lines.push(`${FIELD_LABELS[key] ?? key}: ${current ? `"${before}" → ` : ""}"${after}"`);
306
+ }
307
+ }
308
+ }
309
+ if (!request.deletion) {
310
+ lines.push("\nDefault models apply to groups and new threads. Every existing thread keeps its current model and permissions.");
311
+ // Informed consent: creation locks the new bot down, and the owner
312
+ // signs off on that whole state, not just the two facts named before.
313
+ if (request.operations.some((operation) => operation.action === "create")) {
314
+ lines.push("New bots stay owner-owned after creation: connected apps off, approvals at Ask, a private workspace by default, avatar untouched.");
206
315
  }
316
+ lines.push("Existing execution permissions are unchanged.");
207
317
  }
208
- if (!request.deletion)
209
- lines.push("\nDefault models apply to groups and new threads. Every existing thread keeps its current model and permissions.", "New bots start in Ask with connected apps disabled. Existing execution permissions are unchanged.");
210
318
  lines.push(immediate ? "Full Access applies this request in the current turn without another confirmation." : "After this decision the Chief continues once with the result.");
211
319
  const title = request.deletion ? `Delete @${request.deletion.name}?` : `Apply setup for ${request.operations.length} ${request.operations.length === 1 ? "bot" : "bots"}?`;
212
320
  const detail = lines.join("\n");
@@ -256,7 +364,7 @@ export class TeamSetupRequestService {
256
364
  let messageId;
257
365
  try {
258
366
  messageId = this.options.store.appendMessage(request.threadId, { role: "bot", kind: "options", ...(from ? { from } : {}), card: {
259
- ...card, title, options: [], answered: result.state === "applied" ? "allow" : "deny", held: result.error,
367
+ ...card, title, options: [], ...(result.state === "applied" ? { answered: "allow" } : { expired: true }), held: result.error,
260
368
  teamSetupRequest: { ...request, result, resumed: true },
261
369
  } }).id;
262
370
  }
@@ -278,7 +386,7 @@ export class TeamSetupRequestService {
278
386
  throw new TeamSetupError("Confirm or cancel this setup", 400);
279
387
  // Stop/dismiss can close a card without a setup decision. It must never
280
388
  // resurrect that review or wake the stopped conversation.
281
- if (card.answered || card.dismissed) {
389
+ if (card.answered || card.dismissed || card.expired) {
282
390
  const result = request.result ?? { state: "cancelled", bots: [], newTeams: [], error: "This setup card was closed" };
283
391
  return { result, request: { ...request, result }, messageId: message.id, duplicate: true };
284
392
  }
@@ -294,7 +402,11 @@ export class TeamSetupRequestService {
294
402
  result = { state: "denied", bots: [], newTeams: [] };
295
403
  else
296
404
  result = await this.apply(request);
297
- this.options.store.patchMessage(args.threadId, message.id, { card: { ...card, answered: result.state === "applied" ? "allow" : "deny", held: result.error, teamSetupRequest: { ...request, result } } });
405
+ // A setup that could not apply is dead, not denied: the card settles
406
+ // expired with its options removed, so it stops looking actionable and
407
+ // the one-line held note says to review a fresh proposal.
408
+ const dead = result.state !== "applied" && result.state !== "denied";
409
+ this.options.store.patchMessage(args.threadId, message.id, { card: { ...card, ...(dead ? { expired: true, options: [] } : { answered: result.state === "applied" ? "allow" : "deny" }), held: result.error, teamSetupRequest: { ...request, result } } });
298
410
  return { result, request: { ...request, result }, messageId: message.id, duplicate: false };
299
411
  }
300
412
  finally {
@@ -133,6 +133,10 @@ const stringOrMissing = (value) => value === undefined || typeof value === "stri
133
133
  const stringOrNullOrMissing = (value) => value === undefined || value === null || typeof value === "string";
134
134
  const numberOrNullOrMissing = (value) => value === undefined || value === null || typeof value === "number";
135
135
  const stringsOrMissing = (value) => value === undefined || (Array.isArray(value) && value.every((item) => typeof item === "string"));
136
+ /** A computer holder as the wait events carry it: a name, and the holding
137
+ * thread's title when one was known. */
138
+ const computerHolderOrMissing = (value) => value === undefined ||
139
+ (isRecord(value) && typeof value.name === "string" && stringOrMissing(value.task));
136
140
  /** A replayed structured ask. Only the shape the card actually reads is
137
141
  * required; the rest is optional and simply absent on an older event. */
138
142
  const askQuestionsOrMissing = (value) => value === undefined ||
@@ -175,6 +179,15 @@ function isRuntimeEvent(value) {
175
179
  stringsOrMissing(value.denials) &&
176
180
  (value.usage === undefined ||
177
181
  (isRecord(value.usage) && typeof value.usage.input === "number" && typeof value.usage.output === "number")));
182
+ case "turn.wait_started":
183
+ return typeof value.resource === "string" && computerHolderOrMissing(value.holder);
184
+ case "turn.wait_ended":
185
+ return (typeof value.resource === "string" &&
186
+ computerHolderOrMissing(value.holder) &&
187
+ typeof value.waitedMs === "number" &&
188
+ Number.isFinite(value.waitedMs) &&
189
+ value.waitedMs >= 0 &&
190
+ (value.outcome === "acquired" || value.outcome === "gave_up" || value.outcome === "stopped"));
178
191
  case "item.started":
179
192
  return (value.itemType === "tool" || value.itemType === "reasoning") && stringOrMissing(value.title);
180
193
  case "item.updated":
@@ -188,7 +201,10 @@ function isRuntimeEvent(value) {
188
201
  typeof value.tool === "string" &&
189
202
  typeof value.summary === "string" &&
190
203
  stringsOrMissing(value.choices) &&
191
- askQuestionsOrMissing(value.questions));
204
+ askQuestionsOrMissing(value.questions) &&
205
+ // Required fields only: events recorded before origin exists replay
206
+ // unchanged, and a value other than the two documented marks is not.
207
+ (value.origin === undefined || value.origin === "tool" || value.origin === "output"));
192
208
  case "request.resolved":
193
209
  return ((value.behavior === "allow" || value.behavior === "deny" || value.behavior === "answer") &&
194
210
  (value.source === "user" ||
@@ -200,7 +216,9 @@ function isRuntimeEvent(value) {
200
216
  case "thread.token-usage.updated":
201
217
  return typeof value.input === "number" && typeof value.output === "number";
202
218
  case "runtime.error":
203
- return typeof value.message === "string" && (value.setup === undefined || typeof value.setup === "boolean");
219
+ return typeof value.message === "string" &&
220
+ (value.setup === undefined || typeof value.setup === "boolean") &&
221
+ (value.claudeUpdate === undefined || typeof value.claudeUpdate === "boolean");
204
222
  default:
205
223
  return false;
206
224
  }