@ai-ecoverse/slicc-agent 6.3.2 → 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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/freezer/v6.ts"],
4
- "sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport type { ExecutionEnv } from '@earendil-works/pi-durable/env';\nimport {\n checkLicks,\n checkManifest,\n checkTranscript,\n type Licks,\n type Manifest,\n type Transcript,\n} from './v6-schema.ts';\nimport { ImportError, isLink, readEntry, type ZipEntry, zipEntries } from './zip.ts';\n\nconst MiB = 1024 ** 2;\nexport const LIMITS = {\n archive: 2048 * MiB,\n entries: 60_000,\n file: 256 * MiB,\n transcript: 64 * MiB,\n total: 4096 * MiB,\n ratio: 200,\n};\n\nexport interface ImportPreview {\n exportId: string;\n origin: string;\n createdAt: string;\n cones: number;\n scoops: number;\n frozen: number;\n bytes: number;\n skills: string[];\n crons: number;\n webhooks: number;\n flagged: number;\n providers: string[];\n mounts: string[];\n text: string;\n}\n\nexport type ImportSource = { path: string } | { bytes: Uint8Array };\n\nexport type Opened = {\n manifest: Manifest;\n entries: Map<string, ZipEntry>;\n bytes: Uint8Array;\n key: string;\n};\n\nexport type Where =\n | { kind: 'data' | 'skip' | 'attachment' }\n | { kind: 'cone' | 'scoop' | 'home-user' | 'skill' | 'sprinkle'; folder: string; rest: string };\n\nexport const day = (at: number | string) => new Date(at).toISOString().slice(0, 10);\nconst plural = (count: number, word: string) => `${count} ${word}${count === 1 ? '' : 's'}`;\nconst decoder = new TextDecoder('utf-8', { fatal: true });\n\nexport function textOf(bytes: Uint8Array, name: string): string {\n try {\n return decoder.decode(bytes);\n } catch {\n throw new ImportError(`${name} is not UTF-8 text`);\n }\n}\n\nfunction json(bytes: Uint8Array, name: string): unknown {\n const text = textOf(bytes, name);\n try {\n return JSON.parse(text);\n } catch {\n throw new ImportError(`${name} is not JSON`);\n }\n}\n\nconst safe = (name: string) =>\n !/^\\/|^[A-Za-z]:|\\\\|\\0/.test(name) &&\n name.split('/').every((part) => part !== '' && part !== '.' && part !== '..');\n\nconst HOOKS = /(^|\\/)\\.git\\/hooks\\//;\n\nexport function whereOf(manifest: Manifest, name: string): Where {\n const where = placeOf(manifest, name);\n const tree = where.kind === 'cone' || where.kind === 'scoop' || where.kind === 'home-user';\n return tree && HOOKS.test(name) ? { kind: 'skip' } : where;\n}\n\nfunction placeOf(manifest: Manifest, name: string): Where {\n const [top = '', second = '', ...more] = name.split('/');\n const rest = more.join('/');\n if (\n ['transcripts', 'frozen', 'memory'].includes(top) ||\n [manifest.licks, manifest.gitconfig].includes(name)\n )\n return { kind: 'data' };\n if (top === 'attachments' && /^[0-9a-f]{64}$/.test(second) && !rest)\n return { kind: 'attachment' };\n if (top === 'files' && second === 'home-user' && rest)\n return { kind: 'home-user', folder: '', rest };\n const folder = more[0] as string;\n const inner = more.slice(1).join('/');\n if (\n top === 'files' &&\n second === 'cones' &&\n inner &&\n manifest.cones.some((cone) => cone.folder === folder)\n )\n return { kind: 'cone', folder, rest: inner };\n const scoops = manifest.cones.flatMap((cone) => cone.scoops);\n if (\n top === 'files' &&\n second === 'scoops' &&\n inner &&\n scoops.some((scoop) => scoop.folder === folder)\n )\n return { kind: 'scoop', folder, rest: inner };\n const listed = top === 'skills' ? manifest.skills : top === 'sprinkles' ? manifest.sprinkles : [];\n if (rest && listed.some((item) => item.name === second))\n return { kind: top === 'skills' ? 'skill' : 'sprinkle', folder: second, rest };\n throw new ImportError(`${name} has no place in a SLICC 6 profile`);\n}\n\nfunction scan(list: readonly ZipEntry[], archive: number): Map<string, ZipEntry> {\n const entries = new Map<string, ZipEntry>();\n let total = 0;\n for (const entry of list) {\n if (!safe(entry.name))\n throw new ImportError(`refused the entry name ${JSON.stringify(entry.name)}`);\n if (isLink(entry)) throw new ImportError(`${entry.name} is a symlink`);\n if (entries.has(entry.name)) throw new ImportError(`${entry.name} is in the zip twice`);\n const big = entry === list[0] || /^(transcripts|frozen)\\//.test(entry.name);\n const cap = big ? LIMITS.transcript : LIMITS.file;\n if (entry.size > cap) throw new ImportError(`${entry.name} is over ${cap / MiB} MiB`);\n total += entry.size;\n entries.set(entry.name, entry);\n }\n if (total > LIMITS.total) throw new ImportError('the files add up to over 4 GiB');\n if (total > LIMITS.ratio * archive)\n throw new ImportError('the zip expands over 200:1, like a zip bomb');\n return entries;\n}\n\nexport async function openProfile(bytes: Uint8Array): Promise<Opened> {\n if (bytes.length > LIMITS.archive) throw new ImportError('the zip is over 2 GiB');\n const list = zipEntries(bytes);\n if (list.length > LIMITS.entries)\n throw new ImportError(`the zip has over ${LIMITS.entries} entries`);\n if (list[0]?.name !== 'manifest.json')\n throw new ImportError('manifest.json must be the first entry');\n const entries = scan(list, bytes.length);\n const manifest = checkManifest(json(await readEntry(bytes, list[0]), 'manifest.json'));\n const listed = new Set<string>();\n for (const file of manifest.files) {\n if (entries.get(file.path)?.size !== file.size || file.path === 'manifest.json')\n throw new ImportError(\n `${file.path} is in the manifest but not in the zip, or not at that size`\n );\n listed.add(file.path);\n }\n for (const name of entries.keys()) {\n if (name === 'manifest.json') continue;\n if (!listed.has(name)) throw new ImportError(`${name} is not listed in the manifest`);\n whereOf(manifest, name);\n }\n const { exportId, part } = manifest;\n return { manifest, entries, bytes, key: part ? `${exportId}#${part.index}` : exportId };\n}\n\nconst noLicks: Licks = { crontasks: [], tasks: [], webhooks: [] };\n\nasync function licksIn(opened: Opened, data?: Map<string, Uint8Array>): Promise<Licks> {\n const name = opened.manifest.licks;\n if (!name) return noLicks;\n const entry = opened.entries.get(name);\n if (!entry) throw new ImportError(`${name} is named in the manifest but not in the zip`);\n return checkLicks(json(data?.get(name) ?? (await readEntry(opened.bytes, entry)), name));\n}\n\nconst UNITS = [\n [1e9, 'GB'],\n [1e6, 'MB'],\n [1e3, 'KB'],\n] as const;\n\nexport async function previewOf(opened: Opened): Promise<ImportPreview> {\n const { manifest } = opened;\n const licks = await licksIn(opened);\n const bytes = manifest.files\n .filter((file) => file.path.startsWith('files/'))\n .reduce((sum, file) => sum + file.size, 0);\n const scoops = manifest.cones.reduce((sum, cone) => sum + cone.scoops.length, 0);\n const mounts = manifest.skipped\n .filter((item) => item.reason === 'mount')\n .map((item) => item.path.replace(/^\\/mnt\\//, ''));\n const crons =\n licks.crontasks.length + licks.tasks.filter((task) => task.scheduleType === 'cron').length;\n const host = URL.canParse(manifest.producer.origin)\n ? new URL(manifest.producer.origin).host\n : manifest.producer.origin;\n const [at, unit] = UNITS.find(([at]) => bytes >= at) ?? (UNITS[2] as (typeof UNITS)[2]);\n const size = `${(bytes / at).toFixed(1)} ${unit}`;\n const parts = [\n plural(manifest.cones.length, 'cone'),\n plural(scoops, 'scoop'),\n plural(manifest.frozen.length, 'frozen session'),\n `${size} of files`,\n `${plural(manifest.skills.length, 'skill')} (for every agent)`,\n `${plural(crons, 'cron')} (imported off)`,\n `${plural(licks.webhooks.length, 'webhook')} (new URLs needed)`,\n `${plural(manifest.flagged.length, 'credential-like file')} to review`,\n ];\n return {\n exportId: manifest.exportId,\n origin: manifest.producer.origin,\n createdAt: manifest.createdAt,\n cones: manifest.cones.length,\n scoops,\n frozen: manifest.frozen.length,\n bytes,\n skills: manifest.skills.map((skill) => skill.name),\n crons,\n webhooks: licks.webhooks.length,\n flagged: manifest.flagged.length,\n providers: manifest.providers,\n mounts,\n 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')}.`,\n };\n}\n\nasync function sha256(bytes: Uint8Array): Promise<string> {\n const digest = await crypto.subtle.digest('SHA-256', bytes as Uint8Array<ArrayBuffer>);\n return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, '0')).join('');\n}\n\nasync function verify(opened: Opened): Promise<Map<string, Uint8Array>> {\n const data = new Map<string, Uint8Array>();\n for (const file of opened.manifest.files) {\n const bytes = await readEntry(opened.bytes, opened.entries.get(file.path) as ZipEntry);\n if ((await sha256(bytes)) !== file.sha256.toLowerCase())\n throw new ImportError(`${file.path} doesn't match its sha256`);\n if (whereOf(opened.manifest, file.path).kind === 'data') data.set(file.path, bytes);\n }\n return data;\n}\n\nfunction transcriptIn(data: Map<string, Uint8Array>, name: string | null): Transcript | undefined {\n if (!name) return undefined;\n const bytes = data.get(name);\n if (!bytes) throw new ImportError(`${name} is named in the manifest but not in the zip`);\n const value = json(bytes, name);\n try {\n return checkTranscript(value);\n } catch (error) {\n throw new ImportError(`${name}: ${(error as Error).message}`);\n }\n}\n\nfunction present(data: Map<string, Uint8Array>, name: string | null | undefined) {\n if (name && !data.has(name))\n throw new ImportError(`${name} is named in the manifest but not in the zip`);\n}\n\nfunction attached(manifest: Manifest, name: string, doc: Transcript) {\n const files = new Map(manifest.files.map((file) => [file.path, file]));\n for (const item of doc.attachments) {\n const file = files.get(item.path);\n const same =\n file !== undefined &&\n file.size === item.byteLength &&\n file.sha256.toLowerCase() === item.sha256.toLowerCase();\n if (item.present && !(item.path.startsWith('attachments/') && same))\n throw new ImportError(`${name}: attachment ${item.id} doesn't match ${item.path}`);\n }\n}\n\nfunction related(manifest: Manifest, data: Map<string, Uint8Array>) {\n const transcripts = [\n ...manifest.cones.map((cone) => cone.transcript),\n ...manifest.frozen.map((item) => item.transcript),\n ].filter((name): name is string => name !== null);\n const twice = transcripts.find((name, at) => transcripts.indexOf(name) !== at);\n if (twice) throw new ImportError(`${twice} is named twice in the manifest`);\n for (const name of [...manifest.cones.map((cone) => cone.memory), manifest.gitconfig])\n present(data, name);\n for (const [kind, items] of [\n ['skills', manifest.skills],\n ['sprinkles', manifest.sprinkles],\n ] as const)\n for (const item of items)\n if (\n item.path !== `${kind}/${item.name}/` ||\n !manifest.files.some((file) => file.path.startsWith(item.path))\n )\n throw new ImportError(`${item.path} is named in the manifest but has no files`);\n for (const cone of manifest.cones) {\n const doc = transcriptIn(data, cone.transcript);\n const ids = new Set(doc?.conversations.map((item) => item.id));\n for (const id of [cone.conversation, ...cone.scoops.map((scoop) => scoop.conversation)])\n if (id !== null && !ids.has(id))\n throw new ImportError(`${cone.transcript ?? cone.folder} has no conversation ${id}`);\n }\n for (const name of transcripts) attached(manifest, name, transcriptIn(data, name) as Transcript);\n}\n\nasync function checked(opened: Opened, data: Map<string, Uint8Array>): Promise<void> {\n for (const [name, bytes] of data)\n if (name.startsWith('memory/') || name === opened.manifest.gitconfig) textOf(bytes, name);\n related(opened.manifest, data);\n await licksIn(opened, data);\n}\n\nasync function bytesOf(\n env: ExecutionEnv,\n source: ImportSource,\n context: Context\n): Promise<Uint8Array> {\n if ('bytes' in source) return source.bytes;\n const info = await env.fileInfo(source.path, context);\n if (!info.ok) throw new ImportError(`can't read ${source.path}: ${info.error.message}`);\n if (info.value.size > LIMITS.archive) throw new ImportError('the zip is over 2 GiB');\n const read = await env.readBinaryFile(source.path, context);\n if (!read.ok) throw new ImportError(`can't read ${source.path}: ${read.error.message}`);\n return read.value;\n}\n\nexport async function checkProfile(\n env: ExecutionEnv,\n source: ImportSource,\n context: Context\n): Promise<ImportPreview> {\n const opened = await openProfile(await bytesOf(env, source, context));\n const preview = await previewOf(opened);\n await checked(opened, await verify(opened));\n return preview;\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { type Context, type ReplicatedState, replicatedState } from '@earendil-works/chord';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport type { AssistantMessage, Message, ToolCall } from '@earendil-works/pi-ai';\nimport { configure, type EntryDraft, type ModelRef, type Tx } from '@earendil-works/pi-durable';\nimport type { ExecutionEnv } from '@earendil-works/pi-durable/env';\nimport { type Agents, type AgentsState, freeFolder, type ScoopRecord, scoopId } from '../agents.ts';\nimport { homeOf } from '../kernel/accounts.ts';\nimport { quote } from '../kernel/env.ts';\nimport type { KernelUsers } from '../kernel/users.ts';\nimport type { Layout } from '../layout.ts';\nimport { opfsFile } from '../opfs-pool.ts';\nimport { TITLE_TURNS } from '../titles/extension.ts';\nimport { place } from './v6-place.ts';\nimport {\n checkLicks,\n checkManifest,\n checkTranscript,\n type Licks,\n type Manifest,\n type Transcript,\n type V6Conversation,\n type V6Message,\n} from './v6-schema.ts';\nimport { Writer } from './v6-write.ts';\nimport {\n blobSource,\n bytesSource,\n ImportError,\n isLink,\n readEntry,\n type ZipEntry,\n type ZipSource,\n zipEntries,\n} from './zip.ts';\n\nexport const V6_KIND = 'slicc.v6';\nexport const IMPORTS_DIR = '/var/lib/slicc/agent/imports';\nconst MiB = 1024 ** 2;\nexport const LIMITS = {\n archive: 2048 * MiB,\n entries: 60_000,\n file: 256 * MiB,\n transcript: 64 * MiB,\n total: 4096 * MiB,\n ratio: 200,\n};\n\nexport type ImportProgress = {\n phase: 'idle' | 'checking' | 'writing' | 'done' | 'failed';\n done: number;\n total: number;\n text: string;\n};\n\nexport interface ImportPreview {\n exportId: string;\n origin: string;\n createdAt: string;\n cones: number;\n scoops: number;\n frozen: number;\n bytes: number;\n skills: string[];\n crons: number;\n webhooks: number;\n flagged: number;\n providers: string[];\n mounts: string[];\n already: number | null;\n text: string;\n}\n\nexport interface ImportResult {\n done: boolean;\n preview: ImportPreview | null;\n cones: string[];\n report: string | null;\n text: string;\n error: string | null;\n}\n\nexport type ImportSource = { path: string } | { bytes: Uint8Array };\n\nexport interface ImportDeps {\n agents: Agents;\n env: ExecutionEnv;\n layout: Layout;\n users?: Pick<KernelUsers, 'add' | 'remove'>;\n envOf?: (cone: string) => Promise<ExecutionEnv | undefined>;\n opfs?: () => Promise<FileSystemDirectoryHandle>;\n room?: () => Promise<number | undefined>;\n now?: () => number;\n}\n\nexport type Opened = {\n manifest: Manifest;\n entries: Map<string, ZipEntry>;\n source: ZipSource;\n key: string;\n};\n\nexport type Where =\n | { kind: 'data' | 'skip' | 'attachment' }\n | { kind: 'cone' | 'scoop' | 'home-user' | 'skill' | 'sprinkle'; folder: string; rest: string };\n\nexport type Scoop = {\n name: string;\n six: string | null;\n folder: string;\n conversation?: V6Conversation;\n};\n\nexport type Unit = {\n key: string;\n name: string;\n title: string;\n six: string | null;\n memory: string | null;\n doc?: Transcript;\n main?: V6Conversation;\n scoops: Scoop[];\n frozenAt: number;\n tail: string;\n cone: string;\n fresh: boolean;\n record: boolean;\n suffix: string;\n home: string;\n roots: Set<string>;\n files: Map<string, string>;\n};\n\nexport type Parsed = { data: Map<string, Uint8Array>; licks: Licks; units: Unit[] };\n\nexport const day = (at: number | string) => new Date(at).toISOString().slice(0, 10);\nconst plural = (count: number, word: string) => `${count} ${word}${count === 1 ? '' : 's'}`;\nconst decoder = new TextDecoder('utf-8', { fatal: true });\n\nexport function textOf(bytes: Uint8Array, name: string): string {\n try {\n return decoder.decode(bytes);\n } catch {\n throw new ImportError(`${name} is not UTF-8 text`);\n }\n}\n\nfunction json(bytes: Uint8Array, name: string): unknown {\n const text = textOf(bytes, name);\n try {\n return JSON.parse(text);\n } catch {\n throw new ImportError(`${name} is not JSON`);\n }\n}\n\nconst safe = (name: string) =>\n !/^\\/|^[A-Za-z]:|\\\\|\\0/.test(name) &&\n name.split('/').every((part) => part !== '' && part !== '.' && part !== '..');\n\nconst HOOKS = /(^|\\/)\\.git\\/hooks\\//;\n\nexport function whereOf(manifest: Manifest, name: string): Where {\n const where = placeOf(manifest, name);\n const tree = where.kind === 'cone' || where.kind === 'scoop' || where.kind === 'home-user';\n return tree && HOOKS.test(name) ? { kind: 'skip' } : where;\n}\n\nfunction placeOf(manifest: Manifest, name: string): Where {\n const [top = '', second = '', ...more] = name.split('/');\n const rest = more.join('/');\n if (\n ['transcripts', 'frozen', 'memory'].includes(top) ||\n [manifest.licks, manifest.gitconfig].includes(name)\n )\n return { kind: 'data' };\n if (top === 'attachments' && /^[0-9a-f]{64}$/.test(second) && !rest)\n return { kind: 'attachment' };\n if (top === 'files' && second === 'home-user' && rest)\n return { kind: 'home-user', folder: '', rest };\n const folder = more[0] as string;\n const inner = more.slice(1).join('/');\n if (\n top === 'files' &&\n second === 'cones' &&\n inner &&\n manifest.cones.some((cone) => cone.folder === folder)\n )\n return { kind: 'cone', folder, rest: inner };\n const scoops = manifest.cones.flatMap((cone) => cone.scoops);\n if (\n top === 'files' &&\n second === 'scoops' &&\n inner &&\n scoops.some((scoop) => scoop.folder === folder)\n )\n return { kind: 'scoop', folder, rest: inner };\n const listed = top === 'skills' ? manifest.skills : top === 'sprinkles' ? manifest.sprinkles : [];\n if (rest && listed.some((item) => item.name === second))\n return { kind: top === 'skills' ? 'skill' : 'sprinkle', folder: second, rest };\n throw new ImportError(`${name} has no place in a SLICC 6 profile`);\n}\n\nfunction scan(list: readonly ZipEntry[], archive: number): Map<string, ZipEntry> {\n const entries = new Map<string, ZipEntry>();\n let total = 0;\n for (const entry of list) {\n if (!safe(entry.name))\n throw new ImportError(`refused the entry name ${JSON.stringify(entry.name)}`);\n if (isLink(entry)) throw new ImportError(`${entry.name} is a symlink`);\n if (entries.has(entry.name)) throw new ImportError(`${entry.name} is in the zip twice`);\n const big = entry === list[0] || /^(transcripts|frozen)\\//.test(entry.name);\n const cap = big ? LIMITS.transcript : LIMITS.file;\n if (entry.size > cap) throw new ImportError(`${entry.name} is over ${cap / MiB} MiB`);\n total += entry.size;\n entries.set(entry.name, entry);\n }\n if (total > LIMITS.total) throw new ImportError('the files add up to over 4 GiB');\n if (total > LIMITS.ratio * archive)\n throw new ImportError('the zip expands over 200:1, like a zip bomb');\n return entries;\n}\n\nexport async function openProfile(source: ZipSource): Promise<Opened> {\n if (source.size > LIMITS.archive) throw new ImportError('the zip is over 2 GiB');\n const list = await zipEntries(source);\n if (list.length > LIMITS.entries)\n throw new ImportError(`the zip has over ${LIMITS.entries} entries`);\n if (list[0]?.name !== 'manifest.json')\n throw new ImportError('manifest.json must be the first entry');\n const entries = scan(list, source.size);\n const manifest = checkManifest(json(await readEntry(source, list[0]), 'manifest.json'));\n const listed = new Set<string>();\n for (const file of manifest.files) {\n if (entries.get(file.path)?.size !== file.size || file.path === 'manifest.json')\n throw new ImportError(\n `${file.path} is in the manifest but not in the zip, or not at that size`\n );\n listed.add(file.path);\n }\n for (const name of entries.keys()) {\n if (name === 'manifest.json') continue;\n if (!listed.has(name)) throw new ImportError(`${name} is not listed in the manifest`);\n whereOf(manifest, name);\n }\n const { exportId, part } = manifest;\n return { manifest, entries, source, key: part ? `${exportId}#${part.index}` : exportId };\n}\n\nconst noLicks: Licks = { crontasks: [], tasks: [], webhooks: [] };\n\nasync function licksIn(opened: Opened, data?: Map<string, Uint8Array>): Promise<Licks> {\n const name = opened.manifest.licks;\n if (!name) return noLicks;\n const entry = opened.entries.get(name);\n if (!entry) throw new ImportError(`${name} is named in the manifest but not in the zip`);\n return checkLicks(json(data?.get(name) ?? (await readEntry(opened.source, entry)), name));\n}\n\nconst UNITS = [\n [1e9, 'GB'],\n [1e6, 'MB'],\n [1e3, 'KB'],\n] as const;\n\nexport async function previewOf(\n opened: Opened,\n state: Readonly<AgentsState>\n): Promise<ImportPreview> {\n const { manifest } = opened;\n const licks = await licksIn(opened);\n const bytes = manifest.files\n .filter((file) => file.path.startsWith('files/'))\n .reduce((sum, file) => sum + file.size, 0);\n const scoops = manifest.cones.reduce((sum, cone) => sum + cone.scoops.length, 0);\n const mounts = manifest.skipped\n .filter((item) => item.reason === 'mount')\n .map((item) => item.path.replace(/^\\/mnt\\//, ''));\n const crons =\n licks.crontasks.length + licks.tasks.filter((task) => task.scheduleType === 'cron').length;\n const host = URL.canParse(manifest.producer.origin)\n ? new URL(manifest.producer.origin).host\n : manifest.producer.origin;\n const [at, unit] = UNITS.find(([at]) => bytes >= at) ?? (UNITS[2] as (typeof UNITS)[2]);\n const size = `${(bytes / at).toFixed(1)} ${unit}`;\n const parts = [\n plural(manifest.cones.length, 'cone'),\n plural(scoops, 'scoop'),\n plural(manifest.frozen.length, 'frozen session'),\n `${size} of files`,\n `${plural(manifest.skills.length, 'skill')} (for every agent)`,\n `${plural(crons, 'cron')} (imported off)`,\n `${plural(licks.webhooks.length, 'webhook')} (new URLs needed)`,\n `${plural(manifest.flagged.length, 'credential-like file')} to review`,\n ];\n return {\n exportId: manifest.exportId,\n origin: manifest.producer.origin,\n createdAt: manifest.createdAt,\n cones: manifest.cones.length,\n scoops,\n frozen: manifest.frozen.length,\n bytes,\n skills: manifest.skills.map((skill) => skill.name),\n crons,\n webhooks: licks.webhooks.length,\n flagged: manifest.flagged.length,\n providers: manifest.providers,\n mounts,\n already: state.imports?.[opened.key]?.at ?? null,\n 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')}.`,\n };\n}\n\nasync function sha256(bytes: Uint8Array): Promise<string> {\n const digest = await crypto.subtle.digest('SHA-256', bytes as Uint8Array<ArrayBuffer>);\n return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, '0')).join('');\n}\n\ntype Show = (phase: ImportProgress['phase'], done: number, total: number) => void;\n\nasync function verify(opened: Opened, writer: Writer, stage: string | null, show: Show) {\n const data = new Map<string, Uint8Array>();\n const { files } = opened.manifest;\n for (const [at, file] of files.entries()) {\n writer.check();\n const bytes = await readEntry(opened.source, opened.entries.get(file.path) as ZipEntry);\n if ((await sha256(bytes)) !== file.sha256.toLowerCase())\n throw new ImportError(`${file.path} doesn't match its sha256`);\n const where = whereOf(opened.manifest, file.path);\n if (where.kind === 'data') data.set(file.path, bytes);\n else if (where.kind !== 'skip' && stage) await writer.put(`${stage}/${file.path}`, bytes, true);\n show('checking', at + 1, files.length);\n }\n return data;\n}\n\nfunction transcriptIn(data: Map<string, Uint8Array>, name: string | null): Transcript | undefined {\n if (!name) return undefined;\n const bytes = data.get(name);\n if (!bytes) throw new ImportError(`${name} is named in the manifest but not in the zip`);\n const value = json(bytes, name);\n try {\n return checkTranscript(value);\n } catch (error) {\n throw new ImportError(`${name}: ${(error as Error).message}`);\n }\n}\n\nfunction draft(\n fields: Pick<Unit, 'key' | 'name' | 'title' | 'six' | 'memory' | 'scoops'> & {\n doc?: Transcript;\n main?: V6Conversation;\n },\n when: (string | null | undefined)[],\n created: string\n): Unit {\n const last = fields.main?.messages.at(-1)?.timestamp;\n const frozenAt = [...when, last, created]\n .map((value) => Date.parse(value ?? ''))\n .find((at) => !Number.isNaN(at)) as number;\n const tail = (fields.doc?.conversations ?? [])\n .map((item) => item.messages.at(-1)?.id ?? '')\n .join(',');\n return {\n ...fields,\n frozenAt,\n tail,\n cone: '',\n fresh: true,\n record: true,\n suffix: '',\n home: '',\n roots: new Set(),\n files: new Map(),\n };\n}\n\nexport function unitsOf(manifest: Manifest, data: Map<string, Uint8Array>): Unit[] {\n const cones = manifest.cones.map((cone) => {\n const doc = transcriptIn(data, cone.transcript);\n const all = doc?.conversations ?? [];\n const main =\n all.find((item) => item.id === cone.conversation) ?? all.find((item) => item.kind === 'cone');\n const scoops = cone.scoops.map((scoop) => ({\n name: scoop.name,\n six: scoop.folder,\n folder: '',\n conversation:\n all.find((item) => item.id === scoop.conversation) ??\n all.find((item) => item.kind === 'scoop' && item.folder === scoop.folder),\n }));\n const title = doc?.session.title || cone.name;\n return draft(\n {\n key: cone.key,\n name: cone.name,\n title,\n six: cone.folder,\n memory: cone.memory,\n doc,\n main,\n scoops,\n },\n [doc?.session.updatedAt],\n manifest.createdAt\n );\n });\n const frozen = manifest.frozen.map((item) => {\n const doc = transcriptIn(data, item.transcript) as Transcript;\n const [main, ...rest] = [...doc.conversations].sort(\n (a, b) => Number(b.kind === 'cone') - Number(a.kind === 'cone')\n );\n const title = item.title || doc.session.title;\n const scoops = rest.map((conversation) => ({\n name: conversation.name,\n six: null,\n folder: '',\n conversation,\n }));\n const when = [item.frozenAt, doc.session.frozenAt, doc.session.updatedAt];\n return draft(\n { key: `frozen:${item.id}`, name: title, title, six: null, memory: null, doc, main, scoops },\n when,\n manifest.createdAt\n );\n });\n return [...cones, ...frozen];\n}\n\nfunction present(data: Map<string, Uint8Array>, name: string | null | undefined) {\n if (name && !data.has(name))\n throw new ImportError(`${name} is named in the manifest but not in the zip`);\n}\n\nfunction attached(manifest: Manifest, name: string, doc: Transcript) {\n const files = new Map(manifest.files.map((file) => [file.path, file]));\n for (const item of doc.attachments) {\n const file = files.get(item.path);\n const same =\n file !== undefined &&\n file.size === item.byteLength &&\n file.sha256.toLowerCase() === item.sha256.toLowerCase();\n if (item.present && !(item.path.startsWith('attachments/') && same))\n throw new ImportError(`${name}: attachment ${item.id} doesn't match ${item.path}`);\n }\n}\n\nfunction related(manifest: Manifest, data: Map<string, Uint8Array>) {\n const transcripts = [\n ...manifest.cones.map((cone) => cone.transcript),\n ...manifest.frozen.map((item) => item.transcript),\n ].filter((name): name is string => name !== null);\n const twice = transcripts.find((name, at) => transcripts.indexOf(name) !== at);\n if (twice) throw new ImportError(`${twice} is named twice in the manifest`);\n for (const name of [...manifest.cones.map((cone) => cone.memory), manifest.gitconfig])\n present(data, name);\n for (const [kind, items] of [\n ['skills', manifest.skills],\n ['sprinkles', manifest.sprinkles],\n ] as const)\n for (const item of items)\n if (\n item.path !== `${kind}/${item.name}/` ||\n !manifest.files.some((file) => file.path.startsWith(item.path))\n )\n throw new ImportError(`${item.path} is named in the manifest but has no files`);\n for (const cone of manifest.cones) {\n const doc = transcriptIn(data, cone.transcript);\n const ids = new Set(doc?.conversations.map((item) => item.id));\n for (const id of [cone.conversation, ...cone.scoops.map((scoop) => scoop.conversation)])\n if (id !== null && !ids.has(id))\n throw new ImportError(`${cone.transcript ?? cone.folder} has no conversation ${id}`);\n }\n for (const name of transcripts) attached(manifest, name, transcriptIn(data, name) as Transcript);\n}\n\nasync function parse(opened: Opened, data: Map<string, Uint8Array>): Promise<Parsed> {\n for (const [name, bytes] of data)\n if (name.startsWith('memory/') || name === opened.manifest.gitconfig) textOf(bytes, name);\n related(opened.manifest, data);\n return { data, licks: await licksIn(opened, data), units: unitsOf(opened.manifest, data) };\n}\n\nfunction prior(state: Readonly<AgentsState>, origin: string, key: string) {\n const known = new Set([\n ...Object.keys(state.cones),\n ...Object.values(state.frozen ?? {}).map((item) => item.cone),\n ]);\n return Object.values(state.imports ?? {})\n .filter((item) => item.origin === origin && known.has(item.cones[key] as string))\n .sort((a, b) => b.at - a.at)\n .map((item) => ({ cone: item.cones[key] as string, tail: item.tails[key] as string }))[0];\n}\n\nasync function assign(\n deps: ImportDeps,\n opened: Opened,\n units: Unit[],\n again: boolean,\n date: string,\n context: Context\n) {\n const { agents } = deps;\n const origin = opened.manifest.producer.origin;\n let fresh = 0;\n for (const unit of units) {\n const before = again ? undefined : prior(agents.state(), origin, unit.key);\n if (!before) fresh += 1;\n if (!before) continue;\n unit.cone = before.cone;\n unit.fresh = false;\n unit.record = before.tail !== unit.tail;\n if (unit.record) unit.title = `${unit.title} (updated)`;\n if (agents.state().cones[before.cone]) unit.suffix = `.v6-${date}`;\n }\n const first = fresh\n ? await agents.update((_tx, doc) => {\n doc.next += fresh;\n return doc.next - fresh;\n }, context)\n : 0;\n const taken: Record<string, ScoopRecord> = { ...agents.state().scoops };\n let next = first;\n for (const unit of units) {\n if (unit.fresh) unit.cone = `cone-${next++}`;\n unit.home = homeOf(unit.cone);\n for (const scoop of unit.scoops) {\n scoop.folder = freeFolder({ scoops: taken }, scoop.name);\n taken[scoopId(scoop.folder)] = {} as ScoopRecord;\n }\n }\n}\n\nconst ZERO = {\n input: 0,\n output: 0,\n cacheRead: 0,\n cacheWrite: 0,\n totalTokens: 0,\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n};\n\nfunction piMessage(\n message: V6Message,\n names: Map<string, string>,\n files: Map<string, string>\n): Message {\n const timestamp = Date.parse(message.timestamp);\n const texts = message.content.flatMap((part) => {\n if (part.type === 'text') return [{ type: 'text' as const, text: part.text }];\n if (part.type === 'tool-call') return [];\n const file = files.get(part.attachmentId);\n return [\n {\n type: 'text' as const,\n text: file ? `[Attached file: ${file}]` : '[An attachment SLICC 6 did not export]',\n },\n ];\n });\n if (message.role === 'user') return { role: 'user', content: texts, timestamp };\n const id = message.toolCallId as string;\n if (message.role === 'tool-result')\n return {\n role: 'toolResult',\n toolCallId: id,\n toolName: names.get(id) ?? 'tool',\n content: texts,\n isError: message.isError === true,\n timestamp,\n };\n const calls = message.content.flatMap((part) => {\n if (part.type !== 'tool-call') return [];\n names.set(part.id, part.name);\n const input = part.input;\n const object = typeof input === 'object' && input !== null && !Array.isArray(input);\n return [\n {\n type: 'toolCall' as const,\n id: part.id,\n name: part.name,\n arguments: (object ? input : { input }) as ToolCall['arguments'],\n },\n ];\n });\n const assistant: AssistantMessage = {\n role: 'assistant',\n content: [...texts, ...calls],\n api: message.model?.api ?? 'slicc-6',\n provider: message.model?.provider ?? 'slicc-6',\n model: message.model?.id ?? 'unknown',\n usage: (message.usage as typeof ZERO | undefined) ?? ZERO,\n stopReason: calls.length ? 'toolUse' : 'stop',\n timestamp,\n };\n return assistant;\n}\n\nexport function entriesOf(\n conversation: V6Conversation | undefined,\n files: Map<string, string>\n): EntryDraft[] {\n const names = new Map<string, string>();\n return (conversation?.messages ?? []).map((message) => ({\n kind: V6_KIND,\n data: { id: message.id },\n model: [piMessage(message, names, files)],\n }));\n}\n\nasync function conversationIn(\n tx: Tx,\n conversation: V6Conversation | undefined,\n unit: Unit,\n model: ModelRef | undefined\n) {\n const record = await tx.createConversation({ ownership: { kind: 'ownerless' } });\n if (model) await configure(tx, record.id, { model });\n for (const entry of entriesOf(conversation, unit.files)) await tx.appendEntry(record.id, entry);\n return record.id as number;\n}\n\nfunction modelOf(conversation: V6Conversation | undefined): string {\n const model = conversation?.messages.findLast((message) => message.model)?.model;\n return model ? `${model.provider}/${model.id}` : '';\n}\n\nasync function commit(\n deps: ImportDeps,\n opened: Opened,\n units: Unit[],\n at: number,\n context: Context\n) {\n const model = (await deps.agents.cone().agent(context)).model;\n const origin = opened.manifest.producer.origin;\n const last = TITLE_TURNS.at(-1) as number;\n return deps.agents.update(async (tx, doc) => {\n const made: string[] = [];\n for (const unit of units.filter((item) => item.record)) {\n const frozenId = `frozen-${doc.frozenNext ?? 1}`;\n doc.frozenNext = (doc.frozenNext ?? 1) + 1;\n const scoops: string[] = [];\n for (const scoop of unit.scoops) {\n const id = scoopId(scoop.folder);\n if (doc.scoops[id])\n throw new ImportError(`the scoop folder ${scoop.folder} was taken during the import`);\n const conversation = await conversationIn(tx, scoop.conversation, unit, model);\n doc.scoops[id] = {\n name: scoop.name,\n folder: scoop.folder,\n cone: unit.cone,\n conversation,\n anchor: 0,\n role: null,\n kind: 'async',\n parent: unit.cone,\n depth: 1,\n roots: null,\n created: null,\n dropped: null,\n gone: true,\n frozen: frozenId,\n title: scoop.name,\n titled: last,\n };\n scoops.push(id);\n }\n doc.frozen ??= {};\n doc.frozen[frozenId] = {\n cone: unit.cone,\n name: unit.name,\n conversation: await conversationIn(tx, unit.main, unit, model),\n title: unit.title,\n model: modelOf(unit.main),\n messages: (unit.main?.messages ?? []).filter((message) => message.role !== 'tool-result')\n .length,\n frozenAt: unit.frozenAt,\n scoops,\n v6: { origin, key: unit.key, ...(unit.suffix ? { suffix: unit.suffix } : {}) },\n };\n made.push(frozenId);\n }\n doc.imports ??= {};\n doc.imports[opened.key] = {\n at,\n origin,\n cones: Object.fromEntries(units.map((unit) => [unit.key, unit.cone])),\n tails: Object.fromEntries(units.map((unit) => [unit.key, unit.tail])),\n };\n return made;\n }, context);\n}\n\nasync function storageRoom(): Promise<number | undefined> {\n const estimate = await globalThis.navigator?.storage?.estimate?.().catch(() => undefined);\n return estimate?.quota === undefined ? undefined : estimate.quota - (estimate.usage ?? 0);\n}\n\nasync function importOpened(\n deps: ImportDeps,\n opened: Opened,\n options: { again: boolean; signal: AbortSignal; show: Show },\n context: Context\n): Promise<ImportResult> {\n const now = (deps.now ?? Date.now)();\n const date = day(now);\n const writer = new Writer(deps.env, context, options.signal);\n const stage = `${IMPORTS_DIR}/${opened.key.replace('#', '-part')}`;\n const total = opened.manifest.files.reduce((sum, file) => sum + file.size, 0);\n const room = await (deps.room ?? storageRoom)();\n if (room !== undefined && room < 2 * total)\n throw new ImportError('there is not enough free storage for this import');\n try {\n await writer.sh(\n `mkdir -p ${IMPORTS_DIR} && chmod 700 ${IMPORTS_DIR} && rm -rf ${quote(stage)} && mkdir -p ${quote(stage)}`\n );\n const data = await verify(opened, writer, stage, options.show);\n const parsed = await parse(opened, data);\n await assign(deps, opened, parsed.units, options.again, date, context);\n const run = {\n deps,\n opened,\n parsed,\n writer,\n stage,\n date,\n show: options.show,\n fresh: new Map(),\n ready: new Set<Unit>(),\n };\n let made: string[];\n try {\n await place(run);\n made = await commit(deps, opened, parsed.units, now, context);\n } catch (error) {\n await writer.rollback();\n throw error;\n }\n const cones = [...new Set(parsed.units.map((unit) => unit.cone))];\n const providers = opened.manifest.providers;\n return {\n done: true,\n preview: null,\n cones,\n report: null,\n text: `Imported ${plural(made.length, 'frozen cone')} from ${opened.manifest.producer.origin}${made.length ? `: ${made.join(', ')}` : ''}.\\nRe-enter in Settings: ${providers.join(', ') || 'nothing'}.\\n`,\n error: null,\n };\n } finally {\n await deps.env.remove(stage, { recursive: true, force: true }, BACKGROUND_CONTEXT);\n }\n}\n\nasync function sourceOf(\n deps: ImportDeps,\n source: ImportSource,\n context: Context\n): Promise<ZipSource> {\n if ('bytes' in source) return bytesSource(source.bytes);\n const { env } = deps;\n const real = await env.canonicalPath(source.path, context);\n const info = real.ok ? await env.fileInfo(real.value, context) : real;\n if (!info.ok) throw new ImportError(`can't read ${source.path}: ${info.error.message}`);\n if (info.value.size > LIMITS.archive) throw new ImportError('the zip is over 2 GiB');\n const file = await opfsFile(info.value.path, deps.opfs);\n if (file?.size === info.value.size) return blobSource(file);\n if (info.value.size > LIMITS.file)\n throw new ImportError(`${source.path} isn't on seven's own disk; copy the zip to /tmp first`);\n const read = await env.readBinaryFile(info.value.path, context);\n if (!read.ok) throw new ImportError(`can't read ${source.path}: ${read.error.message}`);\n return bytesSource(read.value);\n}\n\nexport interface Importer {\n readonly progress: ReplicatedState<ImportProgress>;\n run(\n source: ImportSource,\n options: { again?: boolean; preview?: boolean },\n context: Context\n ): Promise<ImportResult>;\n cancel(): void;\n}\n\nexport const IDLE: ImportProgress = { phase: 'idle', done: 0, total: 0, text: '' };\n\nexport function createImporter(deps: ImportDeps): Importer {\n const progress = replicatedState<ImportProgress>(IDLE);\n let running: AbortController | undefined;\n const result = (fields: Partial<ImportResult>): ImportResult => ({\n done: false,\n preview: null,\n cones: [],\n report: null,\n text: '',\n error: null,\n ...fields,\n });\n return {\n progress,\n cancel: () => running?.abort(),\n async run(source, options, context) {\n if (running) return result({ error: 'an import is already running' });\n const controller = new AbortController();\n running = controller;\n const show: Show = (phase, done, total) =>\n progress.replace(BACKGROUND_CONTEXT, {\n phase,\n done,\n total,\n text: phase === 'checking' ? `Checking ${done}/${total}` : `Importing ${done}/${total}`,\n });\n try {\n const opened = await openProfile(await sourceOf(deps, source, context));\n const preview = await previewOf(opened, deps.agents.state());\n if (options.preview) {\n const writer = new Writer(deps.env, context, controller.signal);\n await parse(opened, await verify(opened, writer, null, show));\n progress.replace(BACKGROUND_CONTEXT, IDLE);\n return result({ preview, text: `${preview.text}\\n` });\n }\n if (preview.already !== null && !options.again)\n return result({\n preview,\n text: `Already imported on ${day(preview.already)}; freezer import --again makes fresh copies.\\n`,\n });\n const done = await importOpened(\n deps,\n opened,\n { again: options.again === true, signal: controller.signal, show },\n context\n );\n progress.replace(BACKGROUND_CONTEXT, { ...progress.value, phase: 'done', text: done.text });\n return { ...done, preview };\n } catch (error) {\n const message =\n error instanceof ImportError\n ? error.message\n : `the import failed: ${(error as Error).message}`;\n progress.replace(BACKGROUND_CONTEXT, { phase: 'failed', done: 0, total: 0, text: message });\n return result({ error: message });\n } finally {\n running = undefined;\n }\n },\n };\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["at"]
7
7
  }
