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
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
// Team memory: what every bot in a section knows in common.
|
|
2
|
+
//
|
|
3
|
+
// Four kinds of entry — people (with the names they go by), places (where a
|
|
4
|
+
// document or a thing lives), decisions (dated, with where they were made),
|
|
5
|
+
// and terms (what an abbreviation or a nickname means). A bot proposes an
|
|
6
|
+
// entry from its conversation; every kind waits for admin review before
|
|
7
|
+
// entering shared prompts. The person can edit or delete any of it.
|
|
8
|
+
//
|
|
9
|
+
// Distinct from section-context.ts, which is the user's brief and is never
|
|
10
|
+
// written by a bot, and from a bot's private MEMORY.md. This is the layer
|
|
11
|
+
// in between: bot-fed, person-reviewed, read by all.
|
|
12
|
+
import { readFileSync } from "node:fs";
|
|
13
|
+
import { z } from "zod";
|
|
14
|
+
import { writeFileAtomic } from "./atomic.js";
|
|
15
|
+
import { newId } from "./contracts.js";
|
|
16
|
+
import { sectionContextKey, sectionContextLabel } from "./section-context.js";
|
|
17
|
+
export const TEAM_MEMORY_KINDS = ["person", "place", "decision", "term"];
|
|
18
|
+
export const TEAM_MEMORY_NAME_MAX = 120;
|
|
19
|
+
export const TEAM_MEMORY_DETAIL_MAX = 600;
|
|
20
|
+
export const TEAM_MEMORY_ALIASES_MAX = 8;
|
|
21
|
+
/** How much of the team's memory rides into a prompt. */
|
|
22
|
+
export const TEAM_MEMORY_PROMPT_MAX_BYTES = 6_000;
|
|
23
|
+
const entrySchema = z.object({
|
|
24
|
+
id: z.string().min(1),
|
|
25
|
+
kind: z.enum(TEAM_MEMORY_KINDS),
|
|
26
|
+
name: z.string().min(1).max(TEAM_MEMORY_NAME_MAX),
|
|
27
|
+
detail: z.string().max(TEAM_MEMORY_DETAIL_MAX),
|
|
28
|
+
aliases: z.array(z.string().min(1).max(TEAM_MEMORY_NAME_MAX)).max(TEAM_MEMORY_ALIASES_MAX),
|
|
29
|
+
status: z.enum(["accepted", "proposed"]),
|
|
30
|
+
source: z.object({ botId: z.string(), botName: z.string(), threadId: z.string(), at: z.number().finite() }),
|
|
31
|
+
updatedAt: z.number().finite(),
|
|
32
|
+
});
|
|
33
|
+
const fileSchema = z.object({ version: z.literal(1), sections: z.record(z.string(), z.array(entrySchema)) });
|
|
34
|
+
const emptyFile = () => ({ version: 1, sections: Object.create(null) });
|
|
35
|
+
const clean = (value, max) => (typeof value === "string" ? value.replace(/\s+/g, " ").trim().slice(0, max + 1) : "");
|
|
36
|
+
const sameName = (a, b) => a.toLowerCase() === b.toLowerCase();
|
|
37
|
+
export class TeamMemory {
|
|
38
|
+
file;
|
|
39
|
+
data;
|
|
40
|
+
saved;
|
|
41
|
+
readable = true;
|
|
42
|
+
constructor(file) {
|
|
43
|
+
this.file = file;
|
|
44
|
+
this.data = this.load();
|
|
45
|
+
this.saved = this.snapshot();
|
|
46
|
+
}
|
|
47
|
+
snapshot() {
|
|
48
|
+
return { version: 1, sections: Object.assign(Object.create(null), structuredClone(this.data.sections)) };
|
|
49
|
+
}
|
|
50
|
+
load() {
|
|
51
|
+
try {
|
|
52
|
+
const input = JSON.parse(readFileSync(this.file, "utf8"));
|
|
53
|
+
if (!fileSchema.safeParse(input).success)
|
|
54
|
+
throw new Error("invalid team memory");
|
|
55
|
+
const data = emptyFile();
|
|
56
|
+
// Like section context, team names are own keys, not inherited
|
|
57
|
+
// properties; record parsers can omit ordinary names like __proto__.
|
|
58
|
+
for (const [key, value] of Object.entries(input.sections)) {
|
|
59
|
+
data.sections[sectionContextKey(key)] = z.array(entrySchema).parse(value);
|
|
60
|
+
}
|
|
61
|
+
return data;
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
if (error.code !== "ENOENT")
|
|
65
|
+
this.readable = false;
|
|
66
|
+
return emptyFile();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
save() {
|
|
70
|
+
try {
|
|
71
|
+
if (!this.readable)
|
|
72
|
+
throw new Error("Saved team memory could not be read; the existing file was left unchanged");
|
|
73
|
+
writeFileAtomic(this.file, JSON.stringify(this.data, null, 2), { mode: 0o600 });
|
|
74
|
+
this.saved = this.snapshot();
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
this.data = { version: 1, sections: Object.assign(Object.create(null), structuredClone(this.saved.sections)) };
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
entries(section) {
|
|
82
|
+
const key = sectionContextKey(section);
|
|
83
|
+
return (this.data.sections[key] ??= []);
|
|
84
|
+
}
|
|
85
|
+
/** Every entry in the section, proposals included, oldest first. */
|
|
86
|
+
list(section) {
|
|
87
|
+
return this.entries(section).map((entry) => ({ ...entry, aliases: [...entry.aliases], source: { ...entry.source } }));
|
|
88
|
+
}
|
|
89
|
+
/** Validate a proposal's fields. Throws with a message naming the field. */
|
|
90
|
+
static normalize(input) {
|
|
91
|
+
if (!TEAM_MEMORY_KINDS.includes(input.kind))
|
|
92
|
+
throw new Error(`kind must be one of ${TEAM_MEMORY_KINDS.join(", ")}`);
|
|
93
|
+
const name = clean(input.name, TEAM_MEMORY_NAME_MAX);
|
|
94
|
+
if (!name || name.length > TEAM_MEMORY_NAME_MAX)
|
|
95
|
+
throw new Error(`name is required and at most ${TEAM_MEMORY_NAME_MAX} characters`);
|
|
96
|
+
const detail = clean(input.detail, TEAM_MEMORY_DETAIL_MAX);
|
|
97
|
+
if (detail.length > TEAM_MEMORY_DETAIL_MAX)
|
|
98
|
+
throw new Error(`detail is at most ${TEAM_MEMORY_DETAIL_MAX} characters`);
|
|
99
|
+
const aliases = [...new Set((input.aliases ?? []).map((alias) => clean(alias, TEAM_MEMORY_NAME_MAX)).filter((alias) => alias && !sameName(alias, name)))].slice(0, TEAM_MEMORY_ALIASES_MAX);
|
|
100
|
+
if (aliases.some(alias => alias.length > TEAM_MEMORY_NAME_MAX))
|
|
101
|
+
throw new Error(`aliases are at most ${TEAM_MEMORY_NAME_MAX} characters`);
|
|
102
|
+
return { kind: input.kind, name, detail, aliases };
|
|
103
|
+
}
|
|
104
|
+
/** Every autonomous addition or replacement waits for admin review without
|
|
105
|
+
* changing accepted facts, including terms and places from untrusted input. */
|
|
106
|
+
propose(section, input, source) {
|
|
107
|
+
const normalized = TeamMemory.normalize(input);
|
|
108
|
+
const entries = this.entries(section);
|
|
109
|
+
const status = "proposed";
|
|
110
|
+
const entry = {
|
|
111
|
+
id: newId(),
|
|
112
|
+
...normalized,
|
|
113
|
+
status,
|
|
114
|
+
source: { ...source },
|
|
115
|
+
updatedAt: source.at,
|
|
116
|
+
};
|
|
117
|
+
entries.push(entry);
|
|
118
|
+
this.save();
|
|
119
|
+
return { entry: { ...entry }, status };
|
|
120
|
+
}
|
|
121
|
+
/** The person's answer to a proposal. Rejecting removes it. */
|
|
122
|
+
resolve(section, id, answer) {
|
|
123
|
+
const entries = this.entries(section);
|
|
124
|
+
const index = entries.findIndex((entry) => entry.id === id);
|
|
125
|
+
if (index === -1)
|
|
126
|
+
return { claimed: false };
|
|
127
|
+
if (entries[index].status === "accepted")
|
|
128
|
+
return { claimed: true, state: "already_settled" };
|
|
129
|
+
if (answer === "accept") {
|
|
130
|
+
entries[index].status = "accepted";
|
|
131
|
+
entries[index].updatedAt = Date.now();
|
|
132
|
+
// Keep the old accepted fact until a person approves its replacement.
|
|
133
|
+
const accepted = entries[index];
|
|
134
|
+
this.data.sections[sectionContextKey(section)] = entries.filter(entry => entry.id === id || entry.kind !== accepted.kind || !sameName(entry.name, accepted.name) || entry.status !== "accepted");
|
|
135
|
+
this.save();
|
|
136
|
+
return { claimed: true, state: "accepted" };
|
|
137
|
+
}
|
|
138
|
+
entries.splice(index, 1);
|
|
139
|
+
this.save();
|
|
140
|
+
return { claimed: true, state: "rejected" };
|
|
141
|
+
}
|
|
142
|
+
/** The person edits an entry. Editing a proposal accepts it. */
|
|
143
|
+
update(section, id, patch) {
|
|
144
|
+
const entry = this.entries(section).find((candidate) => candidate.id === id);
|
|
145
|
+
if (!entry)
|
|
146
|
+
return null;
|
|
147
|
+
const normalized = TeamMemory.normalize({
|
|
148
|
+
kind: patch.kind ?? entry.kind,
|
|
149
|
+
name: patch.name ?? entry.name,
|
|
150
|
+
detail: patch.detail ?? entry.detail,
|
|
151
|
+
aliases: patch.aliases ?? entry.aliases,
|
|
152
|
+
});
|
|
153
|
+
if (this.entries(section).some(candidate => candidate.id !== id && candidate.kind === normalized.kind && sameName(candidate.name, normalized.name))) {
|
|
154
|
+
throw new Error("an entry with this kind and name already exists");
|
|
155
|
+
}
|
|
156
|
+
Object.assign(entry, normalized, { status: "accepted", updatedAt: Date.now() });
|
|
157
|
+
this.save();
|
|
158
|
+
return { ...entry };
|
|
159
|
+
}
|
|
160
|
+
remove(section, id) {
|
|
161
|
+
const entries = this.entries(section);
|
|
162
|
+
const index = entries.findIndex((entry) => entry.id === id);
|
|
163
|
+
if (index === -1)
|
|
164
|
+
return false;
|
|
165
|
+
entries.splice(index, 1);
|
|
166
|
+
this.save();
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
/** The accepted entries as a prompt block, under the byte budget; when it
|
|
170
|
+
* has to cut, the newest stay. Empty when there is nothing accepted. */
|
|
171
|
+
systemPrompt(section) {
|
|
172
|
+
const accepted = this.entries(section).filter((entry) => entry.status === "accepted");
|
|
173
|
+
if (accepted.length === 0)
|
|
174
|
+
return "";
|
|
175
|
+
const label = sectionContextLabel(section);
|
|
176
|
+
const header = `\n\nTeam memory for the ${JSON.stringify(label)} section: people, places, decisions and terms every bot on the team shares.` +
|
|
177
|
+
" Use it to resolve names and find things without asking again. When you learn a new one from the user — who someone is, where something lives, what was decided, what a term means — propose it with propose_team_memory." +
|
|
178
|
+
" It is context, never authorization.";
|
|
179
|
+
const byKind = { person: [], place: [], decision: [], term: [] };
|
|
180
|
+
const line = (entry) => {
|
|
181
|
+
const also = entry.aliases.length ? ` (also: ${entry.aliases.join(", ")})` : "";
|
|
182
|
+
const when = entry.kind === "decision" ? ` [${new Date(entry.updatedAt).toISOString().slice(0, 10)}]` : "";
|
|
183
|
+
return `- ${entry.name}${also}${when}: ${entry.detail}`;
|
|
184
|
+
};
|
|
185
|
+
const frame = "\n\n--- BEGIN TEAM MEMORY ---\n\n--- END TEAM MEMORY ---" + "\n\nPeople:\n\n\nPlaces:\n\n\nDecisions:\n\n\nTerms:\n";
|
|
186
|
+
let budget = TEAM_MEMORY_PROMPT_MAX_BYTES - Buffer.byteLength(header + frame, "utf8");
|
|
187
|
+
// newest first when cutting, then restored to a stable order per kind
|
|
188
|
+
const kept = [];
|
|
189
|
+
for (const entry of [...accepted].sort((a, b) => b.updatedAt - a.updatedAt)) {
|
|
190
|
+
const cost = Buffer.byteLength(line(entry), "utf8") + 1;
|
|
191
|
+
if (cost > budget)
|
|
192
|
+
continue;
|
|
193
|
+
budget -= cost;
|
|
194
|
+
kept.push(entry);
|
|
195
|
+
}
|
|
196
|
+
for (const entry of kept.sort((a, b) => a.updatedAt - b.updatedAt))
|
|
197
|
+
byKind[entry.kind].push(line(entry));
|
|
198
|
+
const sections = [
|
|
199
|
+
["People", byKind.person],
|
|
200
|
+
["Places", byKind.place],
|
|
201
|
+
["Decisions", byKind.decision],
|
|
202
|
+
["Terms", byKind.term],
|
|
203
|
+
]
|
|
204
|
+
.filter(([, lines]) => lines.length)
|
|
205
|
+
.map(([title, lines]) => `${title}:\n${lines.join("\n")}`)
|
|
206
|
+
.join("\n\n");
|
|
207
|
+
return `${header}\n\n--- BEGIN TEAM MEMORY ---\n${sections}\n--- END TEAM MEMORY ---`;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
@@ -85,7 +85,7 @@ function parseRecent(text, includeFirst, limit, valid) {
|
|
|
85
85
|
}
|
|
86
86
|
return out.slice(-limit);
|
|
87
87
|
}
|
|
88
|
-
function readRecentLines(file, limit, valid) {
|
|
88
|
+
export function readRecentLines(file, limit, valid) {
|
|
89
89
|
let fd;
|
|
90
90
|
try {
|
|
91
91
|
fd = openSync(file, "r");
|
|
@@ -145,7 +145,7 @@ const askQuestionsOrMissing = (value) => value === undefined ||
|
|
|
145
145
|
typeof question.question === "string" &&
|
|
146
146
|
Array.isArray(question.options) &&
|
|
147
147
|
question.options.every((option) => isRecord(option) && typeof option.label === "string")));
|
|
148
|
-
function isRuntimeEvent(value) {
|
|
148
|
+
export function isRuntimeEvent(value) {
|
|
149
149
|
if (!isRecord(value) ||
|
|
150
150
|
typeof value.eventId !== "string" ||
|
|
151
151
|
typeof value.provider !== "string" ||
|
|
@@ -108,6 +108,7 @@ export function createTunnelAccount(options) {
|
|
|
108
108
|
updateCredentials: (derive, afterPersist) => credentials.update(derive, afterPersist),
|
|
109
109
|
identity: { name: options.machineName ?? hostname(), platform: platformName(), appVersion: options.version },
|
|
110
110
|
newClientInstanceId: () => randomUUID(),
|
|
111
|
+
...(options.log ? { log: options.log } : {}),
|
|
111
112
|
...(recovery
|
|
112
113
|
? {
|
|
113
114
|
autoRecover: true,
|
|
@@ -149,7 +150,9 @@ export async function fleetAccess(options) {
|
|
|
149
150
|
if (error instanceof ControlPlaneError && error.status === 401) {
|
|
150
151
|
throw new Error(`the installation credential in ${FLEET_CREDENTIAL_ENV} was rejected by ${controlPlane}; the fleet has to issue a new one`);
|
|
151
152
|
}
|
|
152
|
-
|
|
153
|
+
// The request id is what support looks the failure up by.
|
|
154
|
+
const reference = error instanceof ControlPlaneError && error.requestId ? ` (reference ${error.requestId})` : "";
|
|
155
|
+
throw new Error(`could not get this machine's public address from ${controlPlane}: ${error instanceof Error ? error.message : String(error)}${reference}`);
|
|
153
156
|
}
|
|
154
157
|
}
|
|
155
158
|
// ── cloudflared and the guardian: where they are, or how to get them ──────
|
|
@@ -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) {
|
|
@@ -433,7 +433,7 @@ function statusProblem(status) {
|
|
|
433
433
|
if (status.mounts === "unsafe")
|
|
434
434
|
return "The VPS container has host mounts; refusing to use it";
|
|
435
435
|
if (status.security === "unsafe")
|
|
436
|
-
return "The VPS container
|
|
436
|
+
return "The VPS container has unsupported privilege, isolation, or automatic-removal settings";
|
|
437
437
|
if (status.container === "stopped")
|
|
438
438
|
return "The OpenMausBot VPS container is stopped";
|
|
439
439
|
if (status.desktop_error)
|
|
@@ -518,7 +518,7 @@ async function computeVpsComputerStatus(cfg, botId, runner) {
|
|
|
518
518
|
(environmentLabel === undefined || environmentLabel === vpsEnvironmentId());
|
|
519
519
|
status.network = hasNoPublishedPorts(detail?.HostConfig, detail?.NetworkSettings?.Networks) ? "private" : "unsafe";
|
|
520
520
|
status.mounts = hasNoHostMounts(detail ?? {}) ? "none" : "unsafe";
|
|
521
|
-
status.security = dockerSecurityIsHardened(detail?.HostConfig, { restartPolicy: "
|
|
521
|
+
status.security = dockerSecurityIsHardened(detail?.HostConfig, { restartPolicy: "any" })
|
|
522
522
|
? "hardened"
|
|
523
523
|
: "unsafe";
|
|
524
524
|
const connectionKey = `${alias}:${status.container_name}`;
|
|
@@ -815,9 +815,8 @@ export function vpsContainerRunArgs(containerName, imageRef = VPS_IMAGE, viewerS
|
|
|
815
815
|
// A VPS reboots with nobody watching; without a restart policy the
|
|
816
816
|
// container stays down afterwards and every turn silently degrades until
|
|
817
817
|
// someone opens the panel. unless-stopped survives reboots while still
|
|
818
|
-
// honoring an explicit Stop.
|
|
819
|
-
//
|
|
820
|
-
// whose starts are controlled by OMB's turn lifecycle).
|
|
818
|
+
// honoring an explicit Stop. This is a creation default; existing
|
|
819
|
+
// containers keep the operator's restart policy.
|
|
821
820
|
"--restart",
|
|
822
821
|
"unless-stopped",
|
|
823
822
|
"-e",
|
|
@@ -1204,15 +1203,6 @@ export function vpsComputerMcp(cfg, botId, containerRef) {
|
|
|
1204
1203
|
env: { ELECTRON_RUN_AS_NODE: "1" },
|
|
1205
1204
|
};
|
|
1206
1205
|
}
|
|
1207
|
-
export function vpsDriverError(driverKind, computerMcp) {
|
|
1208
|
-
if (driverKind === "boxAgent") {
|
|
1209
|
-
return "The Computer engine runs its agent on Boat and cannot use a self-hosted VPS — choose Claude or an ACP engine";
|
|
1210
|
-
}
|
|
1211
|
-
if (!computerMcp) {
|
|
1212
|
-
return "This model cannot mount a self-hosted VPS computer — choose Claude or an ACP model provider";
|
|
1213
|
-
}
|
|
1214
|
-
return null;
|
|
1215
|
-
}
|
|
1216
1206
|
export async function vpsComputerScreenshot(cfg, botId, runner = defaultRunner) {
|
|
1217
1207
|
cfg = snapshotVpsConfig(cfg);
|
|
1218
1208
|
const alias = vpsSshAlias(cfg);
|