openmausbot 0.1.97 → 0.1.98
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/ActivityPanel-H6D3SD39.js +1 -0
- package/dist/assets/BotSettingsDialog-Dqz_AiBZ.js +7 -0
- package/dist/assets/CloudBackendPicker-iezpJFpF.js +1 -0
- package/dist/assets/ComputerPanel-DZPj-IWu.js +1 -0
- package/dist/assets/DesktopViewer-utONms1p.js +5 -0
- package/dist/assets/InspectorPanel-OVaBVrzC.js +5 -0
- package/dist/assets/KeyboardShortcutsModal-gk9mvHj_.js +1 -0
- package/dist/assets/LocalVmWorkspace-DLIvd2kC.js +1 -0
- package/dist/assets/MacLocalControl-bJv8yiPB.js +1 -0
- package/dist/assets/NewBotDialog-1qGOAdUM.js +2 -0
- package/dist/assets/RemoteAgentSettingsPanel-DqKr0HkO.js +1 -0
- package/dist/assets/RoutinesPage-BM2D4qGi.js +1 -0
- package/dist/assets/RoutinesSection-Dny9ra-c.js +1 -0
- package/dist/assets/SettingsModal-gC6XJJmt.js +11 -0
- package/dist/assets/TeamMapPage-B6fuu4r_.js +8 -0
- package/dist/assets/TriggersPanel-j0qTP2CO.js +1 -0
- package/dist/assets/VisibilitySection-DT0FqPvu.js +12 -0
- package/dist/assets/WebhooksPanel--YQyCjKO.js +3 -0
- package/dist/assets/{abnfDiagram-O67JEVCF-k1UCNXb7.js → abnfDiagram-O67JEVCF-BsjcFX0-.js} +1 -1
- package/dist/assets/activity-BxOkayvD.js +1 -0
- package/dist/assets/{arc-BUWGa9UU.js → arc-CJNA94c6.js} +1 -1
- package/dist/assets/architectureDiagram-NJMV4G6O-dz2ud7lP.js +36 -0
- package/dist/assets/{blockDiagram-BEXU5L5S-Ag9nLW4H.js → blockDiagram-BEXU5L5S-ChBEALoR.js} +1 -1
- package/dist/assets/brain-CFZ7PEXc.js +1 -0
- package/dist/assets/{c4Diagram-YGBWAQC7-Bh0sjgUa.js → c4Diagram-YGBWAQC7-KZNRKDQe.js} +1 -1
- package/dist/assets/channel-WhFyujhZ.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-BFBcIIqu.js → chunk-2Q5K7J3B-BLLwmCET.js} +1 -1
- package/dist/assets/{chunk-3FUC2YCW-Dvfpex-f.js → chunk-3FUC2YCW-CQxyreBZ.js} +2 -2
- package/dist/assets/{chunk-5VM5RSS4-CMjP2YZW.js → chunk-5VM5RSS4-D6sFMW0u.js} +1 -1
- package/dist/assets/{chunk-GTNCS2PH-BryoeTc7.js → chunk-GTNCS2PH-BaZ1YgLF.js} +1 -1
- package/dist/assets/{chunk-GWA4HPMP-BhfvcchO.js → chunk-GWA4HPMP-CcalphmC.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-bcCVp8rp.js → chunk-JWPE2WC7-CSk8R3ve.js} +1 -1
- package/dist/assets/{chunk-NETBCI7D-yKIjIlkQ.js → chunk-NETBCI7D-CTmLjA57.js} +1 -1
- package/dist/assets/{chunk-WEXAMYUT-D3Zqbmup.js → chunk-WEXAMYUT-CM9Z77Ne.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-CjVoIMG6.js → chunk-XXDRQBXY-B1fxpMS2.js} +1 -1
- package/dist/assets/{classDiagram-v2-NBCMYWYE-mUCWdVyH.js → classDiagram-v2-NBCMYWYE-DCCyoLpj.js} +1 -1
- package/dist/assets/computer-control-NLov352p.js +1 -0
- package/dist/assets/cose-bilkent-JH36ORCC-KyImBKvE.js +1 -0
- package/dist/assets/{cynefin-OW5HDTMX-BpRrxoO4.js → cynefin-OW5HDTMX-fsZc6MGa.js} +1 -1
- package/dist/assets/{cynefinDiagram-VND7K2PF-D3VBWCPK.js → cynefinDiagram-VND7K2PF-CGmHsShB.js} +1 -1
- package/dist/assets/{dagre-6A5THRUB-DxEIACOO.js → dagre-6A5THRUB-Dah1h-CH.js} +1 -1
- package/dist/assets/{diagram-22UHCM2B-BANgZEOS.js → diagram-22UHCM2B-BIu71OyV.js} +1 -1
- package/dist/assets/{diagram-3UASUU5V-Cnq7nJir.js → diagram-3UASUU5V-gxhUB_Mu.js} +1 -1
- package/dist/assets/{diagram-ATOU4E4O-4TBDIWnz.js → diagram-ATOU4E4O-CBhusUBn.js} +1 -1
- package/dist/assets/{diagram-CDSNMT55-CD_B39D4.js → diagram-CDSNMT55-BdPrc86x.js} +1 -1
- package/dist/assets/{diagram-MLGK6HIB-CzvnFhEC.js → diagram-MLGK6HIB-Clp110R_.js} +1 -1
- package/dist/assets/{diagram-MPIPVDR6-C4w2SNml.js → diagram-MPIPVDR6-DJKfbugw.js} +1 -1
- package/dist/assets/{ebnfDiagram-ZINNZB2B-gkUEBm2s.js → ebnfDiagram-ZINNZB2B-DnsltoBu.js} +1 -1
- package/dist/assets/{elk-276RUBZZ-B_56X6mw.js → elk-276RUBZZ-jWdq6gqH.js} +1 -1
- package/dist/assets/{erDiagram-OPXOYQCR-C_oByoDS.js → erDiagram-OPXOYQCR-DArPVf8u.js} +1 -1
- package/dist/assets/{flowDiagram-KWPJA3E3-CR9xXJkC.js → flowDiagram-KWPJA3E3-D2HZ-2f7.js} +1 -1
- package/dist/assets/ganttDiagram-FUAMR5RP--o0KIxEo.js +292 -0
- package/dist/assets/{gitGraphDiagram-X574FWY7-VJ6-Tp_p.js → gitGraphDiagram-X574FWY7-CsoXr2D7.js} +1 -1
- package/dist/assets/image-plus-BbVjGHin.js +1 -0
- package/dist/assets/index-BrPW5HDO.css +1 -0
- package/dist/assets/index-C3NqfdVU.js +585 -0
- package/dist/assets/{index-Cp4S4NOL.js → index-CJTp9JEp.js} +1 -1
- package/dist/assets/{infoDiagram-VRGFBTTK-2Lu9te4D.js → infoDiagram-VRGFBTTK-g-6lNp7r.js} +1 -1
- package/dist/assets/{ishikawaDiagram-OU5B5YK6-DR49XY-e.js → ishikawaDiagram-OU5B5YK6-B1d6qVTV.js} +1 -1
- package/dist/assets/{journeyDiagram-ZHPQQLJL-ZoR0M305.js → journeyDiagram-ZHPQQLJL-BTPuiU6T.js} +1 -1
- package/dist/assets/{kanban-definition-PNTS6WVX-DLKHxrO-.js → kanban-definition-PNTS6WVX-bDDuRJvP.js} +1 -1
- package/dist/assets/{linear-BASsfWly.js → linear-BVVKbEMy.js} +1 -1
- package/dist/assets/{mermaid.core-BVidiPkk.js → mermaid.core-JJdoKyKB.js} +5 -5
- package/dist/assets/{mindmap-definition-NLK3R4M7-g_Qx0U5C.js → mindmap-definition-NLK3R4M7-B4aE3T0q.js} +1 -1
- package/dist/assets/module-DCUsy2BQ.js +3 -0
- package/dist/assets/page-visible-BBo_Zm8g.js +1 -0
- package/dist/assets/{pegDiagram-GJSIUBJH-DI9Cjs8w.js → pegDiagram-GJSIUBJH-DFKQWpR7.js} +1 -1
- package/dist/assets/{pieDiagram-5QR66LMP-sRI0Uhr5.js → pieDiagram-5QR66LMP-Iv7OKVJi.js} +1 -1
- package/dist/assets/{quadrantDiagram-O4NWA36T-CamKTby3.js → quadrantDiagram-O4NWA36T-U2YnfwBk.js} +1 -1
- package/dist/assets/{railroadDiagram-XR7U4H2S-DOLlkbf3.js → railroadDiagram-XR7U4H2S-CuPy6RqM.js} +1 -1
- package/dist/assets/remote-desktop-panel-BNEj7tUx.js +1 -0
- package/dist/assets/{requirementDiagram-PLB6GJNP-DrRQ5r97.js → requirementDiagram-PLB6GJNP-bMI5AK7T.js} +1 -1
- package/dist/assets/{sankeyDiagram-IPEJSGJF-DGS_lXcX.js → sankeyDiagram-IPEJSGJF-BKcFE2WQ.js} +1 -1
- package/dist/assets/{sequenceDiagram-PO4LG4MO-CkjWqwtf.js → sequenceDiagram-PO4LG4MO-Bn9q88kl.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-CepRgful.js → sizeCapture-INFHLROL-Cvmdq9HD.js} +1 -1
- package/dist/assets/{stateDiagram-v2-GCMORJYK-hykr-9RS.js → stateDiagram-v2-GCMORJYK-JJQYw3eQ.js} +1 -1
- package/dist/assets/{swimlanes-2SLR337P-CKOHEZbI.js → swimlanes-2SLR337P-BkSCBEwp.js} +1 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-CmTirbVT.js +8 -0
- package/dist/assets/table-file.worker-KkMx-SNx.js +10 -0
- package/dist/assets/{timeline-definition-EJHVYXUP-CB8pJ6AC.js → timeline-definition-EJHVYXUP-DsaM4GHr.js} +1 -1
- package/dist/assets/{usecaseDiagram-POWQR4AR-DrclJHdl.js → usecaseDiagram-POWQR4AR-3FOwLq7B.js} +1 -1
- package/dist/assets/user-DcZBnAuv.js +1 -0
- package/dist/assets/{vennDiagram-UO4OBE2U-zS23qjv8.js → vennDiagram-UO4OBE2U-DyNjJsKt.js} +1 -1
- package/dist/assets/{wardleyDiagram-VNRHLVJA-52jgeKxz.js → wardleyDiagram-VNRHLVJA-Y4ZMcnBC.js} +1 -1
- package/dist/assets/{xychartDiagram-PMCCYNJV-DSdl9rQ8.js → xychartDiagram-PMCCYNJV-BXZpg955.js} +1 -1
- package/dist/index.html +2 -2
- package/dist-server/cloud-home-start.js +19 -8
- package/dist-server/container-mcp.js +8 -0
- package/dist-server/desktop-entry.mjs +54 -0
- package/dist-server/drivers/agents-proxy.js +21 -4
- package/dist-server/index.js +1412 -821
- package/dist-server/openmausbot.js +837 -571
- package/dist-server/pair-cli.js +837 -571
- package/dist-server/permission-proxy.js +2 -0
- package/dist-server/server/atomic.js +70 -2
- package/dist-server/server/cloud-home-start.js +3 -3
- package/dist-server/server/config.js +22 -1
- package/dist-server/server/connector-card-text.js +15 -0
- package/dist-server/server/default-model-selection.js +7 -4
- package/dist-server/server/drivers/acp/core.js +10 -0
- package/dist-server/server/drivers/acp/grok.js +47 -5
- package/dist-server/server/drivers/agents-catalog.js +24 -4
- package/dist-server/server/drivers/agents-proxy.js +1 -0
- package/dist-server/server/drivers/chat-mcp-tools.js +5 -3
- package/dist-server/server/drivers/claude-login-auth.js +1 -1
- package/dist-server/server/drivers/claude.js +35 -11
- package/dist-server/server/drivers/codex-device-auth.js +27 -319
- package/dist-server/server/drivers/device-auth.js +342 -0
- package/dist-server/server/drivers/openai-chat.js +12 -4
- package/dist-server/server/fleet-agent.js +18 -5
- package/dist-server/server/harness/http.js +5 -2
- package/dist-server/server/harness/registry.js +19 -0
- package/dist-server/server/harness-capabilities.js +7 -4
- package/dist-server/server/hosted-models.js +3 -1
- package/dist-server/server/index.js +268 -54
- package/dist-server/server/key-rejections.js +57 -0
- package/dist-server/server/message-file.js +7 -4
- package/dist-server/server/permission-proxy.js +4 -0
- package/dist-server/server/provider-key-check.js +5 -1
- package/dist-server/server/request-auth.js +1 -1
- package/dist-server/server/room-turn-timeout.js +28 -0
- package/dist-server/server/routes/thread-models.js +36 -0
- package/dist-server/server/routines.js +2 -2
- package/dist-server/server/skills.js +3 -2
- package/dist-server/server/store.js +148 -60
- package/dist-server/server/team-backup.js +19 -4
- package/dist-server/server/thread-model.js +25 -0
- package/dist-server/server/turn-continuation.js +8 -2
- package/dist-server/server/webhook-ingress.js +8 -3
- package/dist-server/server/webhooks.js +40 -5
- package/dist-server/shared/device-sign-in.js +57 -0
- package/dist-server/shared/team-backup.js +2 -0
- package/dist-server/shared/thread-model.js +13 -0
- package/dist-server/vps-container-mcp.js +8 -0
- package/dist-server/workspace-backup.worker.js +41 -22
- package/package.json +1 -1
- package/dist/assets/DesktopViewer-CPidMCtt.js +0 -5
- package/dist/assets/architectureDiagram-NJMV4G6O-DiPor3UT.js +0 -36
- package/dist/assets/channel-DR8eD6QW.js +0 -1
- package/dist/assets/cose-bilkent-JH36ORCC-BBmR1bbX.js +0 -1
- package/dist/assets/ganttDiagram-FUAMR5RP-gtYBTu0B.js +0 -292
- package/dist/assets/index-CpaIXRLp.js +0 -614
- package/dist/assets/index-Z81SMsgR.css +0 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-D-8vqZ4-.js +0 -8
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// A saved key counts as working until the provider says otherwise. When a
|
|
2
|
+
// real use of it is refused as a bad key (a chat request, or Test in
|
|
3
|
+
// Settings → API keys), every engine on that key and endpoint reports it as
|
|
4
|
+
// not signed in, until the key is saved or cleared again or a later use of
|
|
5
|
+
// it succeeds. In memory only: after a restart the key shows as working
|
|
6
|
+
// until its next use fails. The key itself is never kept, only a hash of it
|
|
7
|
+
// beside the provider's base URL.
|
|
8
|
+
import { createHash } from "node:crypto";
|
|
9
|
+
export const KEY_REJECTED_REASON = "The provider rejected this key. Change it in Settings → API keys.";
|
|
10
|
+
const rejected = new Set();
|
|
11
|
+
const listeners = new Set();
|
|
12
|
+
const fingerprint = (key) => createHash("sha256").update(key.trim()).digest("hex").slice(0, 32);
|
|
13
|
+
const markOf = (url, key) => `${url.trim().replace(/\/+$/, "")} ${fingerprint(key)}`;
|
|
14
|
+
const changed = () => { for (const listener of listeners)
|
|
15
|
+
listener(); };
|
|
16
|
+
export function keyRejected(url, key) {
|
|
17
|
+
return rejected.size > 0 && rejected.has(markOf(url, key));
|
|
18
|
+
}
|
|
19
|
+
export function noteKeyRejected(url, key) {
|
|
20
|
+
const mark = markOf(url, key);
|
|
21
|
+
if (rejected.has(mark))
|
|
22
|
+
return;
|
|
23
|
+
rejected.add(mark);
|
|
24
|
+
changed();
|
|
25
|
+
}
|
|
26
|
+
export function noteKeyAccepted(url, key) {
|
|
27
|
+
if (rejected.size > 0 && rejected.delete(markOf(url, key)))
|
|
28
|
+
changed();
|
|
29
|
+
}
|
|
30
|
+
/** Drops every mark on this key, whatever endpoint it was used against. */
|
|
31
|
+
export function forgetKey(key) {
|
|
32
|
+
if (rejected.size === 0)
|
|
33
|
+
return;
|
|
34
|
+
const suffix = ` ${fingerprint(key)}`;
|
|
35
|
+
const before = rejected.size;
|
|
36
|
+
for (const mark of rejected)
|
|
37
|
+
if (mark.endsWith(suffix))
|
|
38
|
+
rejected.delete(mark);
|
|
39
|
+
if (rejected.size !== before)
|
|
40
|
+
changed();
|
|
41
|
+
}
|
|
42
|
+
export function onKeyRejectionChange(listener) {
|
|
43
|
+
listeners.add(listener);
|
|
44
|
+
return () => listeners.delete(listener);
|
|
45
|
+
}
|
|
46
|
+
/** The provider refused the key itself: HTTP 401, or a 400/403 whose body
|
|
47
|
+
* says the key is invalid or revoked (xAI answers a wrong key with a 400).
|
|
48
|
+
* Rate limits, quota, billing and model access are not the key's fault. */
|
|
49
|
+
export function rejectsKey(status, body) {
|
|
50
|
+
if (/\b(?:quota|billing|credits?|rate.?limit|insufficient.?(?:balance|funds))\b/i.test(body))
|
|
51
|
+
return false;
|
|
52
|
+
if (status === 401)
|
|
53
|
+
return true;
|
|
54
|
+
if (status !== 400 && status !== 403)
|
|
55
|
+
return false;
|
|
56
|
+
return /\binvalid_api_key\b|\b(?:invalid|incorrect|revoked|expired|disabled|deactivated)[ _-]?(?:api[ _-]?)?key\b|\bapi[ _-]?key (?:is |was |has been )?(?:invalid|incorrect|revoked|expired|disabled|not valid)\b/i.test(body);
|
|
57
|
+
}
|
|
@@ -17,9 +17,12 @@ export function messageFileRoots(options) {
|
|
|
17
17
|
options.attachments,
|
|
18
18
|
])];
|
|
19
19
|
}
|
|
20
|
-
function statusError(status, message) {
|
|
21
|
-
return Object.assign(new Error(message), { status });
|
|
20
|
+
function statusError(status, message, code) {
|
|
21
|
+
return Object.assign(new Error(message), { status, ...(code ? { code } : {}) });
|
|
22
22
|
}
|
|
23
|
+
// The client offers "Show in folder" for exactly this refusal, so it carries a
|
|
24
|
+
// stable code instead of being recognised by its English text.
|
|
25
|
+
const outsideWorkspace = () => statusError(403, "the linked file is outside this conversation's workspace", "outside_workspace");
|
|
23
26
|
function decodePathWithSuffixRemoved(href) {
|
|
24
27
|
// Split before decoding so an encoded `?` or `#` remains part of the
|
|
25
28
|
// filename, while a real Markdown query/fragment never reaches the file
|
|
@@ -396,7 +399,7 @@ export async function openMessageFile(href, roots) {
|
|
|
396
399
|
}
|
|
397
400
|
const canonicalAfter = await realpath(candidate);
|
|
398
401
|
if (!containedBy(root, canonicalAfter))
|
|
399
|
-
throw
|
|
402
|
+
throw outsideWorkspace();
|
|
400
403
|
const after = await stat(canonicalAfter);
|
|
401
404
|
if (opened.dev !== after.dev || opened.ino !== after.ino) {
|
|
402
405
|
throw statusError(409, "the linked file changed while it was being opened");
|
|
@@ -416,7 +419,7 @@ export async function openMessageFile(href, roots) {
|
|
|
416
419
|
}
|
|
417
420
|
}
|
|
418
421
|
if (sawOutsideRoot)
|
|
419
|
-
throw
|
|
422
|
+
throw outsideWorkspace();
|
|
420
423
|
throw statusError(404, "the linked file is unavailable");
|
|
421
424
|
}
|
|
422
425
|
/** A safe attachment header with a readable ASCII fallback and UTF-8 name. */
|
|
@@ -36,6 +36,9 @@ const allowQuestion = allowsTool(selection.scope, { kind: "mcp", server: "ogb",
|
|
|
36
36
|
const socketPath = process.argv[2] ?? "";
|
|
37
37
|
const waiting = new Map();
|
|
38
38
|
const conn = connect(socketPath);
|
|
39
|
+
// Both streams below carry JSON lines that can be larger than one read.
|
|
40
|
+
// Decoding as a stream keeps a character split across two reads whole.
|
|
41
|
+
conn.setEncoding("utf8");
|
|
39
42
|
const dead = () => {
|
|
40
43
|
for (const resolve of waiting.values()) {
|
|
41
44
|
resolve({ behavior: "deny", message: "OpenMausBot: permission broker unavailable — skip this action" });
|
|
@@ -227,6 +230,7 @@ async function handle(msg) {
|
|
|
227
230
|
}
|
|
228
231
|
}
|
|
229
232
|
let inBuf = "";
|
|
233
|
+
process.stdin.setEncoding("utf8");
|
|
230
234
|
process.stdin.on("data", (chunk) => {
|
|
231
235
|
inBuf += chunk;
|
|
232
236
|
let nl;
|
|
@@ -14,9 +14,13 @@ export function isLoopback(hostname) {
|
|
|
14
14
|
const host = hostname.replace(/^\[|\]$/g, "");
|
|
15
15
|
return host === "localhost" || host === "127.0.0.1" || host === "::1" || host.startsWith("127.");
|
|
16
16
|
}
|
|
17
|
+
/** A provider's base URL: the configured one, or its default. */
|
|
18
|
+
export function providerBaseUrl(provider, base) {
|
|
19
|
+
return (base?.trim() || DEFAULT_URLS[provider]).replace(/\/+$/, "");
|
|
20
|
+
}
|
|
17
21
|
/** The models endpoint for a provider, from its base URL or the default. */
|
|
18
22
|
export function providerModelsUrl(provider, base) {
|
|
19
|
-
const root = (
|
|
23
|
+
const root = providerBaseUrl(provider, base);
|
|
20
24
|
if (provider === "anthropic")
|
|
21
25
|
return root.endsWith("/v1") ? `${root}/models` : `${root}/v1/models`;
|
|
22
26
|
return `${root}/models`;
|
|
@@ -340,7 +340,7 @@ export function clientBotPatchViolation(body) {
|
|
|
340
340
|
return null;
|
|
341
341
|
}
|
|
342
342
|
/** Same for a room: name and reading state, never its folder or who answers. */
|
|
343
|
-
const CLIENT_GROUP_PATCH_FIELDS = new Set(["name", "bulletin", "unread", "pinnedMessageId", "section"]);
|
|
343
|
+
const CLIENT_GROUP_PATCH_FIELDS = new Set(["name", "bulletin", "unread", "pinnedMessageId", "section", "turnTimeoutMinutes"]);
|
|
344
344
|
export function clientGroupPatchViolation(body) {
|
|
345
345
|
if (!body || typeof body !== "object" || Array.isArray(body))
|
|
346
346
|
return "body";
|
|
@@ -1,6 +1,34 @@
|
|
|
1
|
+
import { MAX_ROOM_TURN_TIMEOUT_MINUTES, MIN_ROOM_TURN_TIMEOUT_MINUTES, } from "./config.js";
|
|
1
2
|
export function roomTurnTimeoutMs(minutes) {
|
|
2
3
|
return minutes * 60_000;
|
|
3
4
|
}
|
|
5
|
+
/** A conversation may replace the global group ceiling. Null clears that
|
|
6
|
+
* choice. Anything else is refused rather than silently clamped. */
|
|
7
|
+
export function parseConversationTurnTimeout(value) {
|
|
8
|
+
if (value === null)
|
|
9
|
+
return { ok: true, minutes: null };
|
|
10
|
+
if (typeof value !== "number" ||
|
|
11
|
+
!Number.isInteger(value) ||
|
|
12
|
+
value < MIN_ROOM_TURN_TIMEOUT_MINUTES ||
|
|
13
|
+
value > MAX_ROOM_TURN_TIMEOUT_MINUTES) {
|
|
14
|
+
return {
|
|
15
|
+
ok: false,
|
|
16
|
+
error: "Turn limit must be a whole number from 1 to 1,440, or null to use the group default.",
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
return { ok: true, minutes: value };
|
|
20
|
+
}
|
|
21
|
+
/** The ceiling captured for one turn. An override wins; a missing or
|
|
22
|
+
* unusable one keeps the global group limit. */
|
|
23
|
+
export function effectiveRoomTurnTimeoutMinutes(globalMinutes, override) {
|
|
24
|
+
if (typeof override === "number" &&
|
|
25
|
+
Number.isInteger(override) &&
|
|
26
|
+
override >= MIN_ROOM_TURN_TIMEOUT_MINUTES &&
|
|
27
|
+
override <= MAX_ROOM_TURN_TIMEOUT_MINUTES) {
|
|
28
|
+
return override;
|
|
29
|
+
}
|
|
30
|
+
return globalMinutes;
|
|
31
|
+
}
|
|
4
32
|
/**
|
|
5
33
|
* One room turn's slice of the configured room ceiling.
|
|
6
34
|
*
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { sameModelSelection } from "../../shared/thread-model.js";
|
|
2
|
+
import { PASS } from "./table.js";
|
|
3
|
+
const MAX_THREAD_IDS = 10_000;
|
|
4
|
+
export function createThreadModelRoutes(deps) {
|
|
5
|
+
return async ({ req, res, path, method, auth, json, readBody }) => {
|
|
6
|
+
const m = path.match(/^\/api\/bots\/([\w-]+)\/threads\/follow-model$/);
|
|
7
|
+
if (!m || method !== "POST")
|
|
8
|
+
return PASS;
|
|
9
|
+
const bot = deps.bot(m[1]);
|
|
10
|
+
if (!bot)
|
|
11
|
+
return json(res, 404, { error: "no such bot" });
|
|
12
|
+
if (!deps.mayChangeModel(auth))
|
|
13
|
+
return json(res, 403, { error: "On this Cloud only its owner can change a bot's model." });
|
|
14
|
+
const body = await readBody(req);
|
|
15
|
+
if (!body || typeof body !== "object" || Array.isArray(body) || Object.keys(body).some((key) => key !== "threadIds")) {
|
|
16
|
+
return json(res, 400, { error: "send {} or { threadIds }" });
|
|
17
|
+
}
|
|
18
|
+
const raw = body.threadIds;
|
|
19
|
+
if (raw !== undefined && (!Array.isArray(raw) || raw.length > MAX_THREAD_IDS || raw.some((id) => typeof id !== "string"))) {
|
|
20
|
+
return json(res, 400, { error: "threadIds must be a list of this bot's thread ids" });
|
|
21
|
+
}
|
|
22
|
+
const tasks = bot.tasks ?? [];
|
|
23
|
+
const ids = new Set(tasks.map((task) => task.threadId));
|
|
24
|
+
const foreign = raw?.find((id) => !ids.has(id));
|
|
25
|
+
if (foreign !== undefined)
|
|
26
|
+
return json(res, 400, { error: `"${foreign}" is not one of this bot's threads` });
|
|
27
|
+
if (bot.approvalGrant)
|
|
28
|
+
return json(res, 409, { error: "wait for the approval-level change to finish before changing models" });
|
|
29
|
+
const listed = raw === undefined ? null : new Set(raw);
|
|
30
|
+
const own = tasks.filter((task) => (!listed || listed.has(task.threadId)) &&
|
|
31
|
+
task.modelSelection !== undefined && !sameModelSelection(task.modelSelection, bot.modelSelection));
|
|
32
|
+
const busy = own.filter((task) => deps.busy(bot.id, task.threadId));
|
|
33
|
+
const switched = deps.follow(bot.id, own.filter((task) => !busy.includes(task)).map((task) => task.threadId));
|
|
34
|
+
return json(res, 200, { switched: switched.length, busy: busy.length, bot: deps.reply(bot.id) });
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -943,7 +943,7 @@ export class RoutineManager {
|
|
|
943
943
|
this.commitMutation(() => {
|
|
944
944
|
run = this.newRun(routine, this.now(), true, allocations, request?.threadId ?? routine.sourceThreadId);
|
|
945
945
|
// Preserve the invoking chat as provenance/fallback for this run.
|
|
946
|
-
//
|
|
946
|
+
// The routine's results destination continues to win.
|
|
947
947
|
if (request)
|
|
948
948
|
run.sourceThreadId = request.threadId;
|
|
949
949
|
if (request)
|
|
@@ -1546,7 +1546,7 @@ export class RoutineManager {
|
|
|
1546
1546
|
if (value === null) {
|
|
1547
1547
|
const destination = this.options.resolveResultsThread?.(routine, true);
|
|
1548
1548
|
if (!destination)
|
|
1549
|
-
throw new Error("Could not
|
|
1549
|
+
throw new Error("Could not find a results thread for this routine");
|
|
1550
1550
|
routine.resultsThreadId = destination;
|
|
1551
1551
|
return () => this.options.discardResultsThread?.(routine.botId, destination);
|
|
1552
1552
|
}
|
|
@@ -29,7 +29,7 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
29
29
|
import { closeSync, constants, existsSync, fstatSync, lstatSync, mkdirSync, openSync, readdirSync, readFileSync, readlinkSync, realpathSync, renameSync, rmSync, symlinkSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
30
30
|
import { dirname, join, relative, resolve } from "node:path";
|
|
31
31
|
import { z } from "zod";
|
|
32
|
-
import { writeFileAtomic } from "./atomic.js";
|
|
32
|
+
import { writeFileAtomic, writeFileAtomicIfChanged } from "./atomic.js";
|
|
33
33
|
import { DATA_DIR } from "./config.js";
|
|
34
34
|
import { redactSecretsInText } from "./redact.js";
|
|
35
35
|
import { LEARN_SOURCE_PREFIX } from "./skill-learn.js";
|
|
@@ -191,9 +191,10 @@ function readManagedLinks(botId) {
|
|
|
191
191
|
return [];
|
|
192
192
|
}
|
|
193
193
|
}
|
|
194
|
+
// Rebuilt on every turn and almost always the same list.
|
|
194
195
|
function writeManagedLinks(botId, names) {
|
|
195
196
|
mkdirSync(skillStateDir(botId), { recursive: true, mode: 0o700 });
|
|
196
|
-
|
|
197
|
+
writeFileAtomicIfChanged(managedLinksPath(botId), `${JSON.stringify([...new Set(names)].sort(), null, 2)}\n`, { mode: 0o600 });
|
|
197
198
|
}
|
|
198
199
|
function manifestFromFile(path) {
|
|
199
200
|
try {
|