@@ -5,29 +5,51 @@ const CENTRAL = 33639248;
5
5
  const LOCAL = 67324752;
6
6
  const WIDE = 4294967295;
7
7
  const UNIX = 3;
8
+ const TAIL = 22 + 65535;
8
9
  const damaged = (what) => new ImportError(`the zip is damaged: ${what}`);
10
+ function bytesSource(bytes) {
11
+ return { size: bytes.length, read: async (at, length) => bytes.subarray(at, at + length) };
12
+ }
13
+ function blobSource(blob) {
14
+ return {
15
+ size: blob.size,
16
+ async read(at, length) {
17
+ const part = await blob.slice(at, at + length).arrayBuffer().catch(() => {
18
+ throw new ImportError("the zip changed or went away while it was being read");
19
+ });
20
+ return new Uint8Array(part);
21
+ }
22
+ };
23
+ }
9
24
  function viewOf(bytes) {
10
25
  return new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
11
26
  }
12
27
  function endOf(bytes, view) {
13
- for (let at = bytes.length - 22; at >= Math.max(0, bytes.length - 22 - 65535); at--)
14
- if (view.getUint32(at, true) === END) return at;
28
+ for (let at = bytes.length - 22; at >= 0; at--) if (view.getUint32(at, true) === END) return at;
15
29
  throw new ImportError("this is not a zip file");
16
30
  }
