openmausbot 0.1.86 → 0.1.88
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -3
- package/dist/assets/{abnfDiagram-O67JEVCF-DpUZNnFD.js → abnfDiagram-O67JEVCF-qd9VD3GV.js} +1 -1
- package/dist/assets/{arc-DW7mrI4p.js → arc-3RwkUjRe.js} +1 -1
- package/dist/assets/{architectureDiagram-NJMV4G6O-CyeZ5WLZ.js → architectureDiagram-NJMV4G6O-CFKSJS7a.js} +1 -1
- package/dist/assets/{blockDiagram-BEXU5L5S-CBVaNjO7.js → blockDiagram-BEXU5L5S-CG1rEfWD.js} +1 -1
- package/dist/assets/{c4Diagram-YGBWAQC7-sf8d41uT.js → c4Diagram-YGBWAQC7-CBW9N664.js} +1 -1
- package/dist/assets/channel-CnRnO3tF.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-BOGvy7Y_.js → chunk-2Q5K7J3B-2mRfTksB.js} +1 -1
- package/dist/assets/{chunk-3FUC2YCW-BiF9zyiz.js → chunk-3FUC2YCW-Cv1dpErj.js} +2 -2
- package/dist/assets/{chunk-5VM5RSS4-EFuZ1SBZ.js → chunk-5VM5RSS4-B7IACJp-.js} +1 -1
- package/dist/assets/{chunk-GTNCS2PH-pJLNSAT7.js → chunk-GTNCS2PH-An0hZtYa.js} +1 -1
- package/dist/assets/{chunk-GWA4HPMP-Cb-3_3IT.js → chunk-GWA4HPMP-BcJB2nEm.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-DAf5aogn.js → chunk-JWPE2WC7-CV_W7TN7.js} +1 -1
- package/dist/assets/{chunk-NETBCI7D-DR3kiwZz.js → chunk-NETBCI7D-Bcs_kwnA.js} +1 -1
- package/dist/assets/{chunk-WEXAMYUT-BmU8PeyM.js → chunk-WEXAMYUT-lrx_DxFP.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-Buh8g8OT.js → chunk-XXDRQBXY-DabHlLGM.js} +1 -1
- package/dist/assets/{classDiagram-v2-NBCMYWYE-BurfmMWM.js → classDiagram-v2-NBCMYWYE-BnHj6P7d.js} +1 -1
- package/dist/assets/{cose-bilkent-JH36ORCC-KkGZUEkU.js → cose-bilkent-JH36ORCC-CfLp6UDi.js} +1 -1
- package/dist/assets/{cynefin-OW5HDTMX-C3PpV96d.js → cynefin-OW5HDTMX-Dm4dbVA1.js} +1 -1
- package/dist/assets/{cynefinDiagram-VND7K2PF-Dt8JwJaI.js → cynefinDiagram-VND7K2PF-B9mH-PID.js} +1 -1
- package/dist/assets/{dagre-6A5THRUB-DkjN8266.js → dagre-6A5THRUB-DDc_NVQJ.js} +1 -1
- package/dist/assets/{diagram-22UHCM2B-DUAJS0JV.js → diagram-22UHCM2B-lwr8Ip8-.js} +1 -1
- package/dist/assets/{diagram-3UASUU5V-Dw4KhF1a.js → diagram-3UASUU5V-BLLe6dmI.js} +1 -1
- package/dist/assets/{diagram-ATOU4E4O-Dd1gmQqf.js → diagram-ATOU4E4O-6eUIq4lz.js} +1 -1
- package/dist/assets/{diagram-CDSNMT55-CjwGs0Yw.js → diagram-CDSNMT55-O-pRN7VQ.js} +1 -1
- package/dist/assets/{diagram-MLGK6HIB-BL-69Zrq.js → diagram-MLGK6HIB-BietvGAI.js} +1 -1
- package/dist/assets/{diagram-MPIPVDR6-5Zi2N7wo.js → diagram-MPIPVDR6-Bd2Cl62X.js} +1 -1
- package/dist/assets/{ebnfDiagram-ZINNZB2B-D9NekWUX.js → ebnfDiagram-ZINNZB2B-B6iDy69W.js} +1 -1
- package/dist/assets/{elk-276RUBZZ-CkaSQjxw.js → elk-276RUBZZ-DQVHfKe0.js} +1 -1
- package/dist/assets/{erDiagram-OPXOYQCR-CdAVBV8-.js → erDiagram-OPXOYQCR-BWdVp2tQ.js} +1 -1
- package/dist/assets/{flowDiagram-KWPJA3E3-Bm_lWWH7.js → flowDiagram-KWPJA3E3-WLE6rKB4.js} +1 -1
- package/dist/assets/{ganttDiagram-FUAMR5RP-FZ8ejYWJ.js → ganttDiagram-FUAMR5RP-BbjS-4gn.js} +1 -1
- package/dist/assets/{gitGraphDiagram-X574FWY7-Dlv9rizZ.js → gitGraphDiagram-X574FWY7-Dpy2QSGq.js} +1 -1
- package/dist/assets/index-BIMdIbaw.css +1 -0
- package/dist/assets/index-CHnODCr2.js +583 -0
- package/dist/assets/{index-h9FFNwTu.js → index-CIaHn5NS.js} +1 -1
- package/dist/assets/{infoDiagram-VRGFBTTK-BdQ_gW5B.js → infoDiagram-VRGFBTTK-D_jKWlKe.js} +1 -1
- package/dist/assets/{ishikawaDiagram-OU5B5YK6-EwssEp2X.js → ishikawaDiagram-OU5B5YK6-DLJohSuK.js} +1 -1
- package/dist/assets/{journeyDiagram-ZHPQQLJL-BX1G1vOd.js → journeyDiagram-ZHPQQLJL-DPOFj0qN.js} +1 -1
- package/dist/assets/{kanban-definition-PNTS6WVX-BB4kAsyC.js → kanban-definition-PNTS6WVX-BI7v-xpK.js} +1 -1
- package/dist/assets/{linear-3su2TG93.js → linear-C2pWPSxO.js} +1 -1
- package/dist/assets/{mermaid.core-CDD1E0s9.js → mermaid.core-CR-j5WQU.js} +5 -5
- package/dist/assets/{mindmap-definition-NLK3R4M7-Ba50MsNK.js → mindmap-definition-NLK3R4M7-lz9GvXRh.js} +1 -1
- package/dist/assets/{pegDiagram-GJSIUBJH-DZLkkZlM.js → pegDiagram-GJSIUBJH-Dl4_ia61.js} +1 -1
- package/dist/assets/{pieDiagram-5QR66LMP-DaTz77Uq.js → pieDiagram-5QR66LMP-DWuT3n88.js} +1 -1
- package/dist/assets/{quadrantDiagram-O4NWA36T-CpaiidpR.js → quadrantDiagram-O4NWA36T-DXWe9Jx7.js} +1 -1
- package/dist/assets/{railroadDiagram-XR7U4H2S-mN4JrMeI.js → railroadDiagram-XR7U4H2S-COY1IBVY.js} +1 -1
- package/dist/assets/{requirementDiagram-PLB6GJNP-BSNDRpg7.js → requirementDiagram-PLB6GJNP-DnyVuhsQ.js} +1 -1
- package/dist/assets/{sankeyDiagram-IPEJSGJF-FngzGNug.js → sankeyDiagram-IPEJSGJF-BSc4NdGK.js} +1 -1
- package/dist/assets/{sequenceDiagram-PO4LG4MO-CM4pCC3L.js → sequenceDiagram-PO4LG4MO-kqEU2EGq.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-CRICHXkw.js → sizeCapture-INFHLROL-BvB4bkWD.js} +1 -1
- package/dist/assets/{stateDiagram-v2-GCMORJYK-oyTWpxH2.js → stateDiagram-v2-GCMORJYK-BIrqNv7y.js} +1 -1
- package/dist/assets/{swimlanes-2SLR337P-D9h9mPBq.js → swimlanes-2SLR337P-CFnalQUE.js} +1 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-C4a8qNGj.js +8 -0
- package/dist/assets/{timeline-definition-EJHVYXUP-BJUlN9bW.js → timeline-definition-EJHVYXUP-CsSWJnSQ.js} +1 -1
- package/dist/assets/{usecaseDiagram-POWQR4AR-3uBNkbAb.js → usecaseDiagram-POWQR4AR-DkAmJ1EE.js} +1 -1
- package/dist/assets/{vennDiagram-UO4OBE2U-D2odalzT.js → vennDiagram-UO4OBE2U-C3vd9drL.js} +1 -1
- package/dist/assets/{wardleyDiagram-VNRHLVJA-DHgFaB9S.js → wardleyDiagram-VNRHLVJA-j26CM-pi.js} +1 -1
- package/dist/assets/{xychartDiagram-PMCCYNJV-w5GYM2h0.js → xychartDiagram-PMCCYNJV-BYu1Q_at.js} +1 -1
- package/dist/index.html +2 -2
- package/dist-server/browser-proxy.js +2 -1
- package/dist-server/companion/src/routes.js +8 -1
- package/dist-server/connector-proxy.js +105 -1
- package/dist-server/container-mcp.js +1032 -165
- package/dist-server/drivers/agents-proxy.js +338 -29
- package/dist-server/drivers/phone-proxy.js +33 -2
- package/dist-server/index.js +40590 -31811
- package/dist-server/local-computer-proxy.js +5 -3
- package/dist-server/local-computer.js +53 -17
- package/dist-server/mcp-gate.js +2 -0
- package/dist-server/mcp-server.js +5 -3
- package/dist-server/message-search.worker.js +52 -0
- package/dist-server/openmausbot.js +4457 -1399
- package/dist-server/pair-cli.js +4457 -1399
- package/dist-server/permission-proxy.js +29 -9
- package/dist-server/server/admission.js +81 -0
- package/dist-server/server/aside-queue.js +294 -0
- package/dist-server/server/attachments.js +98 -1
- package/dist-server/server/auto-approve.js +4 -3
- package/dist-server/server/auto-vm-claims.js +1 -0
- package/dist-server/server/bot-attachment.js +107 -0
- package/dist-server/server/bot-overview.js +19 -0
- package/dist-server/server/bot-package.js +18 -292
- package/dist-server/server/bot-profile.js +5 -17
- package/dist-server/server/box-create-idempotency.js +3 -3
- package/dist-server/server/box.js +57 -55
- package/dist-server/server/browser-proxy.js +3 -2
- package/dist-server/server/browser-runtime.js +37 -1
- package/dist-server/server/cli-api-setup.js +1 -0
- package/dist-server/server/cli.js +2 -2
- package/dist-server/server/command-allowlist.js +154 -0
- package/dist-server/server/composio.js +297 -1
- package/dist-server/server/computer-wait.js +22 -9
- package/dist-server/server/config.js +84 -12
- package/dist-server/server/connector-advertisement.js +165 -0
- package/dist-server/server/connector-proxy.js +14 -2
- package/dist-server/server/connector-verdict.js +168 -0
- package/dist-server/server/container-computer.js +61 -1
- package/dist-server/server/contracts.js +22 -0
- package/dist-server/server/default-model-selection.js +9 -0
- package/dist-server/server/delegations.js +85 -34
- package/dist-server/server/drivers/acp/core.js +80 -20
- package/dist-server/server/drivers/acp/cursor.js +3 -7
- package/dist-server/server/drivers/acp/droid.js +4 -7
- package/dist-server/server/drivers/acp/gemini.js +4 -4
- package/dist-server/server/drivers/acp/grok.js +4 -9
- package/dist-server/server/drivers/acp/hermes.js +3 -5
- package/dist-server/server/drivers/acp/opencode-go.js +2 -5
- package/dist-server/server/drivers/acp/qwen.js +9 -15
- package/dist-server/server/drivers/agents-call.js +179 -13
- package/dist-server/server/drivers/agents-catalog.js +134 -10
- package/dist-server/server/drivers/boxagent.js +160 -36
- package/dist-server/server/drivers/builtIn.js +2 -0
- package/dist-server/server/drivers/chat-box-tools.js +90 -0
- package/dist-server/server/drivers/chat-images.js +54 -0
- package/dist-server/server/drivers/chat-mcp-tools.js +38 -8
- package/dist-server/server/drivers/chat-tool-approval.js +70 -18
- package/dist-server/server/drivers/claude.js +38 -19
- package/dist-server/server/drivers/codex-catalog.js +14 -6
- package/dist-server/server/drivers/codex-identity.js +2 -1
- package/dist-server/server/drivers/codex-instructions.js +48 -6
- package/dist-server/server/drivers/codex-release.js +48 -0
- package/dist-server/server/drivers/codex-tool-surface.js +20 -0
- package/dist-server/server/drivers/codex.js +102 -55
- package/dist-server/server/drivers/mistral.js +115 -0
- package/dist-server/server/drivers/openai-chat.js +180 -33
- package/dist-server/server/drivers/openai-compat.js +1 -0
- package/dist-server/server/drivers/permission-command.js +35 -0
- package/dist-server/server/drivers/phone-proxy.js +47 -2
- package/dist-server/server/drivers/pi.js +306 -28
- package/dist-server/server/drivers/prompt-split.js +110 -0
- package/dist-server/server/drivers/session-idle.js +29 -0
- package/dist-server/server/env-path.js +15 -0
- package/dist-server/server/fleet.js +1 -1
- package/dist-server/server/group-thread-usage.js +103 -0
- package/dist-server/server/guarded-requests.js +2 -2
- package/dist-server/server/harness/registry.js +1 -0
- package/dist-server/server/harness-capabilities.js +46 -0
- package/dist-server/server/hosted-models.js +1 -1
- package/dist-server/server/index.js +2396 -505
- package/dist-server/server/local-computer.js +72 -23
- package/dist-server/server/managed-desktop.js +1 -1
- package/dist-server/server/mcp-bridge.js +6 -6
- package/dist-server/server/message-db.js +22 -42
- package/dist-server/server/message-file.js +9 -1
- package/dist-server/server/message-search-query.js +43 -0
- package/dist-server/server/message-search-worker.js +80 -0
- package/dist-server/server/message-search.worker.js +21 -0
- package/dist-server/server/model-requests.js +248 -0
- package/dist-server/server/new-bot-defaults.js +115 -0
- package/dist-server/server/options-card.js +21 -0
- package/dist-server/server/org-library.js +1065 -0
- package/dist-server/server/package-export.js +627 -39
- package/dist-server/server/package-import.js +458 -0
- package/dist-server/server/package-parts.js +87 -0
- package/dist-server/server/peer-approval.js +16 -7
- package/dist-server/server/peer-delivery.js +60 -0
- package/dist-server/server/peer-roster.js +67 -0
- package/dist-server/server/presets.js +492 -0
- package/dist-server/server/profile-requests.js +66 -39
- package/dist-server/server/profile-revision.js +9 -1
- package/dist-server/server/profile-versions.js +6 -3
- package/dist-server/server/provider-key-check.js +3 -2
- package/dist-server/server/published-teams.js +80 -0
- package/dist-server/server/room-handoffs.js +19 -3
- package/dist-server/server/routes/bot-presets.js +25 -0
- package/dist-server/server/routine-requests.js +35 -10
- package/dist-server/server/routines.js +41 -1
- package/dist-server/server/section-context.js +10 -0
- package/dist-server/server/skill-fetch.js +72 -14
- package/dist-server/server/skills.js +35 -61
- package/dist-server/server/steer-queue.js +3 -2
- package/dist-server/server/store.js +291 -17
- package/dist-server/server/surface.js +10 -4
- package/dist-server/server/system-prompt.js +38 -6
- package/dist-server/server/team-backup.js +6 -2
- package/dist-server/server/team-computers.js +10 -0
- package/dist-server/server/team-library.js +6 -2
- package/dist-server/server/team-manifest.js +4 -3
- package/dist-server/server/team-setup-requests.js +139 -27
- package/dist-server/server/thread-events.js +20 -2
- package/dist-server/server/tightening-requests.js +405 -0
- package/dist-server/server/tts/chatterbox.js +27 -5
- package/dist-server/server/turn-context.js +2 -0
- package/dist-server/server/turn-watchdog.js +12 -3
- package/dist-server/server/usage-ledger.js +19 -2
- package/dist-server/server/vps-computer.js +139 -51
- package/dist-server/server/vps-container-mcp.js +7 -0
- package/dist-server/server/vps-ssh.js +36 -7
- package/dist-server/server/workspace-backup-policy.js +12 -0
- package/dist-server/server/workspace-backup.js +42 -5
- package/dist-server/server/workspace-backup.worker.js +10 -0
- package/dist-server/server/workspace.js +1 -1
- package/dist-server/shared/approval-mode.js +1 -1
- package/dist-server/shared/ask-question.js +207 -12
- package/dist-server/shared/bot-profile.js +15 -0
- package/dist-server/shared/command-allowlist.js +1 -0
- package/dist-server/shared/credential-request.js +6 -2
- package/dist-server/shared/line-diff.js +23 -0
- package/dist-server/shared/model-request.js +1 -0
- package/dist-server/shared/options-card.js +51 -0
- package/dist-server/shared/package-format.js +967 -0
- package/dist-server/shared/profile-request.js +5 -4
- package/dist-server/shared/skill-md.js +64 -0
- package/dist-server/shared/team-backup.js +7 -0
- package/dist-server/shared/tightening-request.js +87 -0
- package/dist-server/shared/wire.js +4 -0
- package/dist-server/src/lib/composer-attachments.js +25 -3
- package/dist-server/vps-container-mcp.js +1132 -171
- package/dist-server/workspace-backup.worker.js +19474 -0
- package/enterprise/README.md +1 -1
- package/package.json +1 -1
- package/dist/assets/channel-BXFkgus9.js +0 -1
- package/dist/assets/index-BGgqlWPJ.css +0 -1
- package/dist/assets/index-BtHo8ZKl.js +0 -577
- package/dist/assets/swimlanesDiagram-TC7HE7FX-BWrvB2ak.js +0 -8
|
@@ -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
|
-
|
|
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 —
|
|
149
|
-
* where *absence* means allowed, so
|
|
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
|
-
/**
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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("
|
|
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
|
|
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(", ")}
|
|
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
|
-
|
|
205
|
-
|
|
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: [],
|
|
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
|
-
|
|
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" &&
|
|
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
|
}
|