openmausbot 0.1.90 → 0.1.91
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/{abnfDiagram-O67JEVCF-DUyXd0kL.js → abnfDiagram-O67JEVCF-yP-w8pRq.js} +1 -1
- package/dist/assets/{arc-q73qM-At.js → arc-DsivfZEh.js} +1 -1
- package/dist/assets/{architectureDiagram-NJMV4G6O-D33O3Wur.js → architectureDiagram-NJMV4G6O-CILw7lzT.js} +1 -1
- package/dist/assets/{blockDiagram-BEXU5L5S-DLsJeoXC.js → blockDiagram-BEXU5L5S-DEQUV3HF.js} +1 -1
- package/dist/assets/{c4Diagram-YGBWAQC7-DnaljZnu.js → c4Diagram-YGBWAQC7-lSXCZoKA.js} +1 -1
- package/dist/assets/channel-DrfGMu7X.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-Da0h2i79.js → chunk-2Q5K7J3B-QCD5bgWP.js} +1 -1
- package/dist/assets/{chunk-3FUC2YCW-CfzHi54f.js → chunk-3FUC2YCW-C5-iZJuL.js} +2 -2
- package/dist/assets/{chunk-5VM5RSS4-LjE4d9Jf.js → chunk-5VM5RSS4-cxsawMpY.js} +1 -1
- package/dist/assets/{chunk-GTNCS2PH-DY-UGmvc.js → chunk-GTNCS2PH-DV8tV8H7.js} +1 -1
- package/dist/assets/{chunk-GWA4HPMP-BqeGhyhP.js → chunk-GWA4HPMP-CJFZyfbW.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-BYa9EBAg.js → chunk-JWPE2WC7-BigbMXco.js} +1 -1
- package/dist/assets/{chunk-NETBCI7D-vTrDtWyb.js → chunk-NETBCI7D-oVxTfpDy.js} +1 -1
- package/dist/assets/{chunk-WEXAMYUT-ic5tXadM.js → chunk-WEXAMYUT-CH4BiroW.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-DvRCY-c6.js → chunk-XXDRQBXY-KEDa6gax.js} +1 -1
- package/dist/assets/{classDiagram-v2-NBCMYWYE-DVbmgz_L.js → classDiagram-v2-NBCMYWYE-BiPjfqVv.js} +1 -1
- package/dist/assets/{cose-bilkent-JH36ORCC-vdUs2n57.js → cose-bilkent-JH36ORCC-Caxew3OY.js} +1 -1
- package/dist/assets/{cynefin-OW5HDTMX-BvLGlV3U.js → cynefin-OW5HDTMX-B_Pui369.js} +1 -1
- package/dist/assets/{cynefinDiagram-VND7K2PF-C4GcJQDt.js → cynefinDiagram-VND7K2PF-X64ydIhP.js} +1 -1
- package/dist/assets/{dagre-6A5THRUB-BMSaHpt_.js → dagre-6A5THRUB-TqYCI1E1.js} +1 -1
- package/dist/assets/{diagram-22UHCM2B-D5b9UBhC.js → diagram-22UHCM2B-TeOt3rAQ.js} +1 -1
- package/dist/assets/{diagram-3UASUU5V-CIKLlB7Y.js → diagram-3UASUU5V-D70BLhDA.js} +1 -1
- package/dist/assets/{diagram-ATOU4E4O-BzXb-7k7.js → diagram-ATOU4E4O-BbQDLwUC.js} +1 -1
- package/dist/assets/{diagram-CDSNMT55-HsTvJ7uT.js → diagram-CDSNMT55-ChNJZ1u-.js} +1 -1
- package/dist/assets/{diagram-MLGK6HIB-Db9ySNbQ.js → diagram-MLGK6HIB-DMBl2APa.js} +1 -1
- package/dist/assets/{diagram-MPIPVDR6-D6BKbpqY.js → diagram-MPIPVDR6-CEqDchOp.js} +1 -1
- package/dist/assets/{ebnfDiagram-ZINNZB2B-Bq067L0t.js → ebnfDiagram-ZINNZB2B-BVDKRp_-.js} +1 -1
- package/dist/assets/{elk-276RUBZZ-DhaD7fNJ.js → elk-276RUBZZ-CRkofO9B.js} +1 -1
- package/dist/assets/{erDiagram-OPXOYQCR-fwIhfz36.js → erDiagram-OPXOYQCR-DruOMh5e.js} +1 -1
- package/dist/assets/{flowDiagram-KWPJA3E3-DygIyu0V.js → flowDiagram-KWPJA3E3-8GMQeB7t.js} +1 -1
- package/dist/assets/{ganttDiagram-FUAMR5RP-DPYfSO96.js → ganttDiagram-FUAMR5RP-ORE9jcsM.js} +1 -1
- package/dist/assets/{gitGraphDiagram-X574FWY7-DhkHeCle.js → gitGraphDiagram-X574FWY7-BmsedsWh.js} +1 -1
- package/dist/assets/index-BlnCEcU6.js +588 -0
- package/dist/assets/{index-G1PHKGyh.js → index-DKNEM8Q6.js} +1 -1
- package/dist/assets/index-DVxjOvID.css +1 -0
- package/dist/assets/{infoDiagram-VRGFBTTK-DstBLxME.js → infoDiagram-VRGFBTTK-E_p7AooJ.js} +1 -1
- package/dist/assets/{ishikawaDiagram-OU5B5YK6-CrIsGgch.js → ishikawaDiagram-OU5B5YK6-Y-B2mKda.js} +1 -1
- package/dist/assets/{journeyDiagram-ZHPQQLJL-CYypGDbB.js → journeyDiagram-ZHPQQLJL-eA8RELcK.js} +1 -1
- package/dist/assets/{kanban-definition-PNTS6WVX-DGjPt8E_.js → kanban-definition-PNTS6WVX-DqIL2e8e.js} +1 -1
- package/dist/assets/{linear-Dwc3Hrru.js → linear-yC_29R6e.js} +1 -1
- package/dist/assets/{mermaid.core-Dc4OePBL.js → mermaid.core-VhEMkSND.js} +5 -5
- package/dist/assets/{mindmap-definition-NLK3R4M7-DV-vepj-.js → mindmap-definition-NLK3R4M7-oJ7kyQZy.js} +1 -1
- package/dist/assets/{pegDiagram-GJSIUBJH-lx5Kj-n5.js → pegDiagram-GJSIUBJH-BmfYcY0O.js} +1 -1
- package/dist/assets/{pieDiagram-5QR66LMP-Bq7Vmm15.js → pieDiagram-5QR66LMP-DKvgP7w9.js} +1 -1
- package/dist/assets/{quadrantDiagram-O4NWA36T-DeqeqOBj.js → quadrantDiagram-O4NWA36T-BEKL3W0L.js} +1 -1
- package/dist/assets/{railroadDiagram-XR7U4H2S-CXwRQzSC.js → railroadDiagram-XR7U4H2S-BEsiqR5s.js} +1 -1
- package/dist/assets/{requirementDiagram-PLB6GJNP-8tAeoB88.js → requirementDiagram-PLB6GJNP-DWBcWvAP.js} +1 -1
- package/dist/assets/{sankeyDiagram-IPEJSGJF-BG5zjGWU.js → sankeyDiagram-IPEJSGJF-rpZ2_O4s.js} +1 -1
- package/dist/assets/{sequenceDiagram-PO4LG4MO-DKF3iLcw.js → sequenceDiagram-PO4LG4MO-B99HQdJR.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-B-y1qUgT.js → sizeCapture-INFHLROL-BjN0qT5T.js} +1 -1
- package/dist/assets/{stateDiagram-v2-GCMORJYK-MkzPgxAc.js → stateDiagram-v2-GCMORJYK-BbsVgGsC.js} +1 -1
- package/dist/assets/{swimlanes-2SLR337P-DnbaOEwH.js → swimlanes-2SLR337P-DG3gKkeR.js} +1 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-BCZhxl7e.js +8 -0
- package/dist/assets/{timeline-definition-EJHVYXUP-Bb4xG_Bt.js → timeline-definition-EJHVYXUP-BTHOdtF_.js} +1 -1
- package/dist/assets/{usecaseDiagram-POWQR4AR-JgkR9pFf.js → usecaseDiagram-POWQR4AR-acLSLFwc.js} +1 -1
- package/dist/assets/{vennDiagram-UO4OBE2U-CIPIrAYe.js → vennDiagram-UO4OBE2U-C5JzNX9G.js} +1 -1
- package/dist/assets/{wardleyDiagram-VNRHLVJA-BjCLuUvJ.js → wardleyDiagram-VNRHLVJA-DL1-PrR_.js} +1 -1
- package/dist/assets/{xychartDiagram-PMCCYNJV-DKgiaYxT.js → xychartDiagram-PMCCYNJV-Bvr1477p.js} +1 -1
- package/dist/index.html +2 -2
- package/dist-server/cloud-home-start.js +18 -3
- package/dist-server/container-mcp.js +84 -32
- package/dist-server/drivers/agents-proxy.js +11 -0
- package/dist-server/index.js +25183 -23580
- package/dist-server/openmausbot.js +313 -159
- package/dist-server/pair-cli.js +313 -159
- package/dist-server/server/boat.js +66 -21
- package/dist-server/server/bot-overview.js +7 -4
- package/dist-server/server/cloud-home-start.js +23 -5
- package/dist-server/server/cloud-home.js +25 -2
- package/dist-server/server/cloud-lending.js +103 -0
- package/dist-server/server/cloud-move-http.js +268 -0
- package/dist-server/server/cloud-move.js +418 -0
- package/dist-server/server/config.js +42 -4
- package/dist-server/server/decider/index.js +238 -0
- package/dist-server/server/decider/jev.js +183 -0
- package/dist-server/server/decider/log.js +62 -0
- package/dist-server/server/decider/relay.js +52 -0
- package/dist-server/server/decider/room-routing.js +88 -0
- package/dist-server/server/decider/types.js +5 -0
- package/dist-server/server/drivers/agents-catalog.js +16 -0
- package/dist-server/server/drivers/boatagent.js +20 -12
- package/dist-server/server/drivers/claude.js +1 -0
- package/dist-server/server/group-goal-run.js +6 -4
- package/dist-server/server/harness-capabilities.js +2 -0
- package/dist-server/server/included-services.js +70 -0
- package/dist-server/server/index.js +438 -102
- package/dist-server/server/model-context-window.js +4 -4
- package/dist-server/server/package-export.js +17 -10
- package/dist-server/server/package-import.js +6 -3
- package/dist-server/server/package-parts.js +3 -1
- package/dist-server/server/request-auth.js +2 -0
- package/dist-server/server/routes/decider.js +19 -0
- package/dist-server/server/shared-computers.js +116 -30
- package/dist-server/server/store.js +17 -2
- package/dist-server/server/surface.js +13 -0
- package/dist-server/server/system-prompt.js +11 -0
- package/dist-server/server/team-backup.js +6 -2
- package/dist-server/server/team-manifest.js +11 -3
- package/dist-server/server/tts/elevenlabs.js +11 -6
- package/dist-server/server/tts/index.js +17 -10
- package/dist-server/server/workspace-backup-policy.js +22 -3
- package/dist-server/server/workspace-backup.js +25 -8
- package/dist-server/shared/cloud-home.js +4 -0
- package/dist-server/shared/package-format.js +30 -4
- package/dist-server/shared/team-backup.js +20 -3
- package/dist-server/vps-container-mcp.js +84 -32
- package/dist-server/workspace-backup.worker.js +113 -31
- package/package.json +1 -1
- package/dist/assets/channel-BCuyHjFV.js +0 -1
- package/dist/assets/index-CRvo-DS2.css +0 -1
- package/dist/assets/index-CWYGlA63.js +0 -588
- package/dist/assets/swimlanesDiagram-TC7HE7FX-BZQ07DGE.js +0 -8
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { backupsBytes, beginUpload, CLOUD_MOVE_MAX_BYTES, CLOUD_MOVE_MAX_PART_BYTES, CLOUD_MOVE_PART_BYTES, completedUpload, discardNextPreviousCloud, discardUpload, forgetMoveRestore, freeVolumeBytes, isEmptyWorkspace, moveSpaceNeeded, noteMoveRestore, prepareNextPreviousCloud, previousCloud, previousCloudArchiveBytes, removeMoveFiles, stagePreviousCloud, tidyCloudMoveStorage, uploadStatus, validUploadDeclaration, workspaceContents, workspaceMoveSize, writeUploadPart, } from "./cloud-move.js";
|
|
3
|
+
import { commitPendingWorkspaceRestore, createWorkspaceBackup, stageWorkspaceBackup } from "./workspace-backup.js";
|
|
4
|
+
export const CLOUD_MOVE_PREFIX = "/api/cloud-move";
|
|
5
|
+
const failure = (message, status) => Object.assign(new Error(message), { status });
|
|
6
|
+
const HELD_CACHE_MS = 30_000;
|
|
7
|
+
export function createCloudMoveRoutes(options) {
|
|
8
|
+
const freeBytes = options.freeBytes ?? freeVolumeBytes;
|
|
9
|
+
// A move's restore that startup has installed keeps no safety copy or
|
|
10
|
+
// staged files; an abandoned upload goes after a day.
|
|
11
|
+
if (options.cloudHome) {
|
|
12
|
+
try {
|
|
13
|
+
tidyCloudMoveStorage(options.dataDir, options.restored);
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
console.warn(`cloud move: could not tidy backup storage (${error instanceof Error ? error.message : "unknown"})`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
// Staged here by a preview; restore accepts nothing else.
|
|
20
|
+
const staged = new Set();
|
|
21
|
+
let job = null;
|
|
22
|
+
let writing = false, discardPreview = false;
|
|
23
|
+
let held = null;
|
|
24
|
+
const json = (res, status, body) => {
|
|
25
|
+
res.writeHead(status, { "content-type": "application/json", "cache-control": "no-store" });
|
|
26
|
+
res.end(JSON.stringify(body));
|
|
27
|
+
};
|
|
28
|
+
const check = (req, auth) => {
|
|
29
|
+
if (!options.authorized(req, auth))
|
|
30
|
+
throw failure("Your session changed. Start the move again.", 403);
|
|
31
|
+
};
|
|
32
|
+
const ready = () => {
|
|
33
|
+
if (job?.state === "running" || writing)
|
|
34
|
+
throw failure("Another move step is running on your Cloud. Wait for it to finish.", 409);
|
|
35
|
+
const status = options.status();
|
|
36
|
+
if (status.pendingRestore)
|
|
37
|
+
throw failure("Your Cloud is restarting to finish a restore. Try again in a minute.", 409);
|
|
38
|
+
if (status.busy)
|
|
39
|
+
throw failure("Your Cloud is busy with a backup. Try again when it finishes.", 409);
|
|
40
|
+
};
|
|
41
|
+
/** Delete what earlier previews staged and no restore took. */
|
|
42
|
+
const dropStaged = () => {
|
|
43
|
+
for (const id of staged)
|
|
44
|
+
removeMoveFiles(options.dataDir, id);
|
|
45
|
+
staged.clear();
|
|
46
|
+
if (job?.kind === "preview" && job.state === "done")
|
|
47
|
+
job = null;
|
|
48
|
+
};
|
|
49
|
+
function start(kind, work, after) {
|
|
50
|
+
const current = { kind, state: "running" };
|
|
51
|
+
job = current;
|
|
52
|
+
held = null;
|
|
53
|
+
void work().then((result) => {
|
|
54
|
+
if (job !== current)
|
|
55
|
+
return;
|
|
56
|
+
job = result;
|
|
57
|
+
after?.(result);
|
|
58
|
+
}, (error) => {
|
|
59
|
+
if (job === current)
|
|
60
|
+
job = { kind, state: "failed", error: error instanceof Error ? error.message : "The move could not finish." };
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
// A person's own page can hold the workspace gate for a moment (an ordinary
|
|
64
|
+
// request in flight); try the gate a few times before giving up.
|
|
65
|
+
const quietly = async (work) => {
|
|
66
|
+
for (let attempt = 0;; attempt++) {
|
|
67
|
+
try {
|
|
68
|
+
return await options.exclusive(work, true);
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
if (attempt >= 4 || error.status !== 409)
|
|
72
|
+
throw error;
|
|
73
|
+
await new Promise((resolve) => setTimeout(resolve, options.gateRetryMs ?? 2_000));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
/** Back up the workspace about to be replaced, then commit `id`. On any
|
|
78
|
+
* failure nothing is left behind: not the new backup, not the staged files. */
|
|
79
|
+
async function replaceWith(id, req, auth) {
|
|
80
|
+
noteMoveRestore(options.dataDir, id);
|
|
81
|
+
try {
|
|
82
|
+
return await quietly(async () => {
|
|
83
|
+
check(req, auth);
|
|
84
|
+
const previous = await prepareNextPreviousCloud(options.dataDir, id, (password) => createWorkspaceBackup(options.dataDir, { password, appVersion: options.appVersion }));
|
|
85
|
+
return { committed: commitPendingWorkspaceRestore(options.dataDir, id), previous };
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
discardNextPreviousCloud(options.dataDir);
|
|
90
|
+
forgetMoveRestore(options.dataDir, id);
|
|
91
|
+
removeMoveFiles(options.dataDir, id);
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
staged.delete(id);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/** Room for staging `archive` bytes and installing them, plus a backup of the
|
|
99
|
+
* workspace they replace; 507 with both numbers otherwise. */
|
|
100
|
+
const roomFor = (archive, already = 0) => {
|
|
101
|
+
const needed = moveSpaceNeeded(archive, workspaceMoveSize(options.dataDir).bytes, true) - already;
|
|
102
|
+
const free = freeBytes(options.dataDir);
|
|
103
|
+
return free >= needed ? null : { error: "Your Cloud does not have enough free space for this.", freeBytes: free, neededBytes: needed };
|
|
104
|
+
};
|
|
105
|
+
// The answer that the restore is ready goes out first; then the launcher
|
|
106
|
+
// starts the server again, and startup installs the restore before anything else loads.
|
|
107
|
+
const restartSoon = () => { setTimeout(() => options.restart(), options.restartDelayMs ?? 1_500).unref?.(); };
|
|
108
|
+
return async (req, res, path, auth) => {
|
|
109
|
+
if (path !== CLOUD_MOVE_PREFIX && !path.startsWith(`${CLOUD_MOVE_PREFIX}/`))
|
|
110
|
+
return false;
|
|
111
|
+
const method = req.method ?? "GET";
|
|
112
|
+
try {
|
|
113
|
+
if (!auth.scopes.includes("admin"))
|
|
114
|
+
throw failure("Only the owner can move a workspace.", 403);
|
|
115
|
+
if (method === "GET" && path === `${CLOUD_MOVE_PREFIX}/estimate`) {
|
|
116
|
+
json(res, 200, { ...workspaceContents(options.dataDir), ...workspaceMoveSize(options.dataDir), maxBytes: CLOUD_MOVE_MAX_BYTES });
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
if (!options.cloudHome)
|
|
120
|
+
throw failure("Only an OMB Cloud home receives a moved workspace.", 404);
|
|
121
|
+
if (auth.kind !== "session")
|
|
122
|
+
throw failure("Move to Cloud needs the owner's signed-in app.", 403);
|
|
123
|
+
check(req, auth);
|
|
124
|
+
if (method === "GET" && path === CLOUD_MOVE_PREFIX) {
|
|
125
|
+
const contents = workspaceContents(options.dataDir);
|
|
126
|
+
if (!held || held.at + HELD_CACHE_MS < Date.now())
|
|
127
|
+
held = { at: Date.now(), bytes: backupsBytes(options.dataDir) };
|
|
128
|
+
json(res, 200, {
|
|
129
|
+
contents, empty: isEmptyWorkspace(contents), freeBytes: freeBytes(options.dataDir), maxBytes: CLOUD_MOVE_MAX_BYTES,
|
|
130
|
+
partBytes: CLOUD_MOVE_PART_BYTES, upload: uploadStatus(options.dataDir), previous: previousCloud(options.dataDir), job,
|
|
131
|
+
// What backups and the previous Cloud hold on the volume.
|
|
132
|
+
heldBytes: held.bytes,
|
|
133
|
+
...options.status(),
|
|
134
|
+
lastRestoreId: options.restored.restored ? options.restored.id ?? null : null,
|
|
135
|
+
rolledBackId: options.restored.rolledBack ? options.restored.id ?? null : null,
|
|
136
|
+
});
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
if (method === "POST" && path === `${CLOUD_MOVE_PREFIX}/upload`) {
|
|
140
|
+
const declared = validUploadDeclaration(z.object({ sha256: z.unknown(), bytes: z.unknown(), files: z.unknown().optional() }).parse(await options.readBody(req, 4096)));
|
|
141
|
+
ready();
|
|
142
|
+
// A new upload ends any earlier attempt: its staged files go, and a
|
|
143
|
+
// stored part (whichever file it was) counts as space about to be freed.
|
|
144
|
+
dropStaged();
|
|
145
|
+
const refused = roomFor(declared.bytes, uploadStatus(options.dataDir)?.received ?? 0);
|
|
146
|
+
if (refused) {
|
|
147
|
+
json(res, 507, refused);
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
check(req, auth);
|
|
151
|
+
held = null;
|
|
152
|
+
json(res, 200, { ...beginUpload(options.dataDir, declared), partBytes: CLOUD_MOVE_PART_BYTES });
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
const part = /^\/api\/cloud-move\/upload\/([a-f0-9]{64})$/.exec(path);
|
|
156
|
+
if (method === "PUT" && part) {
|
|
157
|
+
const offset = Number(new URL(req.url ?? "/", "http://cloud-move.invalid").searchParams.get("offset"));
|
|
158
|
+
const length = Number(req.headers["content-length"]);
|
|
159
|
+
if (!Number.isSafeInteger(length) || length <= 0 || length > CLOUD_MOVE_MAX_PART_BYTES)
|
|
160
|
+
throw failure("Send each part with its length, at most 64 MB.", 411);
|
|
161
|
+
ready();
|
|
162
|
+
writing = true;
|
|
163
|
+
let received;
|
|
164
|
+
try {
|
|
165
|
+
received = await writeUploadPart(options.dataDir, part[1], offset, length, req.iterator({ destroyOnReturn: false }));
|
|
166
|
+
}
|
|
167
|
+
finally {
|
|
168
|
+
writing = false;
|
|
169
|
+
}
|
|
170
|
+
json(res, 200, { received });
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
if (method === "POST" && path === `${CLOUD_MOVE_PREFIX}/preview`) {
|
|
174
|
+
const body = z.object({ sha256: z.string().regex(/^[a-f0-9]{64}$/), password: z.string().min(12).max(1024) }).parse(await options.readBody(req, 8192));
|
|
175
|
+
ready();
|
|
176
|
+
discardPreview = false;
|
|
177
|
+
start("preview", async () => {
|
|
178
|
+
const file = await completedUpload(options.dataDir, body.sha256);
|
|
179
|
+
let result;
|
|
180
|
+
try {
|
|
181
|
+
result = await stageWorkspaceBackup(options.dataDir, file, { password: body.password, currentAppVersion: options.appVersion });
|
|
182
|
+
}
|
|
183
|
+
finally {
|
|
184
|
+
discardUpload(options.dataDir);
|
|
185
|
+
}
|
|
186
|
+
if (discardPreview) {
|
|
187
|
+
removeMoveFiles(options.dataDir, result.id);
|
|
188
|
+
throw failure("The move was stopped; what it staged was removed.", 409);
|
|
189
|
+
}
|
|
190
|
+
try {
|
|
191
|
+
check(req, auth);
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
removeMoveFiles(options.dataDir, result.id);
|
|
195
|
+
throw error;
|
|
196
|
+
}
|
|
197
|
+
staged.add(result.id);
|
|
198
|
+
const { summary } = result;
|
|
199
|
+
return { kind: "preview", state: "done", id: result.id, summary: {
|
|
200
|
+
appVersion: summary.appVersion, files: summary.files, bytes: summary.bytes, bots: summary.bots,
|
|
201
|
+
groups: summary.groups, threads: summary.threads, messages: summary.messages,
|
|
202
|
+
} };
|
|
203
|
+
});
|
|
204
|
+
json(res, 202, { job });
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
if (method === "POST" && path === `${CLOUD_MOVE_PREFIX}/discard`) {
|
|
208
|
+
await options.readBody(req, 4096);
|
|
209
|
+
if (job?.state === "running" && job.kind !== "preview")
|
|
210
|
+
throw failure("Your Cloud is already replacing its workspace.", 409);
|
|
211
|
+
// A preview still running removes what it stages when it ends.
|
|
212
|
+
if (job?.state === "running")
|
|
213
|
+
discardPreview = true;
|
|
214
|
+
dropStaged();
|
|
215
|
+
held = null;
|
|
216
|
+
json(res, 200, { ok: true });
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
if (method === "POST" && path === `${CLOUD_MOVE_PREFIX}/restore`) {
|
|
220
|
+
const body = z.object({ id: z.string().uuid() }).parse(await options.readBody(req, 4096));
|
|
221
|
+
ready();
|
|
222
|
+
if (!staged.has(body.id))
|
|
223
|
+
throw failure("This move is no longer ready on your Cloud. Start it again.", 404);
|
|
224
|
+
start("restore", async () => {
|
|
225
|
+
const { committed, previous } = await replaceWith(body.id, req, auth);
|
|
226
|
+
return { kind: "restore", state: "done", id: committed.id, previous };
|
|
227
|
+
}, restartSoon);
|
|
228
|
+
json(res, 202, { job });
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
231
|
+
if (method === "POST" && path === `${CLOUD_MOVE_PREFIX}/undo`) {
|
|
232
|
+
await options.readBody(req, 4096);
|
|
233
|
+
ready();
|
|
234
|
+
if (!previousCloud(options.dataDir))
|
|
235
|
+
throw failure("There is no previous Cloud to restore.", 404);
|
|
236
|
+
const refused = roomFor(previousCloudArchiveBytes(options.dataDir));
|
|
237
|
+
if (refused) {
|
|
238
|
+
json(res, 507, refused);
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
241
|
+
dropStaged();
|
|
242
|
+
// A swap: what the Cloud has now becomes the previous Cloud, so this
|
|
243
|
+
// can be undone the same way.
|
|
244
|
+
start("undo", async () => {
|
|
245
|
+
const prepared = await stagePreviousCloud(options.dataDir, options.appVersion);
|
|
246
|
+
const { committed, previous } = await replaceWith(prepared.id, req, auth);
|
|
247
|
+
return { kind: "undo", state: "done", id: committed.id, previous };
|
|
248
|
+
}, restartSoon);
|
|
249
|
+
json(res, 202, { job });
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
json(res, 404, { error: "Unknown move operation." });
|
|
253
|
+
}
|
|
254
|
+
catch (error) {
|
|
255
|
+
if (res.headersSent) {
|
|
256
|
+
res.destroy();
|
|
257
|
+
return true;
|
|
258
|
+
}
|
|
259
|
+
const status = error instanceof z.ZodError ? 400 : error.status ?? 400;
|
|
260
|
+
const received = error.received;
|
|
261
|
+
json(res, status, {
|
|
262
|
+
error: error instanceof z.ZodError ? "Invalid move request." : error instanceof Error ? error.message : "The move failed.",
|
|
263
|
+
...(Number.isSafeInteger(received) ? { received } : {}),
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
return true;
|
|
267
|
+
};
|
|
268
|
+
}
|