openmausbot 0.1.94 → 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-bxM7NV3N.js → DesktopViewer-DsnA23KM.js} +1 -1
- package/dist/assets/{abnfDiagram-O67JEVCF-BhMdJhRJ.js → abnfDiagram-O67JEVCF-Dkbd2hcj.js} +1 -1
- package/dist/assets/{arc-B5itKDcx.js → arc-Duj5Coy2.js} +1 -1
- package/dist/assets/{architectureDiagram-NJMV4G6O-C5d7UY6U.js → architectureDiagram-NJMV4G6O-uYUsDawP.js} +1 -1
- package/dist/assets/{blockDiagram-BEXU5L5S-BrZIhMmu.js → blockDiagram-BEXU5L5S-CWDlTpXe.js} +1 -1
- package/dist/assets/{c4Diagram-YGBWAQC7-7I0ArfbD.js → c4Diagram-YGBWAQC7-B0t15PDc.js} +1 -1
- package/dist/assets/channel-BZxwC-Gw.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-BnEsyN36.js → chunk-2Q5K7J3B-BJSCL-HV.js} +1 -1
- package/dist/assets/{chunk-3FUC2YCW-BXATQ52s.js → chunk-3FUC2YCW-D3_ILccd.js} +2 -2
- package/dist/assets/{chunk-5VM5RSS4-B0BpTkEx.js → chunk-5VM5RSS4-D3dmwT_S.js} +1 -1
- package/dist/assets/{chunk-GTNCS2PH-BtLTz6FR.js → chunk-GTNCS2PH-Br73HFPD.js} +1 -1
- package/dist/assets/{chunk-GWA4HPMP-D6gcZOYt.js → chunk-GWA4HPMP-uSsjxubC.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-DCLrosvf.js → chunk-JWPE2WC7-C40VWFyr.js} +1 -1
- package/dist/assets/{chunk-NETBCI7D-B9JsknV9.js → chunk-NETBCI7D-D6WNNYwU.js} +1 -1
- package/dist/assets/{chunk-WEXAMYUT-CsoTxw7z.js → chunk-WEXAMYUT-DOEhq64P.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-0F_frB0c.js → chunk-XXDRQBXY-BqJEi6Pi.js} +1 -1
- package/dist/assets/{classDiagram-v2-NBCMYWYE-DkrpnflX.js → classDiagram-v2-NBCMYWYE-ByU_GnxT.js} +1 -1
- package/dist/assets/{cose-bilkent-JH36ORCC-BNDwYBt9.js → cose-bilkent-JH36ORCC-D-Uglxqq.js} +1 -1
- package/dist/assets/{cynefin-OW5HDTMX-JUkNr1Mk.js → cynefin-OW5HDTMX-CZWO7aYj.js} +1 -1
- package/dist/assets/{cynefinDiagram-VND7K2PF-DKeh9N4M.js → cynefinDiagram-VND7K2PF-C70-w0la.js} +1 -1
- package/dist/assets/{dagre-6A5THRUB-ve-6T_wX.js → dagre-6A5THRUB-CnhjhExB.js} +1 -1
- package/dist/assets/{diagram-22UHCM2B-BuJ2d8rT.js → diagram-22UHCM2B-bnjWCNb4.js} +1 -1
- package/dist/assets/{diagram-3UASUU5V-BgOs_Ni-.js → diagram-3UASUU5V-A4uTWoFV.js} +1 -1
- package/dist/assets/{diagram-ATOU4E4O-DMK6VmcP.js → diagram-ATOU4E4O-Zi6m3NUl.js} +1 -1
- package/dist/assets/{diagram-CDSNMT55-DksASlcY.js → diagram-CDSNMT55-DtCcmd5k.js} +1 -1
- package/dist/assets/{diagram-MLGK6HIB-Bgn8I5k6.js → diagram-MLGK6HIB-DofuETS3.js} +1 -1
- package/dist/assets/{diagram-MPIPVDR6-zK0jqO8N.js → diagram-MPIPVDR6-Dw2T9PuU.js} +1 -1
- package/dist/assets/{ebnfDiagram-ZINNZB2B-vKrbzTwr.js → ebnfDiagram-ZINNZB2B-DbrwPiem.js} +1 -1
- package/dist/assets/{elk-276RUBZZ-Bzv2mPNf.js → elk-276RUBZZ-BvCt1dCb.js} +1 -1
- package/dist/assets/{erDiagram-OPXOYQCR-CvOyYuxu.js → erDiagram-OPXOYQCR-Y-QKqPnU.js} +1 -1
- package/dist/assets/{flowDiagram-KWPJA3E3-CtbnDbc0.js → flowDiagram-KWPJA3E3-CDkL1B5w.js} +1 -1
- package/dist/assets/{ganttDiagram-FUAMR5RP-c4dMRYyG.js → ganttDiagram-FUAMR5RP-CKV5D2F6.js} +1 -1
- package/dist/assets/{gitGraphDiagram-X574FWY7-B34uaQho.js → gitGraphDiagram-X574FWY7-nL9iFTCv.js} +1 -1
- package/dist/assets/{index-CPiqc_DN.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-o64ma4CI.js → infoDiagram-VRGFBTTK-C7aHjzsf.js} +1 -1
- package/dist/assets/{ishikawaDiagram-OU5B5YK6-DV4C9BAm.js → ishikawaDiagram-OU5B5YK6-KUZsNBTh.js} +1 -1
- package/dist/assets/{journeyDiagram-ZHPQQLJL-Bf0AHk1H.js → journeyDiagram-ZHPQQLJL-CGUuodFl.js} +1 -1
- package/dist/assets/{kanban-definition-PNTS6WVX-B1UMSKmE.js → kanban-definition-PNTS6WVX-Cy6r868Z.js} +1 -1
- package/dist/assets/{linear-BpKhuyLm.js → linear-Bl669148.js} +1 -1
- package/dist/assets/{mermaid.core-B_BKNCw5.js → mermaid.core-86Xqz5Mr.js} +5 -5
- package/dist/assets/{mindmap-definition-NLK3R4M7-DeARruT1.js → mindmap-definition-NLK3R4M7-G4DzHLrd.js} +1 -1
- package/dist/assets/{pegDiagram-GJSIUBJH-BOQEk_1D.js → pegDiagram-GJSIUBJH-K4eruzY0.js} +1 -1
- package/dist/assets/{pieDiagram-5QR66LMP-EIfoiTZo.js → pieDiagram-5QR66LMP-DYdR9PPq.js} +1 -1
- package/dist/assets/{quadrantDiagram-O4NWA36T-GgZwT-fi.js → quadrantDiagram-O4NWA36T-BCAh0nEy.js} +1 -1
- package/dist/assets/{railroadDiagram-XR7U4H2S-D9rDQomj.js → railroadDiagram-XR7U4H2S-p0dGRc4g.js} +1 -1
- package/dist/assets/{requirementDiagram-PLB6GJNP-DAh1MIeo.js → requirementDiagram-PLB6GJNP-tLeBG0aL.js} +1 -1
- package/dist/assets/{sankeyDiagram-IPEJSGJF-CPr6Uz64.js → sankeyDiagram-IPEJSGJF-QRuN16rV.js} +1 -1
- package/dist/assets/{sequenceDiagram-PO4LG4MO-BU6VIdcm.js → sequenceDiagram-PO4LG4MO-ueT1nVoa.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-qF2HO41r.js → sizeCapture-INFHLROL-wRS2zWg2.js} +1 -1
- package/dist/assets/{stateDiagram-v2-GCMORJYK-DlJ8G4MD.js → stateDiagram-v2-GCMORJYK-CYzA_hE1.js} +1 -1
- package/dist/assets/{swimlanes-2SLR337P-DzYE8I54.js → swimlanes-2SLR337P-kDnv6LMc.js} +1 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-C7voMDle.js +8 -0
- package/dist/assets/{timeline-definition-EJHVYXUP-B7wzEQ0A.js → timeline-definition-EJHVYXUP-D1y2UbbH.js} +1 -1
- package/dist/assets/{usecaseDiagram-POWQR4AR-BvGgrxIP.js → usecaseDiagram-POWQR4AR-IqB4v9HX.js} +1 -1
- package/dist/assets/{vennDiagram-UO4OBE2U-CEECdgQg.js → vennDiagram-UO4OBE2U-BEi7KYVF.js} +1 -1
- package/dist/assets/{wardleyDiagram-VNRHLVJA-XQHA0P_P.js → wardleyDiagram-VNRHLVJA-Dkybo-Yf.js} +1 -1
- package/dist/assets/{xychartDiagram-PMCCYNJV-B5mLcZI2.js → xychartDiagram-PMCCYNJV-BAPFJLpg.js} +1 -1
- package/dist/index.html +2 -2
- package/dist-server/cloud-home-start.js +27 -13
- package/dist-server/companion/src/listener.js +39 -18
- package/dist-server/companion/src/routes.js +39 -0
- package/dist-server/container-mcp.js +16 -29
- package/dist-server/drivers/agents-proxy.js +161 -82
- package/dist-server/enterprise/server/index.js +2 -1
- package/dist-server/{browser-proxy.js → harness-mcp-proxy.js} +34 -19
- package/dist-server/index.js +4991 -4908
- package/dist-server/local-computer.js +2 -1
- package/dist-server/openmausbot.js +906 -1785
- package/dist-server/pair-cli.js +906 -1785
- package/dist-server/proxy-paths.js +2 -1
- package/dist-server/server/account-signin.js +0 -2
- package/dist-server/server/activity.js +201 -0
- package/dist-server/server/admin-activity.js +1 -4
- package/dist-server/server/auto-approve.js +25 -4
- package/dist-server/server/automatic-recovery.js +1 -1
- package/dist-server/server/boat-create-idempotency.js +0 -7
- package/dist-server/server/boat-delete-journal.js +0 -4
- package/dist-server/server/boat.js +4 -4
- package/dist-server/server/bot-package.js +1 -19
- package/dist-server/server/browser-runtime.js +1 -2
- package/dist-server/server/checkpoints.js +90 -148
- package/dist-server/server/chief-of-staff.js +9 -9
- package/dist-server/server/cli.js +33 -26
- package/dist-server/server/cloud-backend.js +0 -4
- package/dist-server/server/cloud-computer-tools.js +110 -0
- package/dist-server/server/cloud-home-start.js +12 -15
- package/dist-server/server/cloud-home.js +0 -5
- package/dist-server/server/cloud-lending.js +0 -3
- 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 +41 -5
- package/dist-server/server/computer-control.js +0 -15
- package/dist-server/server/computer-observation.js +0 -74
- package/dist-server/server/config.js +121 -65
- package/dist-server/server/container-computer.js +23 -24
- package/dist-server/server/control-client.js +4 -10
- 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 +11 -8
- package/dist-server/server/drivers/acp/opencode-go.js +17 -16
- package/dist-server/server/drivers/agents-call.js +88 -92
- package/dist-server/server/drivers/agents-catalog.js +90 -11
- package/dist-server/server/drivers/agents-proxy.js +10 -31
- package/dist-server/server/drivers/antigravity.js +1 -4
- package/dist-server/server/drivers/boatagent.js +23 -4
- package/dist-server/server/drivers/cerebras.js +3 -3
- package/dist-server/server/drivers/chat-mcp-tools.js +4 -7
- package/dist-server/server/drivers/claude-login-auth.js +2 -11
- package/dist-server/server/drivers/claude.js +16 -14
- 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 +18 -11
- package/dist-server/server/drivers/prompt-split.js +4 -4
- package/dist-server/server/drivers/retry.js +8 -4
- package/dist-server/server/engine-install.js +1 -5
- package/dist-server/server/harness/bus.js +49 -0
- package/dist-server/server/harness/registry.js +0 -1
- package/dist-server/server/{browser-proxy.js → harness-mcp-proxy.js} +40 -20
- package/dist-server/server/index.js +1190 -971
- package/dist-server/server/lending-memory.js +12 -8
- package/dist-server/server/mcp-registry.js +35 -0
- package/dist-server/server/memory-entries.js +38 -8
- package/dist-server/server/memory-organize.js +1 -8
- package/dist-server/server/memory-topics.js +4 -4
- package/dist-server/server/memory-upkeep.js +13 -15
- package/dist-server/server/message-file.js +9 -13
- package/dist-server/server/outbound-counts.js +79 -0
- package/dist-server/server/outbound-requests.js +60 -0
- package/dist-server/server/package-import.js +1 -1
- package/dist-server/server/peer-delivery.js +5 -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/proxy-paths.js +2 -1
- package/dist-server/server/restart.js +40 -0
- package/dist-server/server/room-handoffs.js +17 -13
- package/dist-server/server/routes/bot-memory.js +0 -24
- package/dist-server/server/routine-requests.js +2 -5
- package/dist-server/server/routines.js +10 -5
- package/dist-server/server/server-launcher.js +53 -0
- package/dist-server/server/setup-mode.js +0 -2
- package/dist-server/server/skill-learn.js +0 -1
- package/dist-server/server/skill-library.js +0 -15
- package/dist-server/server/store.js +10 -2
- package/dist-server/server/surface.js +69 -28
- package/dist-server/server/system-prompt.js +17 -7
- package/dist-server/server/team-manifest.js +1 -6
- package/dist-server/server/team-memory.js +209 -0
- package/dist-server/server/thread-events.js +2 -2
- 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/vps-computer.js +4 -14
- package/dist-server/server/workspace.js +87 -50
- package/dist-server/server-launcher.js +72 -0
- package/dist-server/shared/activity.js +3 -0
- package/dist-server/shared/ask-question.js +1 -1
- package/dist-server/shared/bot-avatar.js +8 -0
- package/dist-server/shared/cloud-computer.js +12 -0
- package/dist-server/shared/connector-scopes.js +78 -0
- package/dist-server/shared/failed-turn.js +25 -0
- package/dist-server/shared/live-approval.js +2 -2
- package/dist-server/shared/outbound.js +128 -0
- package/dist-server/shared/package-format.js +3 -3
- package/dist-server/shared/pairing-link.js +144 -0
- package/dist-server/shared/tool-surface.js +3 -2
- package/dist-server/vps-container-mcp.js +16 -29
- package/dist-server/workspace-backup.worker.js +14 -27
- package/enterprise/server/index.js +2 -1
- package/package.json +1 -1
- package/dist/assets/channel-CdJX6i8k.js +0 -1
- package/dist/assets/index-CPYrGsv1.js +0 -610
- package/dist/assets/index-CzT42GR8.css +0 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-D-U4GtLQ.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/drivers/chat-boat-tools.js +0 -90
- package/dist-server/server/drivers/grokagent.js +0 -4
- package/dist-server/server/openmaus-status-capsule.js +0 -329
- package/dist-server/server/tightening-requests.js +0 -435
- package/dist-server/shared/tightening-request.js +0 -87
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
/** Consent, cost, and idle-stop policy for overflowing a local computer
|
|
2
|
-
* wait onto a per-second-billed cloud seat (#1655).
|
|
3
|
-
*
|
|
4
|
-
* Like server/claim-idle.ts this module is deliberately synchronous and
|
|
5
|
-
* clock-injected: the server decides every transition on its single
|
|
6
|
-
* thread and tests pin time exactly. Nothing here starts or stops a
|
|
7
|
-
* machine — the wiring does, and only when the decision says to. Keep
|
|
8
|
-
* this module free of server imports. */
|
|
9
|
-
import { computerWaitDuration } from "./computer-wait.js";
|
|
10
|
-
/** Default idle stop: a cloud seat no real screen work has touched for
|
|
11
|
-
* this long is stopped (archived), because billing runs per second while
|
|
12
|
-
* it runs. "A few idle minutes" per the issue. */
|
|
13
|
-
export const CLOUD_SEAT_IDLE_STOP_MS = 5 * 60_000;
|
|
14
|
-
/** Retry cadence for an idle stop that failed (#1655): the first retry
|
|
15
|
-
* after one minute, doubling per failure up to ten. A failed stop keeps
|
|
16
|
-
* its lease — the seat still bills — so the sweep always retries instead
|
|
17
|
-
* of abandoning a running machine. */
|
|
18
|
-
export const CLOUD_SEAT_SLEEP_RETRY_MS = 60_000;
|
|
19
|
-
export const CLOUD_SEAT_SLEEP_RETRY_MAX_MS = 10 * 60_000;
|
|
20
|
-
export function cloudOverflowAction(situation) {
|
|
21
|
-
if (!situation.featureEnabled || !situation.cloudConfigured || situation.perSecondCostUsd === null)
|
|
22
|
-
return { kind: "off" };
|
|
23
|
-
if (situation.started)
|
|
24
|
-
return { kind: "none" };
|
|
25
|
-
if (situation.consented)
|
|
26
|
-
return { kind: "start" };
|
|
27
|
-
if (situation.offered)
|
|
28
|
-
return { kind: "none" };
|
|
29
|
-
return { kind: "offer" };
|
|
30
|
-
}
|
|
31
|
-
/** Per-conversation consent plus the offer bookkeeping that keeps the
|
|
32
|
-
* card to one per conversation until it is revoked. A thread the operator
|
|
33
|
-
* allowlisted in config carries standing consent and never needs the
|
|
34
|
-
* card. */
|
|
35
|
-
export class CloudOverflowConsent {
|
|
36
|
-
grants = new Map();
|
|
37
|
-
offers = new Map();
|
|
38
|
-
revocations = new Set();
|
|
39
|
-
/** Consent for this conversation. With `perSecondCostUsd`, a card
|
|
40
|
-
* consent counts only at the rate its card showed — an allowlisted
|
|
41
|
-
* thread carries the operator's standing consent, and the operator is
|
|
42
|
-
* also the one who sets the rate. */
|
|
43
|
-
consented(threadId, allowlistedThreads = new Set(), perSecondCostUsd) {
|
|
44
|
-
if (this.revocations.has(threadId))
|
|
45
|
-
return false;
|
|
46
|
-
if (allowlistedThreads.has(threadId))
|
|
47
|
-
return true;
|
|
48
|
-
const grant = this.grants.get(threadId);
|
|
49
|
-
if (!grant)
|
|
50
|
-
return false;
|
|
51
|
-
return perSecondCostUsd === undefined || grant.perSecondCostUsd === perSecondCostUsd;
|
|
52
|
-
}
|
|
53
|
-
/** `perSecondCostUsd` is the rate the answered card showed. */
|
|
54
|
-
grant(threadId, perSecondCostUsd, now = Date.now()) {
|
|
55
|
-
this.revocations.delete(threadId);
|
|
56
|
-
this.grants.set(threadId, { at: now, perSecondCostUsd });
|
|
57
|
-
}
|
|
58
|
-
/** Revoking also clears the offered mark, so a later wait in the same
|
|
59
|
-
* conversation may ask again instead of meeting silence. */
|
|
60
|
-
revoke(threadId) {
|
|
61
|
-
const had = this.grants.delete(threadId);
|
|
62
|
-
this.revocations.add(threadId);
|
|
63
|
-
this.offers.delete(threadId);
|
|
64
|
-
return had;
|
|
65
|
-
}
|
|
66
|
-
/** A card is outstanding for this conversation — at the given rate
|
|
67
|
-
* when one is supplied, so a config change re-offers with the new
|
|
68
|
-
* price instead of meeting silence at the old one. */
|
|
69
|
-
offered(threadId, perSecondCostUsd) {
|
|
70
|
-
const offer = this.offers.get(threadId);
|
|
71
|
-
if (!offer)
|
|
72
|
-
return false;
|
|
73
|
-
return perSecondCostUsd === undefined || offer.perSecondCostUsd === perSecondCostUsd;
|
|
74
|
-
}
|
|
75
|
-
/** The rate the outstanding card showed, or null when no card is out. */
|
|
76
|
-
offeredRate(threadId) {
|
|
77
|
-
return this.offers.get(threadId)?.perSecondCostUsd ?? null;
|
|
78
|
-
}
|
|
79
|
-
markOffered(threadId, perSecondCostUsd, now = Date.now()) {
|
|
80
|
-
this.offers.set(threadId, { at: now, perSecondCostUsd });
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
/** One running cloud seat started by overflow. `touch` is called only
|
|
84
|
-
* for real computer tool completions on that seat — screen-poller frames
|
|
85
|
-
* never reach it, exactly like #1653's activity clock, so preview
|
|
86
|
-
* traffic cannot keep a paid machine awake. */
|
|
87
|
-
export class CloudSeatLease {
|
|
88
|
-
botId;
|
|
89
|
-
threadId;
|
|
90
|
-
/** The claim generation that started the seat: a stop releases the Box
|
|
91
|
-
* claim for exactly this owner, never a newer turn's. */
|
|
92
|
-
generation;
|
|
93
|
-
startedAt;
|
|
94
|
-
idleStopMs;
|
|
95
|
-
lastActivityAt;
|
|
96
|
-
sleepRetryNotBefore = 0;
|
|
97
|
-
sleepRetryMs = CLOUD_SEAT_SLEEP_RETRY_MS;
|
|
98
|
-
constructor(init) {
|
|
99
|
-
const idleStopMs = init.idleStopMs ?? CLOUD_SEAT_IDLE_STOP_MS;
|
|
100
|
-
if (!Number.isFinite(idleStopMs) || idleStopMs <= 0)
|
|
101
|
-
throw new Error("Cloud seat idle stop window must be positive");
|
|
102
|
-
this.botId = init.botId;
|
|
103
|
-
this.threadId = init.threadId;
|
|
104
|
-
this.generation = init.generation;
|
|
105
|
-
this.startedAt = init.now ?? Date.now();
|
|
106
|
-
this.lastActivityAt = this.startedAt;
|
|
107
|
-
this.idleStopMs = idleStopMs;
|
|
108
|
-
}
|
|
109
|
-
touch(now = Date.now()) {
|
|
110
|
-
this.lastActivityAt = now;
|
|
111
|
-
// Real work earns a fresh stop attempt: the backoff only paces
|
|
112
|
-
// retries against an idle seat that keeps failing to sleep.
|
|
113
|
-
this.sleepRetryNotBefore = 0;
|
|
114
|
-
this.sleepRetryMs = CLOUD_SEAT_SLEEP_RETRY_MS;
|
|
115
|
-
}
|
|
116
|
-
/** An idle stop may run now — true until a failure defers it. */
|
|
117
|
-
sleepDue(now = Date.now()) {
|
|
118
|
-
return now >= this.sleepRetryNotBefore;
|
|
119
|
-
}
|
|
120
|
-
/** The idle stop failed: keep the lease and back off, bounded, so the
|
|
121
|
-
* sweep retries gently instead of hammering a sick Box — and never
|
|
122
|
-
* abandons a seat that still bills. */
|
|
123
|
-
deferSleep(now = Date.now()) {
|
|
124
|
-
this.sleepRetryNotBefore = now + this.sleepRetryMs;
|
|
125
|
-
this.sleepRetryMs = Math.min(this.sleepRetryMs * 2, CLOUD_SEAT_SLEEP_RETRY_MAX_MS);
|
|
126
|
-
}
|
|
127
|
-
idleFor(now = Date.now()) {
|
|
128
|
-
return Math.max(0, now - this.lastActivityAt);
|
|
129
|
-
}
|
|
130
|
-
idleElapsed(now = Date.now()) {
|
|
131
|
-
return now - this.lastActivityAt >= this.idleStopMs;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
/** Dollars per second at rate-card precision: enough places for small
|
|
135
|
-
* per-second rates to stay visible, trailing zeros trimmed but never
|
|
136
|
-
* past two decimals. */
|
|
137
|
-
export function formatPerSecondUsd(perSecondCostUsd) {
|
|
138
|
-
let text = perSecondCostUsd.toFixed(6).replace(/0+$/, "");
|
|
139
|
-
if (text.endsWith("."))
|
|
140
|
-
text += "00";
|
|
141
|
-
else {
|
|
142
|
-
const decimals = text.length - text.indexOf(".") - 1;
|
|
143
|
-
if (decimals < 2)
|
|
144
|
-
text += "0".repeat(2 - decimals);
|
|
145
|
-
}
|
|
146
|
-
return "$" + text;
|
|
147
|
-
}
|
|
148
|
-
/** The consent card (#1655): the per-second cost sits beside the local
|
|
149
|
-
* wait picture before any choice is made, and nothing starts without an
|
|
150
|
-
* explicit consent the person still has to give. */
|
|
151
|
-
export function cloudOverflowOfferText(opts) {
|
|
152
|
-
const idleStopMs = opts.idleStopMs ?? CLOUD_SEAT_IDLE_STOP_MS;
|
|
153
|
-
const estimate = opts.waitEstimateMs === undefined ? "" : ` Recent local waits here have taken about ${computerWaitDuration(opts.waitEstimateMs)}.`;
|
|
154
|
-
return `This computer is busy. Its work can overflow to a cloud computer for ${formatPerSecondUsd(opts.perSecondCostUsd)} per second.${estimate} Nothing starts without your consent — allow cloud overflow for this conversation to start it; the cloud computer stops after ${computerWaitDuration(idleStopMs)} idle.`;
|
|
155
|
-
}
|
|
156
|
-
export function cloudSeatStartedText(opts) {
|
|
157
|
-
const idleStopMs = opts.idleStopMs ?? CLOUD_SEAT_IDLE_STOP_MS;
|
|
158
|
-
return `Cloud computer started at ${formatPerSecondUsd(opts.perSecondCostUsd)} per second. It stops automatically after ${computerWaitDuration(idleStopMs)} idle, and the local wait continues until this computer is free.`;
|
|
159
|
-
}
|
|
160
|
-
export function cloudSeatStoppedText(idleMs) {
|
|
161
|
-
return `Cloud computer stopped after ${computerWaitDuration(idleMs)} idle — billing pauses while it sleeps.`;
|
|
162
|
-
}
|
|
163
|
-
export function cloudOverflowConsentText(allowed, idleStopMs = CLOUD_SEAT_IDLE_STOP_MS) {
|
|
164
|
-
return allowed
|
|
165
|
-
? `Cloud overflow allowed for this conversation. A waiting turn may start the cloud computer; it stops after ${computerWaitDuration(idleStopMs)} idle.`
|
|
166
|
-
: "Cloud overflow consent revoked. Waits stay local, and no cloud computer starts without a new consent.";
|
|
167
|
-
}
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import { isolatedRemoteCommand, runCommand, screenshotBoat } from "../boat.js";
|
|
2
|
-
import { createControlClient, CONTROL_REFUSAL_PLAIN } from "../control-client.js";
|
|
3
|
-
const coordinate = { type: "integer", minimum: 0, maximum: 32767 };
|
|
4
|
-
const tool = (name, description, properties = {}, required = []) => ({ name, description, inputSchema: { type: "object", properties, required, additionalProperties: false } });
|
|
5
|
-
const TOOLS = [
|
|
6
|
-
tool("screenshot", "See the assigned cloud desktop at native pixel resolution."),
|
|
7
|
-
tool("get_screen_size", "Get the cloud desktop width and height."),
|
|
8
|
-
tool("click", "Click a point on the cloud desktop.", { x: coordinate, y: coordinate, button: { type: "string", enum: ["left", "middle", "right"] }, count: { type: "integer", minimum: 1, maximum: 3 } }, ["x", "y"]),
|
|
9
|
-
tool("move", "Move the cloud mouse pointer.", { x: coordinate, y: coordinate }, ["x", "y"]),
|
|
10
|
-
tool("drag", "Drag on the cloud desktop.", { x: coordinate, y: coordinate, to_x: coordinate, to_y: coordinate }, ["x", "y", "to_x", "to_y"]),
|
|
11
|
-
tool("type_text", "Type Unicode text into the focused cloud application.", { text: { type: "string", maxLength: 4000 } }, ["text"]),
|
|
12
|
-
tool("key_press", "Press an X11 key or shortcut.", { key: { type: "string", pattern: "^[A-Za-z0-9_+]+$", minLength: 1, maxLength: 100 } }, ["key"]),
|
|
13
|
-
tool("scroll", "Scroll at a point on the cloud desktop.", { x: coordinate, y: coordinate, direction: { type: "string", enum: ["up", "down", "left", "right"] }, amount: { type: "integer", minimum: 1, maximum: 30 } }, ["x", "y", "direction"]),
|
|
14
|
-
tool("open_url", "Request opening an HTTP or HTTPS URL in the cloud desktop browser; inspect the screen to confirm it loaded.", { url: { type: "string", pattern: "^https?://", maxLength: 2000 } }, ["url"]),
|
|
15
|
-
tool("exec", "Run a shell command on the assigned cloud computer.", { command: { type: "string", minLength: 1, maxLength: 4000 } }, ["command"]),
|
|
16
|
-
];
|
|
17
|
-
const quote = (value) => "'" + value.replace(/'/g, "'\\''") + "'";
|
|
18
|
-
const buttons = { left: 1, middle: 2, right: 3 };
|
|
19
|
-
const scrollButtons = { up: 4, down: 5, left: 6, right: 7 };
|
|
20
|
-
/** Uses only the leased descriptor supplied by the harness. Schema validation
|
|
21
|
-
* and user approval happen in the shared chat tool executor before this call. */
|
|
22
|
-
export class ChatBoatClient {
|
|
23
|
-
closed = false;
|
|
24
|
-
control;
|
|
25
|
-
descriptor;
|
|
26
|
-
constructor(descriptor) {
|
|
27
|
-
this.descriptor = descriptor;
|
|
28
|
-
if (!descriptor.control?.url || !descriptor.control.token)
|
|
29
|
-
throw new Error("Cloud computer control gate missing");
|
|
30
|
-
this.control = createControlClient(descriptor.control);
|
|
31
|
-
}
|
|
32
|
-
async tools() { return TOOLS; }
|
|
33
|
-
async close() { this.closed = true; }
|
|
34
|
-
async call(_method, params, signal) {
|
|
35
|
-
signal.throwIfAborted();
|
|
36
|
-
if (this.closed)
|
|
37
|
-
throw new Error("Cloud computer session closed");
|
|
38
|
-
const state = await this.control.state(true);
|
|
39
|
-
signal.throwIfAborted();
|
|
40
|
-
if (this.closed)
|
|
41
|
-
throw new Error("Cloud computer session closed");
|
|
42
|
-
if (state.held || state.blockedReason)
|
|
43
|
-
return { isError: true, content: [{ type: "text", text: state.blockedReason || CONTROL_REFUSAL_PLAIN }] };
|
|
44
|
-
const { name, arguments: a } = params;
|
|
45
|
-
const config = { box: { token: this.descriptor.token } };
|
|
46
|
-
const id = this.descriptor.boxId;
|
|
47
|
-
if (name === "screenshot") {
|
|
48
|
-
const shot = await screenshotBoat(config, "", id, { signal, nativeSize: true });
|
|
49
|
-
signal.throwIfAborted();
|
|
50
|
-
return { content: [{ type: "image", mimeType: "image/jpeg", data: shot.png }] };
|
|
51
|
-
}
|
|
52
|
-
const mouse = `xdotool mousemove --sync ${a.x} ${a.y}`;
|
|
53
|
-
let command;
|
|
54
|
-
switch (name) {
|
|
55
|
-
case "get_screen_size":
|
|
56
|
-
command = "xdotool getdisplaygeometry";
|
|
57
|
-
break;
|
|
58
|
-
case "click":
|
|
59
|
-
command = `${mouse} click --repeat ${a.count ?? 1} --delay 100 ${buttons[String(a.button ?? "left")]}`;
|
|
60
|
-
break;
|
|
61
|
-
case "move":
|
|
62
|
-
command = mouse;
|
|
63
|
-
break;
|
|
64
|
-
case "drag":
|
|
65
|
-
command = `${mouse} mousedown 1 mousemove --sync ${a.to_x} ${a.to_y} mouseup 1`;
|
|
66
|
-
break;
|
|
67
|
-
case "type_text":
|
|
68
|
-
command = `printf %s ${quote(Buffer.from(String(a.text)).toString("base64"))} | base64 -d | xclip -selection clipboard && xdotool key --clearmodifiers ctrl+v`;
|
|
69
|
-
break;
|
|
70
|
-
case "key_press":
|
|
71
|
-
command = `xdotool key --clearmodifiers ${quote(String(a.key))}`;
|
|
72
|
-
break;
|
|
73
|
-
case "scroll":
|
|
74
|
-
command = `${mouse} click --repeat ${a.amount ?? 3} --delay 80 ${scrollButtons[String(a.direction)]}`;
|
|
75
|
-
break;
|
|
76
|
-
case "open_url":
|
|
77
|
-
command = `nohup xdg-open ${quote(String(a.url))} >/dev/null 2>&1 </dev/null &`;
|
|
78
|
-
break;
|
|
79
|
-
case "exec":
|
|
80
|
-
command = String(a.command);
|
|
81
|
-
break;
|
|
82
|
-
default: throw new Error("Unknown cloud computer tool");
|
|
83
|
-
}
|
|
84
|
-
const result = await runCommand(config, id, isolatedRemoteCommand(command), { signal });
|
|
85
|
-
signal.throwIfAborted();
|
|
86
|
-
if (name === "open_url" && result.ok)
|
|
87
|
-
return { content: [{ type: "text", text: "Browser launch requested. Page loading is not confirmed; inspect the screen before continuing." }] };
|
|
88
|
-
return { isError: !result.ok, content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
89
|
-
}
|
|
90
|
-
}
|
|
@@ -1,329 +0,0 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
import { closeSync, constants, fstatSync, lstatSync, openSync, readFileSync, } from "node:fs";
|
|
3
|
-
import { homedir } from "node:os";
|
|
4
|
-
import { dirname, join } from "node:path";
|
|
5
|
-
import { z } from "zod";
|
|
6
|
-
import { parseJson } from "./schema.js";
|
|
7
|
-
const SCHEMA = "aos.openmausbot_status.v1";
|
|
8
|
-
const TTL_SECONDS = 300;
|
|
9
|
-
const MAX_CACHE_BYTES = 16_384;
|
|
10
|
-
const DIGEST = /^sha256:[a-f0-9]{64}$/;
|
|
11
|
-
const UTC_TIMESTAMP = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/;
|
|
12
|
-
const digestSchema = z.string().regex(DIGEST);
|
|
13
|
-
const slotSchema = z.object({
|
|
14
|
-
slot: z.string().regex(/^vm-[1-4]$/),
|
|
15
|
-
container: z.enum(["running", "stopped", "missing"]),
|
|
16
|
-
readiness: z.enum(["ready", "not_ready"]),
|
|
17
|
-
network: z.enum(["loopback", "unsafe", "unknown"]),
|
|
18
|
-
security: z.enum(["hardened", "unsafe", "unknown"]),
|
|
19
|
-
persistence: z.enum(["durable", "unsafe", "unknown"]),
|
|
20
|
-
}).strict();
|
|
21
|
-
const uiSchema = z.object({
|
|
22
|
-
two_up: z.boolean(),
|
|
23
|
-
max_visible: z.union([z.literal(1), z.literal(2)]),
|
|
24
|
-
max_interactive: z.literal(1),
|
|
25
|
-
default_watch_only: z.boolean(),
|
|
26
|
-
}).strict();
|
|
27
|
-
const capsuleSchema = z.object({
|
|
28
|
-
schema: z.literal(SCHEMA),
|
|
29
|
-
observed_at: z.string().regex(UTC_TIMESTAMP),
|
|
30
|
-
fresh_until: z.string().regex(UTC_TIMESTAMP),
|
|
31
|
-
ttl_seconds: z.literal(TTL_SECONDS),
|
|
32
|
-
source_sha256: digestSchema.nullable(),
|
|
33
|
-
dual_view_sha256: digestSchema.nullable(),
|
|
34
|
-
receipt_sha256: digestSchema,
|
|
35
|
-
refresh_status: z.enum(["success", "failed"]),
|
|
36
|
-
failure_reason: z.enum([
|
|
37
|
-
"config_unavailable",
|
|
38
|
-
"bots_unavailable",
|
|
39
|
-
"vm_status_unavailable",
|
|
40
|
-
"capacity_exceeded",
|
|
41
|
-
"source_hash_unavailable",
|
|
42
|
-
"source_hash_mismatch",
|
|
43
|
-
]).optional(),
|
|
44
|
-
runtime_state: z.enum(["ready", "degraded", "unknown"]),
|
|
45
|
-
mode: z.enum(["shared", "per-bot", "unknown"]),
|
|
46
|
-
max_instances: z.number().int().min(1).max(4).nullable(),
|
|
47
|
-
ready_count: z.number().int().min(0).max(4),
|
|
48
|
-
slots: z.array(slotSchema).max(4),
|
|
49
|
-
ui: uiSchema,
|
|
50
|
-
}).strict();
|
|
51
|
-
const jsonObjectSchema = z.record(z.string(), z.custom());
|
|
52
|
-
export const OPENMAUS_STATUS_CACHE_PATH = join(homedir(), ".local/state/aos-session-bridge/openmausbot/latest.json");
|
|
53
|
-
function canonicalValue(value) {
|
|
54
|
-
if (Array.isArray(value))
|
|
55
|
-
return value.map(canonicalValue);
|
|
56
|
-
const parsedObject = jsonObjectSchema.safeParse(value);
|
|
57
|
-
if (!parsedObject.success)
|
|
58
|
-
return value;
|
|
59
|
-
const sorted = {};
|
|
60
|
-
for (const key of Object.keys(parsedObject.data).sort()) {
|
|
61
|
-
const child = parsedObject.data[key];
|
|
62
|
-
if (child !== undefined)
|
|
63
|
-
sorted[key] = canonicalValue(child);
|
|
64
|
-
}
|
|
65
|
-
return sorted;
|
|
66
|
-
}
|
|
67
|
-
function canonical(value) {
|
|
68
|
-
return JSON.stringify(canonicalValue(value));
|
|
69
|
-
}
|
|
70
|
-
function sha256(value) {
|
|
71
|
-
return `sha256:${createHash("sha256").update(value, "utf8").digest("hex")}`;
|
|
72
|
-
}
|
|
73
|
-
function timestamp(value) {
|
|
74
|
-
const parsed = Date.parse(value);
|
|
75
|
-
if (!Number.isFinite(parsed))
|
|
76
|
-
return null;
|
|
77
|
-
if (new Date(parsed).toISOString().replace(".000Z", "Z") !== value)
|
|
78
|
-
return null;
|
|
79
|
-
return parsed;
|
|
80
|
-
}
|
|
81
|
-
function expectedUi(twoUp) {
|
|
82
|
-
return {
|
|
83
|
-
two_up: twoUp,
|
|
84
|
-
max_visible: twoUp ? 2 : 1,
|
|
85
|
-
max_interactive: 1,
|
|
86
|
-
default_watch_only: twoUp,
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
function sameUi(left, right) {
|
|
90
|
-
return (left.two_up === right.two_up &&
|
|
91
|
-
left.max_visible === right.max_visible &&
|
|
92
|
-
left.max_interactive === right.max_interactive &&
|
|
93
|
-
left.default_watch_only === right.default_watch_only);
|
|
94
|
-
}
|
|
95
|
-
function unsignedDocument(capsule) {
|
|
96
|
-
const document = {
|
|
97
|
-
schema: capsule.schema,
|
|
98
|
-
observed_at: capsule.observed_at,
|
|
99
|
-
fresh_until: capsule.fresh_until,
|
|
100
|
-
ttl_seconds: capsule.ttl_seconds,
|
|
101
|
-
source_sha256: capsule.source_sha256,
|
|
102
|
-
dual_view_sha256: capsule.dual_view_sha256,
|
|
103
|
-
refresh_status: capsule.refresh_status,
|
|
104
|
-
runtime_state: capsule.runtime_state,
|
|
105
|
-
mode: capsule.mode,
|
|
106
|
-
max_instances: capsule.max_instances,
|
|
107
|
-
ready_count: capsule.ready_count,
|
|
108
|
-
slots: capsule.slots.map((slot) => ({
|
|
109
|
-
slot: slot.slot,
|
|
110
|
-
container: slot.container,
|
|
111
|
-
readiness: slot.readiness,
|
|
112
|
-
network: slot.network,
|
|
113
|
-
security: slot.security,
|
|
114
|
-
persistence: slot.persistence,
|
|
115
|
-
})),
|
|
116
|
-
ui: {
|
|
117
|
-
two_up: capsule.ui.two_up,
|
|
118
|
-
max_visible: capsule.ui.max_visible,
|
|
119
|
-
max_interactive: capsule.ui.max_interactive,
|
|
120
|
-
default_watch_only: capsule.ui.default_watch_only,
|
|
121
|
-
},
|
|
122
|
-
};
|
|
123
|
-
if (capsule.failure_reason !== undefined)
|
|
124
|
-
document.failure_reason = capsule.failure_reason;
|
|
125
|
-
return document;
|
|
126
|
-
}
|
|
127
|
-
function validSlotState(slot) {
|
|
128
|
-
return (slot.readiness !== "ready" ||
|
|
129
|
-
(slot.container === "running" &&
|
|
130
|
-
slot.network === "loopback" &&
|
|
131
|
-
slot.security === "hardened" &&
|
|
132
|
-
slot.persistence === "durable"));
|
|
133
|
-
}
|
|
134
|
-
function validateCapsule(value) {
|
|
135
|
-
const parsed = capsuleSchema.safeParse(value);
|
|
136
|
-
if (!parsed.success)
|
|
137
|
-
return null;
|
|
138
|
-
const capsule = parsed.data;
|
|
139
|
-
const observed = timestamp(capsule.observed_at);
|
|
140
|
-
const freshUntil = timestamp(capsule.fresh_until);
|
|
141
|
-
if (observed === null || freshUntil === null || freshUntil - observed !== TTL_SECONDS * 1000)
|
|
142
|
-
return null;
|
|
143
|
-
if (capsule.receipt_sha256 !== sha256(canonical(unsignedDocument(capsule))))
|
|
144
|
-
return null;
|
|
145
|
-
if (!capsule.slots.every((slot, index) => slot.slot === `vm-${index + 1}` && validSlotState(slot))) {
|
|
146
|
-
return null;
|
|
147
|
-
}
|
|
148
|
-
const readyCount = capsule.slots.filter((slot) => slot.readiness === "ready").length;
|
|
149
|
-
if (capsule.ready_count !== readyCount)
|
|
150
|
-
return null;
|
|
151
|
-
const twoUp = Boolean(capsule.refresh_status === "success" &&
|
|
152
|
-
capsule.mode === "per-bot" &&
|
|
153
|
-
capsule.max_instances !== null &&
|
|
154
|
-
capsule.max_instances >= 2 &&
|
|
155
|
-
capsule.source_sha256 !== null &&
|
|
156
|
-
capsule.dual_view_sha256 !== null &&
|
|
157
|
-
capsule.source_sha256 === capsule.dual_view_sha256);
|
|
158
|
-
if (!sameUi(capsule.ui, expectedUi(twoUp)))
|
|
159
|
-
return null;
|
|
160
|
-
if (capsule.refresh_status === "failed") {
|
|
161
|
-
if (capsule.failure_reason === undefined ||
|
|
162
|
-
capsule.runtime_state !== "unknown" ||
|
|
163
|
-
capsule.mode !== "unknown" ||
|
|
164
|
-
capsule.max_instances !== null ||
|
|
165
|
-
capsule.ready_count !== 0 ||
|
|
166
|
-
capsule.slots.length !== 0 ||
|
|
167
|
-
!sameUi(capsule.ui, expectedUi(false)))
|
|
168
|
-
return null;
|
|
169
|
-
}
|
|
170
|
-
else {
|
|
171
|
-
const expectedRuntime = capsule.slots.length > 0 && readyCount === capsule.slots.length ? "ready" : "degraded";
|
|
172
|
-
if (capsule.failure_reason !== undefined ||
|
|
173
|
-
capsule.runtime_state !== expectedRuntime ||
|
|
174
|
-
(capsule.mode !== "shared" && capsule.mode !== "per-bot") ||
|
|
175
|
-
capsule.max_instances === null ||
|
|
176
|
-
readyCount > capsule.max_instances ||
|
|
177
|
-
capsule.source_sha256 === null ||
|
|
178
|
-
capsule.dual_view_sha256 === null ||
|
|
179
|
-
capsule.source_sha256 !== capsule.dual_view_sha256)
|
|
180
|
-
return null;
|
|
181
|
-
}
|
|
182
|
-
return capsule;
|
|
183
|
-
}
|
|
184
|
-
function privateCache(path) {
|
|
185
|
-
const uid = process.getuid?.();
|
|
186
|
-
if (uid === undefined)
|
|
187
|
-
return null;
|
|
188
|
-
let descriptor = null;
|
|
189
|
-
try {
|
|
190
|
-
const parentStatus = lstatSync(dirname(path));
|
|
191
|
-
const fileStatus = lstatSync(path);
|
|
192
|
-
if (parentStatus.isSymbolicLink() ||
|
|
193
|
-
!parentStatus.isDirectory() ||
|
|
194
|
-
parentStatus.uid !== uid ||
|
|
195
|
-
(parentStatus.mode & 0o777) !== 0o700)
|
|
196
|
-
return null;
|
|
197
|
-
if (fileStatus.isSymbolicLink() ||
|
|
198
|
-
!fileStatus.isFile() ||
|
|
199
|
-
fileStatus.uid !== uid ||
|
|
200
|
-
(fileStatus.mode & 0o777) !== 0o600 ||
|
|
201
|
-
fileStatus.size > MAX_CACHE_BYTES)
|
|
202
|
-
return null;
|
|
203
|
-
descriptor = openSync(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
|
|
204
|
-
const openedStatus = fstatSync(descriptor);
|
|
205
|
-
if (!openedStatus.isFile() ||
|
|
206
|
-
openedStatus.uid !== uid ||
|
|
207
|
-
(openedStatus.mode & 0o777) !== 0o600 ||
|
|
208
|
-
openedStatus.dev !== fileStatus.dev ||
|
|
209
|
-
openedStatus.ino !== fileStatus.ino ||
|
|
210
|
-
openedStatus.size > MAX_CACHE_BYTES)
|
|
211
|
-
return null;
|
|
212
|
-
const contents = readFileSync(descriptor);
|
|
213
|
-
return contents.length <= MAX_CACHE_BYTES ? contents : null;
|
|
214
|
-
}
|
|
215
|
-
catch {
|
|
216
|
-
return null;
|
|
217
|
-
}
|
|
218
|
-
finally {
|
|
219
|
-
if (descriptor !== null) {
|
|
220
|
-
try {
|
|
221
|
-
closeSync(descriptor);
|
|
222
|
-
}
|
|
223
|
-
catch {
|
|
224
|
-
// Cleanup failures must not escape the fail-closed cache read.
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
function unknownDigest(reason, freshness = "unknown", receipt) {
|
|
230
|
-
return {
|
|
231
|
-
schema: SCHEMA,
|
|
232
|
-
freshness,
|
|
233
|
-
reason,
|
|
234
|
-
...receipt,
|
|
235
|
-
runtimeState: "unknown",
|
|
236
|
-
mode: "unknown",
|
|
237
|
-
maxInstances: null,
|
|
238
|
-
readyCount: 0,
|
|
239
|
-
slots: [],
|
|
240
|
-
ui: {
|
|
241
|
-
twoUp: false,
|
|
242
|
-
maxVisible: 1,
|
|
243
|
-
maxInteractive: 1,
|
|
244
|
-
defaultWatchOnly: false,
|
|
245
|
-
oneActiveController: true,
|
|
246
|
-
},
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
export function readOpenMausStatus(options = {}) {
|
|
250
|
-
const raw = privateCache(options.cachePath ?? OPENMAUS_STATUS_CACHE_PATH);
|
|
251
|
-
if (raw === null)
|
|
252
|
-
return unknownDigest("missing_or_insecure");
|
|
253
|
-
let capsule;
|
|
254
|
-
try {
|
|
255
|
-
capsule = validateCapsule(parseJson(raw.toString("utf8")));
|
|
256
|
-
}
|
|
257
|
-
catch {
|
|
258
|
-
capsule = null;
|
|
259
|
-
}
|
|
260
|
-
if (capsule === null)
|
|
261
|
-
return unknownDigest("invalid");
|
|
262
|
-
const observed = timestamp(capsule.observed_at);
|
|
263
|
-
const freshUntil = timestamp(capsule.fresh_until);
|
|
264
|
-
const now = (options.now ?? new Date()).getTime();
|
|
265
|
-
const receipt = { observedAt: capsule.observed_at, receiptSha256: capsule.receipt_sha256 };
|
|
266
|
-
if (!Number.isFinite(now) || now < observed)
|
|
267
|
-
return unknownDigest("clock_skew");
|
|
268
|
-
if (now >= freshUntil)
|
|
269
|
-
return unknownDigest("stale", "stale", receipt);
|
|
270
|
-
if (capsule.refresh_status === "failed")
|
|
271
|
-
return unknownDigest("refresh_failed", "fresh", receipt);
|
|
272
|
-
const result = {
|
|
273
|
-
schema: SCHEMA,
|
|
274
|
-
freshness: "fresh",
|
|
275
|
-
...receipt,
|
|
276
|
-
runtimeState: capsule.runtime_state,
|
|
277
|
-
mode: capsule.mode,
|
|
278
|
-
maxInstances: capsule.max_instances,
|
|
279
|
-
readyCount: capsule.ready_count,
|
|
280
|
-
slots: capsule.slots,
|
|
281
|
-
ui: {
|
|
282
|
-
twoUp: capsule.ui.two_up,
|
|
283
|
-
maxVisible: capsule.ui.max_visible,
|
|
284
|
-
maxInteractive: 1,
|
|
285
|
-
defaultWatchOnly: capsule.ui.default_watch_only,
|
|
286
|
-
oneActiveController: true,
|
|
287
|
-
},
|
|
288
|
-
};
|
|
289
|
-
if (capsule.source_sha256 !== null)
|
|
290
|
-
result.sourceSha256 = capsule.source_sha256;
|
|
291
|
-
if (capsule.dual_view_sha256 !== null)
|
|
292
|
-
result.dualViewSha256 = capsule.dual_view_sha256;
|
|
293
|
-
return result;
|
|
294
|
-
}
|
|
295
|
-
export function openMausStatusSystemPrompt(options = {}) {
|
|
296
|
-
const status = readOpenMausStatus(options);
|
|
297
|
-
const receipt = [
|
|
298
|
-
status.observedAt ? `observed_at=${status.observedAt}` : null,
|
|
299
|
-
status.receiptSha256 ? `receipt_sha256=${status.receiptSha256}` : null,
|
|
300
|
-
status.sourceSha256 ? `source_sha256=${status.sourceSha256}` : null,
|
|
301
|
-
status.dualViewSha256 ? `accepted_dual_view_sha256=${status.dualViewSha256}` : null,
|
|
302
|
-
status.sourceSha256 && status.dualViewSha256
|
|
303
|
-
? `source_match=${status.sourceSha256 === status.dualViewSha256}`
|
|
304
|
-
: null,
|
|
305
|
-
].filter(Boolean).join("; ");
|
|
306
|
-
const runtime = status.freshness === "fresh" && status.reason === undefined
|
|
307
|
-
? [
|
|
308
|
-
`runtime_state=${status.runtimeState}`,
|
|
309
|
-
`mode=${status.mode}`,
|
|
310
|
-
`maximum_instances=${status.maxInstances}`,
|
|
311
|
-
`ready_count=${status.readyCount}`,
|
|
312
|
-
].join("; ")
|
|
313
|
-
: "runtime_state=unknown; mode=unknown; maximum_instances=unknown; ready_count=0";
|
|
314
|
-
const slots = status.slots.length
|
|
315
|
-
? status.slots
|
|
316
|
-
.map((slot) => `${slot.slot}(container=${slot.container},readiness=${slot.readiness},network=${slot.network},security=${slot.security},persistence=${slot.persistence})`)
|
|
317
|
-
.join(",")
|
|
318
|
-
: "none";
|
|
319
|
-
return [
|
|
320
|
-
"TRUSTED OPENMAUSBOT STATUS (read-only, validated, no transcript or credential data):",
|
|
321
|
-
`schema=${status.schema}; freshness=${status.freshness}${status.reason ? `; reason=${status.reason}` : ""}`,
|
|
322
|
-
receipt || "receipt=unavailable",
|
|
323
|
-
runtime,
|
|
324
|
-
`anonymous_slots=${slots}`,
|
|
325
|
-
`ui.two_up=${status.ui.twoUp}; ui.max_visible=${status.ui.maxVisible}; ui.max_interactive=1; ui.default_watch_only=${status.ui.defaultWatchOnly}; one_active_controller=true`,
|
|
326
|
-
"Opening a viewer or two-up workspace never starts or provisions a VM. Only one pane may be interactive at a time; switching control must release the previous pane before activating the next.",
|
|
327
|
-
"Treat missing, stale, failed, clock-skewed, malformed, or receipt-hash-mismatched runtime data as unknown. Do not infer bot identities, viewer URLs, paths, messages, models, accounts, or credentials from this block.",
|
|
328
|
-
].join("\n");
|
|
329
|
-
}
|