openmausbot 0.1.96 → 0.1.97
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/{DesktopViewer-DsnA23KM.js → DesktopViewer-CPidMCtt.js} +1 -1
- package/dist/assets/{abnfDiagram-O67JEVCF-Dkbd2hcj.js → abnfDiagram-O67JEVCF-k1UCNXb7.js} +1 -1
- package/dist/assets/{arc-Duj5Coy2.js → arc-BUWGa9UU.js} +1 -1
- package/dist/assets/{architectureDiagram-NJMV4G6O-uYUsDawP.js → architectureDiagram-NJMV4G6O-DiPor3UT.js} +1 -1
- package/dist/assets/{blockDiagram-BEXU5L5S-CWDlTpXe.js → blockDiagram-BEXU5L5S-Ag9nLW4H.js} +1 -1
- package/dist/assets/{c4Diagram-YGBWAQC7-B0t15PDc.js → c4Diagram-YGBWAQC7-Bh0sjgUa.js} +1 -1
- package/dist/assets/channel-DR8eD6QW.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-BJSCL-HV.js → chunk-2Q5K7J3B-BFBcIIqu.js} +1 -1
- package/dist/assets/{chunk-3FUC2YCW-D3_ILccd.js → chunk-3FUC2YCW-Dvfpex-f.js} +2 -2
- package/dist/assets/{chunk-5VM5RSS4-D3dmwT_S.js → chunk-5VM5RSS4-CMjP2YZW.js} +1 -1
- package/dist/assets/{chunk-GTNCS2PH-Br73HFPD.js → chunk-GTNCS2PH-BryoeTc7.js} +1 -1
- package/dist/assets/{chunk-GWA4HPMP-uSsjxubC.js → chunk-GWA4HPMP-BhfvcchO.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-C40VWFyr.js → chunk-JWPE2WC7-bcCVp8rp.js} +1 -1
- package/dist/assets/{chunk-NETBCI7D-D6WNNYwU.js → chunk-NETBCI7D-yKIjIlkQ.js} +1 -1
- package/dist/assets/{chunk-WEXAMYUT-DOEhq64P.js → chunk-WEXAMYUT-D3Zqbmup.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-BqJEi6Pi.js → chunk-XXDRQBXY-CjVoIMG6.js} +1 -1
- package/dist/assets/{classDiagram-v2-NBCMYWYE-ByU_GnxT.js → classDiagram-v2-NBCMYWYE-mUCWdVyH.js} +1 -1
- package/dist/assets/{cose-bilkent-JH36ORCC-D-Uglxqq.js → cose-bilkent-JH36ORCC-BBmR1bbX.js} +1 -1
- package/dist/assets/{cynefin-OW5HDTMX-CZWO7aYj.js → cynefin-OW5HDTMX-BpRrxoO4.js} +1 -1
- package/dist/assets/{cynefinDiagram-VND7K2PF-C70-w0la.js → cynefinDiagram-VND7K2PF-D3VBWCPK.js} +1 -1
- package/dist/assets/{dagre-6A5THRUB-CnhjhExB.js → dagre-6A5THRUB-DxEIACOO.js} +1 -1
- package/dist/assets/{diagram-22UHCM2B-bnjWCNb4.js → diagram-22UHCM2B-BANgZEOS.js} +1 -1
- package/dist/assets/{diagram-3UASUU5V-A4uTWoFV.js → diagram-3UASUU5V-Cnq7nJir.js} +1 -1
- package/dist/assets/{diagram-ATOU4E4O-Zi6m3NUl.js → diagram-ATOU4E4O-4TBDIWnz.js} +1 -1
- package/dist/assets/{diagram-CDSNMT55-DtCcmd5k.js → diagram-CDSNMT55-CD_B39D4.js} +1 -1
- package/dist/assets/{diagram-MLGK6HIB-DofuETS3.js → diagram-MLGK6HIB-CzvnFhEC.js} +1 -1
- package/dist/assets/{diagram-MPIPVDR6-Dw2T9PuU.js → diagram-MPIPVDR6-C4w2SNml.js} +1 -1
- package/dist/assets/{ebnfDiagram-ZINNZB2B-DbrwPiem.js → ebnfDiagram-ZINNZB2B-gkUEBm2s.js} +1 -1
- package/dist/assets/{elk-276RUBZZ-BvCt1dCb.js → elk-276RUBZZ-B_56X6mw.js} +1 -1
- package/dist/assets/{erDiagram-OPXOYQCR-Y-QKqPnU.js → erDiagram-OPXOYQCR-C_oByoDS.js} +1 -1
- package/dist/assets/{flowDiagram-KWPJA3E3-CDkL1B5w.js → flowDiagram-KWPJA3E3-CR9xXJkC.js} +1 -1
- package/dist/assets/{ganttDiagram-FUAMR5RP-CKV5D2F6.js → ganttDiagram-FUAMR5RP-gtYBTu0B.js} +1 -1
- package/dist/assets/{gitGraphDiagram-X574FWY7-nL9iFTCv.js → gitGraphDiagram-X574FWY7-VJ6-Tp_p.js} +1 -1
- package/dist/assets/{index-DreDnjOu.js → index-Cp4S4NOL.js} +1 -1
- package/dist/assets/index-CpaIXRLp.js +614 -0
- package/dist/assets/index-Z81SMsgR.css +1 -0
- package/dist/assets/{infoDiagram-VRGFBTTK-C7aHjzsf.js → infoDiagram-VRGFBTTK-2Lu9te4D.js} +1 -1
- package/dist/assets/{ishikawaDiagram-OU5B5YK6-KUZsNBTh.js → ishikawaDiagram-OU5B5YK6-DR49XY-e.js} +1 -1
- package/dist/assets/{journeyDiagram-ZHPQQLJL-CGUuodFl.js → journeyDiagram-ZHPQQLJL-ZoR0M305.js} +1 -1
- package/dist/assets/{kanban-definition-PNTS6WVX-Cy6r868Z.js → kanban-definition-PNTS6WVX-DLKHxrO-.js} +1 -1
- package/dist/assets/{linear-Bl669148.js → linear-BASsfWly.js} +1 -1
- package/dist/assets/{mermaid.core-86Xqz5Mr.js → mermaid.core-BVidiPkk.js} +5 -5
- package/dist/assets/{mindmap-definition-NLK3R4M7-G4DzHLrd.js → mindmap-definition-NLK3R4M7-g_Qx0U5C.js} +1 -1
- package/dist/assets/{pegDiagram-GJSIUBJH-K4eruzY0.js → pegDiagram-GJSIUBJH-DI9Cjs8w.js} +1 -1
- package/dist/assets/{pieDiagram-5QR66LMP-DYdR9PPq.js → pieDiagram-5QR66LMP-sRI0Uhr5.js} +1 -1
- package/dist/assets/{quadrantDiagram-O4NWA36T-BCAh0nEy.js → quadrantDiagram-O4NWA36T-CamKTby3.js} +1 -1
- package/dist/assets/{railroadDiagram-XR7U4H2S-p0dGRc4g.js → railroadDiagram-XR7U4H2S-DOLlkbf3.js} +1 -1
- package/dist/assets/{requirementDiagram-PLB6GJNP-tLeBG0aL.js → requirementDiagram-PLB6GJNP-DrRQ5r97.js} +1 -1
- package/dist/assets/{sankeyDiagram-IPEJSGJF-QRuN16rV.js → sankeyDiagram-IPEJSGJF-DGS_lXcX.js} +1 -1
- package/dist/assets/{sequenceDiagram-PO4LG4MO-ueT1nVoa.js → sequenceDiagram-PO4LG4MO-CkjWqwtf.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-wRS2zWg2.js → sizeCapture-INFHLROL-CepRgful.js} +1 -1
- package/dist/assets/{stateDiagram-v2-GCMORJYK-CYzA_hE1.js → stateDiagram-v2-GCMORJYK-hykr-9RS.js} +1 -1
- package/dist/assets/{swimlanes-2SLR337P-kDnv6LMc.js → swimlanes-2SLR337P-CKOHEZbI.js} +1 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-D-8vqZ4-.js +8 -0
- package/dist/assets/{timeline-definition-EJHVYXUP-D1y2UbbH.js → timeline-definition-EJHVYXUP-CB8pJ6AC.js} +1 -1
- package/dist/assets/{usecaseDiagram-POWQR4AR-IqB4v9HX.js → usecaseDiagram-POWQR4AR-DrclJHdl.js} +1 -1
- package/dist/assets/{vennDiagram-UO4OBE2U-BEi7KYVF.js → vennDiagram-UO4OBE2U-zS23qjv8.js} +1 -1
- package/dist/assets/{wardleyDiagram-VNRHLVJA-Dkybo-Yf.js → wardleyDiagram-VNRHLVJA-52jgeKxz.js} +1 -1
- package/dist/assets/{xychartDiagram-PMCCYNJV-BAPFJLpg.js → xychartDiagram-PMCCYNJV-DSdl9rQ8.js} +1 -1
- package/dist/index.html +2 -2
- package/dist-server/cloud-home-start.js +1 -1
- package/dist-server/drivers/agents-proxy.js +5 -5
- package/dist-server/index.js +2379 -1596
- package/dist-server/openmausbot.js +857 -1012
- package/dist-server/pair-cli.js +857 -1012
- package/dist-server/permission-proxy.js +0 -4
- package/dist-server/server/auto-approve.js +1 -1
- package/dist-server/server/auto-vm-claims.js +13 -1
- package/dist-server/server/automatic-recovery.js +1 -2
- package/dist-server/server/boat.js +68 -84
- package/dist-server/server/bot-overview.js +1 -1
- package/dist-server/server/browser-engine.js +38 -1
- package/dist-server/server/browser-live.js +112 -25
- package/dist-server/server/browser-runtime.js +31 -1
- package/dist-server/server/checkpoints.js +275 -44
- package/dist-server/server/cli-setup.js +3 -3
- package/dist-server/server/cloud-computer-tools.js +11 -4
- package/dist-server/server/cloud-home-start.js +1 -1
- package/dist-server/server/cloud-home.js +3 -9
- package/dist-server/server/cloud-owner.js +1 -1
- package/dist-server/server/computer-engine-removal.js +70 -0
- package/dist-server/server/computer-selection.js +18 -0
- package/dist-server/server/config.js +7 -11
- package/dist-server/server/connector-scope-hint.js +83 -0
- package/dist-server/server/default-model-selection.js +20 -14
- package/dist-server/server/delegations.js +11 -5
- package/dist-server/server/delta-context.js +6 -1
- package/dist-server/server/drivers/acp/core.js +1 -1
- package/dist-server/server/drivers/acp/opencode-go.js +16 -21
- package/dist-server/server/drivers/agents-call.js +1 -1
- package/dist-server/server/drivers/agents-catalog.js +3 -3
- package/dist-server/server/drivers/builtIn.js +0 -2
- package/dist-server/server/drivers/claude.js +49 -30
- package/dist-server/server/drivers/codex.js +2 -2
- package/dist-server/server/drivers/openai-chat.js +98 -6
- package/dist-server/server/drivers/pi.js +184 -49
- package/dist-server/server/index.js +555 -279
- package/dist-server/server/live-call.js +11 -6
- package/dist-server/server/managed-policy.js +1 -1
- package/dist-server/server/model-catalog/presets.js +8 -0
- package/dist-server/server/peer-roster.js +10 -2
- package/dist-server/server/room-turn-end.js +15 -0
- package/dist-server/server/routes/live.js +3 -3
- package/dist-server/server/routine-requests.js +1 -1
- package/dist-server/server/routines.js +1 -1
- package/dist-server/server/screen-frame-gate.js +1 -3
- package/dist-server/server/store.js +125 -1
- package/dist-server/server/surface.js +29 -49
- package/dist-server/server/system-prompt.js +10 -10
- package/dist-server/server/turn-continuation.js +162 -0
- package/dist-server/server/workspace.js +1 -1
- package/dist-server/shared/ask-question.js +0 -70
- package/dist-server/shared/cloud-computer.js +4 -6
- package/dist-server/shared/credential-request.js +1 -1
- package/dist-server/shared/failed-turn.js +1 -4
- package/dist-server/shared/place-view.js +409 -0
- package/dist-server/shared/tool-scope-support.js +1 -2
- package/package.json +1 -1
- package/dist/assets/channel-BZxwC-Gw.js +0 -1
- package/dist/assets/index-Du7o6Dsf.js +0 -610
- package/dist/assets/index-KxG4bWSr.css +0 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-C7voMDle.js +0 -8
- package/dist-server/server/drivers/boatagent.js +0 -443
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
/**
|
|
5
|
+
* Automatic continuity: when a turn ends without a final answer and the work
|
|
6
|
+
* is resumable (budget cap or tool errors), persist a structured handoff and
|
|
7
|
+
* let the harness start a `Continue:` thread on the same bot — transparent to
|
|
8
|
+
* the bot, which just sees a normal new thread seeded with the handoff.
|
|
9
|
+
*
|
|
10
|
+
* The handoff is written by the driver that has the full transcript in hand;
|
|
11
|
+
* the continuation is driven by a bus subscriber in the harness (index.ts).
|
|
12
|
+
*/
|
|
13
|
+
/** Resolve the handoff directory the same way the server resolves its data
|
|
14
|
+
* dir (server/cli.ts): OMB_DATA_DIR wins, else ~/.openmausbot. */
|
|
15
|
+
export function handoffDir() {
|
|
16
|
+
return join(process.env.OMB_DATA_DIR || join(homedir(), ".openmausbot"), "handoffs");
|
|
17
|
+
}
|
|
18
|
+
/** Persist a structured markdown handoff for a turn that ended without a
|
|
19
|
+
* final answer. Returns the file path, or null when the write fails. */
|
|
20
|
+
export function writeTurnHandoff(input) {
|
|
21
|
+
try {
|
|
22
|
+
const dir = handoffDir();
|
|
23
|
+
mkdirSync(dir, { recursive: true });
|
|
24
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
25
|
+
const file = join(dir, `cap-${String(input.threadId).slice(0, 8)}-${String(input.turnId).slice(0, 8)}-${stamp}.md`);
|
|
26
|
+
const L = [];
|
|
27
|
+
L.push("# Handoff - turn ended without a final answer", "");
|
|
28
|
+
L.push(`- Thread: ${input.threadId}`);
|
|
29
|
+
L.push(`- Turn: ${input.turnId}`);
|
|
30
|
+
L.push(`- Model: ${input.model ?? "unknown"}`);
|
|
31
|
+
L.push(`- Tool calls used: ${input.toolCalls}`);
|
|
32
|
+
if (input.hasUsage && input.usage)
|
|
33
|
+
L.push(`- Tokens this turn: ${input.usage.input} in / ${input.usage.output} out`);
|
|
34
|
+
L.push(`- Stop reason: ${input.failure ?? "unknown"}`);
|
|
35
|
+
L.push(`- Written at: ${new Date().toISOString()}`, "");
|
|
36
|
+
L.push("## Original task (first user message)", "```");
|
|
37
|
+
const firstUser = input.messages.find((m) => m.role === "user");
|
|
38
|
+
const firstText = firstUser && typeof firstUser.content === "string"
|
|
39
|
+
? firstUser.content
|
|
40
|
+
: JSON.stringify(firstUser?.content ?? null);
|
|
41
|
+
L.push(String(firstText).slice(0, 8000), "```", "");
|
|
42
|
+
L.push("## Work performed (tool calls in order)", "");
|
|
43
|
+
let n = 0;
|
|
44
|
+
for (const m of input.messages) {
|
|
45
|
+
if (m.role === "assistant" && Array.isArray(m.tool_calls)) {
|
|
46
|
+
for (const tc of m.tool_calls) {
|
|
47
|
+
n += 1;
|
|
48
|
+
const args = String(tc.function?.arguments ?? "").replace(/\s+/g, " ").slice(0, 300);
|
|
49
|
+
L.push(`${n}. ${tc.function?.name ?? "tool"} - ${args}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
else if (m.role === "tool") {
|
|
53
|
+
let verdict = "?";
|
|
54
|
+
try {
|
|
55
|
+
verdict = JSON.parse(String(m.content)).ok ? "ok" : "FAILED";
|
|
56
|
+
}
|
|
57
|
+
catch { /* keep "?" */ }
|
|
58
|
+
L.push(` result ${verdict}: ${String(m.content ?? "").replace(/\s+/g, " ").slice(0, 220)}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (n === 0)
|
|
62
|
+
L.push("(none recorded)");
|
|
63
|
+
L.push("", "## Last assistant text", "");
|
|
64
|
+
let last = null;
|
|
65
|
+
for (let i = input.messages.length - 1; i >= 0; i -= 1) {
|
|
66
|
+
const c = input.messages[i].content;
|
|
67
|
+
if (input.messages[i].role === "assistant" && typeof c === "string" && c.trim()) {
|
|
68
|
+
last = c;
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
L.push(last ? last.slice(0, 6000) : "(none)", "");
|
|
73
|
+
L.push("## Instructions for whoever continues this task", "");
|
|
74
|
+
L.push("1. Read this file completely.");
|
|
75
|
+
L.push("2. Re-verify the current state of every file, branch, and PR mentioned above before acting - it may be stale.");
|
|
76
|
+
L.push("3. Continue from the first unfinished step. Do not redo completed work.");
|
|
77
|
+
L.push("4. If you get close to your own turn budget, append your progress to this file before stopping.");
|
|
78
|
+
writeFileSync(file, L.join("\n"), { mode: 0o600 });
|
|
79
|
+
return file;
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/** Provider config errors are not resumable: looping on them burns budget and
|
|
86
|
+
* masks the real fix (change the model or the tool set). Everything else that
|
|
87
|
+
* stops a turn without a final answer is a candidate for auto-continuation. */
|
|
88
|
+
const NOT_RESUMABLE = /not a multimodal model|unknown model|does not support|tool arguments do not match/i;
|
|
89
|
+
/** Classify a terminal turn as resumable. Returns the reason for the
|
|
90
|
+
* continuation (`cap` = budget exhausted), or null when the turn must not be
|
|
91
|
+
* auto-continued. A tool error is never continued: it includes a person's or
|
|
92
|
+
* the approval policy's denial, and a new unattended thread would retry what
|
|
93
|
+
* was refused. */
|
|
94
|
+
export function classifyContinuable(_stopReason, failure) {
|
|
95
|
+
if (!failure)
|
|
96
|
+
return null;
|
|
97
|
+
if (NOT_RESUMABLE.test(failure))
|
|
98
|
+
return null;
|
|
99
|
+
if (/limit reached|stopped after \d+ steps|without a final answer/i.test(failure))
|
|
100
|
+
return "cap";
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
const CONTINUE_TITLE = /^Continue: /;
|
|
104
|
+
const MAX_PER_BOT_PER_HOUR = 5;
|
|
105
|
+
const WINDOW_MS = 3600e3;
|
|
106
|
+
/** Bus subscriber: on `cap.exhausted`, create a `Continue:` task on the same
|
|
107
|
+
* bot and start a turn seeded with the handoff. Bounded — at most five
|
|
108
|
+
* continuations per bot per hour, never a continuation of a continuation, and
|
|
109
|
+
* never the same source thread twice. A failure escalates with an activity
|
|
110
|
+
* message carrying the handoff path instead of silently recursing. */
|
|
111
|
+
export function makeCapContinuationSubscriber(deps) {
|
|
112
|
+
const state = new Map();
|
|
113
|
+
return (event) => {
|
|
114
|
+
if (event.type !== "cap.exhausted" || !event.handoffPath)
|
|
115
|
+
return;
|
|
116
|
+
void (async () => {
|
|
117
|
+
const bot = deps.store.botByThread(event.threadId);
|
|
118
|
+
if (!bot)
|
|
119
|
+
return;
|
|
120
|
+
const now = deps.now ? deps.now() : Date.now();
|
|
121
|
+
let rec = state.get(bot.id);
|
|
122
|
+
if (!rec || now - rec.last > WINDOW_MS || rec.continued.size > 100) {
|
|
123
|
+
rec = { last: now, count: 0, continued: new Set() };
|
|
124
|
+
state.set(bot.id, rec);
|
|
125
|
+
}
|
|
126
|
+
if (rec.count >= MAX_PER_BOT_PER_HOUR || rec.continued.has(event.threadId))
|
|
127
|
+
return;
|
|
128
|
+
const deadTask = deps.store.taskByThread(bot.id, event.threadId);
|
|
129
|
+
if (!deadTask)
|
|
130
|
+
return;
|
|
131
|
+
if (CONTINUE_TITLE.test(deadTask.title ?? ""))
|
|
132
|
+
return;
|
|
133
|
+
const approvalMode = deadTask.approvalMode === "ask" || deadTask.approvalMode === "full" ? deadTask.approvalMode : undefined;
|
|
134
|
+
const contTask = deps.store.createTask(bot.id, `Continue: ${(deadTask.title ?? "task").slice(0, 90)}`, true, deadTask.projectId, undefined, approvalMode, deadTask.modelSelection);
|
|
135
|
+
if (!contTask)
|
|
136
|
+
return;
|
|
137
|
+
rec.count += 1;
|
|
138
|
+
rec.last = now;
|
|
139
|
+
rec.continued.add(event.threadId);
|
|
140
|
+
const prompt = `[Auto-continuation] The task "${deadTask.title}" stopped because its turn ended without a final answer (${event.reason}). A handoff file with the full state was written to ${event.handoffPath}. Read that file first, re-verify the current state of anything it mentions, then continue the task from where it stopped. Do not redo completed work. If you cannot finish, append your progress to the handoff file and say exactly what is blocking.`;
|
|
141
|
+
try {
|
|
142
|
+
await deps.startTurn(bot.id, prompt, { threadId: contTask.threadId, unattended: true });
|
|
143
|
+
deps.store.appendMessage(event.threadId, {
|
|
144
|
+
role: "bot", kind: "activity",
|
|
145
|
+
tool: { name: `Turn ended without a final answer — work continues in a new task (handoff: ${event.handoffPath})`, ok: true },
|
|
146
|
+
threadRef: { botId: bot.id, threadId: contTask.threadId, title: contTask.title },
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
151
|
+
console.error("[cap-continuation] failed:", msg);
|
|
152
|
+
try {
|
|
153
|
+
deps.store.appendMessage(event.threadId, {
|
|
154
|
+
role: "bot", kind: "activity",
|
|
155
|
+
tool: { name: `Turn ended without a final answer — auto-continuation failed (${String(msg).slice(0, 200)}). Handoff: ${event.handoffPath}. Start a new thread that reads it to continue.`, ok: false },
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
catch { /* the escalation message itself failed; nothing else to do */ }
|
|
159
|
+
}
|
|
160
|
+
})();
|
|
161
|
+
};
|
|
162
|
+
}
|
|
@@ -20,7 +20,7 @@ export const WORKSPACES_DIR = join(DATA_DIR, "workspaces");
|
|
|
20
20
|
export const TASK_WORKSPACES_DIR = join(DATA_DIR, "task-workspaces");
|
|
21
21
|
/** MCP support does not imply native filesystem tools or a local working directory. */
|
|
22
22
|
export function supportsWorkspaceFiles(driverKind) {
|
|
23
|
-
return !["grok", "openai-compat", "minimax", "mistral", "cerebras"
|
|
23
|
+
return !["grok", "openai-compat", "minimax", "mistral", "cerebras"].includes(driverKind);
|
|
24
24
|
}
|
|
25
25
|
/** Default task files are private to the thread, outside the bot's shared
|
|
26
26
|
* memory folder. This is directory organization, not a shell sandbox. */
|
|
@@ -109,55 +109,6 @@ export function parseAskQuestions(input) {
|
|
|
109
109
|
}
|
|
110
110
|
return questions.length ? questions : null;
|
|
111
111
|
}
|
|
112
|
-
/** The fenced block a turn-boundary agent ends its run with when it wants
|
|
113
|
-
* to ask the person something (the BoatAgent transport): the harness cannot
|
|
114
|
-
* pause mid-run, so the questions ride the final output and OMB parses them
|
|
115
|
-
* at settle. The block is model-authored — untrusted input like any tool
|
|
116
|
-
* call — so its body runs through parseAskQuestions and the same caps. */
|
|
117
|
-
const OMB_ASK_FENCE = /(^|\n)[ \t]{0,3}(`{3,}|~{3,})[ \t]*omb-ask[ \t]*\r?\n([\s\S]*?)\r?\n[ \t]{0,3}\2[ \t]*(?=\r?\n|$)/;
|
|
118
|
-
const OMB_ASK_FENCE_GLOBAL = new RegExp(OMB_ASK_FENCE.source, OMB_ASK_FENCE.flags + "g");
|
|
119
|
-
/** The questions inside the first fenced omb-ask block in an output, or
|
|
120
|
-
* null when there is nothing worth showing (no fence, invalid JSON, or no
|
|
121
|
-
* entry that parses as a question). The first fence wins: a second block
|
|
122
|
-
* in the same output is ignored, not merged. */
|
|
123
|
-
export function parseOmbAskQuestions(output) {
|
|
124
|
-
const match = OMB_ASK_FENCE.exec(output);
|
|
125
|
-
if (!match)
|
|
126
|
-
return null;
|
|
127
|
-
try {
|
|
128
|
-
return parseAskQuestions(JSON.parse(match[3]));
|
|
129
|
-
}
|
|
130
|
-
catch {
|
|
131
|
-
return null;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
/** The output with its omb-ask block(s) removed, for display: the person
|
|
135
|
-
* reads the prose around the ask, never the raw protocol JSON. Text
|
|
136
|
-
* without a block is returned untouched. */
|
|
137
|
-
export function stripOmbAskBlock(output) {
|
|
138
|
-
if (!OMB_ASK_FENCE.test(output))
|
|
139
|
-
return output;
|
|
140
|
-
return output.replace(OMB_ASK_FENCE_GLOBAL, "").replace(/\n{3,}/g, "\n\n").trim();
|
|
141
|
-
}
|
|
142
|
-
/** The prompt contract that teaches a model the turn-held ask transport: end
|
|
143
|
-
* the reply with a fenced `omb-ask` block carrying the questions, and the
|
|
144
|
-
* answers come back on the next prompt. A harness that cannot pause mid-run
|
|
145
|
-
* (the BoatAgent transport) appends this to every prompt; the caps in the text
|
|
146
|
-
* are the constants above, so the taught contract and the parser cannot
|
|
147
|
-
* drift apart. */
|
|
148
|
-
export function ombAskProtocolPrompt() {
|
|
149
|
-
return [
|
|
150
|
-
"",
|
|
151
|
-
"## Asking the person a question",
|
|
152
|
-
"When a decision belongs to the person, end your reply with a fenced block exactly like this:",
|
|
153
|
-
"",
|
|
154
|
-
"```omb-ask",
|
|
155
|
-
'{"questions":[{"question":"Ship the release now?","header":"Release","options":[{"label":"Ship now"},{"label":"Wait for the QA signoff"}]}]}',
|
|
156
|
-
"```",
|
|
157
|
-
"",
|
|
158
|
-
`The block must be the last thing in your reply. You may ask up to ${MAX_QUESTIONS} questions at once, each with up to ${MAX_OPTIONS} options; the person can always answer in their own words. Their answers arrive on your next prompt as \`Q:\`/\`A:\` lines — never invent them.`,
|
|
159
|
-
].join("\n");
|
|
160
|
-
}
|
|
161
112
|
/** The one line the card subtitle and a spoken prompt show. */
|
|
162
113
|
export function askQuestionSummary(questions) {
|
|
163
114
|
const first = questions[0]?.question ?? "";
|
|
@@ -254,10 +205,6 @@ export const ASK_USER_TOOL_DEFINITION = {
|
|
|
254
205
|
},
|
|
255
206
|
},
|
|
256
207
|
};
|
|
257
|
-
/** The synthetic tool string for an ask parsed out of model-authored final
|
|
258
|
-
* output (the BoatAgent turn-held transport): there is no tool call to name,
|
|
259
|
-
* but the event and the ASKS_A_PERSON backstop need one string. */
|
|
260
|
-
export const OMB_ASK_TOOL = "omb-ask";
|
|
261
208
|
/** The lead-in on a formatted answer. It exists for the model — the answer
|
|
262
209
|
* is delivered on the deny channel, so it has to say what it is — and the
|
|
263
210
|
* card strips it back off when it shows the person what they sent. */
|
|
@@ -283,23 +230,6 @@ export function formatQuestionAnswers(questions, answers) {
|
|
|
283
230
|
export function answerWithoutPreamble(answer) {
|
|
284
231
|
return answer.startsWith(`${ANSWER_PREAMBLE}\n\n`) ? answer.slice(ANSWER_PREAMBLE.length + 2) : answer;
|
|
285
232
|
}
|
|
286
|
-
/** The longest formatted answer OMB will echo back into a follow-up prompt
|
|
287
|
-
* (the BoatAgent continuation). formatQuestionAnswers itself does not
|
|
288
|
-
* truncate — the tool-result channel has no stated limit — but a prompt is
|
|
289
|
-
* not the place to find one: six answers at the custom-answer cap is the
|
|
290
|
-
* most a legitimate reply weighs, so that is the ceiling. */
|
|
291
|
-
export const MAX_ANSWER_ECHO = 6 * MAX_CUSTOM_ANSWER;
|
|
292
|
-
/** Cap a formatted answer for echoing into a follow-up prompt. An over-cap
|
|
293
|
-
* echo is cut back to the last whole block so no partial answer reads as
|
|
294
|
-
* one, and says it was truncated. */
|
|
295
|
-
export function capAnswerEcho(answer, limit = MAX_ANSWER_ECHO) {
|
|
296
|
-
if (answer.length <= limit)
|
|
297
|
-
return answer;
|
|
298
|
-
const cut = answer.slice(0, limit);
|
|
299
|
-
const boundary = cut.lastIndexOf("\n\nQ: ");
|
|
300
|
-
const kept = boundary > 0 ? cut.slice(0, boundary) : cut;
|
|
301
|
-
return kept + "\n\n[answer truncated]";
|
|
302
|
-
}
|
|
303
233
|
/**
|
|
304
234
|
* The answer text, read back as one value per question.
|
|
305
235
|
*
|
|
@@ -3,10 +3,8 @@
|
|
|
3
3
|
// select_computer offer) and the renderer (the Works on panel, the composer's
|
|
4
4
|
// place chip, Webhooks and Routines), so the two never disagree.
|
|
5
5
|
/** An engine with computer tools gets the cloud computer as one more stdio
|
|
6
|
-
* computer server
|
|
7
|
-
*
|
|
8
|
-
export function canWorkOnCloud(engine
|
|
9
|
-
|
|
10
|
-
const hasComputerTools = engine?.computerMcp === true;
|
|
11
|
-
return backend === "vps" ? hasComputerTools && !runsOnBoat : hasComputerTools || runsOnBoat;
|
|
6
|
+
* computer server, on a Boat and on a VPS alike. The turn always stays on the
|
|
7
|
+
* bot's own engine; nothing hands it to another one. */
|
|
8
|
+
export function canWorkOnCloud(engine) {
|
|
9
|
+
return engine?.computerMcp === true;
|
|
12
10
|
}
|
|
@@ -18,7 +18,7 @@ export const CREDENTIAL_TARGETS = {
|
|
|
18
18
|
},
|
|
19
19
|
opencodeGoApiKey: {
|
|
20
20
|
label: "OpenCode API key",
|
|
21
|
-
description: "Used for OpenCode
|
|
21
|
+
description: "Used for OpenCode Zen and Go.",
|
|
22
22
|
placeholder: "Paste your OpenCode API key",
|
|
23
23
|
helpUrl: "https://opencode.ai/docs/providers/",
|
|
24
24
|
},
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
// A failed turn is stored as an activity row named "error: <what went
|
|
2
|
-
// wrong>", in a 1:1 chat and a room alike. The server writes every such row
|
|
3
|
-
// through failedTurnTool and the clients read it back through
|
|
4
|
-
// failedTurnCause, so the marker and the one length limit live here only.
|
|
5
1
|
const MARKER = "error:";
|
|
6
2
|
/** The longest cause a row keeps. Clients wrap the row and show it whole, so
|
|
7
3
|
* this only bounds a pathological message (a provider's HTML body, a stack
|
|
@@ -16,6 +12,7 @@ export function failedTurnTool(cause, flags = {}) {
|
|
|
16
12
|
...(flags.setup ? { setup: true } : {}),
|
|
17
13
|
...(flags.terminal ? { terminal: true } : {}),
|
|
18
14
|
...(flags.claudeUpdate ? { claudeUpdate: true } : {}),
|
|
15
|
+
...(flags.place ? { place: flags.place } : {}),
|
|
19
16
|
};
|
|
20
17
|
}
|
|
21
18
|
/** The cause a failed-turn row carries, without its marker; null for any
|