openmausbot 0.1.76 → 0.1.78
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/dist/assets/{index-C6blGSq5.js → index-BqDJqf2C.js} +1 -1
- package/dist/assets/index-DjwAroIZ.js +305 -0
- package/dist/assets/index-Pbb6Ao0s.css +1 -0
- package/dist/index.html +2 -2
- package/dist-server/container-mcp.js +5 -1
- package/dist-server/drivers/agents-proxy.js +838 -30
- package/dist-server/enterprise/server/index.js +387 -29
- package/dist-server/index.js +4097 -2063
- package/dist-server/openmausbot.js +730 -523
- package/dist-server/pair-cli.js +730 -523
- package/dist-server/server/agent-tool-policy.js +1 -0
- package/dist-server/server/auto-approve.js +16 -0
- package/dist-server/server/bot-overview.js +6 -2
- package/dist-server/server/bot-package.js +17 -3
- package/dist-server/server/browser-live.js +3 -0
- package/dist-server/server/chief-of-staff.js +2 -2
- package/dist-server/server/config.js +16 -0
- package/dist-server/server/drivers/acp/core.js +3 -1
- package/dist-server/server/drivers/agents-proxy.js +132 -27
- package/dist-server/server/drivers/boxagent.js +1 -1
- package/dist-server/server/drivers/claude.js +3 -2
- package/dist-server/server/drivers/codex.js +3 -1
- package/dist-server/server/drivers/pi.js +4 -0
- package/dist-server/server/enterprise.js +38 -0
- package/dist-server/server/environment.js +3 -0
- package/dist-server/server/fleet-agent.js +57 -13
- package/dist-server/server/fleet-cli.js +248 -31
- package/dist-server/server/fleet.js +88 -14
- package/dist-server/server/hosted-contract.js +16 -0
- package/dist-server/server/index.js +877 -197
- package/dist-server/server/message-file.js +4 -0
- package/dist-server/server/package-export.js +16 -14
- package/dist-server/server/peer-approval.js +1 -1
- package/dist-server/server/profile-requests.js +26 -4
- package/dist-server/server/provider-key-check.js +18 -1
- package/dist-server/server/request-auth.js +14 -5
- package/dist-server/server/room-handoffs.js +38 -1
- package/dist-server/server/routine-requests.js +86 -3
- package/dist-server/server/routines.js +31 -13
- package/dist-server/server/sessions.js +115 -8
- package/dist-server/server/setup-mode.js +11 -13
- package/dist-server/server/shared-computer-control.js +24 -0
- package/dist-server/server/shared-computers.js +131 -0
- package/dist-server/server/skill-learn.js +4 -4
- package/dist-server/server/skills.js +21 -0
- package/dist-server/server/steer-queue.js +2 -0
- package/dist-server/server/store.js +110 -8
- package/dist-server/server/system-prompt.js +7 -5
- package/dist-server/server/team-backup.js +6 -2
- package/dist-server/server/team-computers.js +108 -0
- package/dist-server/server/team-setup-requests.js +83 -31
- package/dist-server/server/tool-summary.js +53 -1
- package/dist-server/server/workspace-backup.js +1 -0
- package/dist-server/shared/approval-mode.js +4 -1
- package/dist-server/shared/cron-label.js +39 -0
- package/dist-server/shared/routine-schedule.js +89 -0
- package/dist-server/shared/team-backup.js +1 -1
- package/dist-server/shared/team-computer.js +1 -0
- package/dist-server/vps-container-mcp.js +5 -1
- package/enterprise/README.md +21 -3
- package/enterprise/server/index.js +387 -29
- package/package.json +1 -1
- package/dist/assets/index-BtT047dk.css +0 -1
- package/dist/assets/index-fKXFpB_J.js +0 -305
|
@@ -7,6 +7,22 @@
|
|
|
7
7
|
// verdict the app synthesizes is Full access, because that level is the
|
|
8
8
|
// person's explicit, separately confirmed grant to answer every prompt.
|
|
9
9
|
// Questions never come through here: a bot's question always reaches a human.
|
|
10
|
+
/** A failed delivery is a runtime error, not another permission decision.
|
|
11
|
+
* An expired ask must never become a fresh Allow/Deny card. */
|
|
12
|
+
export async function deliverFullAccessApproval(adapter, threadId, requestId, turnId, isCurrent = () => false) {
|
|
13
|
+
if (!adapter)
|
|
14
|
+
return "failed";
|
|
15
|
+
try {
|
|
16
|
+
return await adapter.respondToRequest(threadId, requestId, { behavior: "allow" });
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
// Some adapters ignore the optional native turn id. Recheck the
|
|
20
|
+
// server's owning generation before interrupting that thread.
|
|
21
|
+
if (turnId && isCurrent())
|
|
22
|
+
await adapter.interruptTurn(threadId, turnId).catch(() => { });
|
|
23
|
+
return "failed";
|
|
24
|
+
}
|
|
25
|
+
}
|
|
10
26
|
/** Full access is the person's explicit grant to this receiving bot, including
|
|
11
27
|
* delegated work. It never inherits the sender's mode or elevates another bot.
|
|
12
28
|
* Custom is a provider-config choice rather than an app Full-access grant, so
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { approvalModeFor } from "../shared/approval-mode.js";
|
|
2
|
+
import { cronScheduleLabel } from "../shared/cron-label.js";
|
|
2
3
|
/** The first paragraph of a SOUL.md-style persona, capped at 240 characters
|
|
3
4
|
* so a settings-dialog card never renders a full standing-instructions
|
|
4
5
|
* document inline. */
|
|
@@ -32,6 +33,8 @@ function clockTime(hhmm) {
|
|
|
32
33
|
* approval card's scheduleText() carries the anchor instant and timezone
|
|
33
34
|
* name because a card must be exact; a plain-language overview must not. */
|
|
34
35
|
function schedulePhrase(schedule, timeZone) {
|
|
36
|
+
if (schedule.type === "cron")
|
|
37
|
+
return cronScheduleLabel(schedule);
|
|
35
38
|
if (schedule.type === "once") {
|
|
36
39
|
const date = new Date(schedule.at).toLocaleDateString("en-US", { month: "long", day: "numeric", timeZone });
|
|
37
40
|
return `Once on ${date} at ${time(schedule.at, timeZone)}`;
|
|
@@ -178,7 +181,7 @@ function wontLines(facts) {
|
|
|
178
181
|
lines.push("Command approvals follow the provider's custom configuration.");
|
|
179
182
|
if (facts.bot.peers?.length === 0)
|
|
180
183
|
lines.push("Cannot initiate contact with other bots.");
|
|
181
|
-
else if (facts.bot.approvePeerComms)
|
|
184
|
+
else if (mode !== "full" && facts.bot.approvePeerComms)
|
|
182
185
|
lines.push("Asks before contacting other bots.");
|
|
183
186
|
// "Has no connected apps." is definite when apps are off for this bot,
|
|
184
187
|
// not configured, or unsupported by its engine — no inventory needed. Only
|
|
@@ -191,7 +194,8 @@ function wontLines(facts) {
|
|
|
191
194
|
lines.push("Can't use a computer.");
|
|
192
195
|
if (!facts.routines.some((routine) => routine.enabled))
|
|
193
196
|
lines.push("Won't act on a schedule.");
|
|
194
|
-
|
|
197
|
+
if (mode !== "full")
|
|
198
|
+
lines.push("Profile proposal cards require your approval.");
|
|
195
199
|
return lines;
|
|
196
200
|
}
|
|
197
201
|
export function buildBotOverview(facts) {
|
|
@@ -3,6 +3,8 @@ import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
|
3
3
|
import { schemaIssue } from "./schema.js";
|
|
4
4
|
import { isSkillName, parseSkillMd, SKILL_FILE_MAX_BYTES } from "./skills.js";
|
|
5
5
|
import { BOT_PROFILE_LIMITS } from "../shared/bot-profile.js";
|
|
6
|
+
import { normalizeCronSchedule } from "../shared/routine-schedule.js";
|
|
7
|
+
import { cronScheduleLabel } from "../shared/cron-label.js";
|
|
6
8
|
export const BOT_PACKAGE_FORMAT = "openmaus.package";
|
|
7
9
|
export const BOT_PACKAGE_VERSION = 1;
|
|
8
10
|
export const BOTMRR_MARKDOWN_VERSION = 1;
|
|
@@ -58,6 +60,7 @@ const intervalWindow = z.object({
|
|
|
58
60
|
message: "must end later on the same day",
|
|
59
61
|
});
|
|
60
62
|
const packageRoutineScheduleSchema = z.discriminatedUnion("type", [
|
|
63
|
+
z.object({ type: z.literal("cron"), expression: requiredText(256), timeZone: requiredText(100) }).strict(),
|
|
61
64
|
z.object({ type: z.literal("once"), at: z.number().int() }),
|
|
62
65
|
z.object({
|
|
63
66
|
type: z.literal("daily"),
|
|
@@ -73,6 +76,15 @@ const packageRoutineScheduleSchema = z.discriminatedUnion("type", [
|
|
|
73
76
|
endsAt: z.number().int().nonnegative().max(MAX_DATE_MS).optional(),
|
|
74
77
|
}),
|
|
75
78
|
]).superRefine((schedule, context) => {
|
|
79
|
+
if (schedule.type === "cron") {
|
|
80
|
+
try {
|
|
81
|
+
normalizeCronSchedule(schedule);
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
context.addIssue({ code: "custom", message: error instanceof Error ? error.message : "Invalid cron schedule" });
|
|
85
|
+
}
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
76
88
|
if (schedule.type !== "interval")
|
|
77
89
|
return;
|
|
78
90
|
if (schedule.window) {
|
|
@@ -321,9 +333,11 @@ export function renderBotPackageMarkdown(document) {
|
|
|
321
333
|
`**Owner:** \`${routine.agent}\` `,
|
|
322
334
|
`**Schedule:** ${routine.schedule.type === "daily"
|
|
323
335
|
? `${routine.schedule.time} on weekdays ${routine.schedule.weekdays.join(", ")}`
|
|
324
|
-
: routine.schedule.type === "
|
|
325
|
-
?
|
|
326
|
-
:
|
|
336
|
+
: routine.schedule.type === "cron"
|
|
337
|
+
? `${cronScheduleLabel(routine.schedule)} (\`${routine.schedule.expression}\`)`
|
|
338
|
+
: routine.schedule.type === "interval"
|
|
339
|
+
? intervalScheduleText(routine.schedule)
|
|
340
|
+
: `once at ${routine.schedule.at}`} `,
|
|
327
341
|
`**Run limit:** ${routine.timeoutMinutes === undefined ? "none" : `${routine.timeoutMinutes} minutes`} `,
|
|
328
342
|
"**Initial state:** paused — the user must enable it",
|
|
329
343
|
"",
|
|
@@ -564,6 +564,9 @@ export class BrowserLive {
|
|
|
564
564
|
closeForSession(session) { for (const viewer of this.viewers.values())
|
|
565
565
|
if (viewer.session === session)
|
|
566
566
|
this.close(viewer); }
|
|
567
|
+
closeForOwner(owner) { for (const viewer of this.viewers.values())
|
|
568
|
+
if (viewer.owner === owner)
|
|
569
|
+
this.close(viewer); }
|
|
567
570
|
closeForBot(botId) { for (const viewer of this.viewers.values())
|
|
568
571
|
if (viewer.botId === botId)
|
|
569
572
|
this.close(viewer); }
|
|
@@ -29,7 +29,7 @@ export function chiefOfStaffSystemPrompt(chiefId, bots, canDelegate, trustedOpen
|
|
|
29
29
|
});
|
|
30
30
|
const delegation = canDelegate
|
|
31
31
|
? boundedCoordination
|
|
32
|
-
? "Use list_bots or list_room_targets for the live reachable roster. Use coordinate_bots to ask actual teammates for advice or assign concrete work. Outside a room,
|
|
32
|
+
? "Use list_bots or list_room_targets for the live reachable roster. Use coordinate_bots to ask actual teammates for advice or assign concrete work. Outside a room, everything you send a teammate continues your one standing conversation with them, using their own model and permissions, so they still have the context of your earlier assignments. Busy teammates queue. Give self-contained briefs, then end your turn; you resume automatically after their results return. Leads can coordinate their own specialists. Do not poll, send acknowledgements as new work, or substitute native helpers for named bots. On return, verify the requested outcome, resolve decisions within the user's scope, request concrete corrections with rework=true when necessary, and return one consolidated answer. Consultations are advice, not proof that work or tests ran."
|
|
33
33
|
: [
|
|
34
34
|
"Use list_bots to confirm the live roster and IDs. When assigning work to a teammate, use delegate_bot: it returns immediately, keeps you available to the user, and delivers the teammate's outcome back into this conversation automatically — success or failure. When the result arrives you are woken with it: report it to the user and act. If the teammate fails or stalls, tell the user plainly and decide the next step yourself.",
|
|
35
35
|
"After delegate_bot accepts the task, acknowledge the handoff and continue with any independent work or end your turn. Do not call wait_delegation or repeatedly poll check_delegation in the same turn.",
|
|
@@ -46,7 +46,7 @@ export function chiefOfStaffSystemPrompt(chiefId, bots, canDelegate, trustedOpen
|
|
|
46
46
|
"Own the outcome: understand the request, decide what to handle yourself, coordinate the right specialists when useful, and return one concise consolidated answer.",
|
|
47
47
|
"Do not delegate trivial work merely to appear busy. Never invent a teammate's progress or result. Normal permission and approval rules still apply.",
|
|
48
48
|
delegation,
|
|
49
|
-
canDelegate ? "When the user asks you to assemble or configure a team, use list_team_setup for the exact authorized teams, bot IDs and model catalog, then propose_team_setup once with all named specialists and their profile/model changes. Include new teams explicitly; the
|
|
49
|
+
canDelegate ? "When the user asks you to assemble or configure a team, use list_team_setup for the exact authorized teams, bot IDs and model catalog, then propose_team_setup once with all named specialists and their profile/model changes. Include new teams explicitly; the plan covers their creation and your access. Existing thread models and other bots' execution permissions stay unchanged. Follow the tool result: granted Full Access may apply the plan immediately; after an applied result, continue already-requested work without another confirmation. Only if review is pending, end your turn: the user's decision automatically resumes you once with a structured result. Report failed or cancelled results honestly. Do not ask for another yes, poll, or repeat the proposal. After successful setup, use the available coordination tools for already requested work. Use create_bot only for a single specialist when no combined setup was requested. For explicitly requested bot deletion, use propose_bot_deletion separately and follow its applied or pending result too. Do not create duplicate or unnecessary bots." : "",
|
|
50
50
|
chief?.managedSections?.length ? "Reachable teammates in your allowed teams:" : `Current ${sectionName} section team:`,
|
|
51
51
|
roster,
|
|
52
52
|
trustedOpenMausStatus,
|
|
@@ -208,6 +208,10 @@ const featureConfigSchema = z.object({
|
|
|
208
208
|
/** Experimental built-in browser. Off until explicitly enabled; each bot
|
|
209
209
|
* also has its own switch. */
|
|
210
210
|
browser: z.boolean().optional(),
|
|
211
|
+
/** Opt-in computer sharing (a desktop lending folders, a terminal or
|
|
212
|
+
* computer control to a workspace). Off until explicitly enabled; there is
|
|
213
|
+
* no Settings toggle — see sharedComputersEnabled. */
|
|
214
|
+
sharedComputers: z.boolean().optional(),
|
|
211
215
|
});
|
|
212
216
|
/** First-run progress. Kept in the workspace config rather than a browser so
|
|
213
217
|
* it survives cleared site data and is shared by every paired client. Hint
|
|
@@ -435,6 +439,18 @@ export function showToolCallsEnabled(cfg) {
|
|
|
435
439
|
export function builtInBrowserEnabled(cfg) {
|
|
436
440
|
return cfg.features?.browser === true;
|
|
437
441
|
}
|
|
442
|
+
/** Opt-in computer sharing: the routes, the agent tools, the advertised
|
|
443
|
+
* capability and the desktop connector. Off unless an explicit `true` turns
|
|
444
|
+
* it on, because the reviewed feature still has open security holes (a
|
|
445
|
+
* read-only folder grant could be escalated to a shell).
|
|
446
|
+
*
|
|
447
|
+
* Deliberately NOT a Settings toggle: this is a maintainer-only escape hatch
|
|
448
|
+
* for an unfinished feature, not a user preference. Someone who needs it
|
|
449
|
+
* enables it by hand in `~/.openmausbot/config.json`
|
|
450
|
+
* (`{"features": {"sharedComputers": true}}`) and restarts the server. */
|
|
451
|
+
export function sharedComputersEnabled(cfg) {
|
|
452
|
+
return cfg.features?.sharedComputers === true;
|
|
453
|
+
}
|
|
438
454
|
/** Config sections no provider driver reads. A write that touches only
|
|
439
455
|
* these must not rebuild the fleet: rebuilding disposes every engine child
|
|
440
456
|
* and reloads it, seconds of work that would also interrupt in-flight
|
|
@@ -35,7 +35,7 @@ import { supportsApprovalMode } from "../../../shared/approval-mode.js";
|
|
|
35
35
|
// packaged server dir entirely. See server/proxy-paths.ts.
|
|
36
36
|
const COMPUTER_PROXY_PATH = SPAWNED_PROXIES.computer;
|
|
37
37
|
import { appendNative } from "../native.js";
|
|
38
|
-
import { commandSummary } from "../../tool-summary.js";
|
|
38
|
+
import { commandSummary, toolDetailPreview } from "../../tool-summary.js";
|
|
39
39
|
import { SPAWNED_PROXIES } from "../../proxy-paths.js";
|
|
40
40
|
const envOr = (key, fallback) => Number(process.env[key] ?? fallback);
|
|
41
41
|
const INIT_TIMEOUT = envOr("OPENMAUS_ACP_INIT_TIMEOUT_MS", 300_000);
|
|
@@ -620,6 +620,7 @@ export function createAcpDriver(support) {
|
|
|
620
620
|
itemId: u.toolCallId,
|
|
621
621
|
title: String(u.rawInput?.command ?? u.title ?? "tool").slice(0, 80),
|
|
622
622
|
summary: commandSummary(u.rawInput),
|
|
623
|
+
input: toolDetailPreview(u.rawInput),
|
|
623
624
|
});
|
|
624
625
|
break;
|
|
625
626
|
}
|
|
@@ -631,6 +632,7 @@ export function createAcpDriver(support) {
|
|
|
631
632
|
itemType: "tool",
|
|
632
633
|
itemId: u.toolCallId,
|
|
633
634
|
ok: u.status !== "failed",
|
|
635
|
+
output: toolDetailPreview(u.rawOutput ?? u.content),
|
|
634
636
|
});
|
|
635
637
|
}
|
|
636
638
|
break;
|
|
@@ -22,9 +22,9 @@
|
|
|
22
22
|
// never move bots or sections
|
|
23
23
|
// request_credential(id, reason?) → show a secure, allowlisted key card
|
|
24
24
|
// list_routines() → inspect this bot's scheduled work
|
|
25
|
-
// propose_routine(...) →
|
|
26
|
-
// propose_routine_action(...) →
|
|
27
|
-
// propose_profile(...) →
|
|
25
|
+
// propose_routine(...) → apply or request confirmation for a new routine
|
|
26
|
+
// propose_routine_action(...) → apply or request confirmation for a routine change
|
|
27
|
+
// propose_profile(...) → apply or request confirmation for a profile change
|
|
28
28
|
//
|
|
29
29
|
// Speaks raw JSON-RPC 2.0 over stdio (no MCP SDK — house style, matches
|
|
30
30
|
// computer-proxy / permission-proxy). All state comes from env, injected by
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
// OMB_TURN_DEPTH this turn's comms depth (the harness refuses recursion)
|
|
36
36
|
import readline from "node:readline";
|
|
37
37
|
import { CREDENTIAL_TARGETS, isCredentialTargetId } from "../../shared/credential-request.js";
|
|
38
|
+
import { normalizeCronSchedule } from "../../shared/routine-schedule.js";
|
|
38
39
|
import { agentToolAnnotations } from "../agent-tool-policy.js";
|
|
39
40
|
import { peerName } from "../peer-roster.js";
|
|
40
41
|
const HARNESS = process.env.OMB_HARNESS_URL ?? "http://127.0.0.1:8799";
|
|
@@ -43,6 +44,9 @@ const THREAD_ID = process.env.OMB_THREAD_ID ?? "";
|
|
|
43
44
|
const TOKEN = process.env.OMB_COMMS_TOKEN ?? "";
|
|
44
45
|
const DEPTH = Number(process.env.OMB_TURN_DEPTH ?? "0") || 0;
|
|
45
46
|
const SKILL_AUTHORING_ENABLED = process.env.OMB_SKILL_AUTHORING_ENABLED === "1";
|
|
47
|
+
// Opt-in computer sharing (server features.sharedComputers). Off unless the
|
|
48
|
+
// harness says "1", the same way skill authoring is gated above.
|
|
49
|
+
const SHARED_COMPUTERS_ENABLED = process.env.OMB_SHARED_COMPUTERS_ENABLED === "1";
|
|
46
50
|
const MAX_CREATED_PER_TURN = 4;
|
|
47
51
|
let createdThisTurn = 0;
|
|
48
52
|
// Same spirit as MAX_CREATED_PER_TURN above and MAX_QUEUED_PER_THREAD in
|
|
@@ -83,12 +87,22 @@ const WEEKDAYS = [
|
|
|
83
87
|
const ROUTINE_SCHEDULE_SCHEMA = {
|
|
84
88
|
type: "object",
|
|
85
89
|
additionalProperties: false,
|
|
86
|
-
description: '
|
|
90
|
+
description: 'Use {"type":"cron","expression":"0 9 1 * *","timeZone":"Asia/Kolkata"} for 09:00 on the first of each month, once with at for one future run, weekly with time + weekdays, daily with time, or interval with every_minutes for elapsed-time repetition. Intervals can optionally be limited with weekdays, window_start + window_end, and ends_at.',
|
|
87
91
|
properties: {
|
|
88
92
|
type: {
|
|
89
93
|
type: "string",
|
|
90
|
-
enum: ["once", "weekly", "daily", "interval"],
|
|
91
|
-
description: "once = a single future run; weekly = chosen weekdays; daily = every day; interval = every N minutes.",
|
|
94
|
+
enum: ["once", "weekly", "daily", "interval", "cron"],
|
|
95
|
+
description: "once = a single future run; weekly = chosen weekdays; daily = every day; interval = every N elapsed minutes; cron = a calendar rule in an explicit timezone.",
|
|
96
|
+
},
|
|
97
|
+
expression: {
|
|
98
|
+
type: "string",
|
|
99
|
+
maxLength: 256,
|
|
100
|
+
description: "Only for cron: five fields, minute hour day-of-month month weekday. Examples: 0 9 1 * * = monthly on day 1 at 09:00; 0 9 L * * = last day of each month; 0 9 * * MON#2 = second Monday of each month. Lists, ranges and steps are supported. No seconds, year, or @ macros. Never substitute daily AI date checks for a calendar rule.",
|
|
101
|
+
},
|
|
102
|
+
timeZone: {
|
|
103
|
+
type: "string",
|
|
104
|
+
maxLength: 128,
|
|
105
|
+
description: "Required for cron: explicit IANA timezone, for example Asia/Kolkata, America/New_York, or UTC. Use the user's requested zone; resolve ambiguity before proposing. Do not send a numeric offset or local-time abbreviation.",
|
|
92
106
|
},
|
|
93
107
|
at: {
|
|
94
108
|
type: "string",
|
|
@@ -111,7 +125,7 @@ const ROUTINE_SCHEDULE_SCHEMA = {
|
|
|
111
125
|
},
|
|
112
126
|
starts_at: {
|
|
113
127
|
type: "string",
|
|
114
|
-
description: "Optional for type interval: RFC3339 date-time with an explicit timezone offset that anchors the cadence. Omit to start one interval after confirmation.",
|
|
128
|
+
description: "Optional for type interval: RFC3339 date-time with an explicit timezone offset that anchors the cadence. Omit to start one interval after the routine is applied (immediately with granted Full Access, otherwise after confirmation).",
|
|
115
129
|
},
|
|
116
130
|
window_start: {
|
|
117
131
|
type: "string",
|
|
@@ -154,7 +168,8 @@ const SHORT_WEEKDAYS = {
|
|
|
154
168
|
};
|
|
155
169
|
const SUPPORTED_SCHEDULES = 'Supported schedules: {"type":"once","at":"2026-09-01T09:00:00+05:30"} (future RFC3339 with explicit offset), ' +
|
|
156
170
|
'{"type":"weekly","time":"09:00","weekdays":["monday","friday"]}, {"type":"daily","time":"09:00"}, ' +
|
|
157
|
-
'
|
|
171
|
+
'{"type":"interval","every_minutes":15,"weekdays":["monday","friday"],"window_start":"09:00","window_end":"17:00"}, ' +
|
|
172
|
+
'or {"type":"cron","expression":"0 9 1 * *","timeZone":"Asia/Kolkata"} (monthly at 09:00 on day 1; five fields and an explicit IANA timezone).';
|
|
158
173
|
/** A schedule as the harness accepts it, or a message telling the model
|
|
159
174
|
* exactly what to send instead. Coercion first, error second: models
|
|
160
175
|
* routinely stringify nested objects, say "daily", or shorten weekday
|
|
@@ -179,7 +194,9 @@ function normalizeScheduleInput(args) {
|
|
|
179
194
|
? ["type", "time", "weekdays"]
|
|
180
195
|
: type === "interval"
|
|
181
196
|
? ["type", "every_minutes", "everyMinutes", "starts_at", "anchorAt", "weekdays", "every_day", "window_start", "window_end", "window", "all_day", "ends_at", "endsAt", "never_ends"]
|
|
182
|
-
:
|
|
197
|
+
: type === "cron"
|
|
198
|
+
? ["type", "expression", "timeZone"]
|
|
199
|
+
: null;
|
|
183
200
|
// Provider conversions may send unused optional fields as null. Ignore
|
|
184
201
|
// those, but never silently discard an actual scheduling constraint (for
|
|
185
202
|
// example timezone or a misspelled starts_at) and approve different work.
|
|
@@ -187,6 +204,14 @@ function normalizeScheduleInput(args) {
|
|
|
187
204
|
if (unsupported) {
|
|
188
205
|
return { error: `Unsupported ${type} schedule field "${unsupported}". Weekly and daily times use the computer's timezone from list_routines. ${SUPPORTED_SCHEDULES}` };
|
|
189
206
|
}
|
|
207
|
+
if (type === "cron") {
|
|
208
|
+
try {
|
|
209
|
+
return { schedule: { ...normalizeCronSchedule({ type, expression: raw.expression, timeZone: raw.timeZone }) } };
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
return { error: `${error instanceof Error ? error.message : "Invalid cron schedule"}. ${SUPPORTED_SCHEDULES}` };
|
|
213
|
+
}
|
|
214
|
+
}
|
|
190
215
|
if (type === "once") {
|
|
191
216
|
if (typeof raw.at !== "string" || !raw.at.trim()) {
|
|
192
217
|
return { error: `A once schedule needs "at": a future RFC3339 date-time with an explicit offset, for example 2026-09-01T09:00:00+05:30.` };
|
|
@@ -308,7 +333,7 @@ function normalizeScheduleInput(args) {
|
|
|
308
333
|
},
|
|
309
334
|
};
|
|
310
335
|
}
|
|
311
|
-
if (type === "
|
|
336
|
+
if (type === "hourly" || type === "minutes") {
|
|
312
337
|
return { error: `Use an interval schedule for every-N-minutes work. ${SUPPORTED_SCHEDULES}` };
|
|
313
338
|
}
|
|
314
339
|
return { error: `Unknown schedule type "${type || "(missing)"}". ${SUPPORTED_SCHEDULES}` };
|
|
@@ -339,10 +364,25 @@ const ROUTINE_FIELDS_SCHEMA = {
|
|
|
339
364
|
},
|
|
340
365
|
continuity: {
|
|
341
366
|
type: "boolean",
|
|
342
|
-
description: "Opt in to using the latest completed run's bounded report as historical context. Defaults to false; set false in an update to start fresh again.
|
|
367
|
+
description: "Opt in to using the latest completed run's bounded report as historical context. Defaults to false; set false in an update to start fresh again. Included in the applied result or pending confirmation.",
|
|
343
368
|
},
|
|
344
369
|
};
|
|
370
|
+
const 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.";
|
|
345
371
|
const TOOLS = [
|
|
372
|
+
{
|
|
373
|
+
name: "list_shared_computers",
|
|
374
|
+
description: "List online desktop computers explicitly shared with this workspace, and their allowed folders/capabilities. These are the user's computers, not this server. An offline or unshared computer cannot be accessed. Folder paths use opaque folder IDs and relative paths.",
|
|
375
|
+
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
name: "shared_computer",
|
|
379
|
+
description: "Use a desktop explicitly shared by the user. Discover computer_id and folder_id with list_shared_computers. list_files/read_file/write_file are confined to chosen folders; paths are relative. read_file returns sha256; overwriting requires expected_sha256. Binary files support base64 encoding. run_command requires a SEPARATE unrestricted terminal grant. computer_tools lists the native computer-control tools; computer_call invokes one with arguments and needs a SEPARATE computer-control grant. Never substitute the server's filesystem when this desktop is offline. Actions are not retried automatically; inspect an uncertain outcome before retrying.",
|
|
380
|
+
inputSchema: { type: "object", additionalProperties: false, properties: {
|
|
381
|
+
computer_id: { type: "string" }, action: { type: "string", enum: ["list_files", "read_file", "write_file", "run_command", "computer_tools", "computer_call"] },
|
|
382
|
+
folder_id: { type: "string" }, path: { type: "string" }, content: { type: "string" }, encoding: { type: "string", enum: ["utf8", "base64"] }, expected_sha256: { type: "string" },
|
|
383
|
+
command: { type: "string" }, tool_name: { type: "string" }, arguments: { type: "object", additionalProperties: true },
|
|
384
|
+
}, required: ["computer_id", "action"] },
|
|
385
|
+
},
|
|
346
386
|
{
|
|
347
387
|
name: "list_room_targets",
|
|
348
388
|
description: "Discover actual OpenMausBot teammates and rooms in your allowed teams. Works in a normal bot conversation too; no room is required. Returns bot and room IDs, roles and working folders, never other conversations' history. Use these bots, not native coding helpers with similar names, when the user asks their team to work together.",
|
|
@@ -350,13 +390,14 @@ const TOOLS = [
|
|
|
350
390
|
},
|
|
351
391
|
{
|
|
352
392
|
name: "coordinate_bots",
|
|
353
|
-
description: "Ask existing OpenMausBot teammates for advice or assign concrete work. From normal chat
|
|
393
|
+
description: "Ask existing OpenMausBot teammates for advice or assign concrete work. From normal chat every assignment you send a teammate continues your one standing conversation with that teammate, so they keep the context of what you asked before; from a room it defaults to this room. Use group_id from list_room_targets for a specific room. Name 1-4 bot_ids: they receive only your brief and use their own model, tools and permissions. Busy bots queue. They can consult their specialists; all results return here and resume you automatically. Include exact file paths, constraints and what must be verified. After sending all assignments, END your turn; do not poll or wait. On return, resolve tradeoffs, verify the requested outcome and request concrete corrections if necessary before giving one final answer. Do not send acknowledgements as new work.",
|
|
354
394
|
inputSchema: { type: "object", additionalProperties: false, properties: {
|
|
355
|
-
group_id: { type: "string", description: "Optional destination room. Omit for this room, or
|
|
395
|
+
group_id: { type: "string", description: "Optional destination room. Omit for this room, or your standing conversation with each teammate when chatting directly." },
|
|
356
396
|
bot_ids: { type: "array", items: { type: "string" }, minItems: 1, maxItems: 4, uniqueItems: true },
|
|
357
397
|
message: { type: "string", minLength: 1, maxLength: 4000, description: "Self-contained question or task for these teammates. Send separate requests when responsibilities differ." },
|
|
358
398
|
request_key: { type: "string", description: "A short unique assignment key. Reuse for an identical retry." },
|
|
359
399
|
rework: { type: "boolean", description: "True only for concrete additional work from someone who already completed a request." },
|
|
400
|
+
label: { type: "string", description: "Optional short name (one line, at most 60 characters) for this job. Used only when the teammate is still working on your previous assignment and this one therefore runs in its own thread beside your standing conversation." },
|
|
360
401
|
}, required: ["bot_ids", "message", "request_key"] },
|
|
361
402
|
},
|
|
362
403
|
{
|
|
@@ -480,7 +521,7 @@ const TOOLS = [
|
|
|
480
521
|
},
|
|
481
522
|
{
|
|
482
523
|
name: "propose_team_setup",
|
|
483
|
-
description: "Chief of Staff only:
|
|
524
|
+
description: "Chief of Staff only: submit all requested specialist creation, profile/model configuration, and authorized team moves in ONE combined plan. Use exact catalog engine/model IDs from list_team_setup. Combine all fields for each bot; use the same create key or botId to coalesce repeated entries. New teams must be named explicitly in newTeams and have a specialist in this plan; access is granted only to those new teams. Existing unauthorized teams cannot be included. Models change bot defaults for groups/new threads; existing threads and execution permissions stay unchanged. If review is pending, the decision and structured result automatically resume you once; do not ask again, poll, or repeat the proposal." + PROPOSAL_OUTCOME,
|
|
484
525
|
inputSchema: {
|
|
485
526
|
type: "object", additionalProperties: false,
|
|
486
527
|
properties: {
|
|
@@ -507,14 +548,14 @@ const TOOLS = [
|
|
|
507
548
|
},
|
|
508
549
|
{
|
|
509
550
|
name: "propose_bot_deletion",
|
|
510
|
-
description: "Chief of Staff only: when the user explicitly asks to delete a named teammate,
|
|
551
|
+
description: "Chief of Staff only: when the user explicitly asks to delete a named teammate, submit a separate deletion request for that exact bot. Deletion removes its conversations, memory, instructions and skills; generated project files remain. Running work and owned computers can block deletion. Never delete yourself, substitute an archive, or put deletion into a setup batch. If review is pending, the decision and result resume you once." + PROPOSAL_OUTCOME,
|
|
511
552
|
inputSchema: { type: "object", additionalProperties: false, properties: {
|
|
512
553
|
bot_id: { type: "string", minLength: 1 }, reason: { type: "string", minLength: 1, maxLength: 500 },
|
|
513
554
|
}, required: ["bot_id", "reason"] },
|
|
514
555
|
},
|
|
515
556
|
{
|
|
516
557
|
name: "create_room",
|
|
517
|
-
description: "Create a room in your own section when the user asks for one (maximum four per turn). Chiefs only. Choose active peers from list_bots; you are included automatically as the default responder. This creates no turns or messages. Section moves stay with the user.
|
|
558
|
+
description: "Create a room in your own section when the user asks for one (maximum four per turn). Chiefs only. Choose active peers from list_bots; you are included automatically as the default responder. This creates no turns or messages. Section moves stay with the user. Follow the tool result under the effective access level; if permission is refused, ask the user to make the room change instead, without trying another route.",
|
|
518
559
|
inputSchema: {
|
|
519
560
|
type: "object",
|
|
520
561
|
additionalProperties: false,
|
|
@@ -538,7 +579,7 @@ const TOOLS = [
|
|
|
538
579
|
},
|
|
539
580
|
{
|
|
540
581
|
name: "manage_room",
|
|
541
|
-
description: "Manage a room from list_rooms: rename it, change its bulletin, or add/remove/set members. Chiefs only, within your own section and allowed peers; keep yourself as a member. Busy rooms, pending approvals and team-goal leads are protected. You cannot move rooms or bots between sections.
|
|
582
|
+
description: "Manage a room from list_rooms: rename it, change its bulletin, or add/remove/set members. Chiefs only, within your own section and allowed peers; keep yourself as a member. Busy rooms, pending approvals and team-goal leads are protected. You cannot move rooms or bots between sections. Follow the tool result under the effective access level; if the change is refused, report the blocker and ask the user to make the change instead, without trying another route.",
|
|
542
583
|
inputSchema: {
|
|
543
584
|
type: "object",
|
|
544
585
|
additionalProperties: false,
|
|
@@ -646,7 +687,7 @@ const TOOLS = [
|
|
|
646
687
|
},
|
|
647
688
|
{
|
|
648
689
|
name: "propose_routine",
|
|
649
|
-
description: "Prepare a new routine after the user explicitly asks to schedule recurring or future work. Call list_routines first for relative dates or times so you use its authoritative current time and timezone.
|
|
690
|
+
description: "Prepare a new routine after the user explicitly asks to schedule recurring or future work. Call list_routines first for relative dates or times so you use its authoritative current time and timezone. Convert calendar requests (monthly dates, last days, nth weekdays) into a validated five-field cron schedule with an explicit IANA timeZone; keep elapsed every-N-minutes work as interval. Never approximate unsupported requests with a different weekly schedule or an AI date-check routine; explain the limitation instead. Resolve ambiguous dates, times, timezone, destination, or instructions with the user first, and always give one-time schedules an explicit RFC3339 offset. If the user asks for the routine to run as ANOTHER bot in your section, call list_bots and pass that bot's id as for_bot_id; each run retains that bot's own permissions." + PROPOSAL_OUTCOME,
|
|
650
691
|
inputSchema: {
|
|
651
692
|
type: "object",
|
|
652
693
|
additionalProperties: false,
|
|
@@ -662,7 +703,7 @@ const TOOLS = [
|
|
|
662
703
|
},
|
|
663
704
|
{
|
|
664
705
|
name: "propose_routine_action",
|
|
665
|
-
description: "Prepare a user-requested change to one of this bot's existing routines.
|
|
706
|
+
description: "Prepare a user-requested change to one of this bot's existing routines. Use list_routines first to get the routine id." + PROPOSAL_OUTCOME,
|
|
666
707
|
inputSchema: {
|
|
667
708
|
type: "object",
|
|
668
709
|
additionalProperties: false,
|
|
@@ -685,7 +726,7 @@ const TOOLS = [
|
|
|
685
726
|
},
|
|
686
727
|
{
|
|
687
728
|
name: "propose_profile",
|
|
688
|
-
description: "
|
|
729
|
+
description: "Submit user-requested changes to your own name, title, description, standing instructions (SOUL.md), or working folder (cwd). 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,
|
|
689
730
|
inputSchema: {
|
|
690
731
|
type: "object",
|
|
691
732
|
additionalProperties: false,
|
|
@@ -715,7 +756,7 @@ const TOOLS = [
|
|
|
715
756
|
},
|
|
716
757
|
{
|
|
717
758
|
name: "skill_manage",
|
|
718
|
-
description: "
|
|
759
|
+
description: "Submit a new or updated reusable SKILL.md. Never update unless the user explicitly asked to revise that named skill. While review is pending, a create stays inactive and an update leaves the current version unchanged." + PROPOSAL_OUTCOME,
|
|
719
760
|
inputSchema: {
|
|
720
761
|
type: "object",
|
|
721
762
|
additionalProperties: false,
|
|
@@ -735,7 +776,7 @@ const TOOLS = [
|
|
|
735
776
|
},
|
|
736
777
|
gist: {
|
|
737
778
|
type: "string",
|
|
738
|
-
description: "Optional one-line summary
|
|
779
|
+
description: "Optional one-line summary of the skill change, included in its applied result or pending review.",
|
|
739
780
|
},
|
|
740
781
|
source: {
|
|
741
782
|
type: "string",
|
|
@@ -753,15 +794,30 @@ const SKILL_TOOL_NAMES = new Set(["skills_list", "skill_manage"]);
|
|
|
753
794
|
const AUTHORING_TOOLS = SKILL_AUTHORING_ENABLED
|
|
754
795
|
? TOOLS
|
|
755
796
|
: TOOLS.filter((tool) => !SKILL_TOOL_NAMES.has(tool.name));
|
|
797
|
+
// A workspace with computer sharing off refuses the routes behind these two,
|
|
798
|
+
// so they must not be advertised at all: a model that sees a tool it cannot
|
|
799
|
+
// use spends turns discovering that.
|
|
800
|
+
const SHARED_COMPUTER_TOOL_NAMES = new Set(["list_shared_computers", "shared_computer"]);
|
|
801
|
+
const SHAREABLE_TOOLS = SHARED_COMPUTERS_ENABLED
|
|
802
|
+
? AUTHORING_TOOLS
|
|
803
|
+
: AUTHORING_TOOLS.filter((tool) => !SHARED_COMPUTER_TOOL_NAMES.has(tool.name));
|
|
756
804
|
// One teamwork path in room turns; keep all unrelated integrations available.
|
|
757
805
|
// Ordinary direct chats use this same bounded coordinator. Goal-owned turns
|
|
758
806
|
// retain their independent loop and cannot start a second coordinator.
|
|
759
807
|
const ROOM_ONLY_TOOLS = new Set(["list_room_targets", "coordinate_bots"]);
|
|
760
808
|
const ROOM_REPLACED_TOOLS = new Set(["ask_bot", "delegate_bot", "check_delegation", "wait_delegation", "start_thread", "send_to_thread", "wait_thread"]);
|
|
761
809
|
const COORDINATING = process.env.OMB_ROOM_TURN === "1";
|
|
810
|
+
const OWN_THREAD_CREATION = process.env.OMB_OWN_THREAD_CREATION === "1";
|
|
762
811
|
const AVAILABLE_TOOLS = COORDINATING
|
|
763
|
-
?
|
|
764
|
-
|
|
812
|
+
? SHAREABLE_TOOLS.filter(tool => !ROOM_REPLACED_TOOLS.has(tool.name) || (tool.name === "start_thread" && OWN_THREAD_CREATION))
|
|
813
|
+
.map(tool => tool.name === "start_thread" ? {
|
|
814
|
+
...tool,
|
|
815
|
+
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.",
|
|
816
|
+
inputSchema: { ...tool.inputSchema, properties: { ...tool.inputSchema.properties,
|
|
817
|
+
bot_id: { type: "string", enum: [BOT_ID], description: "Leave out, or use your own bot ID. For teammates use coordinate_bots." },
|
|
818
|
+
} },
|
|
819
|
+
} : tool)
|
|
820
|
+
: SHAREABLE_TOOLS.filter(tool => !ROOM_ONLY_TOOLS.has(tool.name));
|
|
765
821
|
const send = (msg) => process.stdout.write(JSON.stringify(msg) + "\n");
|
|
766
822
|
const ok = (id, result) => send({ jsonrpc: "2.0", id, result });
|
|
767
823
|
const rpcErr = (id, code, message) => send({ jsonrpc: "2.0", id, error: { code, message } });
|
|
@@ -845,7 +901,26 @@ function routineFields(args) {
|
|
|
845
901
|
fields.continuity = args.continuity;
|
|
846
902
|
return { fields };
|
|
847
903
|
}
|
|
904
|
+
/** Full Access is decided by the harness, not inferred from a model claim or
|
|
905
|
+
* local environment flag. Missing state preserves older pending responses. */
|
|
906
|
+
function completedProposalResult(r, subject) {
|
|
907
|
+
const state = r.state;
|
|
908
|
+
const result = jsonRecord(r.result) ? r.result : undefined;
|
|
909
|
+
const error = typeof r.error === "string" ? r.error : typeof result?.error === "string" ? result.error : undefined;
|
|
910
|
+
const attention = error ?? (r.settlementPending && typeof r.message === "string" ? r.message : undefined);
|
|
911
|
+
if ((!state || state === "pending") && !error)
|
|
912
|
+
return undefined;
|
|
913
|
+
const summary = typeof r.summary === "string" && r.summary.trim() ? `\n\n${r.summary.trim()}` : "";
|
|
914
|
+
const details = result ? `\n\nResult: ${JSON.stringify(result)}` : "";
|
|
915
|
+
if (state !== "applied" || (result?.state !== undefined && result.state !== "applied")) {
|
|
916
|
+
return { text: `The request for ${subject} did not complete successfully.${error ? ` ${error}` : ""}${summary}${details}\n\nDo not claim it was applied. Address the reported blocker rather than repeating the request or asking for a duplicate confirmation.`, isError: true };
|
|
917
|
+
}
|
|
918
|
+
return { text: `Applied ${subject}.${summary}${details}${attention ? `\n\nNeeds attention: ${attention}` : ""}\n\nNo additional confirmation is needed. Continue the requested work; do not wait for a review card or ask the user to approve this change again.` };
|
|
919
|
+
}
|
|
848
920
|
function confirmationResult(r, fallback, noun = "routine") {
|
|
921
|
+
const completed = completedProposalResult(r, fallback);
|
|
922
|
+
if (completed)
|
|
923
|
+
return completed;
|
|
849
924
|
const summary = typeof r.summary === "string" && r.summary.trim() ? `\n\n${r.summary.trim()}` : "";
|
|
850
925
|
return {
|
|
851
926
|
text: `A confirmation card is now visible to the user for ${fallback}.${summary}\n\nThis change has not been applied yet. End this turn and wait for the user to confirm or deny the card; do not claim the ${noun} was created or changed before confirmation.`,
|
|
@@ -871,7 +946,7 @@ async function callTool(name, args) {
|
|
|
871
946
|
if (name === "coordinate_bots") {
|
|
872
947
|
const r = await api("/api/internal/coordinate-bots", { method: "POST", body: JSON.stringify({
|
|
873
948
|
groupId: args.group_id, botIds: args.bot_ids, message: args.message,
|
|
874
|
-
requestKey: args.request_key, rework: args.rework,
|
|
949
|
+
requestKey: args.request_key, rework: args.rework, label: args.label,
|
|
875
950
|
}) });
|
|
876
951
|
return { text: JSON.stringify(r), ...(r.error ? { isError: true } : {}) };
|
|
877
952
|
}
|
|
@@ -1089,6 +1164,9 @@ async function callTool(name, args) {
|
|
|
1089
1164
|
};
|
|
1090
1165
|
}
|
|
1091
1166
|
const toBotId = typeof args.bot_id === "string" ? args.bot_id.trim() : "";
|
|
1167
|
+
if (COORDINATING && toBotId && toBotId !== BOT_ID) {
|
|
1168
|
+
return { text: "Use coordinate_bots for teammates; start_thread only opens a separate job on yourself.", isError: true };
|
|
1169
|
+
}
|
|
1092
1170
|
const folder = typeof args.folder === "string" ? args.folder.trim() : "";
|
|
1093
1171
|
const body = { fromBotId: BOT_ID, fromThreadId: THREAD_ID, title, message, depth: DEPTH };
|
|
1094
1172
|
if (toBotId)
|
|
@@ -1142,6 +1220,9 @@ async function callTool(name, args) {
|
|
|
1142
1220
|
...(deleting ? { targetBotId: args.bot_id, reason: args.reason } : { plan: args }),
|
|
1143
1221
|
}),
|
|
1144
1222
|
});
|
|
1223
|
+
const completed = completedProposalResult(result, deleting ? "the requested bot deletion" : "the requested team setup");
|
|
1224
|
+
if (completed)
|
|
1225
|
+
return completed;
|
|
1145
1226
|
return { text: `One review card is visible: ${String(result.title)}. Nothing has been applied. End this turn; the decision and structured result resume you automatically once. Do not ask again, poll, or repeat this proposal.` };
|
|
1146
1227
|
}
|
|
1147
1228
|
if (name === "create_bot") {
|
|
@@ -1458,7 +1539,7 @@ async function callTool(name, args) {
|
|
|
1458
1539
|
const skills = Array.isArray(r.skills) ? r.skills : [];
|
|
1459
1540
|
const staged = Array.isArray(r.staged) ? r.staged : [];
|
|
1460
1541
|
if (!skills.length && !staged.length) {
|
|
1461
|
-
return { text: "This bot has no imported skills and nothing staged. Use skill_manage action=\"create\"
|
|
1542
|
+
return { text: "This bot has no imported skills and nothing staged. Use skill_manage action=\"create\" for a user-requested skill, then follow its applied or pending result." };
|
|
1462
1543
|
}
|
|
1463
1544
|
const live = skills.length
|
|
1464
1545
|
? skills.map((skill) => {
|
|
@@ -1511,7 +1592,10 @@ async function callTool(name, args) {
|
|
|
1511
1592
|
}),
|
|
1512
1593
|
});
|
|
1513
1594
|
const nameLabel = typeof r.name === "string" ? r.name : "the skill";
|
|
1514
|
-
const warningText = Array.isArray(r.warnings) && r.warnings.length ? `\n\nScan warnings
|
|
1595
|
+
const warningText = Array.isArray(r.warnings) && r.warnings.length ? `\n\nScan warnings:\n- ${r.warnings.join("\n- ")}` : "";
|
|
1596
|
+
const completed = completedProposalResult(r, args.action === "update" ? `the update to skill “${nameLabel}”` : `the new skill “${nameLabel}”`);
|
|
1597
|
+
if (completed)
|
|
1598
|
+
return { ...completed, text: completed.text + warningText };
|
|
1515
1599
|
const status = args.action === "update"
|
|
1516
1600
|
? "The current version remains unchanged until the user reviews and applies the update."
|
|
1517
1601
|
: "The skill is staged and inactive until the user reviews and enables it.";
|
|
@@ -1550,6 +1634,27 @@ async function handle(msg) {
|
|
|
1550
1634
|
if (!AVAILABLE_TOOLS.some((t) => t.name === name))
|
|
1551
1635
|
return rpcErr(id, -32602, `Unknown tool: ${name}`);
|
|
1552
1636
|
try {
|
|
1637
|
+
// Second lock. With sharing off the tool is not in AVAILABLE_TOOLS, so
|
|
1638
|
+
// a call is already refused above as an unknown tool — the same answer
|
|
1639
|
+
// a build without the feature gives. This keeps the handler itself
|
|
1640
|
+
// refusing if that list is ever assembled differently.
|
|
1641
|
+
if (SHARED_COMPUTER_TOOL_NAMES.has(name) && !SHARED_COMPUTERS_ENABLED) {
|
|
1642
|
+
textResult(id, "Computer sharing is turned off in this workspace. There are no shared computers to use.", true);
|
|
1643
|
+
return;
|
|
1644
|
+
}
|
|
1645
|
+
if (name === "list_shared_computers") {
|
|
1646
|
+
textResult(id, JSON.stringify(await api("/api/internal/shared-computers")));
|
|
1647
|
+
return;
|
|
1648
|
+
}
|
|
1649
|
+
if (name === "shared_computer") {
|
|
1650
|
+
const response = await api("/api/internal/shared-computers", { method: "POST", body: JSON.stringify(params.arguments ?? {}) });
|
|
1651
|
+
const result = response.result;
|
|
1652
|
+
if (Array.isArray(result?.content))
|
|
1653
|
+
ok(id, result);
|
|
1654
|
+
else
|
|
1655
|
+
textResult(id, JSON.stringify(result));
|
|
1656
|
+
return;
|
|
1657
|
+
}
|
|
1553
1658
|
const { text, isError } = await callTool(name, (params.arguments ?? {}));
|
|
1554
1659
|
textResult(id, text, isError);
|
|
1555
1660
|
}
|
|
@@ -64,7 +64,7 @@ export const BoxAgentDriver = {
|
|
|
64
64
|
const model = turn.model || MODELS.default;
|
|
65
65
|
const prompt = [
|
|
66
66
|
turn.system,
|
|
67
|
-
"You are working on
|
|
67
|
+
"You are working on the assigned cloud computer — use its desktop, Chrome, and shell within the access described above.",
|
|
68
68
|
"",
|
|
69
69
|
turn.text,
|
|
70
70
|
]
|