@ai-ecoverse/slicc-agent 6.3.1 → 6.4.0
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/README.md +18 -2
- package/dist/agents.js.map +2 -2
- package/dist/freezer/index.js +124 -30
- package/dist/freezer/index.js.map +2 -2
- package/dist/freezer/v6-place.js +86 -0
- package/dist/freezer/v6-place.js.map +7 -0
- package/dist/freezer/v6-write.js +88 -0
- package/dist/freezer/v6-write.js.map +7 -0
- package/dist/freezer/v6.js +407 -26
- package/dist/freezer/v6.js.map +2 -2
- package/dist/freezer/zip.js +42 -18
- package/dist/freezer/zip.js.map +2 -2
- package/dist/kernel/accounts.js +5 -1
- package/dist/kernel/accounts.js.map +2 -2
- package/dist/main.js +9 -4
- package/dist/main.js.map +2 -2
- package/dist/mcp/accounts.js +7 -4
- package/dist/mcp/accounts.js.map +2 -2
- package/dist/mcp/credentials.js +10 -1
- package/dist/mcp/credentials.js.map +2 -2
- package/dist/opfs-pool.js +15 -6
- package/dist/opfs-pool.js.map +2 -2
- package/dist/secrets/fetch.js +5 -2
- package/dist/secrets/fetch.js.map +2 -2
- package/dist/secrets/records.js +65 -4
- package/dist/secrets/records.js.map +2 -2
- package/dist/settings.js +1 -0
- package/dist/settings.js.map +2 -2
- package/dist/spectrum/messages.js +11 -1
- package/dist/spectrum/messages.js.map +2 -2
- package/dist/types/agents.d.ts +12 -0
- package/dist/types/freezer/index.d.ts +11 -4
- package/dist/types/freezer/v6-place.d.ts +16 -0
- package/dist/types/freezer/v6-write.d.ts +18 -0
- package/dist/types/freezer/v6.d.ts +79 -7
- package/dist/types/freezer/zip.d.ts +8 -2
- package/dist/types/kernel/accounts.d.ts +1 -0
- package/dist/types/mcp/credentials.d.ts +5 -0
- package/dist/types/opfs-pool.d.ts +2 -0
- package/dist/types/secrets/fetch.d.ts +1 -1
- package/dist/types/secrets/records.d.ts +4 -1
- package/package.json +3 -3
package/dist/freezer/v6.js
CHANGED
|
@@ -1,9 +1,28 @@
|
|
|
1
|
+
import { replicatedState } from "@earendil-works/chord";
|
|
2
|
+
import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
|
|
3
|
+
import { configure } from "@earendil-works/pi-durable";
|
|
4
|
+
import { freeFolder, scoopId } from "../agents.js";
|
|
5
|
+
import { homeOf } from "../kernel/accounts.js";
|
|
6
|
+
import { quote } from "../kernel/env.js";
|
|
7
|
+
import { opfsFile } from "../opfs-pool.js";
|
|
8
|
+
import { TITLE_TURNS } from "../titles/extension.js";
|
|
9
|
+
import { place } from "./v6-place.js";
|
|
1
10
|
import {
|
|
2
11
|
checkLicks,
|
|
3
12
|
checkManifest,
|
|
4
13
|
checkTranscript
|
|
5
14
|
} from "./v6-schema.js";
|
|
6
|
-
import {
|
|
15
|
+
import { Writer } from "./v6-write.js";
|
|
16
|
+
import {
|
|
17
|
+
blobSource,
|
|
18
|
+
bytesSource,
|
|
19
|
+
ImportError,
|
|
20
|
+
isLink,
|
|
21
|
+
readEntry,
|
|
22
|
+
zipEntries
|
|
23
|
+
} from "./zip.js";
|
|
24
|
+
const V6_KIND = "slicc.v6";
|
|
25
|
+
const IMPORTS_DIR = "/var/lib/slicc/agent/imports";
|
|
7
26
|
const MiB = 1024 ** 2;
|
|
8
27
|
const LIMITS = {
|
|
9
28
|
archive: 2048 * MiB,
|
|
@@ -78,15 +97,15 @@ function scan(list, archive) {
|
|
|
78
97
|
throw new ImportError("the zip expands over 200:1, like a zip bomb");
|
|
79
98
|
return entries;
|
|
80
99
|
}
|
|
81
|
-
async function openProfile(
|
|
82
|
-
if (
|
|
83
|
-
const list = zipEntries(
|
|
100
|
+
async function openProfile(source) {
|
|
101
|
+
if (source.size > LIMITS.archive) throw new ImportError("the zip is over 2 GiB");
|
|
102
|
+
const list = await zipEntries(source);
|
|
84
103
|
if (list.length > LIMITS.entries)
|
|
85
104
|
throw new ImportError(`the zip has over ${LIMITS.entries} entries`);
|
|
86
105
|
if (list[0]?.name !== "manifest.json")
|
|
87
106
|
throw new ImportError("manifest.json must be the first entry");
|
|
88
|
-
const entries = scan(list,
|
|
89
|
-
const manifest = checkManifest(json(await readEntry(
|
|
107
|
+
const entries = scan(list, source.size);
|
|
108
|
+
const manifest = checkManifest(json(await readEntry(source, list[0]), "manifest.json"));
|
|
90
109
|
const listed = /* @__PURE__ */ new Set();
|
|
91
110
|
for (const file of manifest.files) {
|
|
92
111
|
if (entries.get(file.path)?.size !== file.size || file.path === "manifest.json")
|
|
@@ -101,7 +120,7 @@ async function openProfile(bytes) {
|
|
|
101
120
|
whereOf(manifest, name);
|
|
102
121
|
}
|
|
103
122
|
const { exportId, part } = manifest;
|
|
104
|
-
return { manifest, entries,
|
|
123
|
+
return { manifest, entries, source, key: part ? `${exportId}#${part.index}` : exportId };
|
|
105
124
|
}
|
|
106
125
|
const noLicks = { crontasks: [], tasks: [], webhooks: [] };
|
|
107
126
|
async function licksIn(opened, data) {
|
|
@@ -109,14 +128,14 @@ async function licksIn(opened, data) {
|
|
|
109
128
|
if (!name) return noLicks;
|
|
110
129
|
const entry = opened.entries.get(name);
|
|
111
130
|
if (!entry) throw new ImportError(`${name} is named in the manifest but not in the zip`);
|
|
112
|
-
return checkLicks(json(data?.get(name) ?? await readEntry(opened.
|
|
131
|
+
return checkLicks(json(data?.get(name) ?? await readEntry(opened.source, entry), name));
|
|
113
132
|
}
|
|
114
133
|
const UNITS = [
|
|
115
134
|
[1e9, "GB"],
|
|
116
135
|
[1e6, "MB"],
|
|
117
136
|
[1e3, "KB"]
|
|
118
137
|
];
|
|
119
|
-
async function previewOf(opened) {
|
|
138
|
+
async function previewOf(opened, state) {
|
|
120
139
|
const { manifest } = opened;
|
|
121
140
|
const licks = await licksIn(opened);
|
|
122
141
|
const bytes = manifest.files.filter((file) => file.path.startsWith("files/")).reduce((sum, file) => sum + file.size, 0);
|
|
@@ -150,6 +169,7 @@ async function previewOf(opened) {
|
|
|
150
169
|
flagged: manifest.flagged.length,
|
|
151
170
|
providers: manifest.providers,
|
|
152
171
|
mounts,
|
|
172
|
+
already: state.imports?.[opened.key]?.at ?? null,
|
|
153
173
|
text: `SLICC 6 profile from ${host}, exported ${day(manifest.createdAt)}: ${parts.join(", ")}. Re-enter in Settings: ${manifest.providers.join(", ") || "nothing"}. Not imported: ${plural(mounts.length, "mount")}.`
|
|
154
174
|
};
|
|
155
175
|
}
|
|
@@ -157,13 +177,18 @@ async function sha256(bytes) {
|
|
|
157
177
|
const digest = await crypto.subtle.digest("SHA-256", bytes);
|
|
158
178
|
return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
159
179
|
}
|
|
160
|
-
async function verify(opened) {
|
|
180
|
+
async function verify(opened, writer, stage, show) {
|
|
161
181
|
const data = /* @__PURE__ */ new Map();
|
|
162
|
-
|
|
163
|
-
|
|
182
|
+
const { files } = opened.manifest;
|
|
183
|
+
for (const [at, file] of files.entries()) {
|
|
184
|
+
writer.check();
|
|
185
|
+
const bytes = await readEntry(opened.source, opened.entries.get(file.path));
|
|
164
186
|
if (await sha256(bytes) !== file.sha256.toLowerCase())
|
|
165
187
|
throw new ImportError(`${file.path} doesn't match its sha256`);
|
|
166
|
-
|
|
188
|
+
const where = whereOf(opened.manifest, file.path);
|
|
189
|
+
if (where.kind === "data") data.set(file.path, bytes);
|
|
190
|
+
else if (where.kind !== "skip" && stage) await writer.put(`${stage}/${file.path}`, bytes, true);
|
|
191
|
+
show("checking", at + 1, files.length);
|
|
167
192
|
}
|
|
168
193
|
return data;
|
|
169
194
|
}
|
|
@@ -178,6 +203,71 @@ function transcriptIn(data, name) {
|
|
|
178
203
|
throw new ImportError(`${name}: ${error.message}`);
|
|
179
204
|
}
|
|
180
205
|
}
|
|
206
|
+
function draft(fields, when, created) {
|
|
207
|
+
const last = fields.main?.messages.at(-1)?.timestamp;
|
|
208
|
+
const frozenAt = [...when, last, created].map((value) => Date.parse(value ?? "")).find((at) => !Number.isNaN(at));
|
|
209
|
+
const tail = (fields.doc?.conversations ?? []).map((item) => item.messages.at(-1)?.id ?? "").join(",");
|
|
210
|
+
return {
|
|
211
|
+
...fields,
|
|
212
|
+
frozenAt,
|
|
213
|
+
tail,
|
|
214
|
+
cone: "",
|
|
215
|
+
fresh: true,
|
|
216
|
+
record: true,
|
|
217
|
+
suffix: "",
|
|
218
|
+
home: "",
|
|
219
|
+
roots: /* @__PURE__ */ new Set(),
|
|
220
|
+
files: /* @__PURE__ */ new Map()
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
function unitsOf(manifest, data) {
|
|
224
|
+
const cones = manifest.cones.map((cone) => {
|
|
225
|
+
const doc = transcriptIn(data, cone.transcript);
|
|
226
|
+
const all = doc?.conversations ?? [];
|
|
227
|
+
const main = all.find((item) => item.id === cone.conversation) ?? all.find((item) => item.kind === "cone");
|
|
228
|
+
const scoops = cone.scoops.map((scoop) => ({
|
|
229
|
+
name: scoop.name,
|
|
230
|
+
six: scoop.folder,
|
|
231
|
+
folder: "",
|
|
232
|
+
conversation: all.find((item) => item.id === scoop.conversation) ?? all.find((item) => item.kind === "scoop" && item.folder === scoop.folder)
|
|
233
|
+
}));
|
|
234
|
+
const title = doc?.session.title || cone.name;
|
|
235
|
+
return draft(
|
|
236
|
+
{
|
|
237
|
+
key: cone.key,
|
|
238
|
+
name: cone.name,
|
|
239
|
+
title,
|
|
240
|
+
six: cone.folder,
|
|
241
|
+
memory: cone.memory,
|
|
242
|
+
doc,
|
|
243
|
+
main,
|
|
244
|
+
scoops
|
|
245
|
+
},
|
|
246
|
+
[doc?.session.updatedAt],
|
|
247
|
+
manifest.createdAt
|
|
248
|
+
);
|
|
249
|
+
});
|
|
250
|
+
const frozen = manifest.frozen.map((item) => {
|
|
251
|
+
const doc = transcriptIn(data, item.transcript);
|
|
252
|
+
const [main, ...rest] = [...doc.conversations].sort(
|
|
253
|
+
(a, b) => Number(b.kind === "cone") - Number(a.kind === "cone")
|
|
254
|
+
);
|
|
255
|
+
const title = item.title || doc.session.title;
|
|
256
|
+
const scoops = rest.map((conversation) => ({
|
|
257
|
+
name: conversation.name,
|
|
258
|
+
six: null,
|
|
259
|
+
folder: "",
|
|
260
|
+
conversation
|
|
261
|
+
}));
|
|
262
|
+
const when = [item.frozenAt, doc.session.frozenAt, doc.session.updatedAt];
|
|
263
|
+
return draft(
|
|
264
|
+
{ key: `frozen:${item.id}`, name: title, title, six: null, memory: null, doc, main, scoops },
|
|
265
|
+
when,
|
|
266
|
+
manifest.createdAt
|
|
267
|
+
);
|
|
268
|
+
});
|
|
269
|
+
return [...cones, ...frozen];
|
|
270
|
+
}
|
|
181
271
|
function present(data, name) {
|
|
182
272
|
if (name && !data.has(name))
|
|
183
273
|
throw new ImportError(`${name} is named in the manifest but not in the zip`);
|
|
@@ -216,34 +306,325 @@ function related(manifest, data) {
|
|
|
216
306
|
}
|
|
217
307
|
for (const name of transcripts) attached(manifest, name, transcriptIn(data, name));
|
|
218
308
|
}
|
|
219
|
-
async function
|
|
309
|
+
async function parse(opened, data) {
|
|
220
310
|
for (const [name, bytes] of data)
|
|
221
311
|
if (name.startsWith("memory/") || name === opened.manifest.gitconfig) textOf(bytes, name);
|
|
222
312
|
related(opened.manifest, data);
|
|
223
|
-
await licksIn(opened, data);
|
|
313
|
+
return { data, licks: await licksIn(opened, data), units: unitsOf(opened.manifest, data) };
|
|
224
314
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
315
|
+
function prior(state, origin, key) {
|
|
316
|
+
const known = /* @__PURE__ */ new Set([
|
|
317
|
+
...Object.keys(state.cones),
|
|
318
|
+
...Object.values(state.frozen ?? {}).map((item) => item.cone)
|
|
319
|
+
]);
|
|
320
|
+
return Object.values(state.imports ?? {}).filter((item) => item.origin === origin && known.has(item.cones[key])).sort((a, b) => b.at - a.at).map((item) => ({ cone: item.cones[key], tail: item.tails[key] }))[0];
|
|
321
|
+
}
|
|
322
|
+
async function assign(deps, opened, units, again, date, context) {
|
|
323
|
+
const { agents } = deps;
|
|
324
|
+
const origin = opened.manifest.producer.origin;
|
|
325
|
+
let fresh = 0;
|
|
326
|
+
for (const unit of units) {
|
|
327
|
+
const before = again ? void 0 : prior(agents.state(), origin, unit.key);
|
|
328
|
+
if (!before) fresh += 1;
|
|
329
|
+
if (!before) continue;
|
|
330
|
+
unit.cone = before.cone;
|
|
331
|
+
unit.fresh = false;
|
|
332
|
+
unit.record = before.tail !== unit.tail;
|
|
333
|
+
if (unit.record) unit.title = `${unit.title} (updated)`;
|
|
334
|
+
if (agents.state().cones[before.cone]) unit.suffix = `.v6-${date}`;
|
|
335
|
+
}
|
|
336
|
+
const first = fresh ? await agents.update((_tx, doc) => {
|
|
337
|
+
doc.next += fresh;
|
|
338
|
+
return doc.next - fresh;
|
|
339
|
+
}, context) : 0;
|
|
340
|
+
const taken = { ...agents.state().scoops };
|
|
341
|
+
let next = first;
|
|
342
|
+
for (const unit of units) {
|
|
343
|
+
if (unit.fresh) unit.cone = `cone-${next++}`;
|
|
344
|
+
unit.home = homeOf(unit.cone);
|
|
345
|
+
for (const scoop of unit.scoops) {
|
|
346
|
+
scoop.folder = freeFolder({ scoops: taken }, scoop.name);
|
|
347
|
+
taken[scoopId(scoop.folder)] = {};
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
const ZERO = {
|
|
352
|
+
input: 0,
|
|
353
|
+
output: 0,
|
|
354
|
+
cacheRead: 0,
|
|
355
|
+
cacheWrite: 0,
|
|
356
|
+
totalTokens: 0,
|
|
357
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }
|
|
358
|
+
};
|
|
359
|
+
function piMessage(message, names, files) {
|
|
360
|
+
const timestamp = Date.parse(message.timestamp);
|
|
361
|
+
const texts = message.content.flatMap((part) => {
|
|
362
|
+
if (part.type === "text") return [{ type: "text", text: part.text }];
|
|
363
|
+
if (part.type === "tool-call") return [];
|
|
364
|
+
const file = files.get(part.attachmentId);
|
|
365
|
+
return [
|
|
366
|
+
{
|
|
367
|
+
type: "text",
|
|
368
|
+
text: file ? `[Attached file: ${file}]` : "[An attachment SLICC 6 did not export]"
|
|
369
|
+
}
|
|
370
|
+
];
|
|
371
|
+
});
|
|
372
|
+
if (message.role === "user") return { role: "user", content: texts, timestamp };
|
|
373
|
+
const id = message.toolCallId;
|
|
374
|
+
if (message.role === "tool-result")
|
|
375
|
+
return {
|
|
376
|
+
role: "toolResult",
|
|
377
|
+
toolCallId: id,
|
|
378
|
+
toolName: names.get(id) ?? "tool",
|
|
379
|
+
content: texts,
|
|
380
|
+
isError: message.isError === true,
|
|
381
|
+
timestamp
|
|
382
|
+
};
|
|
383
|
+
const calls = message.content.flatMap((part) => {
|
|
384
|
+
if (part.type !== "tool-call") return [];
|
|
385
|
+
names.set(part.id, part.name);
|
|
386
|
+
const input = part.input;
|
|
387
|
+
const object = typeof input === "object" && input !== null && !Array.isArray(input);
|
|
388
|
+
return [
|
|
389
|
+
{
|
|
390
|
+
type: "toolCall",
|
|
391
|
+
id: part.id,
|
|
392
|
+
name: part.name,
|
|
393
|
+
arguments: object ? input : { input }
|
|
394
|
+
}
|
|
395
|
+
];
|
|
396
|
+
});
|
|
397
|
+
const assistant = {
|
|
398
|
+
role: "assistant",
|
|
399
|
+
content: [...texts, ...calls],
|
|
400
|
+
api: message.model?.api ?? "slicc-6",
|
|
401
|
+
provider: message.model?.provider ?? "slicc-6",
|
|
402
|
+
model: message.model?.id ?? "unknown",
|
|
403
|
+
usage: message.usage ?? ZERO,
|
|
404
|
+
stopReason: calls.length ? "toolUse" : "stop",
|
|
405
|
+
timestamp
|
|
406
|
+
};
|
|
407
|
+
return assistant;
|
|
408
|
+
}
|
|
409
|
+
function entriesOf(conversation, files) {
|
|
410
|
+
const names = /* @__PURE__ */ new Map();
|
|
411
|
+
return (conversation?.messages ?? []).map((message) => ({
|
|
412
|
+
kind: V6_KIND,
|
|
413
|
+
data: { id: message.id },
|
|
414
|
+
model: [piMessage(message, names, files)]
|
|
415
|
+
}));
|
|
416
|
+
}
|
|
417
|
+
async function conversationIn(tx, conversation, unit, model) {
|
|
418
|
+
const record = await tx.createConversation({ ownership: { kind: "ownerless" } });
|
|
419
|
+
if (model) await configure(tx, record.id, { model });
|
|
420
|
+
for (const entry of entriesOf(conversation, unit.files)) await tx.appendEntry(record.id, entry);
|
|
421
|
+
return record.id;
|
|
422
|
+
}
|
|
423
|
+
function modelOf(conversation) {
|
|
424
|
+
const model = conversation?.messages.findLast((message) => message.model)?.model;
|
|
425
|
+
return model ? `${model.provider}/${model.id}` : "";
|
|
426
|
+
}
|
|
427
|
+
async function commit(deps, opened, units, at, context) {
|
|
428
|
+
const model = (await deps.agents.cone().agent(context)).model;
|
|
429
|
+
const origin = opened.manifest.producer.origin;
|
|
430
|
+
const last = TITLE_TURNS.at(-1);
|
|
431
|
+
return deps.agents.update(async (tx, doc) => {
|
|
432
|
+
const made = [];
|
|
433
|
+
for (const unit of units.filter((item) => item.record)) {
|
|
434
|
+
const frozenId = `frozen-${doc.frozenNext ?? 1}`;
|
|
435
|
+
doc.frozenNext = (doc.frozenNext ?? 1) + 1;
|
|
436
|
+
const scoops = [];
|
|
437
|
+
for (const scoop of unit.scoops) {
|
|
438
|
+
const id = scoopId(scoop.folder);
|
|
439
|
+
if (doc.scoops[id])
|
|
440
|
+
throw new ImportError(`the scoop folder ${scoop.folder} was taken during the import`);
|
|
441
|
+
const conversation = await conversationIn(tx, scoop.conversation, unit, model);
|
|
442
|
+
doc.scoops[id] = {
|
|
443
|
+
name: scoop.name,
|
|
444
|
+
folder: scoop.folder,
|
|
445
|
+
cone: unit.cone,
|
|
446
|
+
conversation,
|
|
447
|
+
anchor: 0,
|
|
448
|
+
role: null,
|
|
449
|
+
kind: "async",
|
|
450
|
+
parent: unit.cone,
|
|
451
|
+
depth: 1,
|
|
452
|
+
roots: null,
|
|
453
|
+
created: null,
|
|
454
|
+
dropped: null,
|
|
455
|
+
gone: true,
|
|
456
|
+
frozen: frozenId,
|
|
457
|
+
title: scoop.name,
|
|
458
|
+
titled: last
|
|
459
|
+
};
|
|
460
|
+
scoops.push(id);
|
|
461
|
+
}
|
|
462
|
+
doc.frozen ??= {};
|
|
463
|
+
doc.frozen[frozenId] = {
|
|
464
|
+
cone: unit.cone,
|
|
465
|
+
name: unit.name,
|
|
466
|
+
conversation: await conversationIn(tx, unit.main, unit, model),
|
|
467
|
+
title: unit.title,
|
|
468
|
+
model: modelOf(unit.main),
|
|
469
|
+
messages: (unit.main?.messages ?? []).filter((message) => message.role !== "tool-result").length,
|
|
470
|
+
frozenAt: unit.frozenAt,
|
|
471
|
+
scoops,
|
|
472
|
+
v6: { origin, key: unit.key, ...unit.suffix ? { suffix: unit.suffix } : {} }
|
|
473
|
+
};
|
|
474
|
+
made.push(frozenId);
|
|
475
|
+
}
|
|
476
|
+
doc.imports ??= {};
|
|
477
|
+
doc.imports[opened.key] = {
|
|
478
|
+
at,
|
|
479
|
+
origin,
|
|
480
|
+
cones: Object.fromEntries(units.map((unit) => [unit.key, unit.cone])),
|
|
481
|
+
tails: Object.fromEntries(units.map((unit) => [unit.key, unit.tail]))
|
|
482
|
+
};
|
|
483
|
+
return made;
|
|
484
|
+
}, context);
|
|
485
|
+
}
|
|
486
|
+
async function storageRoom() {
|
|
487
|
+
const estimate = await globalThis.navigator?.storage?.estimate?.().catch(() => void 0);
|
|
488
|
+
return estimate?.quota === void 0 ? void 0 : estimate.quota - (estimate.usage ?? 0);
|
|
489
|
+
}
|
|
490
|
+
async function importOpened(deps, opened, options, context) {
|
|
491
|
+
const now = (deps.now ?? Date.now)();
|
|
492
|
+
const date = day(now);
|
|
493
|
+
const writer = new Writer(deps.env, context, options.signal);
|
|
494
|
+
const stage = `${IMPORTS_DIR}/${opened.key.replace("#", "-part")}`;
|
|
495
|
+
const total = opened.manifest.files.reduce((sum, file) => sum + file.size, 0);
|
|
496
|
+
const room = await (deps.room ?? storageRoom)();
|
|
497
|
+
if (room !== void 0 && room < 2 * total)
|
|
498
|
+
throw new ImportError("there is not enough free storage for this import");
|
|
499
|
+
try {
|
|
500
|
+
await writer.sh(
|
|
501
|
+
`mkdir -p ${IMPORTS_DIR} && chmod 700 ${IMPORTS_DIR} && rm -rf ${quote(stage)} && mkdir -p ${quote(stage)}`
|
|
502
|
+
);
|
|
503
|
+
const data = await verify(opened, writer, stage, options.show);
|
|
504
|
+
const parsed = await parse(opened, data);
|
|
505
|
+
await assign(deps, opened, parsed.units, options.again, date, context);
|
|
506
|
+
const run = {
|
|
507
|
+
deps,
|
|
508
|
+
opened,
|
|
509
|
+
parsed,
|
|
510
|
+
writer,
|
|
511
|
+
stage,
|
|
512
|
+
date,
|
|
513
|
+
show: options.show,
|
|
514
|
+
fresh: /* @__PURE__ */ new Map(),
|
|
515
|
+
ready: /* @__PURE__ */ new Set()
|
|
516
|
+
};
|
|
517
|
+
let made;
|
|
518
|
+
try {
|
|
519
|
+
await place(run);
|
|
520
|
+
made = await commit(deps, opened, parsed.units, now, context);
|
|
521
|
+
} catch (error) {
|
|
522
|
+
await writer.rollback();
|
|
523
|
+
throw error;
|
|
524
|
+
}
|
|
525
|
+
const cones = [...new Set(parsed.units.map((unit) => unit.cone))];
|
|
526
|
+
const providers = opened.manifest.providers;
|
|
527
|
+
return {
|
|
528
|
+
done: true,
|
|
529
|
+
preview: null,
|
|
530
|
+
cones,
|
|
531
|
+
report: null,
|
|
532
|
+
text: `Imported ${plural(made.length, "frozen cone")} from ${opened.manifest.producer.origin}${made.length ? `: ${made.join(", ")}` : ""}.
|
|
533
|
+
Re-enter in Settings: ${providers.join(", ") || "nothing"}.
|
|
534
|
+
`,
|
|
535
|
+
error: null
|
|
536
|
+
};
|
|
537
|
+
} finally {
|
|
538
|
+
await deps.env.remove(stage, { recursive: true, force: true }, BACKGROUND_CONTEXT);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
async function sourceOf(deps, source, context) {
|
|
542
|
+
if ("bytes" in source) return bytesSource(source.bytes);
|
|
543
|
+
const { env } = deps;
|
|
544
|
+
const real = await env.canonicalPath(source.path, context);
|
|
545
|
+
const info = real.ok ? await env.fileInfo(real.value, context) : real;
|
|
228
546
|
if (!info.ok) throw new ImportError(`can't read ${source.path}: ${info.error.message}`);
|
|
229
547
|
if (info.value.size > LIMITS.archive) throw new ImportError("the zip is over 2 GiB");
|
|
230
|
-
const
|
|
548
|
+
const file = await opfsFile(info.value.path, deps.opfs);
|
|
549
|
+
if (file?.size === info.value.size) return blobSource(file);
|
|
550
|
+
if (info.value.size > LIMITS.file)
|
|
551
|
+
throw new ImportError(`${source.path} isn't on seven's own disk; copy the zip to /tmp first`);
|
|
552
|
+
const read = await env.readBinaryFile(info.value.path, context);
|
|
231
553
|
if (!read.ok) throw new ImportError(`can't read ${source.path}: ${read.error.message}`);
|
|
232
|
-
return read.value;
|
|
554
|
+
return bytesSource(read.value);
|
|
233
555
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
|
|
556
|
+
const IDLE = { phase: "idle", done: 0, total: 0, text: "" };
|
|
557
|
+
function createImporter(deps) {
|
|
558
|
+
const progress = replicatedState(IDLE);
|
|
559
|
+
let running;
|
|
560
|
+
const result = (fields) => ({
|
|
561
|
+
done: false,
|
|
562
|
+
preview: null,
|
|
563
|
+
cones: [],
|
|
564
|
+
report: null,
|
|
565
|
+
text: "",
|
|
566
|
+
error: null,
|
|
567
|
+
...fields
|
|
568
|
+
});
|
|
569
|
+
return {
|
|
570
|
+
progress,
|
|
571
|
+
cancel: () => running?.abort(),
|
|
572
|
+
async run(source, options, context) {
|
|
573
|
+
if (running) return result({ error: "an import is already running" });
|
|
574
|
+
const controller = new AbortController();
|
|
575
|
+
running = controller;
|
|
576
|
+
const show = (phase, done, total) => progress.replace(BACKGROUND_CONTEXT, {
|
|
577
|
+
phase,
|
|
578
|
+
done,
|
|
579
|
+
total,
|
|
580
|
+
text: phase === "checking" ? `Checking ${done}/${total}` : `Importing ${done}/${total}`
|
|
581
|
+
});
|
|
582
|
+
try {
|
|
583
|
+
const opened = await openProfile(await sourceOf(deps, source, context));
|
|
584
|
+
const preview = await previewOf(opened, deps.agents.state());
|
|
585
|
+
if (options.preview) {
|
|
586
|
+
const writer = new Writer(deps.env, context, controller.signal);
|
|
587
|
+
await parse(opened, await verify(opened, writer, null, show));
|
|
588
|
+
progress.replace(BACKGROUND_CONTEXT, IDLE);
|
|
589
|
+
return result({ preview, text: `${preview.text}
|
|
590
|
+
` });
|
|
591
|
+
}
|
|
592
|
+
if (preview.already !== null && !options.again)
|
|
593
|
+
return result({
|
|
594
|
+
preview,
|
|
595
|
+
text: `Already imported on ${day(preview.already)}; freezer import --again makes fresh copies.
|
|
596
|
+
`
|
|
597
|
+
});
|
|
598
|
+
const done = await importOpened(
|
|
599
|
+
deps,
|
|
600
|
+
opened,
|
|
601
|
+
{ again: options.again === true, signal: controller.signal, show },
|
|
602
|
+
context
|
|
603
|
+
);
|
|
604
|
+
progress.replace(BACKGROUND_CONTEXT, { ...progress.value, phase: "done", text: done.text });
|
|
605
|
+
return { ...done, preview };
|
|
606
|
+
} catch (error) {
|
|
607
|
+
const message = error instanceof ImportError ? error.message : `the import failed: ${error.message}`;
|
|
608
|
+
progress.replace(BACKGROUND_CONTEXT, { phase: "failed", done: 0, total: 0, text: message });
|
|
609
|
+
return result({ error: message });
|
|
610
|
+
} finally {
|
|
611
|
+
running = void 0;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
};
|
|
239
615
|
}
|
|
240
616
|
export {
|
|
617
|
+
IDLE,
|
|
618
|
+
IMPORTS_DIR,
|
|
241
619
|
LIMITS,
|
|
242
|
-
|
|
620
|
+
V6_KIND,
|
|
621
|
+
createImporter,
|
|
243
622
|
day,
|
|
623
|
+
entriesOf,
|
|
244
624
|
openProfile,
|
|
245
625
|
previewOf,
|
|
246
626
|
textOf,
|
|
627
|
+
unitsOf,
|
|
247
628
|
whereOf
|
|
248
629
|
};
|
|
249
630
|
//# sourceMappingURL=v6.js.map
|