openmausbot 0.1.77 → 0.1.79
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-DeK-z9ld.js → index-C2Je5jdF.js} +1 -1
- package/dist/assets/index-D_TFZLlo.js +305 -0
- package/dist/assets/index-Pbb6Ao0s.css +1 -0
- package/dist/index.html +2 -2
- package/dist-server/container-mcp.js +11 -5
- package/dist-server/drivers/agents-proxy.js +786 -29
- package/dist-server/enterprise/server/index.js +0 -1
- package/dist-server/index.js +17598 -7843
- package/dist-server/local-computer.js +0 -1
- package/dist-server/mcp-server.js +8 -1
- package/dist-server/openmausbot.js +9195 -759
- package/dist-server/pair-cli.js +9195 -759
- package/dist-server/proxy-paths.js +0 -1
- 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/box.js +98 -41
- package/dist-server/server/browser-engine.js +1 -1
- package/dist-server/server/browser-runtime.js +11 -3
- package/dist-server/server/browser-tool-shape.js +72 -0
- package/dist-server/server/chief-of-staff.js +2 -2
- package/dist-server/server/claude-accounts.js +1 -0
- package/dist-server/server/cli-setup.js +1 -1
- package/dist-server/server/composio.js +18 -0
- package/dist-server/server/config.js +16 -4
- package/dist-server/server/container-computer.js +0 -12
- package/dist-server/server/drivers/acp/core.js +4 -19
- package/dist-server/server/drivers/agents-proxy.js +74 -25
- package/dist-server/server/drivers/boxagent.js +48 -4
- package/dist-server/server/drivers/chat-mcp-tools.js +368 -0
- package/dist-server/server/drivers/chat-tool-approval.js +42 -0
- package/dist-server/server/drivers/claude.js +87 -32
- package/dist-server/server/drivers/codex.js +69 -31
- package/dist-server/server/drivers/grok.js +4 -0
- package/dist-server/server/drivers/minimax.js +11 -3
- package/dist-server/server/drivers/openai-chat-protocol.js +92 -0
- package/dist-server/server/drivers/openai-chat.js +332 -88
- package/dist-server/server/drivers/openai-compat.js +4 -0
- package/dist-server/server/drivers/pi.js +1 -9
- package/dist-server/server/index.js +271 -136
- 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/proxy-paths.js +0 -1
- package/dist-server/server/room-handoffs.js +85 -10
- package/dist-server/server/routine-requests.js +86 -3
- package/dist-server/server/routines.js +31 -13
- package/dist-server/server/setup-mode.js +11 -13
- package/dist-server/server/skill-learn.js +4 -4
- package/dist-server/server/skills.js +21 -0
- package/dist-server/server/store.js +90 -1
- package/dist-server/server/system-prompt.js +7 -5
- package/dist-server/server/team-backup.js +6 -2
- package/dist-server/server/team-setup-requests.js +83 -31
- package/dist-server/server/tts/chatterbox.js +83 -0
- package/dist-server/server/tts/index.js +45 -12
- package/dist-server/server/workspace.js +12 -2
- package/dist-server/shared/ask-question.js +28 -0
- package/dist-server/shared/computer-contention.js +17 -0
- 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/vps-container-mcp.js +11 -5
- package/enterprise/server/index.js +0 -1
- package/package.json +1 -1
- package/dist/assets/index-XdHJ-9CF.css +0 -1
- package/dist/assets/index-lhqE1umn.js +0 -305
- package/dist-server/computer-proxy.js +0 -1196
- package/dist-server/server/computer-proxy.js +0 -1070
- package/dist-server/server/remote-computer.js +0 -170
|
@@ -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";
|
|
@@ -86,12 +87,22 @@ const WEEKDAYS = [
|
|
|
86
87
|
const ROUTINE_SCHEDULE_SCHEMA = {
|
|
87
88
|
type: "object",
|
|
88
89
|
additionalProperties: false,
|
|
89
|
-
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.',
|
|
90
91
|
properties: {
|
|
91
92
|
type: {
|
|
92
93
|
type: "string",
|
|
93
|
-
enum: ["once", "weekly", "daily", "interval"],
|
|
94
|
-
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.",
|
|
95
106
|
},
|
|
96
107
|
at: {
|
|
97
108
|
type: "string",
|
|
@@ -114,7 +125,7 @@ const ROUTINE_SCHEDULE_SCHEMA = {
|
|
|
114
125
|
},
|
|
115
126
|
starts_at: {
|
|
116
127
|
type: "string",
|
|
117
|
-
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).",
|
|
118
129
|
},
|
|
119
130
|
window_start: {
|
|
120
131
|
type: "string",
|
|
@@ -157,7 +168,8 @@ const SHORT_WEEKDAYS = {
|
|
|
157
168
|
};
|
|
158
169
|
const SUPPORTED_SCHEDULES = 'Supported schedules: {"type":"once","at":"2026-09-01T09:00:00+05:30"} (future RFC3339 with explicit offset), ' +
|
|
159
170
|
'{"type":"weekly","time":"09:00","weekdays":["monday","friday"]}, {"type":"daily","time":"09:00"}, ' +
|
|
160
|
-
'
|
|
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).';
|
|
161
173
|
/** A schedule as the harness accepts it, or a message telling the model
|
|
162
174
|
* exactly what to send instead. Coercion first, error second: models
|
|
163
175
|
* routinely stringify nested objects, say "daily", or shorten weekday
|
|
@@ -182,7 +194,9 @@ function normalizeScheduleInput(args) {
|
|
|
182
194
|
? ["type", "time", "weekdays"]
|
|
183
195
|
: type === "interval"
|
|
184
196
|
? ["type", "every_minutes", "everyMinutes", "starts_at", "anchorAt", "weekdays", "every_day", "window_start", "window_end", "window", "all_day", "ends_at", "endsAt", "never_ends"]
|
|
185
|
-
:
|
|
197
|
+
: type === "cron"
|
|
198
|
+
? ["type", "expression", "timeZone"]
|
|
199
|
+
: null;
|
|
186
200
|
// Provider conversions may send unused optional fields as null. Ignore
|
|
187
201
|
// those, but never silently discard an actual scheduling constraint (for
|
|
188
202
|
// example timezone or a misspelled starts_at) and approve different work.
|
|
@@ -190,6 +204,14 @@ function normalizeScheduleInput(args) {
|
|
|
190
204
|
if (unsupported) {
|
|
191
205
|
return { error: `Unsupported ${type} schedule field "${unsupported}". Weekly and daily times use the computer's timezone from list_routines. ${SUPPORTED_SCHEDULES}` };
|
|
192
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
|
+
}
|
|
193
215
|
if (type === "once") {
|
|
194
216
|
if (typeof raw.at !== "string" || !raw.at.trim()) {
|
|
195
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.` };
|
|
@@ -311,7 +333,7 @@ function normalizeScheduleInput(args) {
|
|
|
311
333
|
},
|
|
312
334
|
};
|
|
313
335
|
}
|
|
314
|
-
if (type === "
|
|
336
|
+
if (type === "hourly" || type === "minutes") {
|
|
315
337
|
return { error: `Use an interval schedule for every-N-minutes work. ${SUPPORTED_SCHEDULES}` };
|
|
316
338
|
}
|
|
317
339
|
return { error: `Unknown schedule type "${type || "(missing)"}". ${SUPPORTED_SCHEDULES}` };
|
|
@@ -342,9 +364,10 @@ const ROUTINE_FIELDS_SCHEMA = {
|
|
|
342
364
|
},
|
|
343
365
|
continuity: {
|
|
344
366
|
type: "boolean",
|
|
345
|
-
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.",
|
|
346
368
|
},
|
|
347
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.";
|
|
348
371
|
const TOOLS = [
|
|
349
372
|
{
|
|
350
373
|
name: "list_shared_computers",
|
|
@@ -367,13 +390,14 @@ const TOOLS = [
|
|
|
367
390
|
},
|
|
368
391
|
{
|
|
369
392
|
name: "coordinate_bots",
|
|
370
|
-
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.",
|
|
371
394
|
inputSchema: { type: "object", additionalProperties: false, properties: {
|
|
372
|
-
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." },
|
|
373
396
|
bot_ids: { type: "array", items: { type: "string" }, minItems: 1, maxItems: 4, uniqueItems: true },
|
|
374
397
|
message: { type: "string", minLength: 1, maxLength: 4000, description: "Self-contained question or task for these teammates. Send separate requests when responsibilities differ." },
|
|
375
398
|
request_key: { type: "string", description: "A short unique assignment key. Reuse for an identical retry." },
|
|
376
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." },
|
|
377
401
|
}, required: ["bot_ids", "message", "request_key"] },
|
|
378
402
|
},
|
|
379
403
|
{
|
|
@@ -497,7 +521,7 @@ const TOOLS = [
|
|
|
497
521
|
},
|
|
498
522
|
{
|
|
499
523
|
name: "propose_team_setup",
|
|
500
|
-
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,
|
|
501
525
|
inputSchema: {
|
|
502
526
|
type: "object", additionalProperties: false,
|
|
503
527
|
properties: {
|
|
@@ -524,14 +548,14 @@ const TOOLS = [
|
|
|
524
548
|
},
|
|
525
549
|
{
|
|
526
550
|
name: "propose_bot_deletion",
|
|
527
|
-
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,
|
|
528
552
|
inputSchema: { type: "object", additionalProperties: false, properties: {
|
|
529
553
|
bot_id: { type: "string", minLength: 1 }, reason: { type: "string", minLength: 1, maxLength: 500 },
|
|
530
554
|
}, required: ["bot_id", "reason"] },
|
|
531
555
|
},
|
|
532
556
|
{
|
|
533
557
|
name: "create_room",
|
|
534
|
-
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.",
|
|
535
559
|
inputSchema: {
|
|
536
560
|
type: "object",
|
|
537
561
|
additionalProperties: false,
|
|
@@ -555,7 +579,7 @@ const TOOLS = [
|
|
|
555
579
|
},
|
|
556
580
|
{
|
|
557
581
|
name: "manage_room",
|
|
558
|
-
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.",
|
|
559
583
|
inputSchema: {
|
|
560
584
|
type: "object",
|
|
561
585
|
additionalProperties: false,
|
|
@@ -663,7 +687,7 @@ const TOOLS = [
|
|
|
663
687
|
},
|
|
664
688
|
{
|
|
665
689
|
name: "propose_routine",
|
|
666
|
-
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,
|
|
667
691
|
inputSchema: {
|
|
668
692
|
type: "object",
|
|
669
693
|
additionalProperties: false,
|
|
@@ -679,7 +703,7 @@ const TOOLS = [
|
|
|
679
703
|
},
|
|
680
704
|
{
|
|
681
705
|
name: "propose_routine_action",
|
|
682
|
-
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,
|
|
683
707
|
inputSchema: {
|
|
684
708
|
type: "object",
|
|
685
709
|
additionalProperties: false,
|
|
@@ -702,7 +726,7 @@ const TOOLS = [
|
|
|
702
726
|
},
|
|
703
727
|
{
|
|
704
728
|
name: "propose_profile",
|
|
705
|
-
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,
|
|
706
730
|
inputSchema: {
|
|
707
731
|
type: "object",
|
|
708
732
|
additionalProperties: false,
|
|
@@ -732,7 +756,7 @@ const TOOLS = [
|
|
|
732
756
|
},
|
|
733
757
|
{
|
|
734
758
|
name: "skill_manage",
|
|
735
|
-
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,
|
|
736
760
|
inputSchema: {
|
|
737
761
|
type: "object",
|
|
738
762
|
additionalProperties: false,
|
|
@@ -752,7 +776,7 @@ const TOOLS = [
|
|
|
752
776
|
},
|
|
753
777
|
gist: {
|
|
754
778
|
type: "string",
|
|
755
|
-
description: "Optional one-line summary
|
|
779
|
+
description: "Optional one-line summary of the skill change, included in its applied result or pending review.",
|
|
756
780
|
},
|
|
757
781
|
source: {
|
|
758
782
|
type: "string",
|
|
@@ -877,7 +901,26 @@ function routineFields(args) {
|
|
|
877
901
|
fields.continuity = args.continuity;
|
|
878
902
|
return { fields };
|
|
879
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
|
+
}
|
|
880
920
|
function confirmationResult(r, fallback, noun = "routine") {
|
|
921
|
+
const completed = completedProposalResult(r, fallback);
|
|
922
|
+
if (completed)
|
|
923
|
+
return completed;
|
|
881
924
|
const summary = typeof r.summary === "string" && r.summary.trim() ? `\n\n${r.summary.trim()}` : "";
|
|
882
925
|
return {
|
|
883
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.`,
|
|
@@ -903,7 +946,7 @@ async function callTool(name, args) {
|
|
|
903
946
|
if (name === "coordinate_bots") {
|
|
904
947
|
const r = await api("/api/internal/coordinate-bots", { method: "POST", body: JSON.stringify({
|
|
905
948
|
groupId: args.group_id, botIds: args.bot_ids, message: args.message,
|
|
906
|
-
requestKey: args.request_key, rework: args.rework,
|
|
949
|
+
requestKey: args.request_key, rework: args.rework, label: args.label,
|
|
907
950
|
}) });
|
|
908
951
|
return { text: JSON.stringify(r), ...(r.error ? { isError: true } : {}) };
|
|
909
952
|
}
|
|
@@ -1177,6 +1220,9 @@ async function callTool(name, args) {
|
|
|
1177
1220
|
...(deleting ? { targetBotId: args.bot_id, reason: args.reason } : { plan: args }),
|
|
1178
1221
|
}),
|
|
1179
1222
|
});
|
|
1223
|
+
const completed = completedProposalResult(result, deleting ? "the requested bot deletion" : "the requested team setup");
|
|
1224
|
+
if (completed)
|
|
1225
|
+
return completed;
|
|
1180
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.` };
|
|
1181
1227
|
}
|
|
1182
1228
|
if (name === "create_bot") {
|
|
@@ -1493,7 +1539,7 @@ async function callTool(name, args) {
|
|
|
1493
1539
|
const skills = Array.isArray(r.skills) ? r.skills : [];
|
|
1494
1540
|
const staged = Array.isArray(r.staged) ? r.staged : [];
|
|
1495
1541
|
if (!skills.length && !staged.length) {
|
|
1496
|
-
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." };
|
|
1497
1543
|
}
|
|
1498
1544
|
const live = skills.length
|
|
1499
1545
|
? skills.map((skill) => {
|
|
@@ -1546,7 +1592,10 @@ async function callTool(name, args) {
|
|
|
1546
1592
|
}),
|
|
1547
1593
|
});
|
|
1548
1594
|
const nameLabel = typeof r.name === "string" ? r.name : "the skill";
|
|
1549
|
-
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 };
|
|
1550
1599
|
const status = args.action === "update"
|
|
1551
1600
|
? "The current version remains unchanged until the user reviews and applies the update."
|
|
1552
1601
|
: "The skill is staged and inactive until the user reviews and enables it.";
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { newEventId, newId } from "../contracts.js";
|
|
2
2
|
import { appendNative } from "./native.js";
|
|
3
3
|
const DRIVER_KIND = "boxAgent";
|
|
4
|
-
|
|
4
|
+
// overridable so tests and a dev backend can be pointed at instead of the live provider
|
|
5
|
+
const BOX_API = process.env.OMB_BOX_API || "https://ascii.dev/api/box/v1";
|
|
5
6
|
const MODELS = {
|
|
6
7
|
default: "claude-fable-5",
|
|
7
8
|
options: [
|
|
@@ -10,7 +11,28 @@ const MODELS = {
|
|
|
10
11
|
{ id: "gpt-5.4", label: "GPT-5.4 (Codex) · on the box" },
|
|
11
12
|
],
|
|
12
13
|
};
|
|
13
|
-
|
|
14
|
+
/** The box runs every harness ascii.dev ships (claude-code, codex, pi, opencode,
|
|
15
|
+
* prime-agent, kimi). Which one a model id belongs to comes from the public
|
|
16
|
+
* catalog, `GET /api/provider-models` at the API root: an object keyed by
|
|
17
|
+
* harness, each with its `models`. A bot that arrives here from another engine
|
|
18
|
+
* carries that engine's model id, so this is what lets it keep its model. */
|
|
19
|
+
let catalog = null;
|
|
20
|
+
async function loadCatalog() {
|
|
21
|
+
const root = BOX_API.replace(/\/api\/box\/v1\/?$/, "");
|
|
22
|
+
catalog = await fetch(`${root}/api/provider-models`, { signal: AbortSignal.timeout(15_000) })
|
|
23
|
+
.then((res) => (res.ok ? res.json() : null))
|
|
24
|
+
.catch(() => null);
|
|
25
|
+
}
|
|
26
|
+
const providerFor = (model) => {
|
|
27
|
+
const slash = model.indexOf("/");
|
|
28
|
+
if (slash > 0 && catalog?.[model.slice(0, slash)])
|
|
29
|
+
return { provider: model.slice(0, slash), model: model.slice(slash + 1) };
|
|
30
|
+
for (const [provider, harness] of Object.entries(catalog ?? {})) {
|
|
31
|
+
if (harness.models?.some((m) => m?.id === model))
|
|
32
|
+
return { provider, model };
|
|
33
|
+
}
|
|
34
|
+
return { provider: model.startsWith("gpt") ? "codex" : "claude-code", model };
|
|
35
|
+
};
|
|
14
36
|
function decodeConfig(raw) {
|
|
15
37
|
const o = (raw ?? {});
|
|
16
38
|
return { pollMs: typeof o.pollMs === "number" ? o.pollMs : 2500 };
|
|
@@ -70,9 +92,11 @@ export const BoxAgentDriver = {
|
|
|
70
92
|
]
|
|
71
93
|
.filter((s) => s !== undefined)
|
|
72
94
|
.join("\n");
|
|
95
|
+
if (!catalog)
|
|
96
|
+
await loadCatalog();
|
|
73
97
|
const started = await api(`/boxes/${boxId}/prompt`, {
|
|
74
98
|
method: "POST",
|
|
75
|
-
body: JSON.stringify({
|
|
99
|
+
body: JSON.stringify({ ...providerFor(model), prompt }),
|
|
76
100
|
});
|
|
77
101
|
appendNative(threadId, { dir: "out", source: "box.prompt", msg: { model, prompt, response: started } });
|
|
78
102
|
// real shape (2026-08): {type:"prompt.queued", promptId, promptRun:{id,…},
|
|
@@ -95,6 +119,8 @@ export const BoxAgentDriver = {
|
|
|
95
119
|
const startedAt = Date.now();
|
|
96
120
|
let lastText = "";
|
|
97
121
|
let pendingText = "";
|
|
122
|
+
/** Why the box could not answer (login expired, model refused, …). */
|
|
123
|
+
let problem = null;
|
|
98
124
|
/** Emit unflushed deltas as assistant_text and reset pendingText. */
|
|
99
125
|
const flushAssistantText = () => {
|
|
100
126
|
const text = pendingText;
|
|
@@ -120,6 +146,11 @@ export const BoxAgentDriver = {
|
|
|
120
146
|
const events = await api(`/boxes/${boxId}/events`).catch(() => null);
|
|
121
147
|
const list = events?.events ?? events?.items ?? [];
|
|
122
148
|
for (const ev of list) {
|
|
149
|
+
// The stream is the whole conversation from its start; `taskId`
|
|
150
|
+
// names the prompt run an event belongs to. Earlier turns are
|
|
151
|
+
// history, not this answer.
|
|
152
|
+
if (promptId && ev.taskId && String(ev.taskId) !== promptId)
|
|
153
|
+
continue;
|
|
123
154
|
const id = String(ev.id ?? ev.eventId ?? JSON.stringify(ev).slice(0, 120));
|
|
124
155
|
if (seen.has(id))
|
|
125
156
|
continue;
|
|
@@ -135,6 +166,11 @@ export const BoxAgentDriver = {
|
|
|
135
166
|
if (/assistant|message|output|response/i.test(kind) && typeof text === "string" && text.trim()) {
|
|
136
167
|
ingest(text);
|
|
137
168
|
}
|
|
169
|
+
else if (/usage_limit|error|fail/i.test(kind)) {
|
|
170
|
+
const why = ev.data?.summary ?? ev.data?.message ?? ev.data?.error ?? ev.message;
|
|
171
|
+
if (typeof why === "string" && why.trim())
|
|
172
|
+
problem = why.trim();
|
|
173
|
+
}
|
|
138
174
|
else if (/tool|command|exec|browse/i.test(kind)) {
|
|
139
175
|
flushAssistantText();
|
|
140
176
|
emit({
|
|
@@ -168,14 +204,22 @@ export const BoxAgentDriver = {
|
|
|
168
204
|
if (typeof result === "string" && result.trim() && result !== lastText) {
|
|
169
205
|
ingest(result);
|
|
170
206
|
}
|
|
171
|
-
if (!pendingText.trim() && !lastText.trim())
|
|
207
|
+
if (!pendingText.trim() && !lastText.trim()) {
|
|
208
|
+
// a run that ends with nothing said and a recorded problem
|
|
209
|
+
// (login expired, …) is a failure the person must see
|
|
210
|
+
if (problem)
|
|
211
|
+
throw new Error(problem);
|
|
172
212
|
pendingText = "(finished)";
|
|
213
|
+
}
|
|
173
214
|
flushAssistantText();
|
|
174
215
|
active.delete(threadId);
|
|
175
216
|
emit({ ...base(threadId, turnId), type: "turn.completed", ok: true, stopReason: null, cost: null });
|
|
176
217
|
return;
|
|
177
218
|
}
|
|
178
219
|
if (/failed|error|cancelled|interrupted/i.test(state)) {
|
|
220
|
+
const runError = [run?.error, run?.failureReason, run?.message].find((v) => typeof v === "string" && v.trim());
|
|
221
|
+
if (problem || runError || /failed|error/i.test(state))
|
|
222
|
+
throw new Error(problem ?? runError ?? `the box run ${state}`);
|
|
179
223
|
flushAssistantText();
|
|
180
224
|
active.delete(threadId);
|
|
181
225
|
emit({ ...base(threadId, turnId), type: "turn.completed", ok: false, stopReason: state, cost: null });
|