openmausbot 0.1.87 → 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 (149) hide show
  1. package/README.md +2 -1
  2. package/dist/assets/{abnfDiagram-O67JEVCF-BTiuL79F.js → abnfDiagram-O67JEVCF-qd9VD3GV.js} +1 -1
  3. package/dist/assets/{arc-C88Blc2v.js → arc-3RwkUjRe.js} +1 -1
  4. package/dist/assets/{architectureDiagram-NJMV4G6O-BZsJIZrb.js → architectureDiagram-NJMV4G6O-CFKSJS7a.js} +1 -1
  5. package/dist/assets/{blockDiagram-BEXU5L5S-CVeMe9Ah.js → blockDiagram-BEXU5L5S-CG1rEfWD.js} +1 -1
  6. package/dist/assets/{c4Diagram-YGBWAQC7-Cedk3Bhx.js → c4Diagram-YGBWAQC7-CBW9N664.js} +1 -1
  7. package/dist/assets/channel-CnRnO3tF.js +1 -0
  8. package/dist/assets/{chunk-2Q5K7J3B-D-VRqzTB.js → chunk-2Q5K7J3B-2mRfTksB.js} +1 -1
  9. package/dist/assets/{chunk-3FUC2YCW-BChov01Y.js → chunk-3FUC2YCW-Cv1dpErj.js} +2 -2
  10. package/dist/assets/{chunk-5VM5RSS4-C-TfwvHy.js → chunk-5VM5RSS4-B7IACJp-.js} +1 -1
  11. package/dist/assets/{chunk-GTNCS2PH-Db7CGj5Y.js → chunk-GTNCS2PH-An0hZtYa.js} +1 -1
  12. package/dist/assets/{chunk-GWA4HPMP-CNvIfqKT.js → chunk-GWA4HPMP-BcJB2nEm.js} +1 -1
  13. package/dist/assets/{chunk-JWPE2WC7-C_bEp5tq.js → chunk-JWPE2WC7-CV_W7TN7.js} +1 -1
  14. package/dist/assets/{chunk-NETBCI7D-Coi4BOls.js → chunk-NETBCI7D-Bcs_kwnA.js} +1 -1
  15. package/dist/assets/{chunk-WEXAMYUT-CzylEuL3.js → chunk-WEXAMYUT-lrx_DxFP.js} +1 -1
  16. package/dist/assets/{chunk-XXDRQBXY-m7p7ztaD.js → chunk-XXDRQBXY-DabHlLGM.js} +1 -1
  17. package/dist/assets/{classDiagram-v2-NBCMYWYE-BMy6o9x2.js → classDiagram-v2-NBCMYWYE-BnHj6P7d.js} +1 -1
  18. package/dist/assets/{cose-bilkent-JH36ORCC-BDQ__0_J.js → cose-bilkent-JH36ORCC-CfLp6UDi.js} +1 -1
  19. package/dist/assets/{cynefin-OW5HDTMX-Djv4ghUb.js → cynefin-OW5HDTMX-Dm4dbVA1.js} +1 -1
  20. package/dist/assets/{cynefinDiagram-VND7K2PF-BULrlGYH.js → cynefinDiagram-VND7K2PF-B9mH-PID.js} +1 -1
  21. package/dist/assets/{dagre-6A5THRUB-D-jrT_hU.js → dagre-6A5THRUB-DDc_NVQJ.js} +1 -1
  22. package/dist/assets/{diagram-22UHCM2B-p2UhQmfL.js → diagram-22UHCM2B-lwr8Ip8-.js} +1 -1
  23. package/dist/assets/{diagram-3UASUU5V-DHvGWZ7q.js → diagram-3UASUU5V-BLLe6dmI.js} +1 -1
  24. package/dist/assets/{diagram-ATOU4E4O-ChhKDot-.js → diagram-ATOU4E4O-6eUIq4lz.js} +1 -1
  25. package/dist/assets/{diagram-CDSNMT55-B-T7A43R.js → diagram-CDSNMT55-O-pRN7VQ.js} +1 -1
  26. package/dist/assets/{diagram-MLGK6HIB-CJm8ENNY.js → diagram-MLGK6HIB-BietvGAI.js} +1 -1
  27. package/dist/assets/{diagram-MPIPVDR6-510tlza8.js → diagram-MPIPVDR6-Bd2Cl62X.js} +1 -1
  28. package/dist/assets/{ebnfDiagram-ZINNZB2B-PkvRPHel.js → ebnfDiagram-ZINNZB2B-B6iDy69W.js} +1 -1
  29. package/dist/assets/{elk-276RUBZZ-BVBVPoX3.js → elk-276RUBZZ-DQVHfKe0.js} +1 -1
  30. package/dist/assets/{erDiagram-OPXOYQCR-BgYMVm7L.js → erDiagram-OPXOYQCR-BWdVp2tQ.js} +1 -1
  31. package/dist/assets/{flowDiagram-KWPJA3E3-BGqsnc_3.js → flowDiagram-KWPJA3E3-WLE6rKB4.js} +1 -1
  32. package/dist/assets/{ganttDiagram-FUAMR5RP-D_-UeReH.js → ganttDiagram-FUAMR5RP-BbjS-4gn.js} +1 -1
  33. package/dist/assets/{gitGraphDiagram-X574FWY7-D0uooONx.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-zRsJERGJ.js → index-CIaHn5NS.js} +1 -1
  37. package/dist/assets/{infoDiagram-VRGFBTTK-B9e1z77f.js → infoDiagram-VRGFBTTK-D_jKWlKe.js} +1 -1
  38. package/dist/assets/{ishikawaDiagram-OU5B5YK6-BB4vNwvD.js → ishikawaDiagram-OU5B5YK6-DLJohSuK.js} +1 -1
  39. package/dist/assets/{journeyDiagram-ZHPQQLJL-DpkosOTB.js → journeyDiagram-ZHPQQLJL-DPOFj0qN.js} +1 -1
  40. package/dist/assets/{kanban-definition-PNTS6WVX-DbDP4kUu.js → kanban-definition-PNTS6WVX-BI7v-xpK.js} +1 -1
  41. package/dist/assets/{linear-DBJGPmjT.js → linear-C2pWPSxO.js} +1 -1
  42. package/dist/assets/{mermaid.core-DWPRi-ue.js → mermaid.core-CR-j5WQU.js} +5 -5
  43. package/dist/assets/{mindmap-definition-NLK3R4M7-nszBbJmx.js → mindmap-definition-NLK3R4M7-lz9GvXRh.js} +1 -1
  44. package/dist/assets/{pegDiagram-GJSIUBJH-CWykJWu_.js → pegDiagram-GJSIUBJH-Dl4_ia61.js} +1 -1
  45. package/dist/assets/{pieDiagram-5QR66LMP-g3Da-Vnv.js → pieDiagram-5QR66LMP-DWuT3n88.js} +1 -1
  46. package/dist/assets/{quadrantDiagram-O4NWA36T-Wzrlpzgh.js → quadrantDiagram-O4NWA36T-DXWe9Jx7.js} +1 -1
  47. package/dist/assets/{railroadDiagram-XR7U4H2S-moHTFMC0.js → railroadDiagram-XR7U4H2S-COY1IBVY.js} +1 -1
  48. package/dist/assets/{requirementDiagram-PLB6GJNP-CwfXdFH8.js → requirementDiagram-PLB6GJNP-DnyVuhsQ.js} +1 -1
  49. package/dist/assets/{sankeyDiagram-IPEJSGJF-3ZROYvnq.js → sankeyDiagram-IPEJSGJF-BSc4NdGK.js} +1 -1
  50. package/dist/assets/{sequenceDiagram-PO4LG4MO-E1TBebP8.js → sequenceDiagram-PO4LG4MO-kqEU2EGq.js} +1 -1
  51. package/dist/assets/{sizeCapture-INFHLROL-DHKEU5pu.js → sizeCapture-INFHLROL-BvB4bkWD.js} +1 -1
  52. package/dist/assets/{stateDiagram-v2-GCMORJYK-BguEj09D.js → stateDiagram-v2-GCMORJYK-BIrqNv7y.js} +1 -1
  53. package/dist/assets/{swimlanes-2SLR337P-C7IDYnhd.js → swimlanes-2SLR337P-CFnalQUE.js} +1 -1
  54. package/dist/assets/swimlanesDiagram-TC7HE7FX-C4a8qNGj.js +8 -0
  55. package/dist/assets/{timeline-definition-EJHVYXUP-DCW-F5PW.js → timeline-definition-EJHVYXUP-CsSWJnSQ.js} +1 -1
  56. package/dist/assets/{usecaseDiagram-POWQR4AR-xBMFtWYV.js → usecaseDiagram-POWQR4AR-DkAmJ1EE.js} +1 -1
  57. package/dist/assets/{vennDiagram-UO4OBE2U-B5nIkuH8.js → vennDiagram-UO4OBE2U-C3vd9drL.js} +1 -1
  58. package/dist/assets/{wardleyDiagram-VNRHLVJA-DYFAhdbr.js → wardleyDiagram-VNRHLVJA-j26CM-pi.js} +1 -1
  59. package/dist/assets/{xychartDiagram-PMCCYNJV-DeXwX6Cz.js → xychartDiagram-PMCCYNJV-BYu1Q_at.js} +1 -1
  60. package/dist/index.html +2 -2
  61. package/dist-server/companion/src/routes.js +8 -1
  62. package/dist-server/connector-proxy.js +105 -1
  63. package/dist-server/container-mcp.js +3 -1
  64. package/dist-server/drivers/agents-proxy.js +277 -22
  65. package/dist-server/index.js +23150 -19947
  66. package/dist-server/local-computer-proxy.js +2 -0
  67. package/dist-server/mcp-gate.js +2 -0
  68. package/dist-server/mcp-server.js +5 -3
  69. package/dist-server/message-search.worker.js +52 -0
  70. package/dist-server/openmausbot.js +540 -298
  71. package/dist-server/pair-cli.js +540 -298
  72. package/dist-server/server/admission.js +81 -0
  73. package/dist-server/server/aside-queue.js +294 -0
  74. package/dist-server/server/attachments.js +43 -1
  75. package/dist-server/server/bot-attachment.js +107 -0
  76. package/dist-server/server/bot-overview.js +19 -0
  77. package/dist-server/server/cli-api-setup.js +1 -0
  78. package/dist-server/server/composio.js +297 -1
  79. package/dist-server/server/config.js +25 -3
  80. package/dist-server/server/connector-advertisement.js +165 -0
  81. package/dist-server/server/connector-proxy.js +14 -2
  82. package/dist-server/server/connector-verdict.js +31 -4
  83. package/dist-server/server/container-computer.js +60 -0
  84. package/dist-server/server/contracts.js +22 -0
  85. package/dist-server/server/drivers/acp/core.js +24 -6
  86. package/dist-server/server/drivers/acp/cursor.js +3 -7
  87. package/dist-server/server/drivers/acp/droid.js +4 -7
  88. package/dist-server/server/drivers/acp/gemini.js +4 -4
  89. package/dist-server/server/drivers/acp/grok.js +4 -9
  90. package/dist-server/server/drivers/acp/hermes.js +3 -5
  91. package/dist-server/server/drivers/acp/opencode-go.js +2 -5
  92. package/dist-server/server/drivers/acp/qwen.js +9 -15
  93. package/dist-server/server/drivers/agents-call.js +137 -10
  94. package/dist-server/server/drivers/agents-catalog.js +100 -6
  95. package/dist-server/server/drivers/boxagent.js +2 -16
  96. package/dist-server/server/drivers/claude.js +31 -17
  97. package/dist-server/server/drivers/codex-catalog.js +14 -6
  98. package/dist-server/server/drivers/codex-identity.js +2 -1
  99. package/dist-server/server/drivers/codex-release.js +48 -0
  100. package/dist-server/server/drivers/codex.js +7 -25
  101. package/dist-server/server/drivers/openai-chat.js +26 -2
  102. package/dist-server/server/drivers/pi.js +237 -30
  103. package/dist-server/server/drivers/prompt-split.js +32 -9
  104. package/dist-server/server/drivers/session-idle.js +29 -0
  105. package/dist-server/server/env-path.js +15 -0
  106. package/dist-server/server/guarded-requests.js +2 -2
  107. package/dist-server/server/harness-capabilities.js +46 -0
  108. package/dist-server/server/index.js +1037 -209
  109. package/dist-server/server/message-db.js +22 -42
  110. package/dist-server/server/message-file.js +9 -1
  111. package/dist-server/server/message-search-query.js +43 -0
  112. package/dist-server/server/message-search-worker.js +80 -0
  113. package/dist-server/server/message-search.worker.js +21 -0
  114. package/dist-server/server/model-requests.js +248 -0
  115. package/dist-server/server/options-card.js +21 -0
  116. package/dist-server/server/org-library.js +15 -1
  117. package/dist-server/server/peer-delivery.js +60 -0
  118. package/dist-server/server/peer-roster.js +57 -0
  119. package/dist-server/server/presets.js +32 -32
  120. package/dist-server/server/profile-requests.js +66 -39
  121. package/dist-server/server/profile-revision.js +9 -1
  122. package/dist-server/server/profile-versions.js +6 -3
  123. package/dist-server/server/routine-requests.js +29 -7
  124. package/dist-server/server/skill-fetch.js +72 -14
  125. package/dist-server/server/skills.js +1 -0
  126. package/dist-server/server/store.js +32 -2
  127. package/dist-server/server/system-prompt.js +26 -2
  128. package/dist-server/server/team-setup-requests.js +124 -23
  129. package/dist-server/server/thread-events.js +3 -1
  130. package/dist-server/server/tightening-requests.js +405 -0
  131. package/dist-server/server/tts/chatterbox.js +27 -5
  132. package/dist-server/server/usage-ledger.js +7 -1
  133. package/dist-server/server/workspace-backup.js +40 -3
  134. package/dist-server/server/workspace-backup.worker.js +10 -0
  135. package/dist-server/shared/ask-question.js +19 -0
  136. package/dist-server/shared/credential-request.js +5 -1
  137. package/dist-server/shared/line-diff.js +23 -0
  138. package/dist-server/shared/model-request.js +1 -0
  139. package/dist-server/shared/options-card.js +51 -0
  140. package/dist-server/shared/profile-request.js +5 -4
  141. package/dist-server/shared/tightening-request.js +87 -0
  142. package/dist-server/src/lib/composer-attachments.js +25 -3
  143. package/dist-server/vps-container-mcp.js +3 -1
  144. package/dist-server/workspace-backup.worker.js +19474 -0
  145. package/package.json +1 -1
  146. package/dist/assets/channel-taCNRlUU.js +0 -1
  147. package/dist/assets/index-BwdMAQ6p.css +0 -1
  148. package/dist/assets/index-ByJennr7.js +0 -581
  149. package/dist/assets/swimlanesDiagram-TC7HE7FX-BpEdoB2e.js +0 -8
