openmausbot 0.1.87 → 0.1.89
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 +2 -1
- package/dist/assets/{abnfDiagram-O67JEVCF-BTiuL79F.js → abnfDiagram-O67JEVCF-BwaMaZEm.js} +1 -1
- package/dist/assets/{arc-C88Blc2v.js → arc-Be-8GKxd.js} +1 -1
- package/dist/assets/{architectureDiagram-NJMV4G6O-BZsJIZrb.js → architectureDiagram-NJMV4G6O-Dp_U2Kv1.js} +1 -1
- package/dist/assets/{blockDiagram-BEXU5L5S-CVeMe9Ah.js → blockDiagram-BEXU5L5S-DQjUjYUk.js} +1 -1
- package/dist/assets/{c4Diagram-YGBWAQC7-Cedk3Bhx.js → c4Diagram-YGBWAQC7-Bd3aE4Kc.js} +1 -1
- package/dist/assets/channel-DqY_gMLg.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-D-VRqzTB.js → chunk-2Q5K7J3B-BijjTBeJ.js} +1 -1
- package/dist/assets/{chunk-3FUC2YCW-BChov01Y.js → chunk-3FUC2YCW-CMGdwvzW.js} +2 -2
- package/dist/assets/{chunk-5VM5RSS4-C-TfwvHy.js → chunk-5VM5RSS4-0G09BfsZ.js} +1 -1
- package/dist/assets/{chunk-GTNCS2PH-Db7CGj5Y.js → chunk-GTNCS2PH-FOp3vSHe.js} +1 -1
- package/dist/assets/{chunk-GWA4HPMP-CNvIfqKT.js → chunk-GWA4HPMP-Dv-20VIn.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-C_bEp5tq.js → chunk-JWPE2WC7-CRgrdFcz.js} +1 -1
- package/dist/assets/{chunk-NETBCI7D-Coi4BOls.js → chunk-NETBCI7D-3hP4f7QB.js} +1 -1
- package/dist/assets/{chunk-WEXAMYUT-CzylEuL3.js → chunk-WEXAMYUT-CG98IjlG.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-m7p7ztaD.js → chunk-XXDRQBXY-BrSTC9xG.js} +1 -1
- package/dist/assets/{classDiagram-v2-NBCMYWYE-BMy6o9x2.js → classDiagram-v2-NBCMYWYE-rpQQQDFX.js} +1 -1
- package/dist/assets/{cose-bilkent-JH36ORCC-BDQ__0_J.js → cose-bilkent-JH36ORCC-G6RSafHA.js} +1 -1
- package/dist/assets/{cynefin-OW5HDTMX-Djv4ghUb.js → cynefin-OW5HDTMX-CXjv-IM2.js} +1 -1
- package/dist/assets/{cynefinDiagram-VND7K2PF-BULrlGYH.js → cynefinDiagram-VND7K2PF-Dw07MR9f.js} +1 -1
- package/dist/assets/{dagre-6A5THRUB-D-jrT_hU.js → dagre-6A5THRUB-BwPnlTg2.js} +1 -1
- package/dist/assets/{diagram-22UHCM2B-p2UhQmfL.js → diagram-22UHCM2B-Cm6xcMiz.js} +1 -1
- package/dist/assets/{diagram-3UASUU5V-DHvGWZ7q.js → diagram-3UASUU5V-CDpogPbE.js} +1 -1
- package/dist/assets/{diagram-ATOU4E4O-ChhKDot-.js → diagram-ATOU4E4O-BV8z3Pnc.js} +1 -1
- package/dist/assets/{diagram-CDSNMT55-B-T7A43R.js → diagram-CDSNMT55-CKCQhOOC.js} +1 -1
- package/dist/assets/{diagram-MLGK6HIB-CJm8ENNY.js → diagram-MLGK6HIB-FGvJg69C.js} +1 -1
- package/dist/assets/{diagram-MPIPVDR6-510tlza8.js → diagram-MPIPVDR6-CwATvgnS.js} +1 -1
- package/dist/assets/{ebnfDiagram-ZINNZB2B-PkvRPHel.js → ebnfDiagram-ZINNZB2B-CH_c3SSC.js} +1 -1
- package/dist/assets/{elk-276RUBZZ-BVBVPoX3.js → elk-276RUBZZ-3guZi71R.js} +1 -1
- package/dist/assets/{erDiagram-OPXOYQCR-BgYMVm7L.js → erDiagram-OPXOYQCR-BMHphR2n.js} +1 -1
- package/dist/assets/{flowDiagram-KWPJA3E3-BGqsnc_3.js → flowDiagram-KWPJA3E3-Vm5DkW5x.js} +1 -1
- package/dist/assets/{ganttDiagram-FUAMR5RP-D_-UeReH.js → ganttDiagram-FUAMR5RP-DO-1ct07.js} +1 -1
- package/dist/assets/{gitGraphDiagram-X574FWY7-D0uooONx.js → gitGraphDiagram-X574FWY7-nxeksEIN.js} +1 -1
- package/dist/assets/index-BIMdIbaw.css +1 -0
- package/dist/assets/index-BVPdbq2r.js +583 -0
- package/dist/assets/{index-zRsJERGJ.js → index-Oxk9x45i.js} +1 -1
- package/dist/assets/{infoDiagram-VRGFBTTK-B9e1z77f.js → infoDiagram-VRGFBTTK-Dkcvr0p7.js} +1 -1
- package/dist/assets/{ishikawaDiagram-OU5B5YK6-BB4vNwvD.js → ishikawaDiagram-OU5B5YK6-BwVibkoO.js} +1 -1
- package/dist/assets/{journeyDiagram-ZHPQQLJL-DpkosOTB.js → journeyDiagram-ZHPQQLJL-ByiBZqgG.js} +1 -1
- package/dist/assets/{kanban-definition-PNTS6WVX-DbDP4kUu.js → kanban-definition-PNTS6WVX-qqjgXBF3.js} +1 -1
- package/dist/assets/{linear-DBJGPmjT.js → linear-CtJnKe6A.js} +1 -1
- package/dist/assets/{mermaid.core-DWPRi-ue.js → mermaid.core-BEiDklrY.js} +5 -5
- package/dist/assets/{mindmap-definition-NLK3R4M7-nszBbJmx.js → mindmap-definition-NLK3R4M7-DfIYHia9.js} +1 -1
- package/dist/assets/{pegDiagram-GJSIUBJH-CWykJWu_.js → pegDiagram-GJSIUBJH-CYyRqa1a.js} +1 -1
- package/dist/assets/{pieDiagram-5QR66LMP-g3Da-Vnv.js → pieDiagram-5QR66LMP-hOToy_zH.js} +1 -1
- package/dist/assets/{quadrantDiagram-O4NWA36T-Wzrlpzgh.js → quadrantDiagram-O4NWA36T-DKAsNN9I.js} +1 -1
- package/dist/assets/{railroadDiagram-XR7U4H2S-moHTFMC0.js → railroadDiagram-XR7U4H2S-BSA-d8JU.js} +1 -1
- package/dist/assets/{requirementDiagram-PLB6GJNP-CwfXdFH8.js → requirementDiagram-PLB6GJNP-CSmlvb-p.js} +1 -1
- package/dist/assets/{sankeyDiagram-IPEJSGJF-3ZROYvnq.js → sankeyDiagram-IPEJSGJF-CD5F6fnG.js} +1 -1
- package/dist/assets/{sequenceDiagram-PO4LG4MO-E1TBebP8.js → sequenceDiagram-PO4LG4MO-JUq5sx6d.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-DHKEU5pu.js → sizeCapture-INFHLROL-BjIS-Stk.js} +1 -1
- package/dist/assets/{stateDiagram-v2-GCMORJYK-BguEj09D.js → stateDiagram-v2-GCMORJYK-DklkUxOg.js} +1 -1
- package/dist/assets/{swimlanes-2SLR337P-C7IDYnhd.js → swimlanes-2SLR337P-CsjIU6be.js} +1 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-Bk14M0_B.js +8 -0
- package/dist/assets/{timeline-definition-EJHVYXUP-DCW-F5PW.js → timeline-definition-EJHVYXUP-BkFqK7AB.js} +1 -1
- package/dist/assets/{usecaseDiagram-POWQR4AR-xBMFtWYV.js → usecaseDiagram-POWQR4AR-70gJB1EU.js} +1 -1
- package/dist/assets/{vennDiagram-UO4OBE2U-B5nIkuH8.js → vennDiagram-UO4OBE2U-83Tm62dN.js} +1 -1
- package/dist/assets/{wardleyDiagram-VNRHLVJA-DYFAhdbr.js → wardleyDiagram-VNRHLVJA-tTmseoX9.js} +1 -1
- package/dist/assets/{xychartDiagram-PMCCYNJV-DeXwX6Cz.js → xychartDiagram-PMCCYNJV-R1LBA55a.js} +1 -1
- package/dist/index.html +2 -2
- package/dist-server/companion/src/routes.js +8 -1
- package/dist-server/connector-proxy.js +105 -1
- package/dist-server/container-mcp.js +11 -1
- package/dist-server/drivers/agents-proxy.js +277 -22
- package/dist-server/index.js +23722 -20272
- package/dist-server/local-computer-proxy.js +2 -0
- 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 +755 -404
- package/dist-server/pair-cli.js +755 -404
- package/dist-server/server/admission.js +81 -0
- package/dist-server/server/aside-queue.js +294 -0
- package/dist-server/server/attachments.js +43 -1
- package/dist-server/server/automatic-recovery.js +15 -0
- package/dist-server/server/bot-attachment.js +107 -0
- package/dist-server/server/bot-overview.js +19 -0
- package/dist-server/server/composio.js +297 -1
- package/dist-server/server/config.js +34 -3
- 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 +31 -4
- package/dist-server/server/container-computer.js +70 -0
- package/dist-server/server/contracts.js +32 -0
- package/dist-server/server/drivers/acp/core.js +148 -23
- 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 +12 -15
- package/dist-server/server/drivers/agents-call.js +137 -10
- package/dist-server/server/drivers/agents-catalog.js +100 -6
- package/dist-server/server/drivers/boxagent.js +3 -17
- package/dist-server/server/drivers/claude.js +31 -17
- 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-release.js +48 -0
- package/dist-server/server/drivers/codex.js +7 -25
- package/dist-server/server/drivers/openai-chat.js +31 -3
- package/dist-server/server/drivers/pi.js +237 -30
- package/dist-server/server/drivers/prompt-split.js +32 -9
- package/dist-server/server/drivers/session-idle.js +29 -0
- package/dist-server/server/env-path.js +15 -0
- package/dist-server/server/guarded-requests.js +2 -2
- package/dist-server/server/harness-capabilities.js +61 -0
- package/dist-server/server/index.js +1173 -262
- 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/options-card.js +21 -0
- package/dist-server/server/org-library.js +15 -1
- package/dist-server/server/peer-delivery.js +60 -0
- package/dist-server/server/peer-roster.js +57 -0
- package/dist-server/server/presets.js +32 -32
- 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/routine-requests.js +29 -7
- package/dist-server/server/skill-fetch.js +72 -14
- package/dist-server/server/skills.js +1 -0
- package/dist-server/server/store.js +32 -2
- package/dist-server/server/system-prompt.js +37 -2
- package/dist-server/server/team-setup-requests.js +124 -23
- package/dist-server/server/thread-events.js +3 -1
- package/dist-server/server/tightening-requests.js +405 -0
- package/dist-server/server/tts/chatterbox.js +27 -5
- package/dist-server/server/usage-ledger.js +7 -1
- package/dist-server/server/webhooks.js +21 -3
- package/dist-server/server/workspace-backup.js +40 -3
- package/dist-server/server/workspace-backup.worker.js +10 -0
- package/dist-server/shared/ask-question.js +19 -0
- package/dist-server/shared/credential-request.js +5 -1
- 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/profile-request.js +5 -4
- package/dist-server/shared/tightening-request.js +87 -0
- package/dist-server/src/lib/composer-attachments.js +42 -3
- package/dist-server/vps-container-mcp.js +11 -1
- package/dist-server/workspace-backup.worker.js +19482 -0
- package/package.json +1 -1
- package/dist/assets/channel-taCNRlUU.js +0 -1
- package/dist/assets/index-BwdMAQ6p.css +0 -1
- package/dist/assets/index-ByJennr7.js +0 -581
- package/dist/assets/swimlanesDiagram-TC7HE7FX-BpEdoB2e.js +0 -8
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
// The generic protocol runtime lives in acp/core.ts; this file is only the
|
|
5
5
|
// per-harness quirks. Verified against grok 1.0.0.
|
|
6
6
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
7
|
-
import { homedir } from "node:os";
|
|
8
7
|
import { join } from "node:path";
|
|
8
|
+
import { harnessHome } from "../../env-path.js";
|
|
9
9
|
import { decodeInjectId, hostApiKey, localHost, mergeLocalInject } from "../local-inject.js";
|
|
10
10
|
import { createAcpDriver } from "./core.js";
|
|
11
11
|
export const STATIC_GROK_MODELS = {
|
|
@@ -17,11 +17,6 @@ export const STATIC_GROK_MODELS = {
|
|
|
17
17
|
],
|
|
18
18
|
};
|
|
19
19
|
const SLUG = /^[a-z0-9][a-z0-9._-]*$/i;
|
|
20
|
-
function grokHome(env) {
|
|
21
|
-
if (env.GROK_HOME)
|
|
22
|
-
return env.GROK_HOME;
|
|
23
|
-
return join(env.HOME || env.USERPROFILE || homedir(), ".grok");
|
|
24
|
-
}
|
|
25
20
|
function unquote(raw) {
|
|
26
21
|
const value = raw.trim();
|
|
27
22
|
if (value.length >= 2 && value.startsWith('"') && value.endsWith('"')) {
|
|
@@ -32,7 +27,7 @@ function unquote(raw) {
|
|
|
32
27
|
/** Local slugs from ~/.grok/config.toml, plus the cloud defaults.
|
|
33
28
|
* `grok -m <slug>` already accepts these; the picker just didn't list them. */
|
|
34
29
|
export function readGrokModelCatalog(env = process.env) {
|
|
35
|
-
const path = join(
|
|
30
|
+
const path = join(env.GROK_HOME || harnessHome("grok", env), "config.toml");
|
|
36
31
|
let text = "";
|
|
37
32
|
try {
|
|
38
33
|
text = readFileSync(path, "utf8");
|
|
@@ -114,7 +109,7 @@ export function ensureGrokInjectSlug(modelId, env = process.env) {
|
|
|
114
109
|
const host = localHost(inject.host);
|
|
115
110
|
if (!host)
|
|
116
111
|
return modelId;
|
|
117
|
-
const path = join(
|
|
112
|
+
const path = join(env.GROK_HOME || harnessHome("grok", env), "config.toml");
|
|
118
113
|
let text = "";
|
|
119
114
|
try {
|
|
120
115
|
text = readFileSync(path, "utf8");
|
|
@@ -258,7 +253,7 @@ const support = {
|
|
|
258
253
|
// an unauthenticated CLI is a user action, not something to paper over.
|
|
259
254
|
pickAuthMethod: (methods) => (methods.some((m) => m.id === "cached_token") ? "cached_token" : null),
|
|
260
255
|
authFailure: "fail",
|
|
261
|
-
isAuthenticated: () => existsSync(join(
|
|
256
|
+
isAuthenticated: (env) => existsSync(join(harnessHome("grok", env), "auth.json")),
|
|
262
257
|
// `--append-system-prompt`/`--rules` are accepted by the CLI but do NOT
|
|
263
258
|
// reach the agent-stdio system prompt (verified against 1.0.0), so the
|
|
264
259
|
// persona is prepended codex-style.
|
|
@@ -9,6 +9,7 @@ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
|
9
9
|
import { homedir } from "node:os";
|
|
10
10
|
import { join } from "node:path";
|
|
11
11
|
import { parse as parseYaml } from "yaml";
|
|
12
|
+
import { harnessHome } from "../../env-path.js";
|
|
12
13
|
import { resolveCli } from "../../procs.js";
|
|
13
14
|
import { decodeInjectId, hostApiKey, INJECT_SEP, localHost, mergeLocalInject } from "../local-inject.js";
|
|
14
15
|
import { createAcpDriver } from "./core.js";
|
|
@@ -25,9 +26,6 @@ export function bindHermesScreenshotCompat(env, modelId) {
|
|
|
25
26
|
env[HERMES_OPENMAUS_SCREENSHOT_COMPAT] = "1";
|
|
26
27
|
env[HERMES_OPENMAUS_SCREENSHOT_COMPAT_MODEL] = inject.model;
|
|
27
28
|
}
|
|
28
|
-
function hermesHome(env) {
|
|
29
|
-
return env.HERMES_HOME || join(env.HOME || env.USERPROFILE || homedir(), ".hermes");
|
|
30
|
-
}
|
|
31
29
|
function quoteYaml(value) {
|
|
32
30
|
if (/^[\w./:+-]+$/.test(value))
|
|
33
31
|
return value;
|
|
@@ -69,7 +67,7 @@ export function ensureHermesInjectProvider(modelId, env = process.env) {
|
|
|
69
67
|
const host = localHost(inject.host);
|
|
70
68
|
if (!host)
|
|
71
69
|
return modelId;
|
|
72
|
-
const dir =
|
|
70
|
+
const dir = env.HERMES_HOME || harnessHome("hermes", env);
|
|
73
71
|
mkdirSync(dir, { recursive: true });
|
|
74
72
|
const path = join(dir, "config.yaml");
|
|
75
73
|
let text = "";
|
|
@@ -199,7 +197,7 @@ function hermesConfigDefault(text) {
|
|
|
199
197
|
* catalog exactly as it was for local-only setups.
|
|
200
198
|
*/
|
|
201
199
|
export function hermesConfiguredModel(env = process.env) {
|
|
202
|
-
const dir =
|
|
200
|
+
const dir = env.HERMES_HOME || harnessHome("hermes", env);
|
|
203
201
|
let secrets = "";
|
|
204
202
|
try {
|
|
205
203
|
secrets = readFileSync(join(dir, ".env"), "utf8");
|
|
@@ -6,6 +6,7 @@ import { homedir } from "node:os";
|
|
|
6
6
|
import { join } from "node:path";
|
|
7
7
|
import { decodeInjectId, hostApiKey, localHost, mergeLocalInject } from "../local-inject.js";
|
|
8
8
|
import { createAcpDriver } from "./core.js";
|
|
9
|
+
import { titleCaseModelId } from "../../contracts.js";
|
|
9
10
|
import { execCli } from "../../procs.js";
|
|
10
11
|
const STATIC_MODELS = {
|
|
11
12
|
default: "opencode/x-preview-f-free",
|
|
@@ -21,11 +22,7 @@ let lastSuccessfulCatalog = null;
|
|
|
21
22
|
const MODEL_PROBE_TTL_MS = 30_000;
|
|
22
23
|
const modelProbeCache = new Map();
|
|
23
24
|
function labelForModel(id) {
|
|
24
|
-
return id
|
|
25
|
-
.split(/[-_.]+/g)
|
|
26
|
-
.filter(Boolean)
|
|
27
|
-
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
28
|
-
.join(" ");
|
|
25
|
+
return titleCaseModelId(id, /[-_.]+/);
|
|
29
26
|
}
|
|
30
27
|
function providerLabel(id) {
|
|
31
28
|
if (id === "opencode")
|
|
@@ -3,30 +3,20 @@
|
|
|
3
3
|
// Custom and are written into ~/.qwen/settings.json modelProviders.
|
|
4
4
|
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { createHash } from "node:crypto";
|
|
6
|
-
import { homedir } from "node:os";
|
|
7
6
|
import { join } from "node:path";
|
|
7
|
+
import { qualifiedModelLabel } from "../../contracts.js";
|
|
8
|
+
import { harnessHome } from "../../env-path.js";
|
|
8
9
|
import { decodeInjectId, hostApiKey, localHost, mergeLocalInject } from "../local-inject.js";
|
|
9
10
|
import { createAcpDriver } from "./core.js";
|
|
10
11
|
const EMPTY = { default: "", options: [] };
|
|
11
|
-
function qwenHome(env) {
|
|
12
|
-
const home = process.platform === "win32"
|
|
13
|
-
? env.USERPROFILE || env.HOME || homedir()
|
|
14
|
-
: env.HOME || env.USERPROFILE || homedir();
|
|
15
|
-
return join(home, ".qwen");
|
|
16
|
-
}
|
|
17
12
|
function isRecord(value) {
|
|
18
13
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
19
14
|
}
|
|
20
|
-
function qwenModelLabel(id, name) {
|
|
21
|
-
if (!name || name.toLocaleLowerCase().endsWith(id.toLocaleLowerCase()))
|
|
22
|
-
return id;
|
|
23
|
-
return `${id} — ${name}`;
|
|
24
|
-
}
|
|
25
15
|
const PROTOCOLS = new Set(["openai", "anthropic", "gemini", "vertex-ai"]);
|
|
26
16
|
function readQwenRoutes(env) {
|
|
27
17
|
let settings;
|
|
28
18
|
try {
|
|
29
|
-
settings = JSON.parse(readFileSync(join(
|
|
19
|
+
settings = JSON.parse(readFileSync(join(harnessHome("qwen", env), "settings.json"), "utf8"));
|
|
30
20
|
}
|
|
31
21
|
catch {
|
|
32
22
|
return [];
|
|
@@ -102,7 +92,11 @@ function readQwenRoutes(env) {
|
|
|
102
92
|
/** Public metadata only. Use Qwen's provider-qualified ACP selectors, not raw model IDs. */
|
|
103
93
|
export function readQwenModelCatalog(env = process.env) {
|
|
104
94
|
const options = readQwenRoutes(env).map(({ id, model, label, provider }) => ({
|
|
105
|
-
id,
|
|
95
|
+
id,
|
|
96
|
+
label: qualifiedModelLabel(model, label, {
|
|
97
|
+
redundantWhen: (id, name) => name.toLocaleLowerCase().endsWith(id.toLocaleLowerCase()),
|
|
98
|
+
}),
|
|
99
|
+
custom: true, provider,
|
|
106
100
|
}));
|
|
107
101
|
return { default: options[0]?.id ?? "", options };
|
|
108
102
|
}
|
|
@@ -117,7 +111,7 @@ export function ensureQwenInjectModel(modelId, env = process.env) {
|
|
|
117
111
|
const host = localHost(inject.host);
|
|
118
112
|
if (!host)
|
|
119
113
|
return modelId;
|
|
120
|
-
const dir =
|
|
114
|
+
const dir = harnessHome("qwen", env);
|
|
121
115
|
mkdirSync(dir, { recursive: true });
|
|
122
116
|
const path = join(dir, "settings.json");
|
|
123
117
|
let settings = {};
|
|
@@ -241,6 +235,9 @@ const support = {
|
|
|
241
235
|
// A raw -m only changes the model within the saved provider. ACP switches
|
|
242
236
|
// the complete route and confirms it before any prompt leaves OMB.
|
|
243
237
|
spawnArgs: (config, turn) => ["--acp", ...qwenApprovalArgs(config.fullAuto, turn.approvalMode)],
|
|
238
|
+
// Qwen's live session/load returns its cached MCP clients, ignoring fresh
|
|
239
|
+
// credentials. Cold-load the same conversation when those inputs change.
|
|
240
|
+
restartOnMcpChange: true,
|
|
244
241
|
selectModel: { configId: "model" },
|
|
245
242
|
pickAuthMethod: () => null,
|
|
246
243
|
authFailure: "continue",
|
|
@@ -7,8 +7,10 @@
|
|
|
7
7
|
// Nothing here reads the environment or holds state of its own. The caller
|
|
8
8
|
// passes a ToolCallContext, and the per-turn counters live on it.
|
|
9
9
|
import { CREDENTIAL_TARGETS, isCredentialTargetId } from "../../shared/credential-request.js";
|
|
10
|
+
import { parseOptionsCardInput, WATCHER_OPTIONS_CARD_BOT_ID } from "../../shared/options-card.js";
|
|
10
11
|
import { normalizeCronSchedule } from "../../shared/routine-schedule.js";
|
|
11
12
|
import { peerName } from "../peer-roster.js";
|
|
13
|
+
import { renderPeerDeliveryReceipts } from "../peer-delivery.js";
|
|
12
14
|
import { catalogProfileFromEnv, SHARED_COMPUTER_TOOL_NAMES, WEEKDAYS } from "./agents-catalog.js";
|
|
13
15
|
import { harnessClientFromEnv } from "./agents-client.js";
|
|
14
16
|
import { boundedAgentResult } from "./agents-result.js";
|
|
@@ -352,6 +354,29 @@ function recallSpeaker(hit) {
|
|
|
352
354
|
return hit.from;
|
|
353
355
|
return hit.role === "user" ? "user" : "you";
|
|
354
356
|
}
|
|
357
|
+
const isPeerDeliveryOutcome = (value) => value === "queued" || value === "injected" || value === "failed";
|
|
358
|
+
/** The delivery receipt a peer-send answer carries, as one prose line.
|
|
359
|
+
* Unvalidated JSON stays out: a malformed receipt renders nothing rather
|
|
360
|
+
* than half a line of somebody else's text. */
|
|
361
|
+
function deliveryNote(r) {
|
|
362
|
+
const receipt = r.receipt;
|
|
363
|
+
if (!receipt || typeof receipt !== "object")
|
|
364
|
+
return "";
|
|
365
|
+
const { botId, botName, outcome, detail, taskId, requestId } = receipt;
|
|
366
|
+
if (typeof botId !== "string" || !botId || typeof detail !== "string" || !detail)
|
|
367
|
+
return "";
|
|
368
|
+
if (!isPeerDeliveryOutcome(outcome))
|
|
369
|
+
return "";
|
|
370
|
+
const parsed = {
|
|
371
|
+
botId,
|
|
372
|
+
...(typeof botName === "string" && botName ? { botName } : {}),
|
|
373
|
+
outcome,
|
|
374
|
+
detail,
|
|
375
|
+
...(typeof taskId === "string" && taskId ? { taskId } : {}),
|
|
376
|
+
...(typeof requestId === "string" && requestId ? { requestId } : {}),
|
|
377
|
+
};
|
|
378
|
+
return renderPeerDeliveryReceipts([parsed]);
|
|
379
|
+
}
|
|
355
380
|
/** When a recalled line was said, on the machine's own clock. `toISOString()`
|
|
356
381
|
* answers in UTC, which disagrees with every other day the bot is shown: a
|
|
357
382
|
* bare `since`/`until` date is read as local midnight (recent-work.ts
|
|
@@ -375,6 +400,49 @@ export async function callTool(name, args, context) {
|
|
|
375
400
|
const { botId: BOT_ID, threadId: THREAD_ID, depth: DEPTH, externalRuntime: EXTERNAL_RUNTIME, coordinating: COORDINATING, turn } = context;
|
|
376
401
|
const { delegationTaskIdsThisTurn } = turn;
|
|
377
402
|
const { api, apiResponse } = context.client;
|
|
403
|
+
if (name === "vm_exec") {
|
|
404
|
+
const { ok, body } = await apiResponse("/api/internal/vm-exec", {
|
|
405
|
+
method: "POST",
|
|
406
|
+
body: JSON.stringify({ command: args.command, ...(typeof args.timeout_seconds === "number" ? { timeout_seconds: args.timeout_seconds } : {}) }),
|
|
407
|
+
});
|
|
408
|
+
if (!ok)
|
|
409
|
+
return { text: String(body.error ?? "Could not run that command."), isError: true };
|
|
410
|
+
const exitCode = Number(body.exitCode ?? 0);
|
|
411
|
+
const stdout = String(body.stdout ?? "");
|
|
412
|
+
const stderr = String(body.stderr ?? "");
|
|
413
|
+
const lines = [body.timedOut ? "The command was stopped: it ran past its time limit." : `exit code ${exitCode}`];
|
|
414
|
+
if (stdout)
|
|
415
|
+
lines.push("--- stdout ---", stdout.replace(/\s+$/, ""));
|
|
416
|
+
if (stderr)
|
|
417
|
+
lines.push("--- stderr ---", stderr.replace(/\s+$/, ""));
|
|
418
|
+
if (!stdout && !stderr && !body.timedOut)
|
|
419
|
+
lines.push("(no output)");
|
|
420
|
+
return { text: lines.join("\n"), ...(exitCode !== 0 || body.timedOut ? { isError: true } : {}) };
|
|
421
|
+
}
|
|
422
|
+
if (name === "attach_file") {
|
|
423
|
+
const { ok, body } = await apiResponse("/api/internal/attach-file", {
|
|
424
|
+
method: "POST",
|
|
425
|
+
body: JSON.stringify({ path: args.path, ...(typeof args.name === "string" ? { name: args.name } : {}) }),
|
|
426
|
+
});
|
|
427
|
+
if (!ok)
|
|
428
|
+
return { text: String(body.error ?? "Could not attach that file."), isError: true };
|
|
429
|
+
return { text: `Attached ${String(body.name ?? "the file")} (${Number(body.bytes ?? 0)} bytes). It now appears in the chat with a preview.` };
|
|
430
|
+
}
|
|
431
|
+
if (name === "create_options_card") {
|
|
432
|
+
if (BOT_ID !== WATCHER_OPTIONS_CARD_BOT_ID) {
|
|
433
|
+
return { text: "create_options_card is not enabled for this bot.", isError: true };
|
|
434
|
+
}
|
|
435
|
+
const parsed = parseOptionsCardInput(args);
|
|
436
|
+
if (!parsed.ok)
|
|
437
|
+
return { text: parsed.error, isError: true };
|
|
438
|
+
const result = await api("/api/internal/options-card", {
|
|
439
|
+
method: "POST",
|
|
440
|
+
body: JSON.stringify(parsed.value),
|
|
441
|
+
});
|
|
442
|
+
return {
|
|
443
|
+
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.`,
|
|
444
|
+
};
|
|
445
|
+
}
|
|
378
446
|
// Second lock. With sharing off the tool is not in the catalog, so a front
|
|
379
447
|
// end already refuses the call as an unknown tool — the same answer a build
|
|
380
448
|
// without the feature gives. This keeps the handler itself refusing if that
|
|
@@ -484,6 +552,7 @@ export async function callTool(name, args, context) {
|
|
|
484
552
|
if (name === "post_to_room") {
|
|
485
553
|
const groupId = String(args.group_id ?? "").trim();
|
|
486
554
|
const message = String(args.message ?? "").trim();
|
|
555
|
+
const attachVoiceNote = args.attach_voice_note === true;
|
|
487
556
|
if (!groupId || !message) {
|
|
488
557
|
return { text: "post_to_room needs group_id (from list_rooms) and message.", isError: true };
|
|
489
558
|
}
|
|
@@ -495,13 +564,13 @@ export async function callTool(name, args, context) {
|
|
|
495
564
|
}
|
|
496
565
|
const r = await api("/api/internal/post-to-room", {
|
|
497
566
|
method: "POST",
|
|
498
|
-
body: JSON.stringify({ fromBotId: BOT_ID, fromThreadId: THREAD_ID, groupId, message }),
|
|
567
|
+
body: JSON.stringify({ fromBotId: BOT_ID, fromThreadId: THREAD_ID, groupId, message, attachVoiceNote }),
|
|
499
568
|
});
|
|
500
569
|
if (r.error)
|
|
501
570
|
return { text: String(r.error), isError: true };
|
|
502
571
|
turn.roomPostsThisTurn += 1;
|
|
503
572
|
return {
|
|
504
|
-
text: `Posted in ${r.roomName ?? "the room"}. Nobody's turn was started, so expect no reply — tell the user it is posted.`,
|
|
573
|
+
text: `Posted in ${r.roomName ?? "the room"}${r.attachedVoiceNote ? " with the voice note attached" : ""}. Nobody's turn was started, so expect no reply — tell the user it is posted.`,
|
|
505
574
|
};
|
|
506
575
|
}
|
|
507
576
|
if (name === "ask_bot") {
|
|
@@ -513,6 +582,7 @@ export async function callTool(name, args, context) {
|
|
|
513
582
|
method: "POST",
|
|
514
583
|
body: JSON.stringify({ fromBotId: BOT_ID, fromThreadId: THREAD_ID, toBotId, message, depth: DEPTH }),
|
|
515
584
|
});
|
|
585
|
+
const note = deliveryNote(r);
|
|
516
586
|
if (r.timeout) {
|
|
517
587
|
// The peer's turn outlived the synchronous wait, so the harness
|
|
518
588
|
// converted the ask into a delegation — the reply is not lost.
|
|
@@ -523,10 +593,25 @@ export async function callTool(name, args, context) {
|
|
|
523
593
|
const amount = waitedSeconds < 60 ? waitedSeconds : Math.round(waitedSeconds / 60);
|
|
524
594
|
const unit = waitedSeconds < 60 ? "second" : "minute";
|
|
525
595
|
return {
|
|
526
|
-
text: `${r.toBotName ?? "That bot"} is still working after ${amount} ${unit}${amount === 1 ? "" : "s"} — 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."}`,
|
|
596
|
+
text: `${r.toBotName ?? "That bot"} is still working after ${amount} ${unit}${amount === 1 ? "" : "s"} — 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 ? `\n\n${note}` : ""}`,
|
|
527
597
|
};
|
|
528
598
|
}
|
|
529
599
|
if (r.busy) {
|
|
600
|
+
// Aside lane: the teammate was mid-turn on a seam-capable engine, so
|
|
601
|
+
// the harness folded the message into their running work as peer
|
|
602
|
+
// context. No new turn started and no reply is coming back through
|
|
603
|
+
// this call — the receipt says whether the words are already in the
|
|
604
|
+
// live turn or waiting for it to settle.
|
|
605
|
+
if (r.aside === "injected") {
|
|
606
|
+
return {
|
|
607
|
+
text: `${r.toBotName ?? "That bot"} is mid-turn; your message was handed to them as an aside — 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.`,
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
if (r.aside === "queued") {
|
|
611
|
+
return {
|
|
612
|
+
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 — finish your turn.`,
|
|
613
|
+
};
|
|
614
|
+
}
|
|
530
615
|
// The harness queues the message as a delegation when it can; the
|
|
531
616
|
// task id is the asker's claim ticket for the eventual reply.
|
|
532
617
|
const taskId = String(r.taskId ?? "").trim();
|
|
@@ -534,14 +619,14 @@ export async function callTool(name, args, context) {
|
|
|
534
619
|
if (!EXTERNAL_RUNTIME)
|
|
535
620
|
delegationTaskIdsThisTurn.add(taskId);
|
|
536
621
|
return {
|
|
537
|
-
text: `${r.toBotName ?? "That bot"} is busy right now, so your message was queued as a delegation instead — ${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."}`,
|
|
622
|
+
text: `${r.toBotName ?? "That bot"} is busy right now, so your message was queued as a delegation instead — ${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 ? `\n\n${note}` : ""}`,
|
|
538
623
|
};
|
|
539
624
|
}
|
|
540
|
-
return { text: `That bot is busy right now — try again after it finishes
|
|
625
|
+
return { text: `That bot is busy right now — try again after it finishes.${note ? `\n\n${note}` : ""}` };
|
|
541
626
|
}
|
|
542
627
|
if (r.error)
|
|
543
|
-
return { text: `Couldn't reach that bot: ${r.error}`, isError: true };
|
|
544
|
-
return { text: `${r.botName ?? "Bot"} replied:\n${r.text ?? "(no reply)"}` };
|
|
628
|
+
return { text: `Couldn't reach that bot: ${r.error}${note ? `\n\n${note}` : ""}`, isError: true };
|
|
629
|
+
return { text: `${r.botName ?? "Bot"} replied:\n${r.text ?? "(no reply)"}${note ? `\n\n${note}` : ""}` };
|
|
545
630
|
}
|
|
546
631
|
if (name === "delegate_bot") {
|
|
547
632
|
const toBotId = String(args.bot_id ?? "").trim();
|
|
@@ -559,8 +644,9 @@ export async function callTool(name, args, context) {
|
|
|
559
644
|
if (reason)
|
|
560
645
|
body.reason = reason;
|
|
561
646
|
const r = await api(`/api/internal/delegate-bot`, { method: "POST", body: JSON.stringify(body) });
|
|
647
|
+
const delivery = deliveryNote(r);
|
|
562
648
|
if (r.error)
|
|
563
|
-
return { text: `Couldn't queue the delegation: ${r.error}`, isError: true };
|
|
649
|
+
return { text: `Couldn't queue the delegation: ${r.error}${delivery ? `\n\n${delivery}` : ""}`, isError: true };
|
|
564
650
|
// Fire-and-forget by contract: the harness returns immediately, the
|
|
565
651
|
// peer turn runs after our current turn finishes. The task id is the
|
|
566
652
|
// bot's claim ticket for the outcome.
|
|
@@ -571,7 +657,7 @@ export async function callTool(name, args, context) {
|
|
|
571
657
|
const suffix = taskId
|
|
572
658
|
? ` 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."}`
|
|
573
659
|
: "";
|
|
574
|
-
return { text: `${note}${suffix}` };
|
|
660
|
+
return { text: `${note}${suffix}${delivery ? `\n\n${delivery}` : ""}` };
|
|
575
661
|
}
|
|
576
662
|
if (name === "check_delegation" || name === "wait_delegation") {
|
|
577
663
|
const taskId = String(args.task_id ?? "").trim();
|
|
@@ -745,6 +831,7 @@ export async function callTool(name, args, context) {
|
|
|
745
831
|
role,
|
|
746
832
|
instructions,
|
|
747
833
|
...(args.modelSelection !== undefined ? { modelSelection: args.modelSelection } : {}),
|
|
834
|
+
...(typeof args.cwd === "string" ? { cwd: args.cwd.trim() } : {}),
|
|
748
835
|
}),
|
|
749
836
|
});
|
|
750
837
|
turn.createdThisTurn += 1;
|
|
@@ -923,6 +1010,13 @@ export async function callTool(name, args, context) {
|
|
|
923
1010
|
return confirmationResult(r, `${action.replace("_", " ")} on routine ${routineId}`);
|
|
924
1011
|
}
|
|
925
1012
|
if (name === "propose_profile") {
|
|
1013
|
+
// A non-boolean toggle would be dropped silently while any valid half of
|
|
1014
|
+
// the request went through, applying a partial proposal the model never
|
|
1015
|
+
// described. Reject the whole call instead.
|
|
1016
|
+
if ((args.notifications !== undefined && typeof args.notifications !== "boolean")
|
|
1017
|
+
|| (args.speakReplies !== undefined && typeof args.speakReplies !== "boolean")) {
|
|
1018
|
+
return { text: "propose_profile notifications and speakReplies must be true or false.", isError: true };
|
|
1019
|
+
}
|
|
926
1020
|
const changes = {};
|
|
927
1021
|
if (typeof args.name === "string")
|
|
928
1022
|
changes.name = args.name.trim();
|
|
@@ -934,8 +1028,12 @@ export async function callTool(name, args, context) {
|
|
|
934
1028
|
changes.soul = args.soul;
|
|
935
1029
|
if (typeof args.cwd === "string")
|
|
936
1030
|
changes.cwd = args.cwd.trim();
|
|
1031
|
+
if (typeof args.notifications === "boolean")
|
|
1032
|
+
changes.notifications = args.notifications;
|
|
1033
|
+
if (typeof args.speakReplies === "boolean")
|
|
1034
|
+
changes.speakReplies = args.speakReplies;
|
|
937
1035
|
if (!Object.keys(changes).length) {
|
|
938
|
-
return { text: "propose_profile needs at least one of name, title, description, soul, or
|
|
1036
|
+
return { text: "propose_profile needs at least one of name, title, description, soul, cwd, notifications, or speakReplies.", isError: true };
|
|
939
1037
|
}
|
|
940
1038
|
const forBotId = String(args.for_bot_id ?? "").trim();
|
|
941
1039
|
const r = await api("/api/internal/profile-requests", {
|
|
@@ -951,6 +1049,35 @@ export async function callTool(name, args, context) {
|
|
|
951
1049
|
});
|
|
952
1050
|
return confirmationResult(r, "the profile change", "profile");
|
|
953
1051
|
}
|
|
1052
|
+
if (name === "propose_model") {
|
|
1053
|
+
const raw = args.model_selection;
|
|
1054
|
+
const fields = typeof raw === "object" && raw !== null && !Array.isArray(raw) ? raw : null;
|
|
1055
|
+
if (!fields || typeof fields.instanceId !== "string" || !fields.instanceId.trim() || typeof fields.model !== "string" || !fields.model.trim()) {
|
|
1056
|
+
return { text: "propose_model needs model_selection with instanceId and model.", isError: true };
|
|
1057
|
+
}
|
|
1058
|
+
if ((fields.effort !== undefined && (typeof fields.effort !== "string" || !fields.effort.trim()))
|
|
1059
|
+
|| (fields.variant !== undefined && (typeof fields.variant !== "string" || !fields.variant.trim()))) {
|
|
1060
|
+
return { text: "propose_model effort and variant must be non-empty strings when supplied.", isError: true };
|
|
1061
|
+
}
|
|
1062
|
+
const selection = { instanceId: fields.instanceId.trim(), model: fields.model.trim() };
|
|
1063
|
+
if (typeof fields.effort === "string" && fields.effort.trim())
|
|
1064
|
+
selection.effort = fields.effort.trim();
|
|
1065
|
+
if (typeof fields.variant === "string" && fields.variant.trim())
|
|
1066
|
+
selection.variant = fields.variant.trim();
|
|
1067
|
+
const forBotId = String(args.for_bot_id ?? "").trim();
|
|
1068
|
+
const r = await api("/api/internal/model-requests", {
|
|
1069
|
+
method: "POST",
|
|
1070
|
+
body: JSON.stringify({
|
|
1071
|
+
fromBotId: BOT_ID,
|
|
1072
|
+
fromThreadId: THREAD_ID,
|
|
1073
|
+
modelSelection: selection,
|
|
1074
|
+
reason: args.reason,
|
|
1075
|
+
// JSON.stringify drops the key entirely when no target was named
|
|
1076
|
+
forBotId: forBotId || undefined,
|
|
1077
|
+
}),
|
|
1078
|
+
});
|
|
1079
|
+
return confirmationResult(r, "the default model change", "model");
|
|
1080
|
+
}
|
|
954
1081
|
if (name === "memory_update") {
|
|
955
1082
|
if (!["append", "replace", "remove", "supersede"].includes(String(args.action))
|
|
956
1083
|
|| (args.action !== "remove" && (typeof args.text !== "string" || !args.text.trim()))
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
// agents-catalog-wire.test.ts: change a description or a schema on purpose,
|
|
12
12
|
// then update the goldens there.
|
|
13
13
|
import { CREDENTIAL_TARGETS } from "../../shared/credential-request.js";
|
|
14
|
+
import { OPTIONS_CARD_LIMITS, WATCHER_OPTIONS_CARD_BOT_ID } from "../../shared/options-card.js";
|
|
14
15
|
import { agentToolAnnotations } from "../agent-tool-policy.js";
|
|
15
16
|
/** The profile a spawned proxy was given. Everything is off unless the
|
|
16
17
|
* harness says "1". */
|
|
@@ -154,6 +155,26 @@ const PROPOSAL_OUTCOME = " Read the result: granted Full Access may apply the ch
|
|
|
154
155
|
/** Every tool, in the order it is listed. Four peer tools are worded
|
|
155
156
|
* differently for an external runtime, which may poll inside one process. */
|
|
156
157
|
const toolDefinitions = (externalRuntime) => [
|
|
158
|
+
{
|
|
159
|
+
name: "create_options_card",
|
|
160
|
+
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.",
|
|
161
|
+
inputSchema: {
|
|
162
|
+
type: "object",
|
|
163
|
+
additionalProperties: false,
|
|
164
|
+
properties: {
|
|
165
|
+
title: { type: "string", minLength: 1, maxLength: OPTIONS_CARD_LIMITS.title },
|
|
166
|
+
subtitle: { type: "string", minLength: 1, maxLength: OPTIONS_CARD_LIMITS.subtitle },
|
|
167
|
+
options: {
|
|
168
|
+
type: "array",
|
|
169
|
+
minItems: OPTIONS_CARD_LIMITS.minOptions,
|
|
170
|
+
maxItems: OPTIONS_CARD_LIMITS.maxOptions,
|
|
171
|
+
uniqueItems: true,
|
|
172
|
+
items: { type: "string", minLength: 1, maxLength: OPTIONS_CARD_LIMITS.option },
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
required: ["title", "subtitle", "options"],
|
|
176
|
+
},
|
|
177
|
+
},
|
|
157
178
|
{
|
|
158
179
|
name: "tool_result_read",
|
|
159
180
|
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.",
|
|
@@ -305,15 +326,42 @@ const toolDefinitions = (externalRuntime) => [
|
|
|
305
326
|
required: ["title", "message"],
|
|
306
327
|
},
|
|
307
328
|
},
|
|
329
|
+
{
|
|
330
|
+
name: "vm_exec",
|
|
331
|
+
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.",
|
|
332
|
+
inputSchema: {
|
|
333
|
+
type: "object",
|
|
334
|
+
additionalProperties: false,
|
|
335
|
+
properties: {
|
|
336
|
+
command: { type: "string", description: "The shell command to run, for example: python3 make_report.py && ls -l report.pdf" },
|
|
337
|
+
timeout_seconds: { type: "integer", minimum: 1, maximum: 300, description: "How long it may run before it is stopped. Default 60." },
|
|
338
|
+
},
|
|
339
|
+
required: ["command"],
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
name: "attach_file",
|
|
344
|
+
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.",
|
|
345
|
+
inputSchema: {
|
|
346
|
+
type: "object",
|
|
347
|
+
additionalProperties: false,
|
|
348
|
+
properties: {
|
|
349
|
+
path: { type: "string", description: "The file's path, for example /home/cua/workspace/report.pdf." },
|
|
350
|
+
name: { type: "string", description: "Optional file name to show the user. Defaults to the file's own name." },
|
|
351
|
+
},
|
|
352
|
+
required: ["path"],
|
|
353
|
+
},
|
|
354
|
+
},
|
|
308
355
|
{
|
|
309
356
|
name: "post_to_room",
|
|
310
|
-
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 — 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.",
|
|
357
|
+
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 — 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.",
|
|
311
358
|
inputSchema: {
|
|
312
359
|
type: "object",
|
|
313
360
|
additionalProperties: false,
|
|
314
361
|
properties: {
|
|
315
362
|
group_id: { type: "string", description: "The room's id, copied exactly from list_rooms." },
|
|
316
363
|
message: { type: "string", description: "The complete message to post, written for the room to read as it stands." },
|
|
364
|
+
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." },
|
|
317
365
|
},
|
|
318
366
|
required: ["group_id", "message"],
|
|
319
367
|
},
|
|
@@ -331,6 +379,11 @@ const toolDefinitions = (externalRuntime) => [
|
|
|
331
379
|
instanceId: { type: "string" }, model: { type: "string" },
|
|
332
380
|
effort: { type: "string" }, variant: { type: "string" },
|
|
333
381
|
}, required: ["instanceId", "model"] },
|
|
382
|
+
cwd: {
|
|
383
|
+
type: "string",
|
|
384
|
+
maxLength: 1024,
|
|
385
|
+
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.",
|
|
386
|
+
},
|
|
334
387
|
},
|
|
335
388
|
required: ["name", "role", "instructions"],
|
|
336
389
|
},
|
|
@@ -358,9 +411,10 @@ const toolDefinitions = (externalRuntime) => [
|
|
|
358
411
|
name: { type: "string", maxLength: 100 }, title: { type: "string", maxLength: 200 },
|
|
359
412
|
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." },
|
|
360
413
|
description: { type: "string", maxLength: 4000 }, soul: { type: "string", description: "Standing instructions; required with name/title/modelSelection for every new bot." },
|
|
414
|
+
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." },
|
|
361
415
|
section: { type: "string", maxLength: 60, description: "Exact authorized existing team, or a team explicitly named in newTeams. Empty string means General." },
|
|
362
416
|
modelSelection: { type: "object", additionalProperties: false, properties: {
|
|
363
|
-
instanceId: { type: "string" }, model: { type: "string" }, effort: { type: "string" },
|
|
417
|
+
instanceId: { type: "string" }, model: { type: "string" }, effort: { type: "string" }, variant: { type: "string" },
|
|
364
418
|
}, required: ["instanceId", "model"] },
|
|
365
419
|
} },
|
|
366
420
|
}, required: ["action", "fields"],
|
|
@@ -583,7 +637,7 @@ const toolDefinitions = (externalRuntime) => [
|
|
|
583
637
|
},
|
|
584
638
|
{
|
|
585
639
|
name: "propose_profile",
|
|
586
|
-
description: "Submit user-requested changes to your own name, title, description, standing instructions (SOUL.md),
|
|
640
|
+
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 — 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,
|
|
587
641
|
inputSchema: {
|
|
588
642
|
type: "object",
|
|
589
643
|
additionalProperties: false,
|
|
@@ -597,6 +651,14 @@ const toolDefinitions = (externalRuntime) => [
|
|
|
597
651
|
maxLength: 1024,
|
|
598
652
|
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.",
|
|
599
653
|
},
|
|
654
|
+
notifications: {
|
|
655
|
+
type: "boolean",
|
|
656
|
+
description: "Completion and attention notifications for this bot on the host and paired clients.",
|
|
657
|
+
},
|
|
658
|
+
speakReplies: {
|
|
659
|
+
type: "boolean",
|
|
660
|
+
description: "Speak this bot's replies aloud as they settle, without being asked.",
|
|
661
|
+
},
|
|
600
662
|
reason: { type: "string", minLength: 1, maxLength: 500, description: "One sentence the user will see explaining why." },
|
|
601
663
|
for_bot_id: {
|
|
602
664
|
type: "string",
|
|
@@ -606,6 +668,34 @@ const toolDefinitions = (externalRuntime) => [
|
|
|
606
668
|
required: ["reason"],
|
|
607
669
|
},
|
|
608
670
|
},
|
|
671
|
+
{
|
|
672
|
+
name: "propose_model",
|
|
673
|
+
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,
|
|
674
|
+
inputSchema: {
|
|
675
|
+
type: "object",
|
|
676
|
+
additionalProperties: false,
|
|
677
|
+
properties: {
|
|
678
|
+
model_selection: {
|
|
679
|
+
type: "object",
|
|
680
|
+
additionalProperties: false,
|
|
681
|
+
properties: {
|
|
682
|
+
instanceId: { type: "string", minLength: 1, description: "Engine instance id, for example codex or claude." },
|
|
683
|
+
model: { type: "string", minLength: 1, description: "Exact model id on that instance." },
|
|
684
|
+
effort: { type: "string", description: "Optional effort level the instance offers; omit for the engine default." },
|
|
685
|
+
variant: { type: "string", description: "Optional explicit model variant; choose this or effort, not both." },
|
|
686
|
+
},
|
|
687
|
+
required: ["instanceId", "model"],
|
|
688
|
+
description: "The new default selection.",
|
|
689
|
+
},
|
|
690
|
+
reason: { type: "string", minLength: 1, maxLength: 500, description: "One sentence the user will see explaining why." },
|
|
691
|
+
for_bot_id: {
|
|
692
|
+
type: "string",
|
|
693
|
+
description: "Chief of Staff only: the id of another bot in your section whose default model this changes. Omit to change your own.",
|
|
694
|
+
},
|
|
695
|
+
},
|
|
696
|
+
required: ["model_selection", "reason"],
|
|
697
|
+
},
|
|
698
|
+
},
|
|
609
699
|
{
|
|
610
700
|
name: "skills_list",
|
|
611
701
|
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.",
|
|
@@ -662,12 +752,16 @@ const VOICE_TOOL_NAMES = new Set(["send_voice_note"]);
|
|
|
662
752
|
const ROOM_ONLY_TOOLS = new Set(["list_room_targets", "coordinate_bots"]);
|
|
663
753
|
const ROOM_REPLACED_TOOLS = new Set(["ask_bot", "delegate_bot", "check_delegation", "wait_delegation", "start_thread", "send_to_thread", "wait_thread"]);
|
|
664
754
|
const EXTERNAL_TOOL_NAMES = new Set(["list_bots", "ask_bot", "delegate_bot", "check_delegation", "wait_delegation"]);
|
|
755
|
+
const WATCHER_TOOL_NAMES = new Set(["create_options_card"]);
|
|
665
756
|
/** The tools one turn is shown, exactly as tools/list serializes them. */
|
|
666
757
|
export function availableTools(profile) {
|
|
667
758
|
const TOOLS = toolDefinitions(profile.externalRuntime);
|
|
668
|
-
const
|
|
759
|
+
const BOT_SCOPED_TOOLS = profile.botId === WATCHER_OPTIONS_CARD_BOT_ID
|
|
669
760
|
? TOOLS
|
|
670
|
-
: TOOLS.filter((tool) => !
|
|
761
|
+
: TOOLS.filter((tool) => !WATCHER_TOOL_NAMES.has(tool.name));
|
|
762
|
+
const AUTHORING_TOOLS = profile.skillAuthoring
|
|
763
|
+
? BOT_SCOPED_TOOLS
|
|
764
|
+
: BOT_SCOPED_TOOLS.filter((tool) => !SKILL_TOOL_NAMES.has(tool.name));
|
|
671
765
|
const SHAREABLE_TOOLS = profile.sharedComputers
|
|
672
766
|
? AUTHORING_TOOLS
|
|
673
767
|
: AUTHORING_TOOLS.filter((tool) => !SHARED_COMPUTER_TOOL_NAMES.has(tool.name));
|
|
@@ -675,7 +769,7 @@ export function availableTools(profile) {
|
|
|
675
769
|
? SHAREABLE_TOOLS
|
|
676
770
|
: SHAREABLE_TOOLS.filter((tool) => !VOICE_TOOL_NAMES.has(tool.name));
|
|
677
771
|
return profile.externalRuntime
|
|
678
|
-
?
|
|
772
|
+
? BOT_SCOPED_TOOLS.filter(tool => EXTERNAL_TOOL_NAMES.has(tool.name))
|
|
679
773
|
: profile.coordinating
|
|
680
774
|
? VOICE_READY_TOOLS.filter(tool => !ROOM_REPLACED_TOOLS.has(tool.name) || (tool.name === "start_thread" && profile.ownThreadCreation))
|
|
681
775
|
.map(tool => tool.name === "start_thread" ? {
|