openmausbot 0.1.95 → 0.1.96
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-BwWpc4RK.js → DesktopViewer-DsnA23KM.js} +1 -1
- package/dist/assets/{abnfDiagram-O67JEVCF-jsQkIqTn.js → abnfDiagram-O67JEVCF-Dkbd2hcj.js} +1 -1
- package/dist/assets/{arc-7kei4Hsu.js → arc-Duj5Coy2.js} +1 -1
- package/dist/assets/{architectureDiagram-NJMV4G6O-CRWDFDMq.js → architectureDiagram-NJMV4G6O-uYUsDawP.js} +1 -1
- package/dist/assets/{blockDiagram-BEXU5L5S-CTKW_IKs.js → blockDiagram-BEXU5L5S-CWDlTpXe.js} +1 -1
- package/dist/assets/{c4Diagram-YGBWAQC7-BBTW7-6M.js → c4Diagram-YGBWAQC7-B0t15PDc.js} +1 -1
- package/dist/assets/channel-BZxwC-Gw.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-M_Ox-MkD.js → chunk-2Q5K7J3B-BJSCL-HV.js} +1 -1
- package/dist/assets/{chunk-3FUC2YCW-D2YiN-Ud.js → chunk-3FUC2YCW-D3_ILccd.js} +2 -2
- package/dist/assets/{chunk-5VM5RSS4-BmVYJ0f8.js → chunk-5VM5RSS4-D3dmwT_S.js} +1 -1
- package/dist/assets/{chunk-GTNCS2PH-BtiLVVnZ.js → chunk-GTNCS2PH-Br73HFPD.js} +1 -1
- package/dist/assets/{chunk-GWA4HPMP-DFYSb6J6.js → chunk-GWA4HPMP-uSsjxubC.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-mknKBzgV.js → chunk-JWPE2WC7-C40VWFyr.js} +1 -1
- package/dist/assets/{chunk-NETBCI7D-DgSYGiOu.js → chunk-NETBCI7D-D6WNNYwU.js} +1 -1
- package/dist/assets/{chunk-WEXAMYUT-C7ZvuguH.js → chunk-WEXAMYUT-DOEhq64P.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-CPqxINpA.js → chunk-XXDRQBXY-BqJEi6Pi.js} +1 -1
- package/dist/assets/{classDiagram-v2-NBCMYWYE-Cd6xfJaC.js → classDiagram-v2-NBCMYWYE-ByU_GnxT.js} +1 -1
- package/dist/assets/{cose-bilkent-JH36ORCC-BSXY5qRh.js → cose-bilkent-JH36ORCC-D-Uglxqq.js} +1 -1
- package/dist/assets/{cynefin-OW5HDTMX-D_lhEMj1.js → cynefin-OW5HDTMX-CZWO7aYj.js} +1 -1
- package/dist/assets/{cynefinDiagram-VND7K2PF-Da9FrFwk.js → cynefinDiagram-VND7K2PF-C70-w0la.js} +1 -1
- package/dist/assets/{dagre-6A5THRUB-T77jMOXe.js → dagre-6A5THRUB-CnhjhExB.js} +1 -1
- package/dist/assets/{diagram-22UHCM2B-Dkj96X5E.js → diagram-22UHCM2B-bnjWCNb4.js} +1 -1
- package/dist/assets/{diagram-3UASUU5V-CMi9zlD_.js → diagram-3UASUU5V-A4uTWoFV.js} +1 -1
- package/dist/assets/{diagram-ATOU4E4O-CXK6YUfS.js → diagram-ATOU4E4O-Zi6m3NUl.js} +1 -1
- package/dist/assets/{diagram-CDSNMT55-BE7B1u9Q.js → diagram-CDSNMT55-DtCcmd5k.js} +1 -1
- package/dist/assets/{diagram-MLGK6HIB-CCMQphKr.js → diagram-MLGK6HIB-DofuETS3.js} +1 -1
- package/dist/assets/{diagram-MPIPVDR6-iHJ_-tGH.js → diagram-MPIPVDR6-Dw2T9PuU.js} +1 -1
- package/dist/assets/{ebnfDiagram-ZINNZB2B-DONsCWQ7.js → ebnfDiagram-ZINNZB2B-DbrwPiem.js} +1 -1
- package/dist/assets/{elk-276RUBZZ-CU_WOaXk.js → elk-276RUBZZ-BvCt1dCb.js} +1 -1
- package/dist/assets/{erDiagram-OPXOYQCR-BeYwM04G.js → erDiagram-OPXOYQCR-Y-QKqPnU.js} +1 -1
- package/dist/assets/{flowDiagram-KWPJA3E3-Bdw9DqEl.js → flowDiagram-KWPJA3E3-CDkL1B5w.js} +1 -1
- package/dist/assets/{ganttDiagram-FUAMR5RP-C9iwq3qm.js → ganttDiagram-FUAMR5RP-CKV5D2F6.js} +1 -1
- package/dist/assets/{gitGraphDiagram-X574FWY7-Dx-VWzUg.js → gitGraphDiagram-X574FWY7-nL9iFTCv.js} +1 -1
- package/dist/assets/{index-KygHRE3E.js → index-DreDnjOu.js} +1 -1
- package/dist/assets/index-Du7o6Dsf.js +610 -0
- package/dist/assets/index-KxG4bWSr.css +1 -0
- package/dist/assets/{infoDiagram-VRGFBTTK-BG7uaEd4.js → infoDiagram-VRGFBTTK-C7aHjzsf.js} +1 -1
- package/dist/assets/{ishikawaDiagram-OU5B5YK6-D72WAXnM.js → ishikawaDiagram-OU5B5YK6-KUZsNBTh.js} +1 -1
- package/dist/assets/{journeyDiagram-ZHPQQLJL-DKH1gjYT.js → journeyDiagram-ZHPQQLJL-CGUuodFl.js} +1 -1
- package/dist/assets/{kanban-definition-PNTS6WVX-3upm9JUT.js → kanban-definition-PNTS6WVX-Cy6r868Z.js} +1 -1
- package/dist/assets/{linear-CcR4pc0-.js → linear-Bl669148.js} +1 -1
- package/dist/assets/{mermaid.core-C6yU5FSS.js → mermaid.core-86Xqz5Mr.js} +5 -5
- package/dist/assets/{mindmap-definition-NLK3R4M7-DOI2xEuv.js → mindmap-definition-NLK3R4M7-G4DzHLrd.js} +1 -1
- package/dist/assets/{pegDiagram-GJSIUBJH-BVWJSgLd.js → pegDiagram-GJSIUBJH-K4eruzY0.js} +1 -1
- package/dist/assets/{pieDiagram-5QR66LMP-jtbYezhq.js → pieDiagram-5QR66LMP-DYdR9PPq.js} +1 -1
- package/dist/assets/{quadrantDiagram-O4NWA36T-g0u8mtPX.js → quadrantDiagram-O4NWA36T-BCAh0nEy.js} +1 -1
- package/dist/assets/{railroadDiagram-XR7U4H2S-k8689Wny.js → railroadDiagram-XR7U4H2S-p0dGRc4g.js} +1 -1
- package/dist/assets/{requirementDiagram-PLB6GJNP-DsJ22PDt.js → requirementDiagram-PLB6GJNP-tLeBG0aL.js} +1 -1
- package/dist/assets/{sankeyDiagram-IPEJSGJF-CneWBRgG.js → sankeyDiagram-IPEJSGJF-QRuN16rV.js} +1 -1
- package/dist/assets/{sequenceDiagram-PO4LG4MO-DWsFCWap.js → sequenceDiagram-PO4LG4MO-ueT1nVoa.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-Bvf0YE3S.js → sizeCapture-INFHLROL-wRS2zWg2.js} +1 -1
- package/dist/assets/{stateDiagram-v2-GCMORJYK-Bxb3Iei8.js → stateDiagram-v2-GCMORJYK-CYzA_hE1.js} +1 -1
- package/dist/assets/{swimlanes-2SLR337P-DLkk7W4A.js → swimlanes-2SLR337P-kDnv6LMc.js} +1 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-C7voMDle.js +8 -0
- package/dist/assets/{timeline-definition-EJHVYXUP-KR9bIHYC.js → timeline-definition-EJHVYXUP-D1y2UbbH.js} +1 -1
- package/dist/assets/{usecaseDiagram-POWQR4AR-DfsOrj8t.js → usecaseDiagram-POWQR4AR-IqB4v9HX.js} +1 -1
- package/dist/assets/{vennDiagram-UO4OBE2U-DRSTzG3h.js → vennDiagram-UO4OBE2U-BEi7KYVF.js} +1 -1
- package/dist/assets/{wardleyDiagram-VNRHLVJA-DtEdhPgT.js → wardleyDiagram-VNRHLVJA-Dkybo-Yf.js} +1 -1
- package/dist/assets/{xychartDiagram-PMCCYNJV-B-XhTxjG.js → xychartDiagram-PMCCYNJV-BAPFJLpg.js} +1 -1
- package/dist/index.html +2 -2
- package/dist-server/cloud-home-start.js +25 -12
- package/dist-server/companion/src/listener.js +39 -18
- package/dist-server/container-mcp.js +14 -27
- package/dist-server/drivers/agents-proxy.js +103 -44
- package/dist-server/harness-mcp-proxy.js +1 -2
- package/dist-server/index.js +2633 -3396
- package/dist-server/openmausbot.js +606 -463
- package/dist-server/pair-cli.js +606 -463
- package/dist-server/server/admin-activity.js +1 -4
- package/dist-server/server/auto-approve.js +19 -4
- package/dist-server/server/boat.js +4 -4
- package/dist-server/server/checkpoints.js +90 -148
- package/dist-server/server/chief-of-staff.js +7 -6
- package/dist-server/server/cli.js +33 -26
- package/dist-server/server/cloud-home-start.js +12 -15
- package/dist-server/server/cloud-home.js +0 -4
- package/dist-server/server/cloud-move-http.js +44 -30
- package/dist-server/server/cloud-move.js +20 -18
- package/dist-server/server/composio.js +10 -3
- package/dist-server/server/config.js +92 -64
- package/dist-server/server/container-computer.js +16 -5
- package/dist-server/server/contracts.js +0 -9
- package/dist-server/server/decider/jev.js +1 -4
- package/dist-server/server/decider/log.js +1 -3
- package/dist-server/server/decision-log.js +1 -4
- package/dist-server/server/delegations.js +3 -2
- package/dist-server/server/drivers/acp/core.js +4 -3
- package/dist-server/server/drivers/acp/opencode-go.js +17 -16
- package/dist-server/server/drivers/agents-call.js +46 -63
- package/dist-server/server/drivers/agents-catalog.js +66 -0
- package/dist-server/server/drivers/agents-proxy.js +2 -1
- package/dist-server/server/drivers/claude-login-auth.js +2 -11
- package/dist-server/server/drivers/codex-device-auth.js +3 -1
- package/dist-server/server/drivers/codex.js +5 -4
- package/dist-server/server/drivers/grok.js +1 -1
- package/dist-server/server/drivers/openai-chat-protocol.js +4 -2
- package/dist-server/server/drivers/openai-chat.js +15 -5
- package/dist-server/server/drivers/prompt-split.js +4 -4
- package/dist-server/server/harness/bus.js +49 -0
- package/dist-server/server/harness-mcp-proxy.js +1 -1
- package/dist-server/server/index.js +448 -762
- package/dist-server/server/mcp-registry.js +35 -0
- package/dist-server/server/message-file.js +4 -4
- package/dist-server/server/peer-delivery.js +4 -19
- package/dist-server/server/peer-roster.js +30 -4
- package/dist-server/server/profile-versions.js +0 -31
- package/dist-server/server/provider-key-check.js +2 -1
- package/dist-server/server/restart.js +40 -0
- package/dist-server/server/room-handoffs.js +12 -0
- package/dist-server/server/routes/bot-memory.js +0 -24
- package/dist-server/server/routine-requests.js +1 -4
- package/dist-server/server/routines.js +5 -4
- package/dist-server/server/server-launcher.js +53 -0
- package/dist-server/server/store.js +1 -1
- package/dist-server/server/surface.js +23 -27
- package/dist-server/server/system-prompt.js +13 -3
- package/dist-server/server/team-manifest.js +1 -6
- package/dist-server/server/tunnel.js +4 -1
- package/dist-server/server/turn-images.js +1 -21
- package/dist-server/server/turn-resources.js +19 -150
- package/dist-server/server/usage-ledger.js +3 -1
- package/dist-server/server/workspace.js +2 -3
- package/dist-server/server-launcher.js +72 -0
- package/dist-server/shared/ask-question.js +1 -1
- package/dist-server/shared/cloud-computer.js +12 -0
- package/dist-server/shared/failed-turn.js +25 -0
- package/dist-server/shared/live-approval.js +2 -2
- package/dist-server/shared/package-format.js +3 -3
- package/dist-server/shared/pairing-link.js +144 -0
- package/dist-server/vps-container-mcp.js +14 -27
- package/dist-server/workspace-backup.worker.js +14 -27
- package/package.json +1 -1
- package/dist/assets/channel-DS2NbT5x.js +0 -1
- package/dist/assets/index-NF7XCHAy.css +0 -1
- package/dist/assets/index-X_5ydXIE.js +0 -610
- package/dist/assets/swimlanesDiagram-TC7HE7FX-C6bZtBXk.js +0 -8
- package/dist-server/server/claim-idle.js +0 -33
- package/dist-server/server/cloud-overflow.js +0 -167
- package/dist-server/server/tightening-requests.js +0 -435
- package/dist-server/shared/tightening-request.js +0 -87
|
@@ -9,6 +9,7 @@ import { lstatSync, realpathSync, statSync } from "node:fs";
|
|
|
9
9
|
import { dirname, extname, resolve } from "node:path";
|
|
10
10
|
import { fromMarkdown } from "mdast-util-from-markdown";
|
|
11
11
|
import { ATTACHMENTS_DIR, IMAGE_MAX_BYTES } from "./attachments.js";
|
|
12
|
+
import { decodeAttachmentAttribute } from "./message-file.js";
|
|
12
13
|
/** Matches the companion composer policy. Four maximum-sized images are
|
|
13
14
|
* bounded to 40 MiB before a provider is asked to ingest them. */
|
|
14
15
|
export const TURN_IMAGE_MAX_COUNT = 4;
|
|
@@ -24,27 +25,6 @@ const MIME_BY_EXTENSION = {
|
|
|
24
25
|
function statusError(status, message) {
|
|
25
26
|
return Object.assign(new Error(message), { status });
|
|
26
27
|
}
|
|
27
|
-
/** Decode exactly the entities emitted by the composer. Repeated or unknown
|
|
28
|
-
* encodings stay encoded and therefore cannot acquire a privileged path. */
|
|
29
|
-
function decodeAttachmentAttribute(value) {
|
|
30
|
-
return value.replace(/&(quot|lt|gt|amp);|&#(9|10|13);/g, (entity, named, numeric) => {
|
|
31
|
-
if (numeric === "9")
|
|
32
|
-
return "\t";
|
|
33
|
-
if (numeric === "10")
|
|
34
|
-
return "\n";
|
|
35
|
-
if (numeric === "13")
|
|
36
|
-
return "\r";
|
|
37
|
-
if (named === "quot")
|
|
38
|
-
return '"';
|
|
39
|
-
if (named === "lt")
|
|
40
|
-
return "<";
|
|
41
|
-
if (named === "gt")
|
|
42
|
-
return ">";
|
|
43
|
-
if (named === "amp")
|
|
44
|
-
return "&";
|
|
45
|
-
return entity;
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
28
|
function walk(node, visit) {
|
|
49
29
|
const pending = [node];
|
|
50
30
|
while (pending.length > 0) {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { realpathSync } from "node:fs";
|
|
2
|
-
import { relative, resolve, sep } from "node:path";
|
|
3
1
|
/** One harness owns the data directory. Claims are synchronous and last for
|
|
4
2
|
* the whole turn, not just a click: a screenshot and its following click
|
|
5
|
-
* must see the same desktop. These coordinate app-managed resources
|
|
6
|
-
*
|
|
3
|
+
* must see the same desktop. These coordinate app-managed resources — a
|
|
4
|
+
* desktop, a browser session, a phone — that only one turn can drive at a
|
|
5
|
+
* time. A project folder is not one: a bot's threads work in one folder
|
|
6
|
+
* side by side, as several agent sessions do in one repo. They are not a
|
|
7
|
+
* sandbox for arbitrary shell commands. */
|
|
7
8
|
export class TurnResources {
|
|
8
9
|
owners = new Map();
|
|
9
|
-
reclaims = new Map();
|
|
10
10
|
/** Arrival-ordered waiters per requested resource (#1652). Entries exist
|
|
11
11
|
* only while a turn is genuinely waiting (the exclusive bind's poll loop):
|
|
12
12
|
* a lazy claim that failed once and moved on must not sit at the front and
|
|
@@ -72,25 +72,16 @@ export class TurnResources {
|
|
|
72
72
|
waitEstimateMs(resource) {
|
|
73
73
|
return this.waitStats.get(resource)?.ewmaMs;
|
|
74
74
|
}
|
|
75
|
-
blocker(resource, owner
|
|
76
|
-
this.
|
|
77
|
-
|
|
78
|
-
if (overlaps(key, resource) && !sameOwner(current.owner, owner))
|
|
79
|
-
return current.owner;
|
|
80
|
-
}
|
|
81
|
-
return undefined;
|
|
75
|
+
blocker(resource, owner) {
|
|
76
|
+
const current = this.owners.get(resource);
|
|
77
|
+
return current && !sameOwner(current, owner) ? current : undefined;
|
|
82
78
|
}
|
|
83
|
-
claim(resource, owner
|
|
84
|
-
|
|
85
|
-
if (this.blocker(resource, owner, now))
|
|
79
|
+
claim(resource, owner) {
|
|
80
|
+
if (this.blocker(resource, owner))
|
|
86
81
|
return false;
|
|
87
82
|
const existing = this.owners.get(resource);
|
|
88
|
-
if (existing && sameOwner(existing
|
|
89
|
-
// A repeated claim by the sitting owner changes nothing: replacing
|
|
90
|
-
// the record would restart activityAt, and re-claims alone would
|
|
91
|
-
// hold the seat past every quiet window (#1653).
|
|
83
|
+
if (existing && sameOwner(existing, owner))
|
|
92
84
|
return true;
|
|
93
|
-
}
|
|
94
85
|
// The free seat belongs to the front waiter; a current holder's
|
|
95
86
|
// revalidation above never loses its own claim to this queue.
|
|
96
87
|
const queue = this.waiters.get(resource);
|
|
@@ -101,76 +92,12 @@ export class TurnResources {
|
|
|
101
92
|
if (!queue.length)
|
|
102
93
|
this.waiters.delete(resource);
|
|
103
94
|
}
|
|
104
|
-
|
|
105
|
-
if (options.idle) {
|
|
106
|
-
record.idle = options.idle;
|
|
107
|
-
// Taking (or retaking) a seat is itself screen activity: the quiet
|
|
108
|
-
// window of #1653 starts at the claim, not at the first later call.
|
|
109
|
-
record.activityAt = now;
|
|
110
|
-
}
|
|
111
|
-
this.owners.set(resource, record);
|
|
112
|
-
const reclaimed = this.reclaims.get(resource);
|
|
113
|
-
// Seating an owner closes any idle-release record for this resource:
|
|
114
|
-
// the previous holder re-claimed (its turn lives on), or another turn
|
|
115
|
-
// took the free seat — an occupied seat is exactly where #1653 says
|
|
116
|
-
// the old holder yields.
|
|
117
|
-
if (reclaimed)
|
|
118
|
-
this.reclaims.delete(resource);
|
|
95
|
+
this.owners.set(resource, owner);
|
|
119
96
|
return true;
|
|
120
97
|
}
|
|
121
|
-
owns(resource, owner
|
|
122
|
-
this.expireIdle(resource, now);
|
|
123
|
-
const current = this.owners.get(resource);
|
|
124
|
-
return Boolean(current && sameOwner(current.owner, owner));
|
|
125
|
-
}
|
|
126
|
-
/** A real screen call touched this claim (#1653): restart its quiet
|
|
127
|
-
* window. Claims without an idle policy — and anything the screen
|
|
128
|
-
* poller drives — are no-ops, so preview frames can never hold a seat. */
|
|
129
|
-
activity(resource, owner, now = Date.now()) {
|
|
130
|
-
// Expiry first: a straggler screen completion arriving at or past the
|
|
131
|
-
// quiet boundary must release the seat and open the reclaim window,
|
|
132
|
-
// not restart the clock on a claim that already lapsed.
|
|
133
|
-
this.expireIdle(resource, now);
|
|
98
|
+
owns(resource, owner) {
|
|
134
99
|
const current = this.owners.get(resource);
|
|
135
|
-
|
|
136
|
-
current.activityAt = now;
|
|
137
|
-
}
|
|
138
|
-
/** A screen-touching computer tool call started on this claim (#1653):
|
|
139
|
-
* the desktop is executing real I/O for the sitting owner, so the seat
|
|
140
|
-
* cannot be idle-released mid-call. Only the sitting owner's start
|
|
141
|
-
* counts; a start whose completion never arrives holds the seat until
|
|
142
|
-
* settle — the pre-#1653 behavior, never an overlapping handoff. */
|
|
143
|
-
beginComputerCall(resource, owner) {
|
|
144
|
-
const current = this.owners.get(resource);
|
|
145
|
-
if (current?.idle && sameOwner(current.owner, owner)) {
|
|
146
|
-
current.computerCallsInFlight = (current.computerCallsInFlight ?? 0) + 1;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
/** The in-flight computer call finished: drop its fence and let the
|
|
150
|
-
* completion's own activity tick govern the quiet window. A no-op when
|
|
151
|
-
* the claim lapsed or another turn seated itself in between. */
|
|
152
|
-
endComputerCall(resource, owner) {
|
|
153
|
-
const current = this.owners.get(resource);
|
|
154
|
-
if (!current?.idle || !sameOwner(current.owner, owner))
|
|
155
|
-
return;
|
|
156
|
-
const inFlight = current.computerCallsInFlight ?? 0;
|
|
157
|
-
if (inFlight > 0)
|
|
158
|
-
current.computerCallsInFlight = inFlight - 1;
|
|
159
|
-
}
|
|
160
|
-
/** Who may still re-claim `resource` directly after an idle release
|
|
161
|
-
* (#1653): the mid-task turn that went quiet, until its reclaim window
|
|
162
|
-
* ends or another turn seats itself. The direct re-claim itself is
|
|
163
|
-
* just `claim`: a free seat answers the returning holder's next screen
|
|
164
|
-
* call without the wait a new arrival would enter. */
|
|
165
|
-
reclaimHolder(resource, now = Date.now()) {
|
|
166
|
-
const reclaimed = this.reclaims.get(resource);
|
|
167
|
-
if (!reclaimed)
|
|
168
|
-
return undefined;
|
|
169
|
-
if (reclaimed.until <= now) {
|
|
170
|
-
this.reclaims.delete(resource);
|
|
171
|
-
return undefined;
|
|
172
|
-
}
|
|
173
|
-
return reclaimed.owner;
|
|
100
|
+
return Boolean(current && sameOwner(current, owner));
|
|
174
101
|
}
|
|
175
102
|
release(owner) {
|
|
176
103
|
for (const [key, queue] of this.waiters) {
|
|
@@ -181,81 +108,23 @@ export class TurnResources {
|
|
|
181
108
|
this.waiters.delete(key);
|
|
182
109
|
}
|
|
183
110
|
for (const [key, current] of this.owners) {
|
|
184
|
-
if (sameOwner(current
|
|
111
|
+
if (sameOwner(current, owner))
|
|
185
112
|
this.owners.delete(key);
|
|
186
113
|
}
|
|
187
|
-
// A settling turn keeps no reclaim priority (#1653): only a live
|
|
188
|
-
// mid-task turn may pick its seat back up.
|
|
189
|
-
for (const [key, reclaimed] of this.reclaims) {
|
|
190
|
-
if (sameOwner(reclaimed.owner, owner))
|
|
191
|
-
this.reclaims.delete(key);
|
|
192
|
-
}
|
|
193
114
|
}
|
|
194
115
|
/** Drop one of an owner's claims early, when the sequence that took it
|
|
195
116
|
* could not finish. The owner's other claims stand until settle. */
|
|
196
|
-
releaseOne(resource, owner
|
|
197
|
-
if (this.owns(resource, owner
|
|
117
|
+
releaseOne(resource, owner) {
|
|
118
|
+
if (this.owns(resource, owner))
|
|
198
119
|
this.owners.delete(resource);
|
|
199
120
|
this.stopWaiting(resource, owner);
|
|
200
|
-
// owns() may have just expired a quiet claim into a reclaim record
|
|
201
|
-
// for this same owner: an early release keeps no reclaim priority.
|
|
202
|
-
const reclaimed = this.reclaims.get(resource);
|
|
203
|
-
if (reclaimed && sameOwner(reclaimed.owner, owner))
|
|
204
|
-
this.reclaims.delete(resource);
|
|
205
|
-
}
|
|
206
|
-
/** Quiet-window expiry (#1653), evaluated when the claim is touched: a
|
|
207
|
-
* screen-quiet seat is released while its turn lives and remembered for
|
|
208
|
-
* the reclaim window. Synchronous on purpose — the claim map decides
|
|
209
|
-
* every transition on the server's single thread. */
|
|
210
|
-
expireIdle(resource, now) {
|
|
211
|
-
const current = this.owners.get(resource);
|
|
212
|
-
if (!current?.idle || current.activityAt === undefined)
|
|
213
|
-
return;
|
|
214
|
-
// A call the desktop is still executing fences the seat (#1653):
|
|
215
|
-
// releasing mid-call would hand one desktop to two turns at once.
|
|
216
|
-
if ((current.computerCallsInFlight ?? 0) > 0)
|
|
217
|
-
return;
|
|
218
|
-
if (!current.idle.quietElapsed(current.activityAt, now))
|
|
219
|
-
return;
|
|
220
|
-
this.owners.delete(resource);
|
|
221
|
-
this.reclaims.set(resource, { owner: current.owner, until: now + current.idle.reclaimMs });
|
|
222
121
|
}
|
|
223
122
|
/** Whether any live owner holds this resource: the parked-resume drain's
|
|
224
123
|
* gate (#1651) — a resume fires only when the seat it queued on is free. */
|
|
225
|
-
free(resource
|
|
226
|
-
|
|
227
|
-
if (!overlaps(key, resource))
|
|
228
|
-
continue;
|
|
229
|
-
// A quiet-window claim expires the moment anything needs to know
|
|
230
|
-
// whether the seat is free: the parked-resume gate must not wait on
|
|
231
|
-
// a claim whose window already elapsed (#1653).
|
|
232
|
-
this.expireIdle(key, now);
|
|
233
|
-
if (this.owners.has(key))
|
|
234
|
-
return false;
|
|
235
|
-
}
|
|
236
|
-
return true;
|
|
124
|
+
free(resource) {
|
|
125
|
+
return !this.owners.has(resource);
|
|
237
126
|
}
|
|
238
127
|
}
|
|
239
128
|
function sameOwner(a, b) {
|
|
240
129
|
return a.threadId === b.threadId && a.generation === b.generation;
|
|
241
130
|
}
|
|
242
|
-
export function workspaceResource(cwd) {
|
|
243
|
-
// Selected folders must exist before the engine starts. Resolve symlinks
|
|
244
|
-
// and native filename casing so aliases cannot grant two writers to the
|
|
245
|
-
// same project on case-insensitive volumes.
|
|
246
|
-
const canonical = realpathSync.native(resolve(cwd));
|
|
247
|
-
return `workspace:${process.platform === "win32" ? canonical.toLowerCase() : canonical}`;
|
|
248
|
-
}
|
|
249
|
-
function overlaps(a, b) {
|
|
250
|
-
if (a === b)
|
|
251
|
-
return true;
|
|
252
|
-
if (!a.startsWith("workspace:") || !b.startsWith("workspace:"))
|
|
253
|
-
return false;
|
|
254
|
-
const left = a.slice("workspace:".length);
|
|
255
|
-
const right = b.slice("workspace:".length);
|
|
256
|
-
const contains = (parent, child) => {
|
|
257
|
-
const path = relative(parent, child);
|
|
258
|
-
return path === "" || (path !== ".." && !path.startsWith(`..${sep}`) && !path.startsWith(sep) && !/^[A-Za-z]:/.test(path));
|
|
259
|
-
};
|
|
260
|
-
return contains(left, right) || contains(right, left);
|
|
261
|
-
}
|
|
@@ -26,7 +26,9 @@ const promptBytesOf = (value) => {
|
|
|
26
26
|
? { stable: clean(record.stable), volatile: clean(record.volatile) }
|
|
27
27
|
: null;
|
|
28
28
|
};
|
|
29
|
-
|
|
29
|
+
/** The UTC month a row belongs to, `YYYY-MM`: the name of its month file.
|
|
30
|
+
* The decision, admin-activity and decider logs use the same layout. */
|
|
31
|
+
export function monthKey(at) {
|
|
30
32
|
return `${at.getUTCFullYear()}-${String(at.getUTCMonth() + 1).padStart(2, "0")}`;
|
|
31
33
|
}
|
|
32
34
|
export function usageFileFor(dataDir, at) {
|
|
@@ -588,9 +588,8 @@ export function writeMemoryTopic(botId, name, text) {
|
|
|
588
588
|
writeFileAtomic(join(workspaceDir(botId), "memory", name), redactSecretsInText(text), { mode: 0o600 });
|
|
589
589
|
indexWrittenMemoryFile(botId, `memory/${name}`);
|
|
590
590
|
}
|
|
591
|
-
/** Read one topic file. The name gate runs here
|
|
592
|
-
*
|
|
593
|
-
* arbitrary path. Null for anything invalid or unreadable. */
|
|
591
|
+
/** Read one topic file. The name gate runs here, so no caller can turn this
|
|
592
|
+
* into a read of an arbitrary path. Null for anything invalid or unreadable. */
|
|
594
593
|
export function readMemoryTopic(botId, name) {
|
|
595
594
|
if (!isMemoryTopicName(name))
|
|
596
595
|
return null;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// server/server-launcher.ts
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { realpathSync } from "node:fs";
|
|
4
|
+
import { dirname, join, resolve } from "node:path";
|
|
5
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
6
|
+
|
|
7
|
+
// server/restart.ts
|
|
8
|
+
var RESTART_EXIT_CODE = 75;
|
|
9
|
+
var MAX_RESTARTS = 5;
|
|
10
|
+
var STABLE_RUN_MS = 6e4;
|
|
11
|
+
function restartPolicy(now = Date.now) {
|
|
12
|
+
let restarts = 0, startedAt = now();
|
|
13
|
+
return {
|
|
14
|
+
again(code, stopping = false) {
|
|
15
|
+
if (now() - startedAt >= STABLE_RUN_MS) restarts = 0;
|
|
16
|
+
if (stopping || code !== RESTART_EXIT_CODE || restarts >= MAX_RESTARTS) return false;
|
|
17
|
+
restarts++;
|
|
18
|
+
startedAt = now();
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// server/server-launcher.ts
|
|
25
|
+
function superviseServer(start, options = {}) {
|
|
26
|
+
const signals = options.signals ?? process, now = options.now ?? Date.now;
|
|
27
|
+
return new Promise((done) => {
|
|
28
|
+
let child, stopping = false;
|
|
29
|
+
const policy = restartPolicy(now);
|
|
30
|
+
const pass = (signal) => () => {
|
|
31
|
+
stopping = true;
|
|
32
|
+
child.kill(signal);
|
|
33
|
+
};
|
|
34
|
+
const onTerm = pass("SIGTERM"), onInt = pass("SIGINT");
|
|
35
|
+
const finish = (code) => {
|
|
36
|
+
signals.off("SIGTERM", onTerm);
|
|
37
|
+
signals.off("SIGINT", onInt);
|
|
38
|
+
done(code);
|
|
39
|
+
};
|
|
40
|
+
const run = () => {
|
|
41
|
+
child = start();
|
|
42
|
+
child.once("error", () => finish(1));
|
|
43
|
+
child.once("exit", (code, signal) => {
|
|
44
|
+
if (policy.again(code, stopping)) {
|
|
45
|
+
console.log("OpenMausBot is starting again to finish installing a copy from the desktop app\u2026");
|
|
46
|
+
run();
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
finish(code ?? (stopping && (signal === "SIGTERM" || signal === "SIGINT") ? 0 : 1));
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
signals.on("SIGTERM", onTerm);
|
|
53
|
+
signals.on("SIGINT", onInt);
|
|
54
|
+
run();
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
function isEntry() {
|
|
58
|
+
try {
|
|
59
|
+
return Boolean(process.argv[1]) && import.meta.url === pathToFileURL(realpathSync(resolve(process.argv[1]))).href;
|
|
60
|
+
} catch {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (isEntry()) {
|
|
65
|
+
const server = join(dirname(fileURLToPath(import.meta.url)), "index.js");
|
|
66
|
+
void superviseServer(() => spawn(process.execPath, [server], { stdio: "inherit" })).then((code) => {
|
|
67
|
+
process.exitCode = code;
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
export {
|
|
71
|
+
superviseServer
|
|
72
|
+
};
|
|
@@ -34,7 +34,7 @@ export const MAX_CUSTOM_ANSWER = 2000;
|
|
|
34
34
|
export function isPersistentQuestionCard(card) {
|
|
35
35
|
return Boolean(card && (card.requestType === "question" || card.questionRequest ||
|
|
36
36
|
(!card.requestType && !card.tool && !card.routineRequest && !card.skillRequest && !card.profileRequest &&
|
|
37
|
-
!card.modelRequest && !card.
|
|
37
|
+
!card.modelRequest && !card.teamSetupRequest)));
|
|
38
38
|
}
|
|
39
39
|
/** System/provider completion ends the live run, not the human's question. */
|
|
40
40
|
export function shouldSettleRequestCard(card, source) {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Which engines can work on a cloud computer: Hosted desktop on Boat, or a
|
|
2
|
+
// self-hosted VPS. One rule for the server (every attach, readiness check and
|
|
3
|
+
// select_computer offer) and the renderer (the Works on panel, the composer's
|
|
4
|
+
// place chip, Webhooks and Routines), so the two never disagree.
|
|
5
|
+
/** An engine with computer tools gets the cloud computer as one more stdio
|
|
6
|
+
* computer server. The Computer engine (boxAgent) runs its whole turn on its
|
|
7
|
+
* Boat, so it can work on a Boat but not on a VPS. */
|
|
8
|
+
export function canWorkOnCloud(engine, backend) {
|
|
9
|
+
const runsOnBoat = engine?.driverKind === "boxAgent";
|
|
10
|
+
const hasComputerTools = engine?.computerMcp === true;
|
|
11
|
+
return backend === "vps" ? hasComputerTools && !runsOnBoat : hasComputerTools || runsOnBoat;
|
|
12
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
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
|
+
const MARKER = "error:";
|
|
6
|
+
/** The longest cause a row keeps. Clients wrap the row and show it whole, so
|
|
7
|
+
* this only bounds a pathological message (a provider's HTML body, a stack
|
|
8
|
+
* trace); a sentence a person acts on fits, its next action included. */
|
|
9
|
+
export const FAILED_TURN_MAX_CHARS = 600;
|
|
10
|
+
/** The activity row a failed turn is stored as. */
|
|
11
|
+
export function failedTurnTool(cause, flags = {}) {
|
|
12
|
+
const words = cause.length > FAILED_TURN_MAX_CHARS ? `${cause.slice(0, FAILED_TURN_MAX_CHARS - 1)}…` : cause;
|
|
13
|
+
return {
|
|
14
|
+
name: `${MARKER} ${words}`,
|
|
15
|
+
ok: false,
|
|
16
|
+
...(flags.setup ? { setup: true } : {}),
|
|
17
|
+
...(flags.terminal ? { terminal: true } : {}),
|
|
18
|
+
...(flags.claudeUpdate ? { claudeUpdate: true } : {}),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/** The cause a failed-turn row carries, without its marker; null for any
|
|
22
|
+
* other activity row. */
|
|
23
|
+
export function failedTurnCause(name) {
|
|
24
|
+
return name.startsWith(MARKER) ? name.slice(MARKER.length).trim() : null;
|
|
25
|
+
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/** "approval": a provider or peer ask the voice may decide with a strict yes/no.
|
|
2
2
|
* "review": a harness-native proposal (skill, routine, profile, default
|
|
3
|
-
* model,
|
|
3
|
+
* model, team setup) that must be reviewed on screen.
|
|
4
4
|
* "question": a provider question. An expired proposal is settled: nothing
|
|
5
5
|
* can answer it any more. */
|
|
6
6
|
export function liveCardKind(card) {
|
|
7
7
|
if (!card?.requestId || card.answered || card.dismissed || card.expired)
|
|
8
8
|
return null;
|
|
9
|
-
if (card.skillRequest || card.routineRequest || card.profileRequest || card.modelRequest || card.
|
|
9
|
+
if (card.skillRequest || card.routineRequest || card.profileRequest || card.modelRequest || card.teamSetupRequest) {
|
|
10
10
|
return "review";
|
|
11
11
|
}
|
|
12
12
|
return card.tool ? "approval" : "question";
|
|
@@ -55,9 +55,9 @@ const encoder = new TextEncoder();
|
|
|
55
55
|
export function utf8Bytes(value) {
|
|
56
56
|
return encoder.encode(value).length;
|
|
57
57
|
}
|
|
58
|
-
// ── shared field helpers (
|
|
59
|
-
const requiredText = (max) => z.string({ error: "must be text" }).trim().min(1, { message: "is required" }).max(max, { message: "is too long" });
|
|
60
|
-
const optionalText = (max) => z
|
|
58
|
+
// ── shared field helpers (the team manifest reader uses these too) ────────
|
|
59
|
+
export const requiredText = (max) => z.string({ error: "must be text" }).trim().min(1, { message: "is required" }).max(max, { message: "is too long" });
|
|
60
|
+
export const optionalText = (max) => z
|
|
61
61
|
.union([z.string({ error: "must be text" }), z.null(), z.undefined()])
|
|
62
62
|
.transform((value) => value?.trim() || undefined)
|
|
63
63
|
.refine((value) => value === undefined || value.length <= max, { message: "is too long" })
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// The one `openmausbot://pair` link builder. The desktop's phone setup, the
|
|
2
|
+
// headless server's pairing endpoint and `openmausbot pair` all print this
|
|
3
|
+
// link; the Android and iOS scanners read it (android/core Connection.kt
|
|
4
|
+
// `PairingInvite.parse`, ios CompanionCore Client.swift `PairingInvite.parse`).
|
|
5
|
+
//
|
|
6
|
+
// Every value is written with encodeURIComponent, so a space is `%20`. The
|
|
7
|
+
// desktop used URLSearchParams until Oct 2026, which writes a space as `+`;
|
|
8
|
+
// neither phone read that back as a space, and "Miguel's computer" showed up
|
|
9
|
+
// as "Miguel's+computer". The phones now also read `+` as a space, for links
|
|
10
|
+
// from desktops still running the old builder.
|
|
11
|
+
/** How many fallback routes a link will carry. The list is tiny in practice;
|
|
12
|
+
* the cap only keeps a pathological interface table from bloating the QR. */
|
|
13
|
+
export const MAX_PAIRING_HOSTS = 8;
|
|
14
|
+
const ENDPOINT_KINDS = new Set(["hosted", "tailnet", "lan", "bonjour"]);
|
|
15
|
+
/** Keep the QR contract strict even though its input came from our own
|
|
16
|
+
* sidecar. A public URL with credentials or a path is not a companion base
|
|
17
|
+
* URL, and filtering it is safer than teaching the phone to reinterpret it.
|
|
18
|
+
* Sorted by priority, deduplicated by origin, capped. */
|
|
19
|
+
export function qrEndpoints(endpoints) {
|
|
20
|
+
const seen = new Set();
|
|
21
|
+
const valid = [];
|
|
22
|
+
for (const endpoint of endpoints ?? []) {
|
|
23
|
+
if (!endpoint ||
|
|
24
|
+
!ENDPOINT_KINDS.has(endpoint.kind) ||
|
|
25
|
+
!Number.isInteger(endpoint.priority) ||
|
|
26
|
+
endpoint.priority < 0 ||
|
|
27
|
+
endpoint.priority > 1_000_000) {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
const parsed = new URL(endpoint.url);
|
|
32
|
+
const expectedProtocol = endpoint.kind === "hosted" ? "https:" : "http:";
|
|
33
|
+
const explicitPort = parsed.port ? Number(parsed.port) : null;
|
|
34
|
+
const hostname = parsed.hostname.toLowerCase().replace(/\.$/, "");
|
|
35
|
+
if (parsed.protocol !== expectedProtocol ||
|
|
36
|
+
// No empty DNS label (".ts.net", "mac..local"): the phones refuse
|
|
37
|
+
// one, and one endpoint they refuse makes them refuse the whole QR.
|
|
38
|
+
hostname.split(".").includes("") ||
|
|
39
|
+
(endpoint.kind === "tailnet" && !hostname.endsWith(".ts.net")) ||
|
|
40
|
+
(explicitPort !== null && (!Number.isInteger(explicitPort) || explicitPort < 1 || explicitPort > 65_535)) ||
|
|
41
|
+
parsed.username ||
|
|
42
|
+
parsed.password ||
|
|
43
|
+
parsed.pathname !== "/" ||
|
|
44
|
+
parsed.search ||
|
|
45
|
+
parsed.hash ||
|
|
46
|
+
seen.has(parsed.origin)) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
seen.add(parsed.origin);
|
|
50
|
+
valid.push({ url: parsed.origin, kind: endpoint.kind, priority: endpoint.priority });
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
// One malformed advisory route must not invalidate an otherwise usable
|
|
54
|
+
// pairing QR. It is simply omitted from the route walk.
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return valid.sort((left, right) => left.priority - right.priority).slice(0, MAX_PAIRING_HOSTS);
|
|
58
|
+
}
|
|
59
|
+
/** URL-safe, unpadded base64 keeps the structured JSON smaller than query
|
|
60
|
+
* escaping every quote and slash while remaining straightforward to decode
|
|
61
|
+
* with Foundation on iOS. */
|
|
62
|
+
function encodeEndpoints(endpoints) {
|
|
63
|
+
const bytes = new TextEncoder().encode(JSON.stringify(endpoints));
|
|
64
|
+
let binary = "";
|
|
65
|
+
for (const byte of bytes)
|
|
66
|
+
binary += String.fromCharCode(byte);
|
|
67
|
+
return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
|
|
68
|
+
}
|
|
69
|
+
function validSecretPublicKey(value) {
|
|
70
|
+
if (!value || !/^[A-Za-z0-9_-]{87}$/.test(value))
|
|
71
|
+
return null;
|
|
72
|
+
try {
|
|
73
|
+
const base64 = value.replaceAll("-", "+").replaceAll("_", "/") + "=";
|
|
74
|
+
const decoded = atob(base64);
|
|
75
|
+
if (decoded.length !== 65 || decoded.charCodeAt(0) !== 4)
|
|
76
|
+
return null;
|
|
77
|
+
let binary = "";
|
|
78
|
+
for (let index = 0; index < decoded.length; index += 1)
|
|
79
|
+
binary += decoded[index];
|
|
80
|
+
return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "") === value
|
|
81
|
+
? value
|
|
82
|
+
: null;
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/** The `address` value: `host:port` (IPv6 bracketed) or a bare http(s)
|
|
89
|
+
* origin, or null when it is neither. Both phones refuse an address with
|
|
90
|
+
* credentials, a path, a query or a fragment, as `qrEndpoints` does. */
|
|
91
|
+
function linkAddress(address, port) {
|
|
92
|
+
const value = address.trim();
|
|
93
|
+
if (!value)
|
|
94
|
+
return null;
|
|
95
|
+
if (/^https?:\/\//i.test(value)) {
|
|
96
|
+
try {
|
|
97
|
+
const parsed = new URL(value);
|
|
98
|
+
if (parsed.username || parsed.password || parsed.pathname !== "/" || parsed.search || parsed.hash)
|
|
99
|
+
return null;
|
|
100
|
+
return parsed.origin;
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (port === undefined || !Number.isInteger(port) || port < 1 || port > 65_535)
|
|
107
|
+
return null;
|
|
108
|
+
const authority = value.includes(":") && !value.startsWith("[") ? `[${value}]` : value;
|
|
109
|
+
return `${authority}:${port}`;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* A short-lived handoff from a trusted pairing screen to the mobile app. The
|
|
113
|
+
* credential still has to be redeemed; putting it in the link does not
|
|
114
|
+
* create or expose the long-lived device token. Null when any required part
|
|
115
|
+
* is malformed, so a caller never prints a link a phone would refuse.
|
|
116
|
+
*/
|
|
117
|
+
export function phonePairingLink({ address, port, token, code, name, hosts, endpoints, secretPublicKey, }) {
|
|
118
|
+
const dial = linkAddress(address, port);
|
|
119
|
+
if (!dial || !/^omb_pair_[A-Za-z0-9_-]{43}$/.test(token))
|
|
120
|
+
return null;
|
|
121
|
+
if (code !== undefined && !/^\d{6}$/.test(code))
|
|
122
|
+
return null;
|
|
123
|
+
const fields = [["address", dial], ["token", token]];
|
|
124
|
+
if (code !== undefined)
|
|
125
|
+
fields.push(["code", code]);
|
|
126
|
+
if (name?.trim())
|
|
127
|
+
fields.push(["name", name.trim()]);
|
|
128
|
+
// Comma-joined, which no hostname or IP literal can contain. Filtered
|
|
129
|
+
// rather than refused: a bad candidate costs the phone one failed dial,
|
|
130
|
+
// and dropping the whole link over it would break pairing entirely.
|
|
131
|
+
const candidates = (hosts ?? [])
|
|
132
|
+
.map((candidate) => candidate.trim())
|
|
133
|
+
.filter((candidate) => candidate && !/[\s/?#,[\]]/.test(candidate))
|
|
134
|
+
.slice(0, MAX_PAIRING_HOSTS);
|
|
135
|
+
if (candidates.length)
|
|
136
|
+
fields.push(["hosts", candidates.join(",")]);
|
|
137
|
+
const routes = qrEndpoints(endpoints);
|
|
138
|
+
if (routes.length)
|
|
139
|
+
fields.push(["endpoints", encodeEndpoints(routes)]);
|
|
140
|
+
const secretKey = validSecretPublicKey(secretPublicKey);
|
|
141
|
+
if (secretKey)
|
|
142
|
+
fields.push(["secretKey", secretKey]);
|
|
143
|
+
return `openmausbot://pair?${fields.map(([key, value]) => `${key}=${encodeURIComponent(value)}`).join("&")}`;
|
|
144
|
+
}
|
|
@@ -16202,10 +16202,6 @@ var remoteMutationSchema = remoteEntrySchema.extend({
|
|
|
16202
16202
|
}).optional()
|
|
16203
16203
|
});
|
|
16204
16204
|
|
|
16205
|
-
// server/cloud-overflow.ts
|
|
16206
|
-
var CLOUD_SEAT_IDLE_STOP_MS = 5 * 6e4;
|
|
16207
|
-
var CLOUD_SEAT_SLEEP_RETRY_MAX_MS = 10 * 6e4;
|
|
16208
|
-
|
|
16209
16205
|
// server/config.ts
|
|
16210
16206
|
var optionalText = external_exports.string().optional();
|
|
16211
16207
|
var SSH_ALIAS = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/;
|
|
@@ -16403,17 +16399,6 @@ var featureConfigSchema = external_exports.object({
|
|
|
16403
16399
|
* conversations that share words with the message ride into the turn.
|
|
16404
16400
|
* Read-only; on unless explicitly switched off — see autoRecallEnabled. */
|
|
16405
16401
|
autoRecall: external_exports.boolean().optional(),
|
|
16406
|
-
/** Idle release for computer claims (#1653): a desktop seat that stays
|
|
16407
|
-
* screen-quiet for 90 seconds is released to waiting turns while its
|
|
16408
|
-
* holder's turn still lives; the previous holder re-claims directly
|
|
16409
|
-
* for 10 minutes and yields to an occupied seat. Off until baked; see
|
|
16410
|
-
* computerClaimIdleReleaseEnabled for how to enable it by hand. */
|
|
16411
|
-
computerClaimIdleRelease: external_exports.boolean().optional(),
|
|
16412
|
-
/** Consented cloud overflow for local computer waits (#1655): a wait
|
|
16413
|
-
* may offer a per-second-billed cloud seat, but only through an explicit
|
|
16414
|
-
* consent card or a configured allowlist — never a silent Auto default.
|
|
16415
|
-
* Off until baked; see cloudOverflowEnabled for how to enable it by hand. */
|
|
16416
|
-
cloudOverflow: external_exports.boolean().optional(),
|
|
16417
16402
|
/** Opt-in shared skills library: one store at the data dir that bots
|
|
16418
16403
|
* read by assignment instead of per-workspace copies. Off until
|
|
16419
16404
|
* explicitly enabled — see skillsLibraryEnabled. */
|
|
@@ -16539,8 +16524,12 @@ var appConfigSchema = external_exports.object({
|
|
|
16539
16524
|
* provider is Boat now and the key is kept for compatibility. */
|
|
16540
16525
|
box: external_exports.object({ token: optionalText }).optional(),
|
|
16541
16526
|
vps: vpsConfigSchema.optional(),
|
|
16542
|
-
/** Optional OpenCode key; persisted write-only and passed only to its child.
|
|
16543
|
-
|
|
16527
|
+
/** Optional OpenCode key; persisted write-only and passed only to its child.
|
|
16528
|
+
* `providerKeys`: keys for OpenCode's other providers (Venice, Groq…) by the
|
|
16529
|
+
* environment name OpenCode reads, also write-only and only for its child.
|
|
16530
|
+
* Settings changes go through mergeOpenCodeProviderKeys; the stored copy
|
|
16531
|
+
* is read loosely (storedAppConfigSchema). */
|
|
16532
|
+
opencodeGo: external_exports.object({ apiKey: optionalText, providerKeys: external_exports.record(external_exports.string(), external_exports.string()).optional() }).optional(),
|
|
16544
16533
|
/** Voice settings and the selected voice id. `provider` picks the
|
|
16545
16534
|
* engine: "elevenlabs" (default; needs `key`), "fish" (needs its own
|
|
16546
16535
|
* `fishKey`; `fishModel` picks its speech model), "system" (the Mac's
|
|
@@ -16620,15 +16609,6 @@ var appConfigSchema = external_exports.object({
|
|
|
16620
16609
|
/** The authorization decision log (server/decision-log.ts): days of month
|
|
16621
16610
|
* files kept, at least; OMB_DECISION_RETENTION_DAYS wins when set. */
|
|
16622
16611
|
decisions: external_exports.object({ retentionDays: external_exports.number().int().min(1).max(3650).optional() }).strict().optional(),
|
|
16623
|
-
/** #1655 cloud-overflow settings. perSecondCostUsd is the operator's own
|
|
16624
|
-
* verified rate: with no price configured the feature stays inert rather
|
|
16625
|
-
* than show an invented one. allowlistedThreads carries standing consent
|
|
16626
|
-
* for exact thread ids. */
|
|
16627
|
-
cloudOverflow: external_exports.object({
|
|
16628
|
-
perSecondCostUsd: external_exports.number().positive().max(10).optional(),
|
|
16629
|
-
idleStopMs: external_exports.number().int().positive().max(24 * 60 * 6e4).optional(),
|
|
16630
|
-
allowlistedThreads: external_exports.array(external_exports.string().min(1)).max(1e3).optional()
|
|
16631
|
-
}).strict().optional(),
|
|
16632
16612
|
localVm: localVmConfigSchema.optional(),
|
|
16633
16613
|
features: featureConfigSchema.optional(),
|
|
16634
16614
|
onboarding: onboardingConfigSchema.optional(),
|
|
@@ -16643,7 +16623,14 @@ var appConfigSchema = external_exports.object({
|
|
|
16643
16623
|
mcpServers: external_exports.record(external_exports.string(), external_exports.unknown()).optional()
|
|
16644
16624
|
});
|
|
16645
16625
|
var storedAppConfigSchema = appConfigSchema.extend({
|
|
16646
|
-
browserProfiles: storedBrowserProfilesSchema.optional()
|
|
16626
|
+
browserProfiles: storedBrowserProfilesSchema.optional(),
|
|
16627
|
+
/** Read loosely, like mcpServers: a hand-edited provider key that isn't
|
|
16628
|
+
* text is dropped here, and a bad name or key later
|
|
16629
|
+
* (openCodeProviderKeys), never the whole file. */
|
|
16630
|
+
opencodeGo: external_exports.object({
|
|
16631
|
+
apiKey: optionalText,
|
|
16632
|
+
providerKeys: external_exports.record(external_exports.string(), external_exports.unknown()).catch({}).transform((keys) => Object.fromEntries(Object.entries(keys).filter((entry) => typeof entry[1] === "string"))).optional()
|
|
16633
|
+
}).optional()
|
|
16647
16634
|
});
|
|
16648
16635
|
var appConfigPatchSchema = appConfigSchema.omit({ instances: true, mcpServers: true, cliStartup: true, customDomain: true }).extend({ threads: threadsPatchSchema.optional(), newBots: newBotsPatchSchema.optional() });
|
|
16649
16636
|
var jsonObjectSchema = external_exports.record(external_exports.string(), external_exports.json());
|