@@ -1,11 +1,113 @@
1
1
  // server/connector-proxy.ts
2
2
  import readline from "node:readline";
3
3
  import { randomUUID } from "node:crypto";
4
+
5
+ // shared/wire.ts
6
+ var CONNECTOR_SLUG_PATTERN = /^[a-z0-9][a-z0-9_-]{0,80}$/;
7
+ var CONNECTOR_TOOL_NAME_PATTERN = /^[A-Z][A-Z0-9_]{0,127}$/;
8
+
9
+ // server/connector-verdict.ts
10
+ function serviceSlugFor(tool) {
11
+ const underscore = tool.indexOf("_");
12
+ if (underscore <= 0) return null;
13
+ return tool.slice(0, underscore).toLowerCase();
14
+ }
15
+ function serviceSlugForCandidates(tool, candidates) {
16
+ let match = null;
17
+ for (const candidate of candidates) {
18
+ if (tool.startsWith(candidate.toUpperCase() + "_") && (match === null || candidate.length > match.length)) {
19
+ match = candidate;
20
+ }
21
+ }
22
+ return match;
23
+ }
24
+
25
+ // server/connector-advertisement.ts
26
+ var CONNECTOR_ALLOWED_TOOLS_ENV = "OMB_CONNECTOR_ALLOWED_TOOLS";
27
+ var CONNECTOR_SERVICE_SLUGS_ENV = "OMB_CONNECTOR_SERVICE_SLUGS";
28
+ var CONNECTOR_ALLOWED_TOOLS_MAX_BYTES = 32 * 1024;
29
+ var CONNECTOR_SERVICE_SLUGS_MAX_BYTES = 32 * 1024;
30
+ var CONNECTOR_META_TOOLS = [
31
+ "COMPOSIO_SEARCH_TOOLS",
32
+ "COMPOSIO_GET_TOOL_SCHEMAS",
33
+ "COMPOSIO_MULTI_EXECUTE_TOOL",
34
+ // Session-level connection meta-tools. COMPOSIO_MANAGE_CONNECTIONS ships
35
+ // in every Composio session; COMPOSIO_WAIT_FOR_CONNECTIONS is opt-in and
36
+ // off by default. They prefix as service "composio", which no grant
37
+ // names, so they ride this list instead of CONNECTION_TOOL_SUFFIXES —
38
+ // connector-proxy intercepts their calls to surface connection cards.
39
+ // The filter only keeps names upstream actually lists, so advertising an
40
+ // opted-out tool changes nothing.
41
+ "COMPOSIO_MANAGE_CONNECTIONS",
42
+ "COMPOSIO_WAIT_FOR_CONNECTIONS"
43
+ ];
44
+ var CONNECTION_TOOL_SUFFIXES = ["_MANAGE_CONNECTIONS", "_WAIT_FOR_CONNECTIONS"];
45
+ function parseConnectorAllowedToolsEnv(raw) {
46
+ if (!raw) return null;
47
+ let value;
48
+ try {
49
+ value = JSON.parse(raw);
50
+ } catch {
51
+ return null;
52
+ }
53
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
54
+ const grants = {};
55
+ for (const [slug, grant] of Object.entries(value)) {
56
+ if (!CONNECTOR_SLUG_PATTERN.test(slug)) return null;
57
+ if (!grant || typeof grant !== "object" || Array.isArray(grant)) return null;
58
+ const tools = grant.tools;
59
+ if (tools === "*") {
60
+ grants[slug] = { tools: "*" };
61
+ continue;
62
+ }
63
+ if (!Array.isArray(tools) || tools.length === 0) return null;
64
+ if (!tools.every((tool) => typeof tool === "string" && CONNECTOR_TOOL_NAME_PATTERN.test(tool))) {
65
+ return null;
66
+ }
67
+ grants[slug] = { tools };
68
+ }
69
+ return grants;
70
+ }
71
+ function parseConnectorServiceSlugsEnv(raw) {
72
+ if (!raw) return [];
73
+ let value;
74
+ try {
75
+ value = JSON.parse(raw);
76
+ } catch {
77
+ return [];
78
+ }
79
+ if (!Array.isArray(value)) return [];
80
+ return value.every((slug) => typeof slug === "string" && CONNECTOR_SLUG_PATTERN.test(slug)) ? value : [];
81
+ }
82
+ function connectorToolAdvertised(name, grants, candidates = []) {
83
+ if (Object.keys(grants).length > 0 && CONNECTOR_META_TOOLS.includes(name)) return true;
84
+ const service = serviceSlugForCandidates(name, candidates) ?? serviceSlugFor(name);
85
+ const grant = service === null ? void 0 : grants[service];
86
+ if (!grant) return false;
87
+ if (grant.tools === "*") return true;
88
+ if (grant.tools.includes(name)) return true;
89
+ return CONNECTION_TOOL_SUFFIXES.some((suffix) => name.endsWith(suffix));
90
+ }
91
+ function filterToolsListFrame(frame, grants, candidates = []) {
92
+ const result = frame.result;
93
+ if (!result || typeof result !== "object" || Array.isArray(result)) return frame;
94
+ const tools = result.tools;
95
+ if (!Array.isArray(tools)) return frame;
96
+ const kept = tools.filter((tool) => {
97
+ const name = tool && typeof tool === "object" && !Array.isArray(tool) ? tool.name : void 0;
98
+ return typeof name === "string" ? connectorToolAdvertised(name, grants, candidates) : true;
99
+ });
100
+ return { ...frame, result: { ...result, tools: kept } };
101
+ }
102
+
103
+ // server/connector-proxy.ts
4
104
  var UPSTREAM = process.env.OMB_CONNECTOR_UPSTREAM_URL ?? "";