17
- function zipEntries(bytes) {
18
- const view = viewOf(bytes);
19
- const end = endOf(bytes, view);
20
- const count = view.getUint16(end + 10, true);
21
- let at = view.getUint32(end + 16, true);
22
- if (count === 65535 || at === WIDE) throw new ImportError("ZIP64 archives are not supported");
31
+ async function zipEntries(source) {
32
+ const from = Math.max(0, source.size - TAIL);
33
+ const tail = await source.read(from, source.size - from);
34
+ const end = endOf(tail, viewOf(tail));
35
+ const count = viewOf(tail).getUint16(end + 10, true);
36
+ const length = viewOf(tail).getUint32(end + 12, true);
37
+ const start = viewOf(tail).getUint32(end + 16, true);
38
+ if (count === 65535 || start === WIDE) throw new ImportError("ZIP64 archives are not supported");
39
+ if (start + length > from + end) throw damaged("a bad directory");
40
+ const directory = await source.read(start, length);
41
+ const view = viewOf(directory);
23
42
  const names = new TextDecoder("utf-8", { fatal: true });
24
43
  const out = [];
44
+ let at = 0;
25
45
  for (let n = 0; n < count; n++) {
26
- if (at + 46 > end || view.getUint32(at, true) !== CENTRAL) throw damaged("a bad directory");
27
- const length = view.getUint16(at + 28, true);
28
- const skip = length + view.getUint16(at + 30, true) + view.getUint16(at + 32, true);
46
+ if (at + 46 > directory.length || view.getUint32(at, true) !== CENTRAL)
47
+ throw damaged("a bad directory");
48
+ const named = view.getUint16(at + 28, true);
49
+ const skip = named + view.getUint16(at + 30, true) + view.getUint16(at + 32, true);
50
+ if (at + 46 + named > directory.length) throw damaged("a bad directory");
29
51
  const entry = {
30
- name: names.decode(bytes.subarray(at + 46, at + 46 + length)),
52
+ name: names.decode(directory.subarray(at + 46, at + 46 + named)),
31
53
  flags: view.getUint16(at + 8, true),
32
54
  method: view.getUint16(at + 10, true),
33
55
  compressed: view.getUint32(at + 20, true),
@@ -65,14 +87,14 @@ async function inflate(data, size, name) {
65
87
  if (filled !== size) throw damaged(`${name} is smaller than it says`);
66
88
  return out;
67
89
  }
68
- async function readEntry(bytes, entry) {
69
- const view = viewOf(bytes);
90
+ async function readEntry(source, entry) {
70
91
  if (entry.flags & 1) throw new ImportError(`${entry.name} is encrypted`);
71
- if (entry.offset + 30 > bytes.length || view.getUint32(entry.offset, true) !== LOCAL)
92
+ const head = await source.read(entry.offset, 30);
93
+ if (head.length < 30 || viewOf(head).getUint32(0, true) !== LOCAL)
72
94
  throw damaged(`no local header for ${entry.name}`);
73
- const start = entry.offset + 30 + view.getUint16(entry.offset + 26, true) + view.getUint16(entry.offset + 28, true);
74
- if (start + entry.compressed > bytes.length) throw damaged(`${entry.name} is cut off`);
75
- const data = bytes.subarray(start, start + entry.compressed);
95
+ const start = entry.offset + 30 + viewOf(head).getUint16(26, true) + viewOf(head).getUint16(28, true);
96
+ if (start + entry.compressed > source.size) throw damaged(`${entry.name} is cut off`);
97
+ const data = await source.read(start, entry.compressed);
76
98
  if (entry.method === 0) {
77
99
  if (entry.compressed !== entry.size) throw damaged(`${entry.name} has two sizes`);
78
100
  return data;
@@ -82,6 +104,8 @@ async function readEntry(bytes, entry) {
82
104
  }
83
105
  export {
84
106
  ImportError,
107
+ blobSource,
108
+ bytesSource,
85
109
  isLink,
86
110
  readEntry,
87
111
  zipEntries
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/freezer/zip.ts"],
4
- "sourcesContent": ["export class ImportError extends Error {}\n\nexport type ZipEntry = {\n name: string;\n method: number;\n flags: number;\n compressed: number;\n size: number;\n offset: number;\n mode: number;\n};\n\nconst END = 0x06054b50;\nconst CENTRAL = 0x02014b50;\nconst LOCAL = 0x04034b50;\nconst WIDE = 0xffffffff;\nconst UNIX = 3;\n\nconst damaged = (what: string) => new ImportError(`the zip is damaged: ${what}`);\n\nfunction viewOf(bytes: Uint8Array): DataView {\n return new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);\n}\n\nfunction endOf(bytes: Uint8Array, view: DataView): number {\n for (let at = bytes.length - 22; at >= Math.max(0, bytes.length - 22 - 0xffff); at--)\n if (view.getUint32(at, true) === END) return at;\n throw new ImportError('this is not a zip file');\n}\n\nexport function zipEntries(bytes: Uint8Array): ZipEntry[] {\n const view = viewOf(bytes);\n const end = endOf(bytes, view);\n const count = view.getUint16(end + 10, true);\n let at = view.getUint32(end + 16, true);\n if (count === 0xffff || at === WIDE) throw new ImportError('ZIP64 archives are not supported');\n const names = new TextDecoder('utf-8', { fatal: true });\n const out: ZipEntry[] = [];\n for (let n = 0; n < count; n++) {\n if (at + 46 > end || view.getUint32(at, true) !== CENTRAL) throw damaged('a bad directory');\n const length = view.getUint16(at + 28, true);\n const skip = length + view.getUint16(at + 30, true) + view.getUint16(at + 32, true);\n const entry = {\n name: names.decode(bytes.subarray(at + 46, at + 46 + length)),\n flags: view.getUint16(at + 8, true),\n method: view.getUint16(at + 10, true),\n compressed: view.getUint32(at + 20, true),\n size: view.getUint32(at + 24, true),\n offset: view.getUint32(at + 42, true),\n mode: view.getUint16(at + 4, true) >> 8 === UNIX ? view.getUint32(at + 38, true) >>> 16 : 0,\n };\n if (entry.size === WIDE || entry.compressed === WIDE || entry.offset === WIDE)\n throw new ImportError('ZIP64 archives are not supported');\n out.push(entry);\n at += 46 + skip;\n }\n return out;\n}\n\nexport function isLink(entry: ZipEntry): boolean {\n return (entry.mode & 0o170000) === 0o120000;\n}\n\nasync function inflate(data: Uint8Array, size: number, name: string): Promise<Uint8Array> {\n const stream = new Blob([data as Uint8Array<ArrayBuffer>])\n .stream()\n .pipeThrough(new DecompressionStream('deflate-raw'));\n const reader = stream.getReader();\n const out = new Uint8Array(size);\n let filled = 0;\n for (;;) {\n const { done, value } = await reader.read().catch(() => {\n throw damaged(`${name} does not inflate`);\n });\n if (done) break;\n if (filled + value.length > size) {\n await reader.cancel();\n throw damaged(`${name} is bigger than it says`);\n }\n out.set(value, filled);\n filled += value.length;\n }\n if (filled !== size) throw damaged(`${name} is smaller than it says`);\n return out;\n}\n\nexport async function readEntry(bytes: Uint8Array, entry: ZipEntry): Promise<Uint8Array> {\n const view = viewOf(bytes);\n if (entry.flags & 1) throw new ImportError(`${entry.name} is encrypted`);\n if (entry.offset + 30 > bytes.length || view.getUint32(entry.offset, true) !== LOCAL)\n throw damaged(`no local header for ${entry.name}`);\n const start =\n entry.offset +\n 30 +\n view.getUint16(entry.offset + 26, true) +\n view.getUint16(entry.offset + 28, true);\n if (start + entry.compressed > bytes.length) throw damaged(`${entry.name} is cut off`);\n const data = bytes.subarray(start, start + entry.compressed);\n if (entry.method === 0) {\n if (entry.compressed !== entry.size) throw damaged(`${entry.name} has two sizes`);\n return data;\n }\n if (entry.method !== 8) throw new ImportError(`${entry.name} uses compression ${entry.method}`);\n return inflate(data, entry.size, entry.name);\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["export class ImportError extends Error {}\n\nexport type ZipEntry = {\n name: string;\n method: number;\n flags: number;\n compressed: number;\n size: number;\n offset: number;\n mode: number;\n};\n\nexport interface ZipSource {\n readonly size: number;\n read(offset: number, length: number): Promise<Uint8Array>;\n}\n\nconst END = 0x06054b50;\nconst CENTRAL = 0x02014b50;\nconst LOCAL = 0x04034b50;\nconst WIDE = 0xffffffff;\nconst UNIX = 3;\nconst TAIL = 22 + 0xffff;\n\nconst damaged = (what: string) => new ImportError(`the zip is damaged: ${what}`);\n\nexport function bytesSource(bytes: Uint8Array): ZipSource {\n return { size: bytes.length, read: async (at, length) => bytes.subarray(at, at + length) };\n}\n\nexport function blobSource(blob: Blob): ZipSource {\n return {\n size: blob.size,\n async read(at, length) {\n const part = await blob\n .slice(at, at + length)\n .arrayBuffer()\n .catch(() => {\n throw new ImportError('the zip changed or went away while it was being read');\n });\n return new Uint8Array(part);\n },\n };\n}\n\nfunction viewOf(bytes: Uint8Array): DataView {\n return new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);\n}\n\nfunction endOf(bytes: Uint8Array, view: DataView): number {\n for (let at = bytes.length - 22; at >= 0; at--) if (view.getUint32(at, true) === END) return at;\n throw new ImportError('this is not a zip file');\n}\n\nexport async function zipEntries(source: ZipSource): Promise<ZipEntry[]> {\n const from = Math.max(0, source.size - TAIL);\n const tail = await source.read(from, source.size - from);\n const end = endOf(tail, viewOf(tail));\n const count = viewOf(tail).getUint16(end + 10, true);\n const length = viewOf(tail).getUint32(end + 12, true);\n const start = viewOf(tail).getUint32(end + 16, true);\n if (count === 0xffff || start === WIDE) throw new ImportError('ZIP64 archives are not supported');\n if (start + length > from + end) throw damaged('a bad directory');\n const directory = await source.read(start, length);\n const view = viewOf(directory);\n const names = new TextDecoder('utf-8', { fatal: true });\n const out: ZipEntry[] = [];\n let at = 0;\n for (let n = 0; n < count; n++) {\n if (at + 46 > directory.length || view.getUint32(at, true) !== CENTRAL)\n throw damaged('a bad directory');\n const named = view.getUint16(at + 28, true);\n const skip = named + view.getUint16(at + 30, true) + view.getUint16(at + 32, true);\n if (at + 46 + named > directory.length) throw damaged('a bad directory');\n const entry = {\n name: names.decode(directory.subarray(at + 46, at + 46 + named)),\n flags: view.getUint16(at + 8, true),\n method: view.getUint16(at + 10, true),\n compressed: view.getUint32(at + 20, true),\n size: view.getUint32(at + 24, true),\n offset: view.getUint32(at + 42, true),\n mode: view.getUint16(at + 4, true) >> 8 === UNIX ? view.getUint32(at + 38, true) >>> 16 : 0,\n };\n if (entry.size === WIDE || entry.compressed === WIDE || entry.offset === WIDE)\n throw new ImportError('ZIP64 archives are not supported');\n out.push(entry);\n at += 46 + skip;\n }\n return out;\n}\n\nexport function isLink(entry: ZipEntry): boolean {\n return (entry.mode & 0o170000) === 0o120000;\n}\n\nasync function inflate(data: Uint8Array, size: number, name: string): Promise<Uint8Array> {\n const stream = new Blob([data as Uint8Array<ArrayBuffer>])\n .stream()\n .pipeThrough(new DecompressionStream('deflate-raw'));\n const reader = stream.getReader();\n const out = new Uint8Array(size);\n let filled = 0;\n for (;;) {\n const { done, value } = await reader.read().catch(() => {\n throw damaged(`${name} does not inflate`);\n });\n if (done) break;\n if (filled + value.length > size) {\n await reader.cancel();\n throw damaged(`${name} is bigger than it says`);\n }\n out.set(value, filled);\n filled += value.length;\n }\n if (filled !== size) throw damaged(`${name} is smaller than it says`);\n return out;\n}\n\nexport async function readEntry(source: ZipSource, entry: ZipEntry): Promise<Uint8Array> {\n if (entry.flags & 1) throw new ImportError(`${entry.name} is encrypted`);\n const head = await source.read(entry.offset, 30);\n if (head.length < 30 || viewOf(head).getUint32(0, true) !== LOCAL)\n throw damaged(`no local header for ${entry.name}`);\n const start =\n entry.offset + 30 + viewOf(head).getUint16(26, true) + viewOf(head).getUint16(28, true);\n if (start + entry.compressed > source.size) throw damaged(`${entry.name} is cut off`);\n const data = await source.read(start, entry.compressed);\n if (entry.method === 0) {\n if (entry.compressed !== entry.size) throw damaged(`${entry.name} has two sizes`);\n return data;\n }\n if (entry.method !== 8) throw new ImportError(`${entry.name} uses compression ${entry.method}`);\n return inflate(data, entry.size, entry.name);\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -17,6 +17,9 @@ function userOf(state, agentId) {
17
17
  const record = state.scoops[agentId];
18
18
  return record ? scoopUser(record.folder) : void 0;
19
19
  }
20
+ function coneSpec(cone) {
21
+ return { name: cone, groups: [SUDO_GROUP], homeMode: CONE_HOME_MODE, umask: CONE_UMASK };
22
+ }
20
23
  function wanted(state) {
21
24
  const cones = /* @__PURE__ */ new Set([
22
25
  ...Object.keys(state.cones),
@@ -25,7 +28,7 @@ function wanted(state) {
25
28
  const out = [...cones].map((cone) => ({
26
29
  agentId: `${CONE_PREFIX}${cone}`,
27
30
  cone: null,
28
- spec: { name: cone, groups: [SUDO_GROUP], homeMode: CONE_HOME_MODE, umask: CONE_UMASK }
31
+ spec: coneSpec(cone)
29
32
  }));
30
33
  for (const [id, record] of Object.entries(state.scoops)) {
31
34
  if (record.gone || record.removed || record.dropped !== null) continue;
@@ -132,6 +135,7 @@ export {
132
135
  SCOOP_UMASK,
133
136
  SCOOP_USER,
134
137
  SUDO_GROUP,
138
+ coneSpec,
135
139
  createAccounts,
136
140
  homeOf,
137
141
  scoopUser,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/kernel/accounts.ts"],
4
- "sourcesContent": ["import type { Agents, AgentsState } from '../agents.ts';\nimport { callerOf } from '../scoops/identity.ts';\nimport type { KernelClient } from './client.ts';\nimport type { KernelGroups, UserClients, UserSpec } from './users.ts';\n\nexport type UsersMode = 'off' | 'report' | 'enforce';\n\nexport const SCOOP_UMASK = 0o027;\nexport const CONE_UMASK = 0o077;\nexport const CONE_HOME_MODE = 0o710;\nexport const SCOOP_HOME_MODE = 0o2750;\nexport const SUDO_GROUP = 'sudo';\nexport const CONE_PREFIX = 'cone:';\nexport const SCOOP_USER = 'scoop-';\n\nexport function scoopUser(folder: string): string {\n return `${SCOOP_USER}${folder}`;\n}\n\nexport function homeOf(user: string): string {\n return `/home/${user}`;\n}\n\nexport function userOf(state: Readonly<AgentsState>, agentId: string): string | undefined {\n if (agentId.startsWith(CONE_PREFIX)) return agentId.slice(CONE_PREFIX.length);\n const record = state.scoops[agentId];\n return record ? scoopUser(record.folder) : undefined;\n}\n\nexport type Wanted = { agentId: string; cone: string | null; spec: UserSpec };\n\nexport function wanted(state: Readonly<AgentsState>): Wanted[] {\n const cones = new Set([\n ...Object.keys(state.cones),\n ...Object.values(state.frozen ?? {}).map((frozen) => frozen.cone),\n ]);\n const out: Wanted[] = [...cones].map((cone) => ({\n agentId: `${CONE_PREFIX}${cone}`,\n cone: null,\n spec: { name: cone, groups: [SUDO_GROUP], homeMode: CONE_HOME_MODE, umask: CONE_UMASK },\n }));\n for (const [id, record] of Object.entries(state.scoops)) {\n if (record.gone || record.removed || record.dropped !== null) continue;\n out.push({\n agentId: id,\n cone: `${CONE_PREFIX}${record.cone}`,\n spec: {\n name: scoopUser(record.folder),\n groups: [record.cone],\n homeGroup: record.cone,\n homeMode: SCOOP_HOME_MODE,\n umask: SCOOP_UMASK,\n },\n });\n }\n return out;\n}\n\nexport interface Accounts {\n readonly mode: UsersMode;\n agentOf(conversation: number | undefined): string | undefined;\n home(agentId: string): string | undefined;\n client(agentId: string): Promise<KernelClient>;\n reconcile(): Promise<void>;\n boot(): Promise<void>;\n}\n\nexport function createAccounts(\n pool: UserClients,\n agents: () => Agents | undefined,\n mode: UsersMode = 'off',\n groups?: KernelGroups,\n warn: (message: string) => void = (message) => console.warn(message)\n): Accounts {\n const added = new Map<string, { name: string; group: string | null; ready: Promise<void> }>();\n let chain: Promise<void> = Promise.resolve();\n const state = (): Readonly<AgentsState> =>\n agents()?.state() ?? { cones: {}, active: 'cone', next: 2, scoops: {} };\n const groupOf = (want: Wanted) => (want.cone ? want.cone.slice(CONE_PREFIX.length) : null);\n const moved = async (want: Wanted, from: string) => {\n await groups?.removeMember(from, want.spec.name).catch(() => undefined);\n await pool.drop(want.agentId);\n };\n const ensure = (want: Wanted): Promise<void> => {\n const known = added.get(want.agentId);\n const group = groupOf(want);\n if (known?.name === want.spec.name && known.group === group) return known.ready;\n const parent = want.cone\n ? wanted(state()).find((cone) => cone.agentId === want.cone)\n : undefined;\n if (want.cone && !parent)\n return Promise.reject(\n Object.assign(new Error(`${want.spec.name} has no cone user: there is no cone ${group}`), {\n code: 'ENOENT',\n })\n );\n const before = known?.group && known.group !== group ? moved(want, known.group) : undefined;\n const ready = Promise.all([parent ? ensure(parent) : undefined, before])\n .then(() => pool.users.add(want.spec))\n .then(() => undefined);\n added.set(want.agentId, { name: want.spec.name, group, ready });\n ready.catch(() => {\n if (added.get(want.agentId)?.ready === ready) added.delete(want.agentId);\n });\n return ready;\n };\n const retire = async (agentId: string, name: string) => {\n added.delete(agentId);\n await pool.drop(agentId);\n await pool.users.remove(name).catch(() => false);\n };\n const reconcile = async (stale: readonly string[] = []) => {\n const want = wanted(state());\n const names = new Set(want.map((entry) => entry.agentId));\n for (const entry of want)\n await ensure(entry).catch((error: Error) =>\n warn(`slicc-agent: couldn't add the kernel user ${entry.spec.name}: ${error.message}`)\n );\n const gone = [...added].filter(([agentId]) => !names.has(agentId));\n const scoopsFirst = gone.sort(\n ([a], [b]) => Number(a.startsWith(CONE_PREFIX)) - Number(b.startsWith(CONE_PREFIX))\n );\n for (const [agentId, entry] of scoopsFirst) await retire(agentId, entry.name);\n for (const name of stale) await pool.users.remove(name).catch(() => false);\n };\n const queue = (run: () => Promise<void>) => {\n chain = chain.then(run, run);\n return chain;\n };\n const home = (agentId: string) => {\n const user = userOf(state(), agentId);\n return user === undefined ? undefined : homeOf(user);\n };\n return {\n mode,\n agentOf(conversation) {\n const current = agents();\n return (\n callerOf(current, conversation) ??\n (current ? `${CONE_PREFIX}${current.activeCone()}` : undefined)\n );\n },\n home,\n async client(agentId) {\n const want = wanted(state()).find((entry) => entry.agentId === agentId);\n if (!want)\n throw Object.assign(new Error(`${agentId} has no kernel user`), { code: 'ENOENT' });\n await ensure(want);\n return pool.client(agentId, want.spec.name);\n },\n reconcile: () => queue(() => reconcile()),\n boot: () =>\n queue(async () => {\n const known = new Set(\n Object.values(state().scoops).map((record) => scoopUser(record.folder))\n );\n const keep = new Set(wanted(state()).map((entry) => entry.spec.name));\n const listed = await pool.users.list().catch(() => []);\n const stale = listed\n .map((user) => user.name)\n .filter((name) => known.has(name) && !keep.has(name));\n await reconcile(stale);\n }),\n };\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { Agents, AgentsState } from '../agents.ts';\nimport { callerOf } from '../scoops/identity.ts';\nimport type { KernelClient } from './client.ts';\nimport type { KernelGroups, UserClients, UserSpec } from './users.ts';\n\nexport type UsersMode = 'off' | 'report' | 'enforce';\n\nexport const SCOOP_UMASK = 0o027;\nexport const CONE_UMASK = 0o077;\nexport const CONE_HOME_MODE = 0o710;\nexport const SCOOP_HOME_MODE = 0o2750;\nexport const SUDO_GROUP = 'sudo';\nexport const CONE_PREFIX = 'cone:';\nexport const SCOOP_USER = 'scoop-';\n\nexport function scoopUser(folder: string): string {\n return `${SCOOP_USER}${folder}`;\n}\n\nexport function homeOf(user: string): string {\n return `/home/${user}`;\n}\n\nexport function userOf(state: Readonly<AgentsState>, agentId: string): string | undefined {\n if (agentId.startsWith(CONE_PREFIX)) return agentId.slice(CONE_PREFIX.length);\n const record = state.scoops[agentId];\n return record ? scoopUser(record.folder) : undefined;\n}\n\nexport function coneSpec(cone: string): UserSpec {\n return { name: cone, groups: [SUDO_GROUP], homeMode: CONE_HOME_MODE, umask: CONE_UMASK };\n}\n\nexport type Wanted = { agentId: string; cone: string | null; spec: UserSpec };\n\nexport function wanted(state: Readonly<AgentsState>): Wanted[] {\n const cones = new Set([\n ...Object.keys(state.cones),\n ...Object.values(state.frozen ?? {}).map((frozen) => frozen.cone),\n ]);\n const out: Wanted[] = [...cones].map((cone) => ({\n agentId: `${CONE_PREFIX}${cone}`,\n cone: null,\n spec: coneSpec(cone),\n }));\n for (const [id, record] of Object.entries(state.scoops)) {\n if (record.gone || record.removed || record.dropped !== null) continue;\n out.push({\n agentId: id,\n cone: `${CONE_PREFIX}${record.cone}`,\n spec: {\n name: scoopUser(record.folder),\n groups: [record.cone],\n homeGroup: record.cone,\n homeMode: SCOOP_HOME_MODE,\n umask: SCOOP_UMASK,\n },\n });\n }\n return out;\n}\n\nexport interface Accounts {\n readonly mode: UsersMode;\n agentOf(conversation: number | undefined): string | undefined;\n home(agentId: string): string | undefined;\n client(agentId: string): Promise<KernelClient>;\n reconcile(): Promise<void>;\n boot(): Promise<void>;\n}\n\nexport function createAccounts(\n pool: UserClients,\n agents: () => Agents | undefined,\n mode: UsersMode = 'off',\n groups?: KernelGroups,\n warn: (message: string) => void = (message) => console.warn(message)\n): Accounts {\n const added = new Map<string, { name: string; group: string | null; ready: Promise<void> }>();\n let chain: Promise<void> = Promise.resolve();\n const state = (): Readonly<AgentsState> =>\n agents()?.state() ?? { cones: {}, active: 'cone', next: 2, scoops: {} };\n const groupOf = (want: Wanted) => (want.cone ? want.cone.slice(CONE_PREFIX.length) : null);\n const moved = async (want: Wanted, from: string) => {\n await groups?.removeMember(from, want.spec.name).catch(() => undefined);\n await pool.drop(want.agentId);\n };\n const ensure = (want: Wanted): Promise<void> => {\n const known = added.get(want.agentId);\n const group = groupOf(want);\n if (known?.name === want.spec.name && known.group === group) return known.ready;\n const parent = want.cone\n ? wanted(state()).find((cone) => cone.agentId === want.cone)\n : undefined;\n if (want.cone && !parent)\n return Promise.reject(\n Object.assign(new Error(`${want.spec.name} has no cone user: there is no cone ${group}`), {\n code: 'ENOENT',\n })\n );\n const before = known?.group && known.group !== group ? moved(want, known.group) : undefined;\n const ready = Promise.all([parent ? ensure(parent) : undefined, before])\n .then(() => pool.users.add(want.spec))\n .then(() => undefined);\n added.set(want.agentId, { name: want.spec.name, group, ready });\n ready.catch(() => {\n if (added.get(want.agentId)?.ready === ready) added.delete(want.agentId);\n });\n return ready;\n };\n const retire = async (agentId: string, name: string) => {\n added.delete(agentId);\n await pool.drop(agentId);\n await pool.users.remove(name).catch(() => false);\n };\n const reconcile = async (stale: readonly string[] = []) => {\n const want = wanted(state());\n const names = new Set(want.map((entry) => entry.agentId));\n for (const entry of want)\n await ensure(entry).catch((error: Error) =>\n warn(`slicc-agent: couldn't add the kernel user ${entry.spec.name}: ${error.message}`)\n );\n const gone = [...added].filter(([agentId]) => !names.has(agentId));\n const scoopsFirst = gone.sort(\n ([a], [b]) => Number(a.startsWith(CONE_PREFIX)) - Number(b.startsWith(CONE_PREFIX))\n );\n for (const [agentId, entry] of scoopsFirst) await retire(agentId, entry.name);\n for (const name of stale) await pool.users.remove(name).catch(() => false);\n };\n const queue = (run: () => Promise<void>) => {\n chain = chain.then(run, run);\n return chain;\n };\n const home = (agentId: string) => {\n const user = userOf(state(), agentId);\n return user === undefined ? undefined : homeOf(user);\n };\n return {\n mode,\n agentOf(conversation) {\n const current = agents();\n return (\n callerOf(current, conversation) ??\n (current ? `${CONE_PREFIX}${current.activeCone()}` : undefined)\n );\n },\n home,\n async client(agentId) {\n const want = wanted(state()).find((entry) => entry.agentId === agentId);\n if (!want)\n throw Object.assign(new Error(`${agentId} has no kernel user`), { code: 'ENOENT' });\n await ensure(want);\n return pool.client(agentId, want.spec.name);\n },\n reconcile: () => queue(() => reconcile()),\n boot: () =>\n queue(async () => {\n const known = new Set(\n Object.values(state().scoops).map((record) => scoopUser(record.folder))\n );\n const keep = new Set(wanted(state()).map((entry) => entry.spec.name));\n const listed = await pool.users.list().catch(() => []);\n const stale = listed\n .map((user) => user.name)\n .filter((name) => known.has(name) && !keep.has(name));\n await reconcile(stale);\n }),\n };\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/dist/main.js CHANGED
@@ -289,7 +289,7 @@ function setupTitles(registry, models, home, at, opened) {
289
289
  return { titleFn, cancel: titles.cancel, label: labeler(models, settings) };
290
290
  }
291
291
  function freezerOf(agent, parts) {
292
- const { home, memory, titles, people } = parts;
292
+ const { home, memory, titles, people, users } = parts;
293
293
  return attachFreezer({
294
294
  harness: agent.harness,
295
295
  agents: agent.agents,
@@ -298,7 +298,11 @@ function freezerOf(agent, parts) {
298
298
  title: titles.titleFn,
299
299
  cancelTitle: titles.cancel,
300
300
  ...people ? { layout: people.layout } : {},
301
- imports: { env: home }
301
+ imports: {
302
+ env: home,
303
+ ...users ? { users: users.users } : {},
304
+ ...people ? { envOf: (cone) => people.envOfAgent(`cone:${cone}`) } : {}
305
+ }
302
306
  });
303
307
  }
304
308
  async function start(scope, ports, options, vault) {
@@ -393,7 +397,7 @@ async function start(scope, ports, options, vault) {
393
397
  },
394
398
  BACKGROUND_CONTEXT
395
399
  );
396
- const freezer = freezerOf(agent, { home, memory, titles, people });
400
+ const freezer = freezerOf(agent, { home, memory, titles, people, users });
397
401
  licks.licks.guard(freezer.frozenTarget);
398
402
  const settings = await settingsOf(ready, mcpRuntime, { agent, vault, wiped });
399
403
  const pages = createPages({ claim: sprinkles.claim });