5
105
  var HARNESS = process.env.OMB_HARNESS_URL ?? "http://127.0.0.1:8799";
6
106
  var BOT_ID = process.env.OMB_BOT_ID ?? "";
7
107
  var THREAD_ID = process.env.OMB_THREAD_ID ?? "";
8
108
  var TOKEN = process.env.OMB_CONNECTOR_TOKEN ?? process.env.OMB_COMMS_TOKEN ?? "";
109
+ var ALLOWED_TOOLS = parseConnectorAllowedToolsEnv(process.env[CONNECTOR_ALLOWED_TOOLS_ENV]);
110
+ var SERVICE_SLUGS = parseConnectorServiceSlugsEnv(process.env[CONNECTOR_SERVICE_SLUGS_ENV]);
9
111
  var MAX_RESPONSE_BYTES = 20 * 1024 * 1024;
10
112
  var INITIALIZE_RELAY_TIMEOUT_MS = 1e3;
11
113
  var RELAY_TIMEOUT_MS = 10 * 6e4;
@@ -176,7 +278,9 @@ async function handle(message) {
176
278
  }
177
279
  try {
178
280
  const response = await relay(message);
179
- if (response && id !== void 0) send(response);
281
+ if (response && id !== void 0) {
282
+ send(method === "tools/list" && ALLOWED_TOOLS ? filterToolsListFrame(response, ALLOWED_TOOLS, SERVICE_SLUGS) : response);
283
+ }
180
284
  } catch (error) {
181
285
  if (id === void 0) return;
182
286
  const messageText = error instanceof Error ? error.message : String(error);
@@ -44,6 +44,8 @@ function knownDirs() {
44
44
  join(home, ".volta", "bin"),
45
45
  join(home, ".bun", "bin"),
46
46
  join(home, ".asdf", "shims"),
47
+ join(home, ".local", "share", "mise", "shims"),
48
+ // mise installer
47
49
  join(home, ".deno", "bin"),
48
50
  join(home, "bin"),
49
51
  ...nvmBinDirs()
@@ -15642,7 +15644,7 @@ var MAX_CONCURRENT_BOT_THREADS = 10;
15642
15644
  var MIN_THREAD_EVENT_LOG_BYTES = 256 * 1024;
15643
15645
  var MAX_THREAD_EVENT_LOG_BYTES = 4 * 1024 * 1024 * 1024;
15644
15646
  var MIN_LOCAL_VM_MAX_INSTANCES = 1;
15645
- var MAX_LOCAL_VM_MAX_INSTANCES = 4;
15647
+ var MAX_LOCAL_VM_MAX_INSTANCES = 8;
15646
15648
  function isValidSshAlias(value) {
15647
15649
  return typeof value === "string" && SSH_ALIAS.test(value);
15648
15650
  }
@@ -50,6 +50,47 @@ function isCredentialTargetId(value) {
50
50
  return typeof value === "string" && Object.prototype.hasOwnProperty.call(CREDENTIAL_TARGETS, value);
51
51
  }
52
52
 
53
+ // shared/options-card.ts
54
+ var WATCHER_OPTIONS_CARD_BOT_ID = "f08dd8e3-f942-4eb4-8783-df32b26b88a4";
55
+ var OPTIONS_CARD_LIMITS = {
56
+ title: 120,
57
+ subtitle: 1e3,
58
+ option: 120,
59
+ minOptions: 2,
60
+ maxOptions: 6
61
+ };
62
+ function record(value) {
63
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
64
+ }
65
+ function boundedText(value, field, maximum) {
66
+ if (typeof value !== "string") return { error: `${field} must be a string.` };
67
+ const text = value.trim();
68
+ if (!text) return { error: `${field} must not be blank.` };
69
+ if (text.length > maximum) return { error: `${field} must be at most ${maximum} characters.` };
70
+ return text;
71
+ }
72
+ function parseOptionsCardInput(value) {
73
+ if (!record(value)) return { ok: false, error: "create_options_card needs an object." };
74
+ const title = boundedText(value.title, "title", OPTIONS_CARD_LIMITS.title);
75
+ if (typeof title !== "string") return { ok: false, error: title.error };
76
+ const subtitle = boundedText(value.subtitle, "subtitle", OPTIONS_CARD_LIMITS.subtitle);
77
+ if (typeof subtitle !== "string") return { ok: false, error: subtitle.error };
78
+ if (!Array.isArray(value.options)) return { ok: false, error: "options must be an array of strings." };
79
+ if (value.options.length < OPTIONS_CARD_LIMITS.minOptions || value.options.length > OPTIONS_CARD_LIMITS.maxOptions) {
80
+ return { ok: false, error: `options must contain ${OPTIONS_CARD_LIMITS.minOptions}-${OPTIONS_CARD_LIMITS.maxOptions} items.` };
81
+ }
82
+ const options = [];
83
+ const seen = /* @__PURE__ */ new Set();
84
+ for (let index = 0; index < value.options.length; index += 1) {
85
+ const option = boundedText(value.options[index], `options[${index}]`, OPTIONS_CARD_LIMITS.option);
86
+ if (typeof option !== "string") return { ok: false, error: option.error };
87
+ if (seen.has(option)) return { ok: false, error: `options must be unique; "${option}" is repeated.` };
88
+ seen.add(option);
89
+ options.push(option);
90
+ }
91
+ return { ok: true, value: { title, subtitle, options } };
92
+ }
93
+
53
94
  // server/agent-tool-policy.ts
54
95
  var READ_ONLY_AGENT_TOOL_NAMES = /* @__PURE__ */ new Set([
55
96
  "list_bots",
@@ -206,6 +247,26 @@ var ROUTINE_FIELDS_SCHEMA = {
206
247
  };
207
248
  var PROPOSAL_OUTCOME = " Read the result: granted Full Access may apply the change immediately. If applied, continue the requested work without another confirmation. Only a pending result requires ending the turn and waiting for the in-app decision. Never claim success from the permission mode alone; report failed or cancelled results honestly. This does not elevate another bot's execution permissions.";
208
249
  var toolDefinitions = (externalRuntime) => [
250
+ {
251
+ name: "create_options_card",
252
+ description: "Show the person a native card with 2-6 choices in this Watcher conversation. The card is passive: a click returns the selected words as a reply and never authorizes or performs an external action. Use it for Watcher's review and draft-selection steps, then wait for the person's response.",
253
+ inputSchema: {
254
+ type: "object",
255
+ additionalProperties: false,
256
+ properties: {
257
+ title: { type: "string", minLength: 1, maxLength: OPTIONS_CARD_LIMITS.title },
258
+ subtitle: { type: "string", minLength: 1, maxLength: OPTIONS_CARD_LIMITS.subtitle },
259
+ options: {
260
+ type: "array",
261
+ minItems: OPTIONS_CARD_LIMITS.minOptions,
262
+ maxItems: OPTIONS_CARD_LIMITS.maxOptions,
263
+ uniqueItems: true,
264
+ items: { type: "string", minLength: 1, maxLength: OPTIONS_CARD_LIMITS.option }
265
+ }
266
+ },
267
+ required: ["title", "subtitle", "options"]
268
+ }
269
+ },
209
270
  {
210
271
  name: "tool_result_read",
211
272
  description: "Read a missing portion of an oversized agents-tool result using the saved id and next offset from its notice. Returns at most 16,000 characters, only from this bot in this conversation. Use only when the preview is insufficient; do not load every page by default. Results expire after one hour, on app restart, or under cache pressure. This never reruns the original action.",
@@ -356,15 +417,42 @@ var toolDefinitions = (externalRuntime) => [
356
417
  required: ["title", "message"]
357
418
  }
358
419
  },
420
+ {
421
+ name: "vm_exec",
422
+ description: "Run a shell command inside your own Local VM (as the desktop user, starting in /home/cua/workspace) and get its exit code, stdout and stderr back as text. Use this for all command-line work in the VM: pip install --user, running a script, generating or converting a file, checking that a file exists. Do not type commands into a terminal window and read screenshots: that is slow and unreliable. GUI programs you start appear on the VM desktop. For a long job raise timeout_seconds (default 60, at most 300). Only available while you have a Local VM desktop.",
423
+ inputSchema: {
424
+ type: "object",
425
+ additionalProperties: false,
426
+ properties: {
427
+ command: { type: "string", description: "The shell command to run, for example: python3 make_report.py && ls -l report.pdf" },
428
+ timeout_seconds: { type: "integer", minimum: 1, maximum: 300, description: "How long it may run before it is stopped. Default 60." }
429
+ },
430
+ required: ["command"]
431
+ }
432
+ },
433
+ {
434
+ name: "attach_file",
435
+ description: "Attach a finished file to the chat so the user can preview and download it: an image, video, audio clip, PDF, spreadsheet, slide deck or other document you made. Pass its path: a path inside your computer's /home/cua/workspace (for example /home/cua/workspace/report.pdf), or a file in your working folder. Do this instead of pasting a VM path as a link; a path inside a VM cannot be opened from chat. Supported: images (png, jpg, gif, webp), video (mp4, webm, mov), audio (mp3, m4a, aac, wav, ogg, opus, flac), pdf, Word/Excel/PowerPoint and OpenDocument files, and csv, tsv, txt, md, json, rtf. Up to 25 MB (images 10 MB). Finish writing the file first, then call this directly: it reports an error if the file is missing, so you do not need to list or open the folder to check.",
436
+ inputSchema: {
437
+ type: "object",
438
+ additionalProperties: false,
439
+ properties: {
440
+ path: { type: "string", description: "The file's path, for example /home/cua/workspace/report.pdf." },
441
+ name: { type: "string", description: "Optional file name to show the user. Defaults to the file's own name." }
442
+ },
443
+ required: ["path"]
444
+ }
445
+ },
359
446
  {
360
447
  name: "post_to_room",
361
- description: "Put one message into a shared room you belong to, for example when the user asks you to tell the team something. Get group_id from list_rooms. This posts and returns: no room member's turn starts, nobody replies, and nothing comes back except confirmation \u2014 so never use it to ask a question or hand out work (use ask_bot or delegate_bot for those). Post once, say it in full, and tell the user what you posted. If a post is refused, do not retry it: say what you wanted to post in your reply instead.",
448
+ description: "Put one message into a shared room you belong to, for example when the user asks you to tell the team something. Get group_id from list_rooms. This posts and returns: no room member's turn starts, nobody replies, and nothing comes back except confirmation \u2014 so never use it to ask a question or hand out work (use ask_bot or delegate_bot for those). Post once, say it in full, and tell the user what you posted. Set attach_voice_note true to attach this turn's voice note. If a post is refused, do not retry it: say what you wanted to post in your reply instead.",
362
449
  inputSchema: {
363
450
  type: "object",
364
451
  additionalProperties: false,
365
452
  properties: {
366
453
  group_id: { type: "string", description: "The room's id, copied exactly from list_rooms." },
367
- message: { type: "string", description: "The complete message to post, written for the room to read as it stands." }
454
+ message: { type: "string", description: "The complete message to post, written for the room to read as it stands." },
455
+ attach_voice_note: { type: "boolean", description: "True to attach this turn's send_voice_note recording; the message is its caption. Call send_voice_note first." }
368
456
  },
369
457
  required: ["group_id", "message"]
370
458
  }
@@ -383,7 +471,12 @@ var toolDefinitions = (externalRuntime) => [
383
471
  model: { type: "string" },
384
472
  effort: { type: "string" },
385
473
  variant: { type: "string" }
386
- }, required: ["instanceId", "model"] }
474
+ }, required: ["instanceId", "model"] },
475
+ cwd: {
476
+ type: "string",
477
+ maxLength: 1024,
478
+ description: "Absolute path of the folder this specialist's tools read and write in (for example /Users/me/Projects/site). It must already exist. Leave it out for the specialist's private workspace."
479
+ }
387
480
  },
388
481
  required: ["name", "role", "instructions"]
389
482
  }
@@ -415,11 +508,13 @@ var toolDefinitions = (externalRuntime) => [
415
508
  chiefOfStaff: { type: "boolean", description: "Appoint or remove this team's Chief. At most one Chief per team: explicitly demote the current Chief in the same plan when replacing them. Does not grant access to other teams or change execution permissions." },
416
509
  description: { type: "string", maxLength: 4e3 },
417
510
  soul: { type: "string", description: "Standing instructions; required with name/title/modelSelection for every new bot." },
511
+ cwd: { type: "string", maxLength: 1024, description: "Create only: absolute path of the folder the new bot's tools read and write in. It must already exist. Leave it out for a private workspace." },
418
512
  section: { type: "string", maxLength: 60, description: "Exact authorized existing team, or a team explicitly named in newTeams. Empty string means General." },
419
513
  modelSelection: { type: "object", additionalProperties: false, properties: {
420
514
  instanceId: { type: "string" },
421
515
  model: { type: "string" },
422
- effort: { type: "string" }
516
+ effort: { type: "string" },
517
+ variant: { type: "string" }
423
518
  }, required: ["instanceId", "model"] }
424
519
  } }
425
520
  },
@@ -645,7 +740,7 @@ var toolDefinitions = (externalRuntime) => [
645
740
  },
646
741
  {
647
742
  name: "propose_profile",
648
- description: "Submit user-requested changes to your own name, title, description, standing instructions (SOUL.md), or working folder (cwd). Keep SOUL.md short \u2014 who you are and the rules you never break; put step-by-step procedure into a skill instead. A Chief of Staff may pass for_bot_id (from list_bots) for a requested change to another bot in its section." + PROPOSAL_OUTCOME,
743
+ description: "Submit user-requested changes to your own name, title, description, standing instructions (SOUL.md), working folder (cwd), or your alert and voice toggles (notifications, speakReplies). Keep SOUL.md short \u2014 who you are and the rules you never break; put step-by-step procedure into a skill instead. A Chief of Staff may pass for_bot_id (from list_bots) for a requested change to another bot in its section." + PROPOSAL_OUTCOME,
649
744
  inputSchema: {
650
745
  type: "object",
651
746
  additionalProperties: false,
@@ -659,6 +754,14 @@ var toolDefinitions = (externalRuntime) => [
659
754
  maxLength: 1024,
660
755
  description: "Absolute path of the folder your tools read and write in (for example /Users/me/Projects/site). It must already exist. An empty string means your private workspace."
661
756
  },
757
+ notifications: {
758
+ type: "boolean",
759
+ description: "Completion and attention notifications for this bot on the host and paired clients."
760
+ },
761
+ speakReplies: {
762
+ type: "boolean",
763
+ description: "Speak this bot's replies aloud as they settle, without being asked."
764
+ },
662
765
  reason: { type: "string", minLength: 1, maxLength: 500, description: "One sentence the user will see explaining why." },
663
766
  for_bot_id: {
664
767
  type: "string",
@@ -668,6 +771,34 @@ var toolDefinitions = (externalRuntime) => [
668
771
  required: ["reason"]
669
772
  }
670
773
  },
774
+ {
775
+ name: "propose_model",
776
+ description: "Submit a user-requested switch of this bot's default engine and model. Use the exact instance and model ids the person named, or for a Chief the ids from the team-setup catalog. The card warns about capabilities the switch gains or loses; existing threads keep their current models." + PROPOSAL_OUTCOME,
777
+ inputSchema: {
778
+ type: "object",
779
+ additionalProperties: false,
780
+ properties: {
781
+ model_selection: {
782
+ type: "object",
783
+ additionalProperties: false,
784
+ properties: {
785
+ instanceId: { type: "string", minLength: 1, description: "Engine instance id, for example codex or claude." },
786
+ model: { type: "string", minLength: 1, description: "Exact model id on that instance." },
787
+ effort: { type: "string", description: "Optional effort level the instance offers; omit for the engine default." },
788
+ variant: { type: "string", description: "Optional explicit model variant; choose this or effort, not both." }
789
+ },
790
+ required: ["instanceId", "model"],
791
+ description: "The new default selection."
792
+ },
793
+ reason: { type: "string", minLength: 1, maxLength: 500, description: "One sentence the user will see explaining why." },
794
+ for_bot_id: {
795
+ type: "string",
796
+ description: "Chief of Staff only: the id of another bot in your section whose default model this changes. Omit to change your own."
797
+ }
798
+ },
799
+ required: ["model_selection", "reason"]
800
+ }
801
+ },
671
802
  {
672
803
  name: "skills_list",
673
804
  description: "List this bot's imported skills (enabled and disabled) and any staged skill writes waiting for the user to confirm. Use this before skill_manage to avoid duplicate names. Listing does not enable anything.",
@@ -715,12 +846,14 @@ var VOICE_TOOL_NAMES = /* @__PURE__ */ new Set(["send_voice_note"]);
715
846
  var ROOM_ONLY_TOOLS = /* @__PURE__ */ new Set(["list_room_targets", "coordinate_bots"]);
716
847
  var ROOM_REPLACED_TOOLS = /* @__PURE__ */ new Set(["ask_bot", "delegate_bot", "check_delegation", "wait_delegation", "start_thread", "send_to_thread", "wait_thread"]);
717
848
  var EXTERNAL_TOOL_NAMES = /* @__PURE__ */ new Set(["list_bots", "ask_bot", "delegate_bot", "check_delegation", "wait_delegation"]);
849
+ var WATCHER_TOOL_NAMES = /* @__PURE__ */ new Set(["create_options_card"]);
718
850
  function availableTools(profile) {
719
851
  const TOOLS = toolDefinitions(profile.externalRuntime);
720
- const AUTHORING_TOOLS = profile.skillAuthoring ? TOOLS : TOOLS.filter((tool) => !SKILL_TOOL_NAMES.has(tool.name));
852
+ const BOT_SCOPED_TOOLS = profile.botId === WATCHER_OPTIONS_CARD_BOT_ID ? TOOLS : TOOLS.filter((tool) => !WATCHER_TOOL_NAMES.has(tool.name));
853
+ const AUTHORING_TOOLS = profile.skillAuthoring ? BOT_SCOPED_TOOLS : BOT_SCOPED_TOOLS.filter((tool) => !SKILL_TOOL_NAMES.has(tool.name));
721
854
  const SHAREABLE_TOOLS = profile.sharedComputers ? AUTHORING_TOOLS : AUTHORING_TOOLS.filter((tool) => !SHARED_COMPUTER_TOOL_NAMES.has(tool.name));
722
855
  const VOICE_READY_TOOLS = profile.voiceNotes ? SHAREABLE_TOOLS : SHAREABLE_TOOLS.filter((tool) => !VOICE_TOOL_NAMES.has(tool.name));
723
- return profile.externalRuntime ? TOOLS.filter((tool) => EXTERNAL_TOOL_NAMES.has(tool.name)) : profile.coordinating ? VOICE_READY_TOOLS.filter((tool) => !ROOM_REPLACED_TOOLS.has(tool.name) || tool.name === "start_thread" && profile.ownThreadCreation).map((tool) => tool.name === "start_thread" ? {
856
+ return profile.externalRuntime ? BOT_SCOPED_TOOLS.filter((tool) => EXTERNAL_TOOL_NAMES.has(tool.name)) : profile.coordinating ? VOICE_READY_TOOLS.filter((tool) => !ROOM_REPLACED_TOOLS.has(tool.name) || tool.name === "start_thread" && profile.ownThreadCreation).map((tool) => tool.name === "start_thread" ? {
724
857
  ...tool,
725
858
  description: "Open a separate job on yourself with its own history and run, without switching the person's selected conversation. Use only when the user requests independent jobs (for example one review per pull request). Give a short specific title and complete instructions; you can open at most five per turn. This is not a teammate handoff: use coordinate_bots for teammates and their automatic replies. Self-opened jobs cannot recursively open more jobs. If refused, do not retry; explain what remains.",
726
859
  inputSchema: { ...tool.inputSchema, properties: {
@@ -1632,7 +1765,7 @@ __export(external_exports, {
1632
1765
  promise: () => promise,
1633
1766
  property: () => _property,
1634
1767
  readonly: () => readonly,
1635
- record: () => record,
1768
+ record: () => record2,
1636
1769
  refine: () => refine,
1637
1770
  regex: () => _regex,
1638
1771
  regexes: () => regexes_exports,
@@ -13970,7 +14103,7 @@ __export(schemas_exports2, {
13970
14103
  preprocess: () => preprocess,
13971
14104
  promise: () => promise,
13972
14105
  readonly: () => readonly,
13973
- record: () => record,
14106
+ record: () => record2,
13974
14107
  refine: () => refine,
13975
14108
  set: () => set,
13976
14109
  strictObject: () => strictObject,
@@ -14939,7 +15072,7 @@ var ZodRecord = /* @__PURE__ */ $constructor("ZodRecord", (inst, def) => {
14939
15072
  inst.keyType = def.keyType;
14940
15073
  inst.valueType = def.valueType;
14941
15074
  });
14942
- function record(keyType, valueType, params) {
15075
+ function record2(keyType, valueType, params) {
14943
15076
  if (!valueType || !valueType._zod) {
14944
15077
  return new ZodRecord({
14945
15078
  type: "record",
@@ -15406,7 +15539,7 @@ var stringbool = (...args) => _stringbool({
15406
15539
  }, ...args);
15407
15540
  function json(params) {
15408
15541
  const jsonSchema = lazy(() => {
15409
- return union([string2(params), number2(), boolean2(), _null3(), array(jsonSchema), record(string2(), jsonSchema)]);
15542
+ return union([string2(params), number2(), boolean2(), _null3(), array(jsonSchema), record2(string2(), jsonSchema)]);
15410
15543
  });
15411
15544
  return jsonSchema;
15412
15545
  }
@@ -15975,6 +16108,15 @@ function peerName(value) {
15975
16108
  return clip(value.replace(/[[\]]/g, " "), ROSTER_NAME_MAX);
15976
16109
  }
15977
16110
 
16111
+ // server/peer-delivery.ts
16112
+ var named = (receipt) => peerName(receipt.botName ?? "") || peerName(receipt.botId) || "teammate";
16113
+ function renderPeerDeliveryReceipts(receipts) {
16114
+ if (!receipts.length) return "";
16115
+ const line = (receipt) => `${named(receipt)}: ${receipt.outcome} \u2014 ${receipt.detail}`;
16116
+ if (receipts.length === 1) return `Delivery to ${line(receipts[0])}`;
16117
+ return ["Delivery receipts:", ...receipts.map((receipt) => `- ${line(receipt)}`)].join("\n");
16118
+ }
16119
+
15978
16120
  // server/drivers/agents-client.ts
15979
16121
  function createHarnessClient(baseUrl, token) {
15980
16122
  async function api(path, init) {
@@ -16348,6 +16490,23 @@ function recallSpeaker(hit) {
16348
16490
  if (typeof hit.from === "string" && hit.from) return hit.from;
16349
16491
  return hit.role === "user" ? "user" : "you";
16350
16492
  }
16493
+ var isPeerDeliveryOutcome = (value) => value === "queued" || value === "injected" || value === "failed";
16494
+ function deliveryNote(r) {
16495
+ const receipt = r.receipt;
16496
+ if (!receipt || typeof receipt !== "object") return "";
16497
+ const { botId, botName, outcome, detail, taskId, requestId } = receipt;
16498
+ if (typeof botId !== "string" || !botId || typeof detail !== "string" || !detail) return "";
16499
+ if (!isPeerDeliveryOutcome(outcome)) return "";
16500
+ const parsed = {
16501
+ botId,
16502
+ ...typeof botName === "string" && botName ? { botName } : {},
16503
+ outcome,
16504
+ detail,
16505
+ ...typeof taskId === "string" && taskId ? { taskId } : {},
16506
+ ...typeof requestId === "string" && requestId ? { requestId } : {}
16507
+ };
16508
+ return renderPeerDeliveryReceipts([parsed]);
16509
+ }
16351
16510
  function recallWhen(at, dateOnly) {
16352
16511
  if (typeof at !== "number" || !Number.isFinite(at)) return "";
16353
16512
  const said = new Date(at);
@@ -16360,6 +16519,43 @@ async function callTool(name, args, context) {
16360
16519
  const { botId: BOT_ID, threadId: THREAD_ID, depth: DEPTH, externalRuntime: EXTERNAL_RUNTIME, coordinating: COORDINATING, turn } = context;
16361
16520
  const { delegationTaskIdsThisTurn } = turn;
16362
16521
  const { api, apiResponse } = context.client;
16522
+ if (name === "vm_exec") {
16523
+ const { ok: ok2, body } = await apiResponse("/api/internal/vm-exec", {
16524
+ method: "POST",
16525
+ body: JSON.stringify({ command: args.command, ...typeof args.timeout_seconds === "number" ? { timeout_seconds: args.timeout_seconds } : {} })
16526
+ });
16527
+ if (!ok2) return { text: String(body.error ?? "Could not run that command."), isError: true };
16528
+ const exitCode = Number(body.exitCode ?? 0);
16529
+ const stdout = String(body.stdout ?? "");
16530
+ const stderr = String(body.stderr ?? "");
16531
+ const lines = [body.timedOut ? "The command was stopped: it ran past its time limit." : `exit code ${exitCode}`];
16532
+ if (stdout) lines.push("--- stdout ---", stdout.replace(/\s+$/, ""));
16533
+ if (stderr) lines.push("--- stderr ---", stderr.replace(/\s+$/, ""));
16534
+ if (!stdout && !stderr && !body.timedOut) lines.push("(no output)");
16535
+ return { text: lines.join("\n"), ...exitCode !== 0 || body.timedOut ? { isError: true } : {} };
16536
+ }
16537
+ if (name === "attach_file") {
16538
+ const { ok: ok2, body } = await apiResponse("/api/internal/attach-file", {
16539
+ method: "POST",
16540
+ body: JSON.stringify({ path: args.path, ...typeof args.name === "string" ? { name: args.name } : {} })
16541
+ });
16542
+ if (!ok2) return { text: String(body.error ?? "Could not attach that file."), isError: true };
16543
+ return { text: `Attached ${String(body.name ?? "the file")} (${Number(body.bytes ?? 0)} bytes). It now appears in the chat with a preview.` };
16544
+ }
16545
+ if (name === "create_options_card") {
16546
+ if (BOT_ID !== WATCHER_OPTIONS_CARD_BOT_ID) {
16547
+ return { text: "create_options_card is not enabled for this bot.", isError: true };
16548
+ }
16549
+ const parsed = parseOptionsCardInput(args);
16550
+ if (!parsed.ok) return { text: parsed.error, isError: true };
16551
+ const result = await api("/api/internal/options-card", {
16552
+ method: "POST",
16553
+ body: JSON.stringify(parsed.value)
16554
+ });
16555
+ return {
16556
+ text: `Rendered the native options card in this Watcher thread (message ${String(result.messageId ?? "created")}). Wait for the person's click or custom response; the card itself authorizes no external action.`
16557
+ };
16558
+ }
16363
16559
  if (SHARED_COMPUTER_TOOL_NAMES.has(name) && !context.sharedComputers) {
16364
16560
  return { text: "Computer sharing is turned off in this workspace. There are no shared computers to use.", isError: true };
16365
16561
  }
@@ -16456,6 +16652,7 @@ Use post_to_room with one of these ids. A post adds one message to the room; it
16456
16652
  if (name === "post_to_room") {
16457
16653
  const groupId = String(args.group_id ?? "").trim();
16458
16654
  const message = String(args.message ?? "").trim();
16655
+ const attachVoiceNote = args.attach_voice_note === true;
16459
16656
  if (!groupId || !message) {
16460
16657
  return { text: "post_to_room needs group_id (from list_rooms) and message.", isError: true };
16461
16658
  }
@@ -16467,12 +16664,12 @@ Use post_to_room with one of these ids. A post adds one message to the room; it
16467
16664
  }
16468
16665
  const r = await api("/api/internal/post-to-room", {
16469
16666
  method: "POST",
16470
- body: JSON.stringify({ fromBotId: BOT_ID, fromThreadId: THREAD_ID, groupId, message })
16667
+ body: JSON.stringify({ fromBotId: BOT_ID, fromThreadId: THREAD_ID, groupId, message, attachVoiceNote })
16471
16668
  });
16472
16669
  if (r.error) return { text: String(r.error), isError: true };
16473
16670
  turn.roomPostsThisTurn += 1;
16474
16671
  return {
16475
- text: `Posted in ${r.roomName ?? "the room"}. Nobody's turn was started, so expect no reply \u2014 tell the user it is posted.`
16672
+ text: `Posted in ${r.roomName ?? "the room"}${r.attachedVoiceNote ? " with the voice note attached" : ""}. Nobody's turn was started, so expect no reply \u2014 tell the user it is posted.`
16476
16673
  };
16477
16674
  }
16478
16675
  if (name === "ask_bot") {
@@ -16483,6 +16680,7 @@ Use post_to_room with one of these ids. A post adds one message to the room; it
16483
16680
  method: "POST",
16484
16681
  body: JSON.stringify({ fromBotId: BOT_ID, fromThreadId: THREAD_ID, toBotId, message, depth: DEPTH })
16485
16682
  });
16683
+ const note = deliveryNote(r);
16486
16684
  if (r.timeout) {
16487
16685
  const taskId = String(r.taskId ?? "").trim();
16488
16686
  if (taskId && !EXTERNAL_RUNTIME) delegationTaskIdsThisTurn.add(taskId);
@@ -16490,22 +16688,42 @@ Use post_to_room with one of these ids. A post adds one message to the room; it
16490
16688
  const amount = waitedSeconds < 60 ? waitedSeconds : Math.round(waitedSeconds / 60);
16491
16689
  const unit = waitedSeconds < 60 ? "second" : "minute";
16492
16690
  return {
16493
- text: `${r.toBotName ?? "That bot"} is still working after ${amount} ${unit}${amount === 1 ? "" : "s"} \u2014 the ask was converted to a delegation so the reply is not lost. Task id: ${taskId}. ${EXTERNAL_RUNTIME ? EXTERNAL_STATUS_GUIDANCE : "Finish your turn now; the result will be delivered to this conversation automatically. Use check_delegation in a later turn only if the user asks for status."}`
16691
+ text: `${r.toBotName ?? "That bot"} is still working after ${amount} ${unit}${amount === 1 ? "" : "s"} \u2014 the ask was converted to a delegation so the reply is not lost. Task id: ${taskId}. ${EXTERNAL_RUNTIME ? EXTERNAL_STATUS_GUIDANCE : "Finish your turn now; the result will be delivered to this conversation automatically. Use check_delegation in a later turn only if the user asks for status."}${note ? `
16692
+
16693
+ ${note}` : ""}`
16494
16694
  };
16495
16695
  }
16496
16696
  if (r.busy) {
16697
+ if (r.aside === "injected") {
16698
+ return {
16699
+ text: `${r.toBotName ?? "That bot"} is mid-turn; your message was handed to them as an aside \u2014 peer context folded into their current work, not a request that interrupts or replies. Finish your turn and treat their eventual output as possibly informed by it.`
16700
+ };
16701
+ }
16702
+ if (r.aside === "queued") {
16703
+ return {
16704
+ text: `${r.toBotName ?? "That bot"} is mid-turn; your aside is queued and will reach them as context when the turn settles. No reply is expected \u2014 finish your turn.`
16705
+ };
16706
+ }
16497
16707
  const taskId = String(r.taskId ?? "").trim();
16498
16708
  if (taskId) {
16499
16709
  if (!EXTERNAL_RUNTIME) delegationTaskIdsThisTurn.add(taskId);
16500
16710
  return {
16501
- text: `${r.toBotName ?? "That bot"} is busy right now, so your message was queued as a delegation instead \u2014 ${EXTERNAL_RUNTIME ? "it waits for the peer and any required approval" : "it runs after your current turn ends"}. Task id: ${taskId}. ${EXTERNAL_RUNTIME ? EXTERNAL_STATUS_GUIDANCE : "Finish your turn now; the result will be delivered to this conversation automatically. Use check_delegation in a later turn only if the user asks for status."}`
16711
+ text: `${r.toBotName ?? "That bot"} is busy right now, so your message was queued as a delegation instead \u2014 ${EXTERNAL_RUNTIME ? "it waits for the peer and any required approval" : "it runs after your current turn ends"}. Task id: ${taskId}. ${EXTERNAL_RUNTIME ? EXTERNAL_STATUS_GUIDANCE : "Finish your turn now; the result will be delivered to this conversation automatically. Use check_delegation in a later turn only if the user asks for status."}${note ? `
16712
+
16713
+ ${note}` : ""}`
16502
16714
  };
16503
16715
  }
16504
- return { text: `That bot is busy right now \u2014 try again after it finishes.` };
16716
+ return { text: `That bot is busy right now \u2014 try again after it finishes.${note ? `
16717
+
16718
+ ${note}` : ""}` };
16505
16719
  }
16506
- if (r.error) return { text: `Couldn't reach that bot: ${r.error}`, isError: true };
16720
+ if (r.error) return { text: `Couldn't reach that bot: ${r.error}${note ? `
16721
+
16722
+ ${note}` : ""}`, isError: true };
16507
16723
  return { text: `${r.botName ?? "Bot"} replied:
16508
- ${r.text ?? "(no reply)"}` };
16724
+ ${r.text ?? "(no reply)"}${note ? `
16725
+
16726
+ ${note}` : ""}` };
16509
16727
  }
16510
16728
  if (name === "delegate_bot") {
16511
16729
  const toBotId = String(args.bot_id ?? "").trim();
@@ -16521,12 +16739,17 @@ ${r.text ?? "(no reply)"}` };
16521
16739
  };
16522
16740
  if (reason) body.reason = reason;
16523
16741
  const r = await api(`/api/internal/delegate-bot`, { method: "POST", body: JSON.stringify(body) });
16524
- if (r.error) return { text: `Couldn't queue the delegation: ${r.error}`, isError: true };
16742
+ const delivery = deliveryNote(r);
16743
+ if (r.error) return { text: `Couldn't queue the delegation: ${r.error}${delivery ? `
16744
+
16745
+ ${delivery}` : ""}`, isError: true };
16525
16746
  const note = typeof r.message === "string" ? r.message : "Delegation queued.";
16526
16747
  const taskId = typeof r.taskId === "string" ? r.taskId.trim() : "";
16527
16748
  if (taskId && !EXTERNAL_RUNTIME) delegationTaskIdsThisTurn.add(taskId);
16528
16749
  const suffix = taskId ? ` Task id: ${taskId}. ${EXTERNAL_RUNTIME ? EXTERNAL_STATUS_GUIDANCE : "Acknowledge the assignment and finish your turn; the result will be delivered to this conversation automatically. Do not check or wait for it in this turn."}` : "";
16529
- return { text: `${note}${suffix}` };
16750
+ return { text: `${note}${suffix}${delivery ? `
16751
+
16752
+ ${delivery}` : ""}` };
16530
16753
  }
16531
16754
  if (name === "check_delegation" || name === "wait_delegation") {
16532
16755
  const taskId = String(args.task_id ?? "").trim();
@@ -16680,7 +16903,8 @@ ${lines.join("\n")}` };
16680
16903
  name: botName,
16681
16904
  role,
16682
16905
  instructions,
16683
- ...args.modelSelection !== void 0 ? { modelSelection: args.modelSelection } : {}
16906
+ ...args.modelSelection !== void 0 ? { modelSelection: args.modelSelection } : {},
16907
+ ...typeof args.cwd === "string" ? { cwd: args.cwd.trim() } : {}
16684
16908
  })
16685
16909
  });
16686
16910
  turn.createdThisTurn += 1;
@@ -16846,14 +17070,19 @@ ${JSON.stringify(routines, null, 2)}`
16846
17070
  return confirmationResult(r, `${action.replace("_", " ")} on routine ${routineId}`);
16847
17071
  }
16848
17072
  if (name === "propose_profile") {
17073
+ if (args.notifications !== void 0 && typeof args.notifications !== "boolean" || args.speakReplies !== void 0 && typeof args.speakReplies !== "boolean") {
17074
+ return { text: "propose_profile notifications and speakReplies must be true or false.", isError: true };
17075
+ }
16849
17076
  const changes = {};
16850
17077
  if (typeof args.name === "string") changes.name = args.name.trim();
16851
17078
  if (typeof args.title === "string") changes.title = args.title.trim();
16852
17079
  if (typeof args.description === "string") changes.description = args.description.trim();
16853
17080
  if (typeof args.soul === "string") changes.soul = args.soul;
16854
17081
  if (typeof args.cwd === "string") changes.cwd = args.cwd.trim();
17082
+ if (typeof args.notifications === "boolean") changes.notifications = args.notifications;
17083
+ if (typeof args.speakReplies === "boolean") changes.speakReplies = args.speakReplies;
16855
17084
  if (!Object.keys(changes).length) {
16856
- return { text: "propose_profile needs at least one of name, title, description, soul, or cwd.", isError: true };
17085
+ return { text: "propose_profile needs at least one of name, title, description, soul, cwd, notifications, or speakReplies.", isError: true };
16857
17086
  }
16858
17087
  const forBotId = String(args.for_bot_id ?? "").trim();
16859
17088
  const r = await api("/api/internal/profile-requests", {
@@ -16869,6 +17098,32 @@ ${JSON.stringify(routines, null, 2)}`
16869
17098
  });
16870
17099
  return confirmationResult(r, "the profile change", "profile");
16871
17100
  }
17101
+ if (name === "propose_model") {
17102
+ const raw = args.model_selection;
17103
+ const fields = typeof raw === "object" && raw !== null && !Array.isArray(raw) ? raw : null;
17104
+ if (!fields || typeof fields.instanceId !== "string" || !fields.instanceId.trim() || typeof fields.model !== "string" || !fields.model.trim()) {
17105
+ return { text: "propose_model needs model_selection with instanceId and model.", isError: true };
17106
+ }
17107
+ if (fields.effort !== void 0 && (typeof fields.effort !== "string" || !fields.effort.trim()) || fields.variant !== void 0 && (typeof fields.variant !== "string" || !fields.variant.trim())) {
17108
+ return { text: "propose_model effort and variant must be non-empty strings when supplied.", isError: true };
17109
+ }
17110
+ const selection = { instanceId: fields.instanceId.trim(), model: fields.model.trim() };
17111
+ if (typeof fields.effort === "string" && fields.effort.trim()) selection.effort = fields.effort.trim();
17112
+ if (typeof fields.variant === "string" && fields.variant.trim()) selection.variant = fields.variant.trim();
17113
+ const forBotId = String(args.for_bot_id ?? "").trim();
17114
+ const r = await api("/api/internal/model-requests", {
17115
+ method: "POST",
17116
+ body: JSON.stringify({
17117
+ fromBotId: BOT_ID,
17118
+ fromThreadId: THREAD_ID,
17119
+ modelSelection: selection,
17120
+ reason: args.reason,
17121
+ // JSON.stringify drops the key entirely when no target was named
17122
+ forBotId: forBotId || void 0
17123
+ })
17124
+ });
17125
+ return confirmationResult(r, "the default model change", "model");
17126
+ }
16872
17127
  if (name === "memory_update") {
16873
17128
  if (!["append", "replace", "remove", "supersede"].includes(String(args.action)) || args.action !== "remove" && (typeof args.text !== "string" || !args.text.trim()) || args.action !== "append" && (typeof args.old_text !== "string" || !args.old_text.trim())) {
16874
17129
  return { text: "Use memory_update action=append with text, replace or supersede with text and old_text, or remove with old_text.", isError: true };