@ai-ecoverse/slicc-spectrum 2.9.0 → 2.9.1

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 CHANGED
@@ -483,10 +483,10 @@ Values go only to `vault.put` and are never kept on the element or put in an eve
483
483
  The head shows **Locked** or **Unlocked**, then, when `status.policy` marks a file `paused`, a second light (`[data-paused]`) "Paused: {file} is missing" ("Paused: {file} and 1 more are missing" for both), and the lock mode ("Unlocks automatically in this browser", "Unlocks with a passkey" or "Unlocks with a passphrase"); locked, the vault's **Unlock** button (`slicc-vault-button[data-action=unlock]`); unlocked, **Lock** (`data-action="lock"`, with `lock`) and the vault's **Protect with a passkey** (lock `auto`) or **Change protection** button (`data-action="protect"`).
484
484
  A status line (`[data-availability]`) says "Waiting for the vault" while `availability()` is `connecting` or `waiting` (or `status()` is `null` without it), and "Unlock the vault to continue" while `scrub.needed` and not `scrub.ready`.
485
485
  Waiting requests show under **Waiting for you** (`[data-request]`, the agent's `prompt` as plain text under "From the agent, not checked by SLICC:", capped at 200 characters, and "Expires in N min"), with **Enter value** (`data-action="answer"`, which fires `value-request` with `{ id, vault }` for the `<slicc-value-request>` on the same vault) for `value` requests and **Cancel request** (`data-action="cancel-request"`); `oauth`, `jerk-seal`, `jerk` and `unseal` requests say what they ask for and offer Cancel request only.
486
- With `backupReminder: 'due'`, an alert (`[data-backup]`) offers **Export backup** (`data-action="export"`) and **Not now** (`data-action="dismiss-backup"`, with `dismissBackupReminder`).
487
486
  **Add secret** (`data-action="add-secret"`, `[data-form=add]`, a name and a value; a name that exists asks before it replaces the value; with `vault.enter` and `valueFrame`, a name and **Next** (`data-action="enter-value"`), which opens `vault.enter` (with `replace: true` after that question) and shows the vault's own field (`[data-form][data-entry] slicc-vault-value`); **Replace value** opens `vault.enter({ name, kind, provider | server, replace: true })` and that field at once; the frame's `done` closes the form and says it was saved (with the name the form asked for when `done` carries none), and Cancel, Escape, the frame's `escape`, another form and a change of vault cancel the request), **Import…** (`data-action="import"`, `[data-form=import]`: Bundle, KeePass or six, a file, an optional password, then a pick of candidates, all checked but the `refused` ones (disabled, with their reason), marked "Replaces a secret" when they exist, with their suggested rules as untrusted text and a note that the file's rules aren't applied, and **Import N**, which sends `{ name, kind }` for a candidate of another `kind`; then "Imported N" and "Skipped NAME: reason" for each skipped name, from `reasons`) and **Export…** (`data-action="export"`, `[data-form=export]`: a passphrase twice, at least 8 bytes, behind a confirmation, downloaded as `slicc-secrets-<yyyy-mm-dd>.slicc-vault`, with "Last exported" or "Never exported" from `exportedAt`).
488
487
  Saved shows "Saved NAME in the vault" (`[data-said]`).
489
- The secrets, sorted by name (`li[data-secret][data-state]`, with `[data-rules]`, `[data-exits]` only when restricted, `[data-exec]` (any command, the listed commands, every bash) and how often programs were given it today, `[data-source]`, `[data-used]`, `[data-expires]`; a model record or an MCP token no rule names reads “Used for” (`[data-used-for]`, model requests to its provider or requests to its MCP server, by the agent) instead of where it's sent, with its account's state as its light, and shows its Provider (`[data-provider]`, the account's name) or Server (`[data-server]`)) offer **Replace value** (`data-action="replace"`, for `settings`, `process` and `import:*` secrets that aren't sign-ins) and **Remove** (`data-action="remove"`, behind a confirmation, passing the secret's `kind`), and removed ones, under **Removed**, **Restore** (`data-action="restore"`; ENOENT reads "Removed too long ago to restore").
488
+ The secrets, sorted by name (`li[data-secret][data-state]`, with `[data-rules]`, `[data-exits]` only when restricted, `[data-exec]` (any command, the listed commands, every bash) and how often programs were given it today — the last argv in that "Revealed to programs…" line is capped at 120 characters with an ellipsis, while Activity keeps the full command; the allow-list and reveal lines are not CSS-clamped, `[data-source]`, `[data-used]`, `[data-expires]`; a model record or an MCP token no rule names reads “Used for” (`[data-used-for]`, model requests to its provider or requests to its MCP server, by the agent) instead of where it's sent, with its account's state as its light in `.aside` beside the row actions, and shows its Provider (`[data-provider]`, the account's name) or Server (`[data-server]`)) offer **Replace value** (`data-action="replace"`, for `settings`, `process` and `import:*` secrets that aren't sign-ins) and **Remove** (`data-action="remove"`, behind a confirmation, passing the secret's `kind`), and removed ones, under **Removed**, **Restore** (`data-action="restore"`; ENOENT reads "Removed too long ago to restore").
489
+ With `backupReminder: 'due'`, an alert below the list (`[data-backup]`) offers **Export backup** (`data-action="export"`) and **Not now** (`data-action="dismiss-backup"`, with `dismissBackupReminder`).
490
490
  Locked, every form (Add secret and Replace value without `vault.enter`, Import, Export; with it, the vault's field unlocks itself) says "Unlock the vault first" (`p[data-locked]`) next to the vault's own small **Unlock** button (`slicc-vault-button[data-action=unlock]`) and disables its submit; once unlocked, the form stays open with what was typed and its submit enables.
491
491
  Vault errors show their message, EACCES as "Unlock the vault first", an EACCES with `cause: 'policy-paused'` as "Secrets are paused until the missing policy file is back", and ENOTSUP as "This arrives with a later vault version".
492
492
  Each action shows only when the port has its method.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../node_modules/@ai-ecoverse/slicc-kernel/src/fs/meta-reads.ts", "../node_modules/@ai-ecoverse/slicc-kernel/src/fs/path-map.ts", "../node_modules/@ai-ecoverse/slicc-kernel/src/fs/meta.ts", "../node_modules/@ai-ecoverse/slicc-kernel/src/fs/notice.ts", "../node_modules/@ai-ecoverse/slicc-kernel/src/fs/types.ts", "../node_modules/@ai-ecoverse/slicc-kernel/src/kernel/replacing.ts", "../node_modules/@ai-ecoverse/slicc-kernel/src/meta.ts", "../src/kernel/files.ts", "../src/kernel/idle.ts", "../src/kernel/terminals.ts", "../src/kernel/index.ts"],
4
- "sourcesContent": ["import type { MetaEntry } from './meta.ts';\n\nexport interface Invalidated {\n paths?: readonly string[];\n trees?: readonly string[];\n}\n\nconst parentOf = (path: string): string => path.slice(0, path.lastIndexOf('/')) || '/';\nconst within = (path: string, root: string): boolean =>\n root === '/' || path === root || path.startsWith(`${root}/`);\n\ninterface Noted {\n at: number;\n paths: readonly string[];\n trees: readonly string[];\n}\n\nexport class Invalidations {\n private seq = 0;\n private readonly log: Noted[] = [];\n private readonly open = new Map<number, number>();\n\n start(): number {\n const at = this.seq;\n this.open.set(at, (this.open.get(at) ?? 0) + 1);\n return at;\n }\n\n end(at: number): void {\n const left = (this.open.get(at) ?? 1) - 1;\n if (left > 0) this.open.set(at, left);\n else this.open.delete(at);\n const oldest = this.open.keys().next();\n if (oldest.done) this.log.length = 0;\n else while (this.log.length > 0 && (this.log[0] as Noted).at <= oldest.value) this.log.shift();\n }\n\n note({ paths = [], trees = [] }: Invalidated): void {\n this.seq++;\n if (this.open.size > 0) this.log.push({ at: this.seq, paths, trees });\n }\n\n private since(at: number, hit: (noted: Noted) => boolean): boolean {\n for (let i = this.log.length - 1; i >= 0; i--) {\n const noted = this.log[i] as Noted;\n if (noted.at <= at) return false;\n if (hit(noted)) return true;\n }\n return false;\n }\n\n entryStale(at: number, path: string): boolean {\n return this.since(\n at,\n ({ paths, trees }) => paths.includes(path) || trees.some((tree) => within(path, tree))\n );\n }\n\n listingStale(at: number, dir: string): boolean {\n return this.since(\n at,\n ({ paths, trees }) =>\n paths.some((path) => path !== '/' && parentOf(path) === dir) ||\n trees.some((tree) => within(dir, tree) || (tree !== '/' && parentOf(tree) === dir))\n );\n }\n}\n\nexport type MetaReadKind = 'entry' | 'links';\n\ninterface Wanted<T> {\n promise: Promise<T>;\n resolve: (value: T) => void;\n reject: (error: unknown) => void;\n at?: number;\n}\n\nexport interface ReadBatch {\n entries: readonly string[];\n links: readonly string[];\n}\n\nexport interface ReadResult {\n entries: Map<string, MetaEntry | null>;\n links: Map<string, MetaEntry[]>;\n}\n\nexport const META_READS_IN_FLIGHT = 4;\n\nexport class BatchedReads {\n private readonly run: (batch: ReadBatch) => Promise<ReadResult>;\n private readonly invalidations: Invalidations;\n private readonly keep: (kind: MetaReadKind, key: string, value: unknown) => void;\n private readonly queued: Record<MetaReadKind, Map<string, Wanted<unknown>>> = {\n entry: new Map(),\n links: new Map(),\n };\n private readonly reading: Record<MetaReadKind, Map<string, Wanted<unknown>>> = {\n entry: new Map(),\n links: new Map(),\n };\n private inFlight = 0;\n\n constructor(\n invalidations: Invalidations,\n run: (batch: ReadBatch) => Promise<ReadResult>,\n keep: (kind: MetaReadKind, key: string, value: unknown) => void\n ) {\n this.invalidations = invalidations;\n this.run = run;\n this.keep = keep;\n }\n\n entries(paths: readonly string[]): Promise<Map<string, MetaEntry | null>> {\n const wanted = paths.map((path) => [path, this.want('entry', path)] as const);\n this.pump();\n return Promise.all(wanted.map(([, w]) => w.promise)).then(\n (found) => new Map(wanted.map(([path], i) => [path, found[i] as MetaEntry | null]))\n );\n }\n\n links(dir: string): Promise<MetaEntry[]> {\n const wanted = this.want('links', dir);\n this.pump();\n return wanted.promise as Promise<MetaEntry[]>;\n }\n\n private stale(kind: MetaReadKind, key: string, at: number): boolean {\n return kind === 'entry'\n ? this.invalidations.entryStale(at, key)\n : this.invalidations.listingStale(at, key);\n }\n\n private want(kind: MetaReadKind, key: string): Wanted<unknown> {\n const reading = this.reading[kind].get(key);\n if (reading && !this.stale(kind, key, reading.at as number)) return reading;\n const queued = this.queued[kind].get(key);\n if (queued) return queued;\n const made = Promise.withResolvers<unknown>();\n made.promise.catch(() => undefined);\n this.queued[kind].set(key, made);\n return made;\n }\n\n private pump(): void {\n while (\n this.inFlight < META_READS_IN_FLIGHT &&\n (this.queued.entry.size > 0 || this.queued.links.size > 0)\n ) {\n this.flush();\n }\n }\n\n private flush(): void {\n const entries = this.queued.entry;\n const links = this.queued.links;\n this.queued.entry = new Map();\n this.queued.links = new Map();\n const at = this.invalidations.start();\n for (const [kind, taken] of [\n ['entry', entries],\n ['links', links],\n ] as const) {\n for (const [key, wanted] of taken) {\n wanted.at = at;\n this.reading[kind].set(key, wanted);\n }\n }\n this.inFlight++;\n const settled = (result: ReadResult | undefined, error?: unknown) => {\n this.inFlight--;\n for (const [kind, taken, found] of [\n ['entry', entries, result?.entries],\n ['links', links, result?.links],\n ] as const) {\n for (const [key, wanted] of taken) {\n if (this.reading[kind].get(key) === wanted) this.reading[kind].delete(key);\n if (!found) {\n wanted.reject(error);\n continue;\n }\n const value = found.get(key) ?? (kind === 'entry' ? null : []);\n if (!this.stale(kind, key, at)) this.keep(kind, key, value);\n wanted.resolve(value);\n }\n }\n this.invalidations.end(at);\n this.pump();\n };\n let reading: Promise<ReadResult>;\n try {\n reading = this.run({ entries: [...entries.keys()], links: [...links.keys()] });\n } catch (error) {\n settled(undefined, error);\n return;\n }\n reading.then(\n (result) => settled(result),\n (error: unknown) => settled(undefined, error)\n );\n }\n}\n", "function parentOf(path: string): string | undefined {\n if (path === '/' || path === '') return undefined;\n const at = path.lastIndexOf('/');\n return at <= 0 ? '/' : path.slice(0, at);\n}\n\nexport class PathMap<V> {\n private readonly values = new Map<string, V>();\n private readonly children = new Map<string, Set<string>>();\n\n get size(): number {\n return this.values.size;\n }\n\n has(path: string): boolean {\n return this.values.has(path);\n }\n\n get(path: string): V | undefined {\n return this.values.get(path);\n }\n\n keys(): IterableIterator<string> {\n return this.values.keys();\n }\n\n set(path: string, value: V): this {\n if (!this.values.has(path)) this.link(path);\n this.values.set(path, value);\n return this;\n }\n\n delete(path: string): boolean {\n if (!this.values.delete(path)) return false;\n this.prune(path);\n return true;\n }\n\n subtree(root: string): string[] {\n const out: string[] = [];\n const stack = [root];\n for (let path = stack.pop(); path !== undefined; path = stack.pop()) {\n if (this.values.has(path)) out.push(path);\n const below = this.children.get(path);\n if (below) for (const child of below) stack.push(child);\n }\n return out;\n }\n\n entries(): IterableIterator<[string, V]> {\n return this.values.entries();\n }\n\n deleteTree(root: string): void {\n const stack = [root];\n for (let path = stack.pop(); path !== undefined; path = stack.pop()) {\n this.values.delete(path);\n const below = this.children.get(path);\n if (below) for (const child of below) stack.push(child);\n this.children.delete(path);\n }\n this.prune(root);\n }\n\n clear(): void {\n this.values.clear();\n this.children.clear();\n }\n\n private link(path: string): void {\n for (let child = path, parent = parentOf(path); parent !== undefined; ) {\n const set = this.children.get(parent);\n if (set?.has(child)) return;\n if (set) set.add(child);\n else this.children.set(parent, new Set([child]));\n child = parent;\n parent = parentOf(parent);\n }\n }\n\n private prune(path: string): void {\n for (let child = path, parent = parentOf(path); parent !== undefined; ) {\n if (this.values.has(child) || this.children.has(child)) return;\n const set = this.children.get(parent);\n set?.delete(child);\n if (set && set.size > 0) return;\n this.children.delete(parent);\n child = parent;\n parent = parentOf(parent);\n }\n }\n}\n", "import { BatchedReads, Invalidations, type ReadBatch, type ReadResult } from './meta-reads.ts';\nimport { PathMap } from './path-map.ts';\nexport interface MetaEntry {\n path: string;\n mode?: number;\n atimeMs?: number;\n mtimeMs?: number;\n mtimeFor?: number;\n timesAt?: number;\n timesBy?: string;\n ctimeMs?: number;\n ino?: number;\n fid?: string;\n link?: string;\n socket?: true;\n fifo?: true;\n dir?: string;\n uid?: number;\n gid?: number;\n heir?: [number, number];\n writes?: number;\n epoch?: number;\n vacated?: true;\n rename?: {\n from: string;\n to: string;\n at: number;\n replace?: true;\n lock?: string;\n root?: { ino: number; uid?: number; gid?: number; mode?: number };\n };\n}\n\nexport type MetaChange = (entry: MetaEntry | undefined) => MetaEntry | undefined;\n\nexport interface MetaUpdate {\n path: string;\n change: MetaChange;\n}\n\nexport interface MetaStore {\n get(paths: readonly string[]): Promise<Array<MetaEntry | undefined>>;\n fresh?(paths: readonly string[]): Promise<Array<MetaEntry | undefined>>;\n update(path: string, change: MetaChange): Promise<void>;\n updateMany?(changes: ReadonlyArray<MetaUpdate>): Promise<void>;\n move(\n from: string,\n to: string,\n root: (entry: MetaEntry | undefined) => MetaEntry,\n left?: MetaEntry\n ): Promise<void>;\n remove(paths: readonly string[], left?: MetaEntry): Promise<void>;\n links(dir: string): Promise<MetaEntry[]>;\n under(path: string): Promise<MetaEntry[]>;\n all(): Promise<MetaEntry[]>;\n absent?(path: string): boolean;\n removeFile?(path: string): Promise<void>;\n forgetTrees?(trees: readonly string[]): void;\n transactions?(): MetaTransactions;\n}\n\nexport interface MetaTransactions {\n reads: number;\n writes: number;\n}\n\nexport const META_DB = 'slicc-kernel';\nexport const META_VERSION = 1;\nexport const META_STORE = 'entries';\n\nfunction parentOf(path: string): string {\n return path.slice(0, path.lastIndexOf('/')) || '/';\n}\n\nfunction moved(entry: MetaEntry, path: string): MetaEntry {\n return entry.link === undefined ? { ...entry, path } : { ...entry, path, dir: parentOf(path) };\n}\n\nfunction under(path: string, root: string): boolean {\n return path === root || path.startsWith(`${root}/`);\n}\n\nexport class MemoryMeta implements MetaStore {\n private readonly entries = new PathMap<MetaEntry>();\n\n async get(paths: readonly string[]): Promise<Array<MetaEntry | undefined>> {\n return paths.map((path) => this.entries.get(path));\n }\n\n async fresh(paths: readonly string[]): Promise<Array<MetaEntry | undefined>> {\n return this.get(paths);\n }\n\n async update(path: string, change: MetaChange): Promise<void> {\n this.set(path, change(this.entries.get(path)));\n }\n\n private set(path: string, entry: MetaEntry | undefined): void {\n if (entry) this.entries.set(path, entry);\n else this.entries.delete(path);\n }\n\n async move(\n from: string,\n to: string,\n root: (entry: MetaEntry | undefined) => MetaEntry,\n left?: MetaEntry\n ) {\n await this.remove([to]);\n const carried = this.entries.subtree(from).map((path) => this.entries.get(path) as MetaEntry);\n for (const entry of carried) this.entries.delete(entry.path);\n for (const entry of carried) {\n const path = to + entry.path.slice(from.length);\n if (path !== to) this.entries.set(path, moved(entry, path));\n }\n this.entries.set(to, moved(root(carried.find((entry) => entry.path === from)), to));\n if (left) this.entries.set(from, left);\n }\n\n async remove(paths: readonly string[], left?: MetaEntry): Promise<void> {\n for (const path of paths) this.entries.deleteTree(path);\n if (left) this.entries.set(left.path, left);\n }\n\n absent(path: string): boolean {\n return !this.entries.has(path);\n }\n\n async links(dir: string): Promise<MetaEntry[]> {\n return [...this.entries.entries()].map(([, e]) => e).filter((entry) => entry.dir === dir);\n }\n\n async under(path: string): Promise<MetaEntry[]> {\n return [...this.entries.entries()].map(([, e]) => e).filter((entry) => under(entry.path, path));\n }\n\n async all(): Promise<MetaEntry[]> {\n return [...this.entries.entries()].map(([, e]) => e);\n }\n}\n\nfunction descendants(path: string): IDBKeyRange {\n return IDBKeyRange.bound(`${path}/`, `${path}0`, false, true);\n}\n\nfunction settle<T>(transaction: IDBTransaction, value: () => T): Promise<T> {\n return new Promise((resolve, reject) => {\n transaction.oncomplete = () => resolve(value());\n transaction.onabort = () =>\n reject(transaction.error ?? new Error('metadata transaction aborted'));\n });\n}\n\ninterface Invalidation {\n paths?: readonly string[];\n trees?: readonly string[];\n moved?: { from: string; to: string };\n}\n\nconst REMOVE_BATCH = 256;\nconst REMOVE_WAIT_MS = 5;\nconst LISTINGS_KEPT = 4096;\n\nexport class IndexedDbMeta implements MetaStore {\n private readonly db: IDBDatabase;\n private readonly cache = new PathMap<MetaEntry | null>();\n private readonly listings = new PathMap<readonly MetaEntry[]>();\n private readonly channel: BroadcastChannel;\n private readonly invalidations = new Invalidations();\n private readonly reads = new BatchedReads(\n this.invalidations,\n (batch) => this.read(batch),\n (kind, key, value) => {\n if (kind === 'entry') this.cache.set(key, value as MetaEntry | null);\n else this.keepListing(key, value as MetaEntry[]);\n }\n );\n private readonly listeners = new Set<(change: Invalidation) => void>();\n\n listen(listener: (change: Invalidation) => void): void {\n this.listeners.add(listener);\n }\n\n constructor(db: IDBDatabase) {\n this.db = db;\n db.onversionchange = () => db.close();\n this.channel = new BroadcastChannel(`slicc-kernel-meta:${db.name}`);\n this.channel.onmessage = ({ data }) => {\n this.drop(data as Invalidation);\n for (const listener of this.listeners) listener(data as Invalidation);\n };\n (this.channel as BroadcastChannel & { unref?: () => void }).unref?.();\n }\n\n forgetTrees(trees: readonly string[]): void {\n this.drop({ trees: [...trees] });\n }\n\n private drop({ paths = [], trees = [] }: Invalidation): void {\n this.invalidations.note({ paths, trees });\n for (const path of paths) {\n this.cache.delete(path);\n if (path !== '/') this.listings.delete(parentOf(path));\n }\n for (const tree of trees) {\n this.cache.deleteTree(tree);\n this.listings.deleteTree(tree);\n if (tree !== '/') this.listings.delete(parentOf(tree));\n }\n }\n\n private keepListing(dir: string, links: MetaEntry[]): void {\n if (this.listings.size >= LISTINGS_KEPT) this.listings.clear();\n this.listings.set(dir, links);\n }\n\n private async read({ entries, links }: ReadBatch): Promise<ReadResult> {\n this.settleRemoves();\n const [transaction, store] = this.transaction('readonly');\n const result: ReadResult = { entries: new Map(), links: new Map() };\n for (const path of entries) {\n const request = store.get(path);\n request.onsuccess = () => void result.entries.set(path, request.result ?? null);\n }\n if (links.length > 0) {\n const index = store.index('dir');\n for (const dir of links) {\n const request = index.getAll(dir);\n request.onsuccess = () => void result.links.set(dir, request.result as MetaEntry[]);\n }\n }\n return settle(transaction, () => result);\n }\n\n private changed(invalidation: Invalidation): void {\n this.drop(invalidation);\n this.channel.postMessage(invalidation);\n }\n\n private readonly counted: MetaTransactions = { reads: 0, writes: 0 };\n\n transactions(): MetaTransactions {\n return { ...this.counted };\n }\n\n private transaction(mode: IDBTransactionMode): [IDBTransaction, IDBObjectStore] {\n if (mode === 'readonly') this.counted.reads++;\n else this.counted.writes++;\n const transaction = this.db.transaction(META_STORE, mode);\n return [transaction, transaction.objectStore(META_STORE)];\n }\n\n async get(paths: readonly string[]): Promise<Array<MetaEntry | undefined>> {\n const missing = paths.filter((path) => !this.cache.has(path));\n if (missing.length > 0) {\n const found = await this.reads.entries(missing);\n return paths.map(\n (path) => (found.has(path) ? found.get(path) : this.cache.get(path)) ?? undefined\n );\n }\n return paths.map((path) => this.cache.get(path) ?? undefined);\n }\n\n async fresh(paths: readonly string[]): Promise<Array<MetaEntry | undefined>> {\n const [transaction, store] = this.transaction('readonly');\n const requests = paths.map((path) => store.get(path));\n return settle(transaction, () =>\n requests.map((request) => (request.result as MetaEntry | undefined) ?? undefined)\n );\n }\n\n async update(path: string, change: MetaChange): Promise<void> {\n this.settleRemoves();\n const [transaction, store] = this.transaction('readwrite');\n const request = store.get(path);\n let next: MetaEntry | undefined;\n request.onsuccess = () => {\n next = change(request.result);\n if (next) store.put(next);\n else store.delete(path);\n };\n await settle(transaction, () => undefined);\n this.changed({ paths: [path] });\n this.cache.set(path, next ?? null);\n }\n\n async updateMany(changes: ReadonlyArray<MetaUpdate>): Promise<void> {\n if (changes.length === 0) return;\n this.settleRemoves();\n const [transaction, store] = this.transaction('readwrite');\n const next = new Map<string, MetaEntry | null>();\n for (const { path, change } of changes) {\n const request = store.get(path);\n request.onsuccess = () => {\n const entry = change(request.result);\n next.set(path, entry ?? null);\n if (entry) store.put(entry);\n else store.delete(path);\n };\n }\n await settle(transaction, () => undefined);\n this.changed({ paths: changes.map(({ path }) => path) });\n for (const [path, entry] of next) this.cache.set(path, entry);\n }\n\n close(): void {\n this.channel.close();\n this.db.close();\n }\n\n async move(\n from: string,\n to: string,\n root: (entry: MetaEntry | undefined) => MetaEntry,\n left?: MetaEntry\n ) {\n this.settleRemoves();\n const [transaction, store] = this.transaction('readwrite');\n store.delete(to);\n store.delete(descendants(to));\n const source = store.get(from);\n const below = store.getAll(descendants(from));\n below.onsuccess = () => {\n store.delete(from);\n store.delete(descendants(from));\n for (const entry of below.result as MetaEntry[]) {\n store.put(moved(entry, to + entry.path.slice(from.length)));\n }\n store.put(moved(root(source.result), to));\n if (left) store.put(left);\n };\n await settle(transaction, () => undefined);\n this.changed({ trees: [from, to], moved: { from, to } });\n }\n\n absent(path: string): boolean {\n return this.cache.has(path) && this.cache.get(path) === null;\n }\n\n private removing = new Map<string, string>();\n private removeTimer: ReturnType<typeof setTimeout> | undefined;\n\n async remove(paths: readonly string[], left?: MetaEntry): Promise<void> {\n this.settleRemoves();\n return this.removeNow(paths, left);\n }\n\n async removeFile(path: string): Promise<void> {\n const known = this.cache.get(path);\n if (!known) return this.remove([path]);\n this.invalidations.note({ paths: [path] });\n if (path !== '/') this.listings.delete(parentOf(path));\n this.cache.set(path, null);\n this.removing.set(path, JSON.stringify(known));\n if (this.removing.size >= REMOVE_BATCH) return this.flushRemoves();\n this.removeTimer ??= setTimeout(\n () => void this.flushRemoves().catch(() => undefined),\n REMOVE_WAIT_MS\n );\n }\n\n private async removeNow(paths: readonly string[], left?: MetaEntry): Promise<void> {\n const [transaction, store] = this.transaction('readwrite');\n for (const path of paths) {\n store.delete(path);\n store.delete(descendants(path));\n }\n if (left) store.put(left);\n await settle(transaction, () => undefined);\n this.changed({ trees: paths });\n if (left) this.cache.set(left.path, left);\n }\n\n private settleRemoves(): void {\n if (this.removing.size > 0) void this.flushRemoves().catch(() => undefined);\n }\n\n private flushRemoves(): Promise<void> {\n clearTimeout(this.removeTimer);\n this.removeTimer = undefined;\n const batch = this.removing;\n if (batch.size === 0) return Promise.resolve();\n this.removing = new Map();\n const [transaction, store] = this.transaction('readwrite');\n for (const [path, seen] of batch) {\n const request = store.get(path);\n request.onsuccess = () => {\n if (request.result && JSON.stringify(request.result) === seen) store.delete(path);\n };\n }\n const paths = [...batch.keys()];\n return settle(transaction, () => void this.channel.postMessage({ paths }));\n }\n\n async links(dir: string): Promise<MetaEntry[]> {\n const known = this.listings.get(dir);\n return [...(known ?? (await this.reads.links(dir)))];\n }\n\n async under(path: string): Promise<MetaEntry[]> {\n this.settleRemoves();\n const [transaction, store] = this.transaction('readonly');\n const self = store.get(path);\n const below = store.getAll(descendants(path));\n return settle(transaction, () =>\n [self.result as MetaEntry | undefined, ...(below.result as MetaEntry[])].filter(\n (entry): entry is MetaEntry => entry !== undefined\n )\n );\n }\n\n async all(): Promise<MetaEntry[]> {\n this.settleRemoves();\n const [transaction, store] = this.transaction('readonly');\n const request = store.getAll();\n return settle(transaction, () => request.result as MetaEntry[]);\n }\n}\n\nexport function openMetaDb(name: string, factory: IDBFactory): Promise<IDBDatabase> {\n return new Promise((resolve, reject) => {\n const request = factory.open(name, META_VERSION);\n request.onupgradeneeded = (event) => {\n if (event.oldVersion < 1) {\n request.result.createObjectStore(META_STORE, { keyPath: 'path' }).createIndex('dir', 'dir');\n }\n };\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error);\n });\n}\n\nexport async function openMeta(name: string, factory: IDBFactory): Promise<IndexedDbMeta> {\n return new IndexedDbMeta(await openMetaDb(name, factory));\n}\n", "export interface FsNotice {\n paths?: readonly string[];\n trees?: readonly string[];\n}\n\nexport function dirsChannel(metadata: string): string {\n return `slicc-kernel-dirs:${metadata}`;\n}\n\nexport function noticeOf(data: unknown): { paths: string[]; trees: string[]; outside?: true } {\n if (Array.isArray(data)) return { paths: data.filter((p) => typeof p === 'string'), trees: [] };\n const notice = (data ?? {}) as FsNotice;\n const strings = (list: unknown) =>\n Array.isArray(list) ? list.filter((p): p is string => typeof p === 'string') : [];\n return { paths: strings(notice.paths), trees: strings(notice.trees), outside: true };\n}\n", "export interface FsStat {\n isFile: boolean;\n isDirectory: boolean;\n isSymbolicLink: boolean;\n isSocket?: boolean;\n isFifo?: boolean;\n size: number;\n mode: number;\n mtime: Date;\n atime: Date;\n ctime: Date;\n ino: number;\n fid?: string;\n nlink?: number;\n dev?: number;\n readonly?: boolean;\n maxFile?: number;\n ranged?: boolean;\n linkable?: boolean;\n version?: string;\n uid?: number;\n gid?: number;\n}\n\nexport type UsersMode = 'off' | 'report' | 'enforce';\n\nexport const DEFAULT_USERS: UsersMode = 'enforce';\n\nexport interface Owner {\n uid: number;\n gid: number;\n}\n\nexport interface Perm {\n uid: number;\n gid: number;\n mode?: number;\n link?: string;\n}\n\nexport interface NewOwner extends Owner {\n mode?: number;\n parents?: number;\n socket?: boolean;\n fifo?: boolean;\n existing?: true;\n}\n\nexport interface CopyOptions {\n exclusive: boolean;\n owner?: NewOwner;\n}\n\nexport interface OwnerChange {\n uid?: number;\n gid?: number;\n follow?: boolean;\n kind?: 'chown' | 'adopt' | 'new';\n mode?: number;\n}\n\nexport interface HeldEntry {\n writeFile: KernelFs['writeFile'];\n direct: boolean;\n}\n\nexport interface Peers {\n joined(): Promise<unknown>;\n crowded(): boolean;\n watch(listener: (crowded: boolean) => void): void;\n fresh?(): void;\n}\n\nexport interface KernelFs {\n resolvePath(base: string, path: string): string;\n exclusive?<T>(path: string, run: (held: HeldEntry) => Promise<T>, follow?: boolean): Promise<T>;\n readFile(path: string): Promise<string>;\n readFileBuffer(path: string): Promise<Uint8Array>;\n writeFile(path: string, content: Uint8Array | string, owner?: NewOwner): Promise<void>;\n exists(path: string): Promise<boolean>;\n stat(path: string): Promise<FsStat>;\n lstat(path: string): Promise<FsStat>;\n readdir(path: string): Promise<string[]>;\n readdirStat(path: string): Promise<Array<[string, FsStat | null]>>;\n mkdir(path: string, options?: { recursive?: boolean; owner?: NewOwner }): Promise<void>;\n rm(\n path: string,\n options?: { recursive?: boolean; force?: boolean; file?: boolean }\n ): Promise<void>;\n rename(from: string, to: string): Promise<void>;\n link(from: string, to: string, follow?: boolean): Promise<void>;\n links?(path: string): boolean;\n symlink(target: string, path: string, owner?: NewOwner): Promise<void>;\n readlink(path: string): Promise<string>;\n realpath?(path: string, follow: boolean): Promise<string>;\n createExclusive?(path: string, owner?: NewOwner): Promise<void>;\n copyFile?(from: string, to: string, options: CopyOptions): Promise<void>;\n readonly rootName?: string;\n chown?(path: string, change: OwnerChange): Promise<void>;\n trackOwners?(): void;\n onPeer?(settle: () => Promise<void>): void;\n heldBy?(held: (dir: string) => readonly string[]): void;\n fileId?(path: string): Promise<string>;\n bindId?(path: string): Promise<string | undefined>;\n remoteChanges?(\n listener: (change: {\n paths: readonly string[];\n trees?: readonly string[];\n moved?: { from: string; to: string };\n content?: true;\n outside?: true;\n }) => void\n ): () => void;\n refresh?(trees: readonly string[]): void;\n peers?(): Peers | undefined;\n crossings?(mounted: (path: string) => boolean): void;\n ancestry?(path: string): Promise<Array<Perm | undefined>>;\n hasOwner?(path: string): Promise<boolean>;\n chmod(path: string, mode: number): Promise<void>;\n append?(path: string, bytes: Uint8Array, options?: PwriteOptions): Promise<number>;\n utimes(path: string, atime: Date, mtime: Date): Promise<void>;\n lutimes?(path: string, atime: Date, mtime: Date): Promise<void>;\n pread?(path: string, offset: number, length: number, version?: string): Promise<Uint8Array>;\n pwrite?(\n path: string,\n offset: number,\n bytes: Uint8Array,\n transfer?: boolean,\n options?: PwriteOptions\n ): Promise<void>;\n truncate?(path: string, size: number): Promise<void>;\n protect?(prefixes: readonly string[], versions?: boolean): void;\n unguarded?(): KernelFs;\n noLinks?(): KernelFs;\n mayChange?(path: string, follow: boolean, whole?: boolean): Promise<void>;\n commandTarget?(path: string): Promise<string | undefined>;\n readVersioned?(path: string, cached?: boolean): Promise<{ bytes: Uint8Array; version: string }>;\n version?(path: string, cached?: boolean): Promise<string>;\n opfsCalls?(): number;\n renameWaits?(): {\n acks: number;\n busy: number;\n waits: number;\n queries: number;\n ackMs: readonly number[];\n };\n opfsOps?(): number;\n metaTransactions?(): { reads: number; writes: number } | undefined;\n fileIdWrites?(): number;\n announceHolds?(\n add: ReadonlyArray<[string, string | undefined]>,\n drop: readonly string[],\n wait: boolean\n ): Promise<void>;\n holdCounts?(): { mine: number; peers: number; sent: number; held: string[] } | undefined;\n peerHolds?(listener: (paths: string[]) => void): void;\n rehold?(from: string, to: string): void;\n peerKeeps?(): PeerKeeps | undefined;\n servePeerKeeps?(\n instance: string,\n handlers: {\n keeping(tab: string, paths: readonly string[]): Promise<void>;\n kept(\n tab: string,\n items: ReadonlyArray<{\n path: string;\n hidden: string | undefined;\n fid: string | undefined;\n }>,\n paths: readonly string[]\n ): Promise<void>;\n left(tab: string): void;\n }\n ): void;\n}\n\nexport interface PeerKeeps {\n shared(): boolean;\n heldWithin(\n path: string\n ): Array<{ tab: string; path: string; fid: string | undefined; instance: string | undefined }>;\n ask(\n to: string,\n body: {\n keeping?: { paths: string[] };\n kept?: {\n items: Array<{\n path: string;\n hidden: string | undefined;\n fid: string | undefined;\n }>;\n paths?: string[];\n };\n },\n cap: number\n ): Promise<boolean>;\n}\n\nexport interface PwriteOptions {\n create?: false;\n alone?: true;\n}\n\nexport type RangedOps = Required<Pick<KernelFs, 'pread' | 'pwrite' | 'truncate'>>;\n\nexport function rangedOps(fs: Partial<RangedOps>): RangedOps | undefined {\n const { pread, pwrite, truncate } = fs;\n if (!pread || !pwrite || !truncate) return undefined;\n return { pread: pread.bind(fs), pwrite: pwrite.bind(fs), truncate: truncate.bind(fs) };\n}\n\nexport async function lutimesOf(\n fs: KernelFs,\n path: string,\n atime: Date,\n mtime: Date\n): Promise<void> {\n if (fs.lutimes) return fs.lutimes(path, atime, mtime);\n if ((await fs.lstat(path)).isSymbolicLink) throw fsError('EOPNOTSUPP', path);\n return fs.utimes(path, atime, mtime);\n}\n\nexport function mayLink(fs: { links?(path: string): boolean }, path: string): boolean {\n return fs.links?.(path) ?? true;\n}\n\nexport function modeOption(mode: unknown): number {\n if (!Number.isInteger(mode) || (mode as number) < 0 || (mode as number) > 0o7777) {\n throw fsError('EINVAL', `not a mode: ${String(mode)}`);\n }\n return mode as number;\n}\n\nexport function fsError(code: string, path: string): Error & { code: string } {\n return Object.assign(new Error(`${code}: ${path}`), { code });\n}\n\nexport function normalizePath(path: string): string {\n const parts: string[] = [];\n for (const part of path.split('/')) {\n if (part === '' || part === '.') continue;\n if (part === '..') parts.pop();\n else parts.push(part);\n }\n return `/${parts.join('/')}`;\n}\n\nexport function resolvePath(base: string, path: string): string {\n return normalizePath(path.startsWith('/') ? path : `${base}/${path}`);\n}\n\nexport function inodeOf(path: string): number {\n let h1 = 0xdeadbeef;\n let h2 = 0x41c6ce57;\n for (let i = 0; i < path.length; i++) {\n const c = path.charCodeAt(i);\n h1 = Math.imul(h1 ^ c, 2654435761);\n h2 = Math.imul(h2 ^ c, 1597334677);\n }\n h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909);\n h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);\n return 4294967296 * (2097151 & h2) + (h1 >>> 0) || 1;\n}\n\nexport interface Redirect {\n of: string;\n to: string;\n}\n\nexport function redirected(of: string, to: string): Error {\n return Object.assign(fsError('EXDEV', `${of} leads to the mount at ${to}`), {\n redirect: { of: normalizePath(of), to: normalizePath(to) },\n });\n}\n\nexport function redirectOf(err: unknown): Redirect | undefined {\n return (err as { redirect?: Redirect } | null)?.redirect;\n}\n", "export const REPLACING_LOCK = 'slicc-kernel-replacing:v1:';\nexport const REPLACED_WAIT_MS = 150;\n\nexport interface ReplacingLocks {\n request(\n name: string,\n options: { mode: 'shared' | 'exclusive'; ifAvailable?: boolean },\n callback: (lock: unknown) => unknown\n ): Promise<unknown>;\n}\n\nexport function replacingLocks(scope: object = globalThis): ReplacingLocks | undefined {\n const locks = (scope as { navigator?: { locks?: ReplacingLocks } }).navigator?.locks;\n return typeof locks?.request === 'function' ? locks : undefined;\n}\n\nexport function replacingLock(metadata: string, root: string): string {\n return `${REPLACING_LOCK}${metadata}:${root}`;\n}\n\nexport function exclusively<T>(\n locks: ReplacingLocks,\n names: readonly string[],\n run: () => Promise<T>\n): Promise<T> {\n const [name, ...rest] = names;\n if (name === undefined) return run();\n return locks.request(name, { mode: 'exclusive' }, () =>\n exclusively(locks, rest, run)\n ) as Promise<T>;\n}\n\nfunction shared<T>(\n locks: ReplacingLocks,\n names: readonly string[],\n run: (waited: boolean) => Promise<T>,\n waited: boolean\n): Promise<T> {\n const [name, ...rest] = names;\n if (name === undefined) return run(waited);\n const next = (after: boolean) => shared(locks, rest, run, after);\n return locks.request(name, { mode: 'shared', ifAvailable: true }, (lock) =>\n lock ? next(waited) : locks.request(name, { mode: 'shared' }, () => next(true))\n ) as Promise<T>;\n}\n\nexport class PackageLocks {\n private readonly locks: ReplacingLocks | undefined;\n private readonly metadata: string;\n\n constructor(metadata: string, locks = replacingLocks()) {\n this.metadata = metadata;\n this.locks = locks;\n }\n\n loading<T>(roots: readonly string[], run: (waited: boolean) => Promise<T>): Promise<T> {\n if (!this.locks || roots.length === 0) return run(false);\n const names = roots.map((root) => replacingLock(this.metadata, root));\n return shared(this.locks, names, run, false);\n }\n\n holding(root: string): Promise<() => void> {\n const { locks } = this;\n if (!locks) return Promise.resolve(() => {});\n const { promise: held, resolve: release } = Promise.withResolvers<void>();\n return new Promise((granted, failed) => {\n locks\n .request(replacingLock(this.metadata, root), { mode: 'exclusive' }, () => {\n granted(() => release());\n return held;\n })\n .catch(failed);\n });\n }\n}\n", "import { META_DB, type MetaEntry, openMeta } from './fs/meta.ts';\nimport { dirsChannel, type FsNotice } from './fs/notice.ts';\nimport { fsError, normalizePath } from './fs/types.ts';\nimport {\n exclusively,\n REPLACED_WAIT_MS,\n replacingLock,\n replacingLocks,\n} from './kernel/replacing.ts';\n\nexport type { FsNotice };\n\nexport interface MetadataEntry {\n path: string;\n mode?: number;\n uid?: number;\n gid?: number;\n}\n\nexport interface WriteMetadataOptions {\n metadata?: string;\n indexedDB?: IDBFactory;\n}\n\nconst MAX_ID = 0xfffffffe;\n\nfunction id(value: unknown, what: string): number {\n if (!Number.isInteger(value) || (value as number) < 0 || (value as number) > MAX_ID) {\n throw fsError('EINVAL', `not a ${what}: ${String(value)}`);\n }\n return value as number;\n}\n\nfunction checked(entry: MetadataEntry): MetadataEntry {\n if (!String(entry.path).startsWith('/'))\n throw fsError('EINVAL', `not an absolute path: ${entry.path}`);\n const path = normalizePath(String(entry.path));\n if ((entry.uid === undefined) !== (entry.gid === undefined)) {\n throw fsError('EINVAL', `${path}: give both uid and gid, or neither`);\n }\n const mode = entry.mode;\n if (mode !== undefined && (!Number.isInteger(mode) || mode < 0 || mode > 0o7777)) {\n throw fsError('EINVAL', `${path}: not a mode: ${String(mode)}`);\n }\n return {\n path,\n ...(mode !== undefined ? { mode } : {}),\n ...(entry.uid !== undefined ? { uid: id(entry.uid, 'uid'), gid: id(entry.gid, 'gid') } : {}),\n };\n}\n\nasync function present(root: FileSystemDirectoryHandle, path: string): Promise<boolean> {\n const parts = path.split('/').filter(Boolean);\n let dir = root;\n for (const [i, name] of parts.entries()) {\n const last = i === parts.length - 1;\n try {\n dir = await dir.getDirectoryHandle(name);\n } catch {\n if (!last) return false;\n return dir.getFileHandle(name).then(\n () => true,\n () => false\n );\n }\n }\n return true;\n}\n\nexport async function writeMetadata(\n root: FileSystemDirectoryHandle,\n entries: readonly MetadataEntry[],\n options: WriteMetadataOptions = {}\n): Promise<void> {\n const merged = new Map<string, MetadataEntry>();\n for (const want of entries.map(checked))\n merged.set(want.path, { ...merged.get(want.path), ...want });\n const wanted = [...merged.values()];\n for (const { path } of wanted) {\n if (!(await present(root, path))) throw fsError('ENOENT', path);\n }\n const factory = options.indexedDB ?? globalThis.indexedDB;\n const store = await openMeta(options.metadata ?? META_DB, factory);\n const ctimeMs = Date.now();\n try {\n await store.updateMany(\n wanted.map((want) => ({\n path: want.path,\n change: (entry: MetaEntry | undefined): MetaEntry => ({\n ...entry,\n path: want.path,\n ...(want.mode !== undefined ? { mode: want.mode } : {}),\n ...(want.uid !== undefined ? { uid: want.uid, gid: want.gid as number } : {}),\n ctimeMs,\n }),\n }))\n );\n } finally {\n store.close();\n }\n}\n\nexport interface PostFsNoticeOptions {\n metadata?: string;\n}\n\nfunction absolute(list: readonly string[] | undefined): string[] {\n return (list ?? []).map((path) => {\n if (!String(path).startsWith('/')) throw fsError('EINVAL', `not an absolute path: ${path}`);\n return normalizePath(String(path));\n });\n}\n\nexport function postFsNotice(notice: FsNotice, options: PostFsNoticeOptions = {}): void {\n const paths = absolute(notice.paths);\n const trees = absolute(notice.trees);\n if (paths.length === 0 && trees.length === 0) return;\n const channel = new BroadcastChannel(dirsChannel(options.metadata ?? META_DB));\n try {\n channel.postMessage({ paths, trees });\n } finally {\n channel.close();\n }\n}\n\nexport interface ReplacingOptions {\n metadata?: string;\n}\n\nconst HELD = Symbol.for('slicc-kernel.replacing.v1');\n\nfunction realmHeld(): Set<string> {\n const scope = globalThis as { [HELD]?: Set<string> };\n scope[HELD] ??= new Set();\n return scope[HELD];\n}\n\nexport async function replacing<T>(\n paths: readonly string[],\n task: () => T | Promise<T>,\n options: ReplacingOptions = {}\n): Promise<T> {\n if (!Array.isArray(paths)) throw new TypeError('replacing: paths must be an array of paths');\n if (typeof task !== 'function') throw new TypeError('replacing: task must be a function');\n const roots = [...new Set(absolute(paths))].sort();\n const metadata = options.metadata ?? META_DB;\n const held = realmHeld();\n const busy = roots.find((root) => held.has(root));\n if (busy !== undefined) {\n throw fsError('EDEADLK', `${busy} is already being replaced in this realm`);\n }\n const locks = replacingLocks();\n const run = async () => {\n try {\n return await task();\n } finally {\n postFsNotice({ trees: roots }, { metadata });\n if (locks && roots.length > 0) {\n await new Promise((resolve) => setTimeout(resolve, REPLACED_WAIT_MS));\n }\n }\n };\n for (const root of roots) held.add(root);\n try {\n if (!locks) return await run();\n const names = roots.map((root) => replacingLock(metadata, root));\n return await exclusively(locks, names, run);\n } finally {\n for (const root of roots) held.delete(root);\n }\n}\n", "import { postFsNotice } from '@ai-ecoverse/slicc-kernel/meta';\nimport { Emitter } from '../model/emitter.ts';\nimport type { Change, FileEntry, FileEvents, FilePort } from '../model/types.ts';\n\nexport interface KernelFilesOptions {\n skip?: readonly string[];\n hide?: readonly string[];\n interval?: number;\n metadata?: string;\n}\n\ninterface Observer {\n observe(handle: FileSystemHandle, options: { recursive: boolean }): Promise<void>;\n disconnect(): void;\n}\n\ninterface ChangeRecord {\n type: string;\n relativePathComponents: readonly string[];\n relativePathMovedFrom?: readonly string[] | null;\n}\n\ntype ObserverConstructor = new (callback: (records: readonly ChangeRecord[]) => void) => Observer;\n\nfunction split(path: string): string[] {\n return path.split('/').filter(Boolean);\n}\n\nfunction child(dir: string, name: string): string {\n return dir === '/' ? `/${name}` : `${dir}/${name}`;\n}\n\nfunction parentOf(path: string): string {\n return path.slice(0, path.lastIndexOf('/')) || '/';\n}\n\nasync function opened<T>(\n changed: string[],\n path: string,\n parent: string,\n open: (create: boolean) => Promise<T>\n): Promise<T> {\n try {\n return await open(false);\n } catch {\n const made = await open(true);\n changed.push(path, parent);\n return made;\n }\n}\n\nfunction same(a: FileEntry | undefined, b: FileEntry): boolean {\n return a?.kind === b.kind && a.size === b.size && a.modified === b.modified;\n}\n\nexport class KernelFiles extends Emitter<FileEvents> implements FilePort {\n #root: FileSystemDirectoryHandle;\n #skip: Set<string>;\n #hide: Set<string>;\n #interval: number;\n #metadata: string | undefined;\n #entries = new Map<string, FileEntry>();\n #scanned = false;\n #running: Promise<void> | null = null;\n #again = false;\n #timer: ReturnType<typeof setInterval> | null = null;\n #observer: Observer | null = null;\n #pending: ReturnType<typeof setTimeout> | null = null;\n #last = Number.NEGATIVE_INFINITY;\n\n constructor(\n root: FileSystemDirectoryHandle,\n { skip = [], hide = [], interval = 2000, metadata }: KernelFilesOptions = {}\n ) {\n super();\n this.#root = root;\n this.#metadata = metadata;\n this.#skip = new Set(skip);\n this.#hide = new Set(hide);\n this.#interval = interval;\n }\n\n start(): void {\n if (this.#timer || this.#observer) return;\n const Observer = (globalThis as { FileSystemObserver?: ObserverConstructor })\n .FileSystemObserver;\n if (!Observer) {\n this.#poll();\n return;\n }\n const observer = new Observer((records) => this.#observed(records));\n this.#observer = observer;\n observer.observe(this.#root, { recursive: true }).catch(() => {\n if (this.#observer !== observer) return;\n this.#observer = null;\n this.#poll();\n });\n }\n\n #poll(): void {\n this.#timer = setInterval(() => void this.refresh(), this.#interval);\n }\n\n #observed(records: readonly ChangeRecord[]): void {\n if (records.length > 0 && records.every((record) => this.#unseen(record))) return;\n if (this.#pending) return;\n const wait = Math.max(0, this.#last + this.#interval - performance.now());\n this.#pending = setTimeout(() => {\n this.#pending = null;\n this.#last = performance.now();\n void this.refresh();\n }, wait);\n }\n\n #unseen({ type, relativePathComponents, relativePathMovedFrom }: ChangeRecord): boolean {\n if (type === 'unknown' || type === 'errored') return false;\n return [relativePathComponents, relativePathMovedFrom ?? relativePathComponents].every(\n (parts) => this.#beneath(parts)\n );\n }\n\n #beneath(parts: readonly string[]): boolean {\n let path = '';\n for (const [i, part] of parts.entries()) {\n path += `/${part}`;\n if (this.#hide.has(path)) return true;\n if (this.#skip.has(path) && i < parts.length - 1) return true;\n }\n return false;\n }\n\n stop(): void {\n if (this.#timer) clearInterval(this.#timer);\n this.#timer = null;\n if (this.#pending) clearTimeout(this.#pending);\n this.#pending = null;\n this.#observer?.disconnect();\n this.#observer = null;\n }\n\n async #walk(\n dir: FileSystemDirectoryHandle,\n prefix: string,\n out: Map<string, FileEntry>\n ): Promise<void> {\n const values = (\n dir as FileSystemDirectoryHandle & {\n values(): AsyncIterable<FileSystemDirectoryHandle | FileSystemFileHandle>;\n }\n ).values();\n for await (const handle of values) {\n const path = `${prefix}/${handle.name}`;\n if (this.#hide.has(path)) continue;\n if (handle.kind === 'directory') {\n out.set(path, { path, kind: 'directory', size: 0, modified: 0 });\n if (!this.#skip.has(path)) await this.#walk(handle, path, out).catch(() => {});\n } else {\n const file = await handle.getFile().catch(() => null);\n if (file)\n out.set(path, { path, kind: 'file', size: file.size, modified: file.lastModified });\n }\n }\n }\n\n #list(): FileEntry[] {\n return [...this.#entries.values()].sort((a, b) => a.path.localeCompare(b.path));\n }\n\n async #scan(): Promise<void> {\n const next = new Map<string, FileEntry>();\n await this.#walk(this.#root, '', next);\n const touched = [...next.values()].filter(\n (entry) => !same(this.#entries.get(entry.path), entry)\n );\n const gone = [...this.#entries.values()].filter((entry) => !next.has(entry.path));\n const first = !this.#scanned;\n const previous = this.#entries;\n this.#entries = next;\n this.#scanned = true;\n if (first || touched.length + gone.length === 0) return;\n this.emit('files', this.#list());\n for (const entry of [...touched, ...gone]) {\n if (entry.kind === 'file' || previous.get(entry.path)?.kind === 'file')\n this.emit('file', entry.path);\n }\n }\n\n refresh(): Promise<void> {\n if (this.#running !== null) {\n this.#again = true;\n return this.#running;\n }\n this.#running = (async () => {\n do {\n this.#again = false;\n await this.#scan();\n } while (this.#again);\n })().finally(() => {\n this.#running = null;\n });\n return this.#running;\n }\n\n async list(): Promise<readonly FileEntry[]> {\n if (!this.#scanned) await this.refresh();\n return this.#list();\n }\n\n async #parent(path: string): Promise<[FileSystemDirectoryHandle, string]> {\n const parts = split(path);\n const name = parts.pop();\n if (!name) throw new Error(`EISDIR: ${path}`);\n let dir = this.#root;\n for (const part of parts) dir = await dir.getDirectoryHandle(part);\n return [dir, name];\n }\n\n #notice(paths: readonly string[], trees: readonly string[] = []): void {\n if (paths.length + trees.length === 0) return;\n postFsNotice({ paths, trees }, { metadata: this.#metadata });\n }\n\n async read(path: string): Promise<string> {\n try {\n const [dir, name] = await this.#parent(path);\n return await (await (await dir.getFileHandle(name)).getFile()).text();\n } catch {\n throw new Error(`ENOENT: ${path}`);\n }\n }\n\n async write(path: string, text: string): Promise<void> {\n const parts = split(path);\n const name = parts.pop();\n if (!name) throw new Error(`EISDIR: ${path}`);\n const changed: string[] = [];\n try {\n let dir = this.#root;\n let at = '/';\n for (const part of parts) {\n const parent = dir;\n const next = child(at, part);\n dir = await opened(changed, next, at, (create) =>\n parent.getDirectoryHandle(part, { create })\n );\n at = next;\n }\n const file = child(at, name);\n const handle = await opened(changed, file, at, (create) =>\n dir.getFileHandle(name, { create })\n );\n const writable = await handle.createWritable();\n await writable.write(text);\n await writable.close();\n changed.push(file);\n } finally {\n this.#notice([...new Set(changed)]);\n }\n await this.refresh();\n }\n\n async remove(path: string): Promise<void> {\n let target: string;\n let tree: boolean;\n try {\n const [dir, name] = await this.#parent(path);\n target = `/${split(path).join('/')}`;\n tree = await dir.getFileHandle(name).then(\n () => false,\n () => true\n );\n await dir.removeEntry(name, { recursive: true });\n } catch {\n throw new Error(`ENOENT: ${path}`);\n }\n if (tree) this.#notice([parentOf(target)], [target]);\n else this.#notice([target, parentOf(target)]);\n await this.refresh();\n }\n\n changes(): readonly Change[] {\n return [];\n }\n\n accept(): void {}\n\n async revert(): Promise<void> {}\n}\n", "import { Emitter } from '../model/emitter.ts';\nimport type {\n Agent,\n AgentEvents,\n AgentPort,\n BrowserEvents,\n BrowserPort,\n BrowserTab,\n FrozenCone,\n Memory,\n MemoryEvents,\n MemoryPort,\n Message,\n MonitorEvents,\n MonitorPort,\n MonitorSnapshot,\n SlashCommand,\n SprinkleEvents,\n SprinklePort,\n TrayEvents,\n TrayPort,\n TrayStatus,\n UserMessage,\n} from '../model/types.ts';\n\nfunction missing(what: string): Error {\n return new Error(`No ${what} in this SLICC yet`);\n}\n\nexport class IdleAgent extends Emitter<AgentEvents> implements AgentPort {\n list(): readonly Agent[] {\n return [];\n }\n active(): string {\n return '';\n }\n select(): void {}\n messages(): readonly Message[] {\n return [];\n }\n async send(): Promise<void> {\n throw missing('agent');\n }\n stop(): void {}\n busy(): boolean {\n return false;\n }\n queue(): readonly UserMessage[] {\n return [];\n }\n unqueue(): void {}\n suggestion(): string | null {\n return null;\n }\n answer(): void {}\n resolveLick(): void {}\n compact(): void {}\n clear(): void {}\n setModel(): void {}\n setThinking(): void {}\n async older(): Promise<readonly Message[]> {\n return [];\n }\n commands(): readonly SlashCommand[] {\n return [];\n }\n async ready(): Promise<void> {}\n createScoop(): Agent {\n throw missing('agent');\n }\n frozen(): readonly FrozenCone[] {\n return [];\n }\n freeze(): void {}\n thaw(): Agent | null {\n return null;\n }\n discard(): void {}\n}\n\nexport class IdleBrowser extends Emitter<BrowserEvents> implements BrowserPort {\n list(): readonly BrowserTab[] {\n return [];\n }\n active(): string | null {\n return null;\n }\n activate(): void {}\n open(): BrowserTab {\n throw missing('browser');\n }\n navigate(): void {}\n close(): void {}\n async screenshot(): Promise<string> {\n throw missing('browser');\n }\n}\n\nexport class IdleMemory extends Emitter<MemoryEvents> implements MemoryPort {\n list(): readonly Memory[] {\n return [];\n }\n save(): Memory {\n throw missing('memory');\n }\n remove(): void {}\n}\n\nexport class IdleMonitor extends Emitter<MonitorEvents> implements MonitorPort {\n snapshot(): MonitorSnapshot {\n return { vitals: [], alerts: [], sections: [], updatedAt: 0 };\n }\n resync(): void {}\n}\n\nexport class IdleSprinkles extends Emitter<SprinkleEvents> implements SprinklePort {\n list(): readonly [] {\n return [];\n }\n send(): void {}\n}\n\nexport class IdleTray extends Emitter<TrayEvents> implements TrayPort {\n status(): TrayStatus {\n return {\n name: 'slicc',\n kind: 'hosted',\n connection: 'offline',\n role: 'none',\n followers: [],\n spent: 0,\n rate: 0,\n budget: { percent: 0, window: 'daily', resets: '' },\n joinUrl: '',\n };\n }\n reconnect(): void {}\n disconnect(): void {}\n}\n", "import type { TerminalBackend } from '../backend.ts';\nimport { cwdPromptCommand, kernelBackend, type TerminalKernel } from '../kernel-backend.ts';\nimport { Emitter } from '../model/emitter.ts';\nimport type { TerminalEvents, TerminalInfo, TerminalPort } from '../model/types.ts';\n\nexport interface KernelTerminalsOptions {\n argv?: string[];\n cwd?: string;\n user?: string | number;\n env?: Record<string, string>;\n open?: number;\n}\n\nexport class KernelTerminals extends Emitter<TerminalEvents> implements TerminalPort {\n #kernel: TerminalKernel;\n #argv: string[];\n #cwd: string;\n #user: string | number | undefined;\n #env: Record<string, string> | undefined;\n #terminals: TerminalInfo[] = [];\n #backends = new Map<string, TerminalBackend>();\n #next = 1;\n\n constructor(\n kernel: TerminalKernel,\n { argv = ['bash', '-i'], cwd = '/home', user, env, open = 1 }: KernelTerminalsOptions = {}\n ) {\n super();\n this.#kernel = kernel;\n this.#argv = argv;\n this.#cwd = cwd;\n this.#user = user;\n this.#env = env;\n for (let i = 0; i < open; i++) this.open();\n }\n\n list(): readonly TerminalInfo[] {\n return this.#terminals.map((terminal) => ({ ...terminal }));\n }\n\n open({\n cwd = this.#cwd,\n agentId = null,\n }: {\n cwd?: string;\n agentId?: string | null;\n } = {}): TerminalInfo {\n const terminal: TerminalInfo = {\n id: `term-${this.#next}`,\n title: `${this.#argv[0]} ${this.#next}`,\n cwd,\n agentId,\n };\n this.#next += 1;\n this.#terminals.push(terminal);\n this.emit('terminals', this.list());\n return { ...terminal };\n }\n\n close(id: string): void {\n this.#terminals = this.#terminals.filter((terminal) => terminal.id !== id);\n this.#backends.delete(id);\n this.emit('terminals', this.list());\n }\n\n #move(id: string, cwd: string): void {\n const terminal = this.#terminals.find((candidate) => candidate.id === id);\n if (!terminal || terminal.cwd === cwd) return;\n terminal.cwd = cwd;\n this.emit('terminals', this.list());\n }\n\n backend(id: string): TerminalBackend {\n const terminal = this.#terminals.find((candidate) => candidate.id === id);\n if (!terminal) throw new Error(`No terminal ${id}`);\n let backend = this.#backends.get(id);\n if (!backend) {\n backend = kernelBackend(this.#kernel, {\n argv: this.#argv,\n cwd: terminal.cwd,\n user: this.#user,\n env: { PROMPT_COMMAND: cwdPromptCommand, ...this.#env },\n onCwd: (cwd) => this.#move(id, cwd),\n });\n this.#backends.set(id, backend);\n }\n return backend;\n }\n}\n", "import { clock } from '../dummy/clock.ts';\nimport { DummySettings, dummySettingsKey } from '../dummy/settings.ts';\nimport type { TerminalKernel } from '../kernel-backend.ts';\nimport type { Settings, SliccModel } from '../model/types.ts';\nimport { KernelFiles, type KernelFilesOptions } from './files.ts';\nimport {\n IdleAgent,\n IdleBrowser,\n IdleMemory,\n IdleMonitor,\n IdleSprinkles,\n IdleTray,\n} from './idle.ts';\nimport { KernelTerminals, type KernelTerminalsOptions } from './terminals.ts';\n\nexport interface KernelModelOptions {\n kernel: TerminalKernel;\n root: FileSystemDirectoryHandle;\n files?: KernelFilesOptions;\n terminals?: KernelTerminalsOptions;\n storage?: Storage | null;\n}\n\nexport interface KernelModel extends SliccModel {\n files: KernelFiles;\n terminals: KernelTerminals;\n}\n\nexport const settingsKey = 'slicc-agent.settings';\n\nconst defaults: Settings = {\n color: 'system',\n model: '',\n thinking: 'off',\n sendOnEnter: true,\n pushToTalk: false,\n showThinking: false,\n diffStyle: 'unified',\n};\n\nexport function idleModel(storage: Storage | null = null): Omit<SliccModel, 'files' | 'terminals'> {\n return {\n agent: new IdleAgent(),\n browser: new IdleBrowser(),\n settings: new DummySettings(defaults, [], [], storage, clock(0), [\n settingsKey,\n dummySettingsKey,\n ]),\n memory: new IdleMemory(),\n monitor: new IdleMonitor(),\n sprinkles: new IdleSprinkles(),\n tray: new IdleTray(),\n };\n}\n\nexport function createKernelModel({\n kernel,\n root,\n files,\n terminals,\n storage = null,\n}: KernelModelOptions): KernelModel {\n const model: KernelModel = {\n ...idleModel(storage),\n files: new KernelFiles(root, files),\n terminals: new KernelTerminals(kernel, terminals),\n };\n model.files.start();\n return model;\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { MetaEntry } from './meta.ts';\n\nexport interface Invalidated {\n paths?: readonly string[];\n trees?: readonly string[];\n}\n\nconst parentOf = (path: string): string => path.slice(0, path.lastIndexOf('/')) || '/';\nconst within = (path: string, root: string): boolean =>\n root === '/' || path === root || path.startsWith(`${root}/`);\n\ninterface Noted {\n at: number;\n paths: readonly string[];\n trees: readonly string[];\n}\n\nexport class Invalidations {\n private seq = 0;\n private readonly log: Noted[] = [];\n private readonly open = new Map<number, number>();\n\n start(): number {\n const at = this.seq;\n this.open.set(at, (this.open.get(at) ?? 0) + 1);\n return at;\n }\n\n end(at: number): void {\n const left = (this.open.get(at) ?? 1) - 1;\n if (left > 0) this.open.set(at, left);\n else this.open.delete(at);\n const oldest = this.open.keys().next();\n if (oldest.done) this.log.length = 0;\n else while (this.log.length > 0 && (this.log[0] as Noted).at <= oldest.value) this.log.shift();\n }\n\n note({ paths = [], trees = [] }: Invalidated): void {\n this.seq++;\n if (this.open.size > 0) this.log.push({ at: this.seq, paths, trees });\n }\n\n private since(at: number, hit: (noted: Noted) => boolean): boolean {\n for (let i = this.log.length - 1; i >= 0; i--) {\n const noted = this.log[i] as Noted;\n if (noted.at <= at) return false;\n if (hit(noted)) return true;\n }\n return false;\n }\n\n entryStale(at: number, path: string): boolean {\n return this.since(\n at,\n ({ paths, trees }) => paths.includes(path) || trees.some((tree) => within(path, tree))\n );\n }\n\n listingStale(at: number, dir: string): boolean {\n return this.since(\n at,\n ({ paths, trees }) =>\n paths.some((path) => path !== '/' && parentOf(path) === dir) ||\n trees.some((tree) => within(dir, tree) || (tree !== '/' && parentOf(tree) === dir))\n );\n }\n}\n\nexport type MetaReadKind = 'entry' | 'links';\n\ninterface Wanted<T> {\n promise: Promise<T>;\n resolve: (value: T) => void;\n reject: (error: unknown) => void;\n at?: number;\n}\n\nexport interface ReadBatch {\n entries: readonly string[];\n links: readonly string[];\n}\n\nexport interface ReadResult {\n entries: Map<string, MetaEntry | null>;\n links: Map<string, MetaEntry[]>;\n}\n\nexport const META_READS_IN_FLIGHT = 4;\n\nexport class BatchedReads {\n private readonly run: (batch: ReadBatch) => Promise<ReadResult>;\n private readonly invalidations: Invalidations;\n private readonly keep: (kind: MetaReadKind, key: string, value: unknown) => void;\n private readonly queued: Record<MetaReadKind, Map<string, Wanted<unknown>>> = {\n entry: new Map(),\n links: new Map(),\n };\n private readonly reading: Record<MetaReadKind, Map<string, Wanted<unknown>>> = {\n entry: new Map(),\n links: new Map(),\n };\n private inFlight = 0;\n\n constructor(\n invalidations: Invalidations,\n run: (batch: ReadBatch) => Promise<ReadResult>,\n keep: (kind: MetaReadKind, key: string, value: unknown) => void\n ) {\n this.invalidations = invalidations;\n this.run = run;\n this.keep = keep;\n }\n\n entries(paths: readonly string[]): Promise<Map<string, MetaEntry | null>> {\n const wanted = paths.map((path) => [path, this.want('entry', path)] as const);\n this.pump();\n return Promise.all(wanted.map(([, w]) => w.promise)).then(\n (found) => new Map(wanted.map(([path], i) => [path, found[i] as MetaEntry | null]))\n );\n }\n\n links(dir: string): Promise<MetaEntry[]> {\n const wanted = this.want('links', dir);\n this.pump();\n return wanted.promise as Promise<MetaEntry[]>;\n }\n\n private stale(kind: MetaReadKind, key: string, at: number): boolean {\n return kind === 'entry'\n ? this.invalidations.entryStale(at, key)\n : this.invalidations.listingStale(at, key);\n }\n\n private want(kind: MetaReadKind, key: string): Wanted<unknown> {\n const reading = this.reading[kind].get(key);\n if (reading && !this.stale(kind, key, reading.at as number)) return reading;\n const queued = this.queued[kind].get(key);\n if (queued) return queued;\n const made = Promise.withResolvers<unknown>();\n made.promise.catch(() => undefined);\n this.queued[kind].set(key, made);\n return made;\n }\n\n private pump(): void {\n while (\n this.inFlight < META_READS_IN_FLIGHT &&\n (this.queued.entry.size > 0 || this.queued.links.size > 0)\n ) {\n this.flush();\n }\n }\n\n private flush(): void {\n const entries = this.queued.entry;\n const links = this.queued.links;\n this.queued.entry = new Map();\n this.queued.links = new Map();\n const at = this.invalidations.start();\n for (const [kind, taken] of [\n ['entry', entries],\n ['links', links],\n ] as const) {\n for (const [key, wanted] of taken) {\n wanted.at = at;\n this.reading[kind].set(key, wanted);\n }\n }\n this.inFlight++;\n const settled = (result: ReadResult | undefined, error?: unknown) => {\n this.inFlight--;\n for (const [kind, taken, found] of [\n ['entry', entries, result?.entries],\n ['links', links, result?.links],\n ] as const) {\n for (const [key, wanted] of taken) {\n if (this.reading[kind].get(key) === wanted) this.reading[kind].delete(key);\n if (!found) {\n wanted.reject(error);\n continue;\n }\n const value = found.get(key) ?? (kind === 'entry' ? null : []);\n if (!this.stale(kind, key, at)) this.keep(kind, key, value);\n wanted.resolve(value);\n }\n }\n this.invalidations.end(at);\n this.pump();\n };\n let reading: Promise<ReadResult>;\n try {\n reading = this.run({ entries: [...entries.keys()], links: [...links.keys()] });\n } catch (error) {\n settled(undefined, error);\n return;\n }\n reading.then(\n (result) => settled(result),\n (error: unknown) => settled(undefined, error)\n );\n }\n}\n", "function parentOf(path: string): string | undefined {\n if (path === '/' || path === '') return undefined;\n const at = path.lastIndexOf('/');\n return at <= 0 ? '/' : path.slice(0, at);\n}\n\nexport class PathMap<V> {\n private readonly values = new Map<string, V>();\n private readonly children = new Map<string, Set<string>>();\n\n get size(): number {\n return this.values.size;\n }\n\n has(path: string): boolean {\n return this.values.has(path);\n }\n\n get(path: string): V | undefined {\n return this.values.get(path);\n }\n\n keys(): IterableIterator<string> {\n return this.values.keys();\n }\n\n set(path: string, value: V): this {\n if (!this.values.has(path)) this.link(path);\n this.values.set(path, value);\n return this;\n }\n\n delete(path: string): boolean {\n if (!this.values.delete(path)) return false;\n this.prune(path);\n return true;\n }\n\n subtree(root: string): string[] {\n const out: string[] = [];\n const stack = [root];\n for (let path = stack.pop(); path !== undefined; path = stack.pop()) {\n if (this.values.has(path)) out.push(path);\n const below = this.children.get(path);\n if (below) for (const child of below) stack.push(child);\n }\n return out;\n }\n\n entries(): IterableIterator<[string, V]> {\n return this.values.entries();\n }\n\n deleteTree(root: string): void {\n const stack = [root];\n for (let path = stack.pop(); path !== undefined; path = stack.pop()) {\n this.values.delete(path);\n const below = this.children.get(path);\n if (below) for (const child of below) stack.push(child);\n this.children.delete(path);\n }\n this.prune(root);\n }\n\n clear(): void {\n this.values.clear();\n this.children.clear();\n }\n\n private link(path: string): void {\n for (let child = path, parent = parentOf(path); parent !== undefined; ) {\n const set = this.children.get(parent);\n if (set?.has(child)) return;\n if (set) set.add(child);\n else this.children.set(parent, new Set([child]));\n child = parent;\n parent = parentOf(parent);\n }\n }\n\n private prune(path: string): void {\n for (let child = path, parent = parentOf(path); parent !== undefined; ) {\n if (this.values.has(child) || this.children.has(child)) return;\n const set = this.children.get(parent);\n set?.delete(child);\n if (set && set.size > 0) return;\n this.children.delete(parent);\n child = parent;\n parent = parentOf(parent);\n }\n }\n}\n", "import { BatchedReads, Invalidations, type ReadBatch, type ReadResult } from './meta-reads.ts';\nimport { PathMap } from './path-map.ts';\nexport interface MetaEntry {\n path: string;\n mode?: number;\n atimeMs?: number;\n mtimeMs?: number;\n mtimeFor?: number;\n timesAt?: number;\n timesBy?: string;\n ctimeMs?: number;\n ino?: number;\n fid?: string;\n link?: string;\n socket?: true;\n fifo?: true;\n dir?: string;\n uid?: number;\n gid?: number;\n heir?: [number, number];\n writes?: number;\n epoch?: number;\n vacated?: true;\n rename?: {\n from: string;\n to: string;\n at: number;\n replace?: true;\n lock?: string;\n root?: { ino: number; uid?: number; gid?: number; mode?: number };\n };\n}\n\nexport type MetaChange = (entry: MetaEntry | undefined) => MetaEntry | undefined;\n\nexport interface MetaUpdate {\n path: string;\n change: MetaChange;\n}\n\nexport interface MetaStore {\n get(paths: readonly string[]): Promise<Array<MetaEntry | undefined>>;\n fresh?(paths: readonly string[]): Promise<Array<MetaEntry | undefined>>;\n update(path: string, change: MetaChange): Promise<void>;\n updateMany?(changes: ReadonlyArray<MetaUpdate>): Promise<void>;\n move(\n from: string,\n to: string,\n root: (entry: MetaEntry | undefined) => MetaEntry,\n left?: MetaEntry\n ): Promise<void>;\n remove(paths: readonly string[], left?: MetaEntry): Promise<void>;\n links(dir: string): Promise<MetaEntry[]>;\n under(path: string): Promise<MetaEntry[]>;\n all(): Promise<MetaEntry[]>;\n absent?(path: string): boolean;\n removeFile?(path: string): Promise<void>;\n forgetTrees?(trees: readonly string[]): void;\n transactions?(): MetaTransactions;\n}\n\nexport interface MetaTransactions {\n reads: number;\n writes: number;\n}\n\nexport const META_DB = 'slicc-kernel';\nexport const META_VERSION = 1;\nexport const META_STORE = 'entries';\n\nfunction parentOf(path: string): string {\n return path.slice(0, path.lastIndexOf('/')) || '/';\n}\n\nfunction moved(entry: MetaEntry, path: string): MetaEntry {\n return entry.link === undefined ? { ...entry, path } : { ...entry, path, dir: parentOf(path) };\n}\n\nfunction under(path: string, root: string): boolean {\n return path === root || path.startsWith(`${root}/`);\n}\n\nexport class MemoryMeta implements MetaStore {\n private readonly entries = new PathMap<MetaEntry>();\n\n async get(paths: readonly string[]): Promise<Array<MetaEntry | undefined>> {\n return paths.map((path) => this.entries.get(path));\n }\n\n async fresh(paths: readonly string[]): Promise<Array<MetaEntry | undefined>> {\n return this.get(paths);\n }\n\n async update(path: string, change: MetaChange): Promise<void> {\n this.set(path, change(this.entries.get(path)));\n }\n\n private set(path: string, entry: MetaEntry | undefined): void {\n if (entry) this.entries.set(path, entry);\n else this.entries.delete(path);\n }\n\n async move(\n from: string,\n to: string,\n root: (entry: MetaEntry | undefined) => MetaEntry,\n left?: MetaEntry\n ) {\n await this.remove([to]);\n const carried = this.entries.subtree(from).map((path) => this.entries.get(path) as MetaEntry);\n for (const entry of carried) this.entries.delete(entry.path);\n for (const entry of carried) {\n const path = to + entry.path.slice(from.length);\n if (path !== to) this.entries.set(path, moved(entry, path));\n }\n this.entries.set(to, moved(root(carried.find((entry) => entry.path === from)), to));\n if (left) this.entries.set(from, left);\n }\n\n async remove(paths: readonly string[], left?: MetaEntry): Promise<void> {\n for (const path of paths) this.entries.deleteTree(path);\n if (left) this.entries.set(left.path, left);\n }\n\n absent(path: string): boolean {\n return !this.entries.has(path);\n }\n\n async links(dir: string): Promise<MetaEntry[]> {\n return [...this.entries.entries()].map(([, e]) => e).filter((entry) => entry.dir === dir);\n }\n\n async under(path: string): Promise<MetaEntry[]> {\n return [...this.entries.entries()].map(([, e]) => e).filter((entry) => under(entry.path, path));\n }\n\n async all(): Promise<MetaEntry[]> {\n return [...this.entries.entries()].map(([, e]) => e);\n }\n}\n\nfunction descendants(path: string): IDBKeyRange {\n return IDBKeyRange.bound(`${path}/`, `${path}0`, false, true);\n}\n\nfunction settle<T>(transaction: IDBTransaction, value: () => T): Promise<T> {\n return new Promise((resolve, reject) => {\n transaction.oncomplete = () => resolve(value());\n transaction.onabort = () =>\n reject(transaction.error ?? new Error('metadata transaction aborted'));\n });\n}\n\ninterface Invalidation {\n paths?: readonly string[];\n trees?: readonly string[];\n moved?: { from: string; to: string };\n}\n\nconst REMOVE_BATCH = 256;\nconst REMOVE_WAIT_MS = 5;\nconst LISTINGS_KEPT = 4096;\n\nexport class IndexedDbMeta implements MetaStore {\n private readonly db: IDBDatabase;\n private readonly cache = new PathMap<MetaEntry | null>();\n private readonly listings = new PathMap<readonly MetaEntry[]>();\n private readonly channel: BroadcastChannel;\n private readonly invalidations = new Invalidations();\n private readonly reads = new BatchedReads(\n this.invalidations,\n (batch) => this.read(batch),\n (kind, key, value) => {\n if (kind === 'entry') this.cache.set(key, value as MetaEntry | null);\n else this.keepListing(key, value as MetaEntry[]);\n }\n );\n private readonly listeners = new Set<(change: Invalidation) => void>();\n\n listen(listener: (change: Invalidation) => void): void {\n this.listeners.add(listener);\n }\n\n constructor(db: IDBDatabase) {\n this.db = db;\n db.onversionchange = () => db.close();\n this.channel = new BroadcastChannel(`slicc-kernel-meta:${db.name}`);\n this.channel.onmessage = ({ data }) => {\n this.drop(data as Invalidation);\n for (const listener of this.listeners) listener(data as Invalidation);\n };\n (this.channel as BroadcastChannel & { unref?: () => void }).unref?.();\n }\n\n forgetTrees(trees: readonly string[]): void {\n this.drop({ trees: [...trees] });\n }\n\n private drop({ paths = [], trees = [] }: Invalidation): void {\n this.invalidations.note({ paths, trees });\n for (const path of paths) {\n this.cache.delete(path);\n if (path !== '/') this.listings.delete(parentOf(path));\n }\n for (const tree of trees) {\n this.cache.deleteTree(tree);\n this.listings.deleteTree(tree);\n if (tree !== '/') this.listings.delete(parentOf(tree));\n }\n }\n\n private keepListing(dir: string, links: MetaEntry[]): void {\n if (this.listings.size >= LISTINGS_KEPT) this.listings.clear();\n this.listings.set(dir, links);\n }\n\n private async read({ entries, links }: ReadBatch): Promise<ReadResult> {\n this.settleRemoves();\n const [transaction, store] = this.transaction('readonly');\n const result: ReadResult = { entries: new Map(), links: new Map() };\n for (const path of entries) {\n const request = store.get(path);\n request.onsuccess = () => void result.entries.set(path, request.result ?? null);\n }\n if (links.length > 0) {\n const index = store.index('dir');\n for (const dir of links) {\n const request = index.getAll(dir);\n request.onsuccess = () => void result.links.set(dir, request.result as MetaEntry[]);\n }\n }\n return settle(transaction, () => result);\n }\n\n private changed(invalidation: Invalidation): void {\n this.drop(invalidation);\n this.channel.postMessage(invalidation);\n }\n\n private readonly counted: MetaTransactions = { reads: 0, writes: 0 };\n\n transactions(): MetaTransactions {\n return { ...this.counted };\n }\n\n private transaction(mode: IDBTransactionMode): [IDBTransaction, IDBObjectStore] {\n if (mode === 'readonly') this.counted.reads++;\n else this.counted.writes++;\n const transaction = this.db.transaction(META_STORE, mode);\n return [transaction, transaction.objectStore(META_STORE)];\n }\n\n async get(paths: readonly string[]): Promise<Array<MetaEntry | undefined>> {\n const missing = paths.filter((path) => !this.cache.has(path));\n if (missing.length > 0) {\n const found = await this.reads.entries(missing);\n return paths.map(\n (path) => (found.has(path) ? found.get(path) : this.cache.get(path)) ?? undefined\n );\n }\n return paths.map((path) => this.cache.get(path) ?? undefined);\n }\n\n async fresh(paths: readonly string[]): Promise<Array<MetaEntry | undefined>> {\n const [transaction, store] = this.transaction('readonly');\n const requests = paths.map((path) => store.get(path));\n return settle(transaction, () =>\n requests.map((request) => (request.result as MetaEntry | undefined) ?? undefined)\n );\n }\n\n async update(path: string, change: MetaChange): Promise<void> {\n this.settleRemoves();\n const [transaction, store] = this.transaction('readwrite');\n const request = store.get(path);\n let next: MetaEntry | undefined;\n request.onsuccess = () => {\n next = change(request.result);\n if (next) store.put(next);\n else store.delete(path);\n };\n await settle(transaction, () => undefined);\n this.changed({ paths: [path] });\n this.cache.set(path, next ?? null);\n }\n\n async updateMany(changes: ReadonlyArray<MetaUpdate>): Promise<void> {\n if (changes.length === 0) return;\n this.settleRemoves();\n const [transaction, store] = this.transaction('readwrite');\n const next = new Map<string, MetaEntry | null>();\n for (const { path, change } of changes) {\n const request = store.get(path);\n request.onsuccess = () => {\n const entry = change(request.result);\n next.set(path, entry ?? null);\n if (entry) store.put(entry);\n else store.delete(path);\n };\n }\n await settle(transaction, () => undefined);\n this.changed({ paths: changes.map(({ path }) => path) });\n for (const [path, entry] of next) this.cache.set(path, entry);\n }\n\n close(): void {\n this.channel.close();\n this.db.close();\n }\n\n async move(\n from: string,\n to: string,\n root: (entry: MetaEntry | undefined) => MetaEntry,\n left?: MetaEntry\n ) {\n this.settleRemoves();\n const [transaction, store] = this.transaction('readwrite');\n store.delete(to);\n store.delete(descendants(to));\n const source = store.get(from);\n const below = store.getAll(descendants(from));\n below.onsuccess = () => {\n store.delete(from);\n store.delete(descendants(from));\n for (const entry of below.result as MetaEntry[]) {\n store.put(moved(entry, to + entry.path.slice(from.length)));\n }\n store.put(moved(root(source.result), to));\n if (left) store.put(left);\n };\n await settle(transaction, () => undefined);\n this.changed({ trees: [from, to], moved: { from, to } });\n }\n\n absent(path: string): boolean {\n return this.cache.has(path) && this.cache.get(path) === null;\n }\n\n private removing = new Map<string, string>();\n private removeTimer: ReturnType<typeof setTimeout> | undefined;\n\n async remove(paths: readonly string[], left?: MetaEntry): Promise<void> {\n this.settleRemoves();\n return this.removeNow(paths, left);\n }\n\n async removeFile(path: string): Promise<void> {\n const known = this.cache.get(path);\n if (!known) return this.remove([path]);\n this.invalidations.note({ paths: [path] });\n if (path !== '/') this.listings.delete(parentOf(path));\n this.cache.set(path, null);\n this.removing.set(path, JSON.stringify(known));\n if (this.removing.size >= REMOVE_BATCH) return this.flushRemoves();\n this.removeTimer ??= setTimeout(\n () => void this.flushRemoves().catch(() => undefined),\n REMOVE_WAIT_MS\n );\n }\n\n private async removeNow(paths: readonly string[], left?: MetaEntry): Promise<void> {\n const [transaction, store] = this.transaction('readwrite');\n for (const path of paths) {\n store.delete(path);\n store.delete(descendants(path));\n }\n if (left) store.put(left);\n await settle(transaction, () => undefined);\n this.changed({ trees: paths });\n if (left) this.cache.set(left.path, left);\n }\n\n private settleRemoves(): void {\n if (this.removing.size > 0) void this.flushRemoves().catch(() => undefined);\n }\n\n private flushRemoves(): Promise<void> {\n clearTimeout(this.removeTimer);\n this.removeTimer = undefined;\n const batch = this.removing;\n if (batch.size === 0) return Promise.resolve();\n this.removing = new Map();\n const [transaction, store] = this.transaction('readwrite');\n for (const [path, seen] of batch) {\n const request = store.get(path);\n request.onsuccess = () => {\n if (request.result && JSON.stringify(request.result) === seen) store.delete(path);\n };\n }\n const paths = [...batch.keys()];\n return settle(transaction, () => void this.channel.postMessage({ paths }));\n }\n\n async links(dir: string): Promise<MetaEntry[]> {\n const known = this.listings.get(dir);\n return [...(known ?? (await this.reads.links(dir)))];\n }\n\n async under(path: string): Promise<MetaEntry[]> {\n this.settleRemoves();\n const [transaction, store] = this.transaction('readonly');\n const self = store.get(path);\n const below = store.getAll(descendants(path));\n return settle(transaction, () =>\n [self.result as MetaEntry | undefined, ...(below.result as MetaEntry[])].filter(\n (entry): entry is MetaEntry => entry !== undefined\n )\n );\n }\n\n async all(): Promise<MetaEntry[]> {\n this.settleRemoves();\n const [transaction, store] = this.transaction('readonly');\n const request = store.getAll();\n return settle(transaction, () => request.result as MetaEntry[]);\n }\n}\n\nexport function openMetaDb(name: string, factory: IDBFactory): Promise<IDBDatabase> {\n return new Promise((resolve, reject) => {\n const request = factory.open(name, META_VERSION);\n request.onupgradeneeded = (event) => {\n if (event.oldVersion < 1) {\n request.result.createObjectStore(META_STORE, { keyPath: 'path' }).createIndex('dir', 'dir');\n }\n };\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error);\n });\n}\n\nexport async function openMeta(name: string, factory: IDBFactory): Promise<IndexedDbMeta> {\n return new IndexedDbMeta(await openMetaDb(name, factory));\n}\n", "export interface FsNotice {\n paths?: readonly string[];\n trees?: readonly string[];\n}\n\nexport function dirsChannel(metadata: string): string {\n return `slicc-kernel-dirs:${metadata}`;\n}\n\nexport function noticeOf(data: unknown): { paths: string[]; trees: string[]; outside?: true } {\n if (Array.isArray(data)) return { paths: data.filter((p) => typeof p === 'string'), trees: [] };\n const notice = (data ?? {}) as FsNotice;\n const strings = (list: unknown) =>\n Array.isArray(list) ? list.filter((p): p is string => typeof p === 'string') : [];\n return { paths: strings(notice.paths), trees: strings(notice.trees), outside: true };\n}\n", "export interface FsStat {\n isFile: boolean;\n isDirectory: boolean;\n isSymbolicLink: boolean;\n isSocket?: boolean;\n isFifo?: boolean;\n size: number;\n mode: number;\n mtime: Date;\n atime: Date;\n ctime: Date;\n ino: number;\n fid?: string;\n nlink?: number;\n dev?: number;\n readonly?: boolean;\n maxFile?: number;\n ranged?: boolean;\n linkable?: boolean;\n version?: string;\n uid?: number;\n gid?: number;\n fat?: true;\n}\n\nexport type UsersMode = 'off' | 'report' | 'enforce';\n\nexport const DEFAULT_USERS: UsersMode = 'enforce';\n\nexport interface Owner {\n uid: number;\n gid: number;\n}\n\nexport interface Perm {\n uid: number;\n gid: number;\n mode?: number;\n link?: string;\n}\n\nexport interface NewOwner extends Owner {\n mode?: number;\n parents?: number;\n socket?: boolean;\n fifo?: boolean;\n existing?: true;\n}\n\nexport interface CopyOptions {\n exclusive: boolean;\n owner?: NewOwner;\n}\n\nexport interface OwnerChange {\n uid?: number;\n gid?: number;\n follow?: boolean;\n kind?: 'chown' | 'adopt' | 'new';\n mode?: number;\n}\n\nexport interface HeldEntry {\n writeFile: KernelFs['writeFile'];\n direct: boolean;\n}\n\nexport interface Peers {\n joined(): Promise<unknown>;\n crowded(): boolean;\n watch(listener: (crowded: boolean) => void): void;\n fresh?(): void;\n}\n\nexport interface KernelFs {\n resolvePath(base: string, path: string): string;\n exclusive?<T>(path: string, run: (held: HeldEntry) => Promise<T>, follow?: boolean): Promise<T>;\n readFile(path: string): Promise<string>;\n readFileBuffer(path: string): Promise<Uint8Array>;\n writeFile(path: string, content: Uint8Array | string, owner?: NewOwner): Promise<void>;\n exists(path: string): Promise<boolean>;\n stat(path: string): Promise<FsStat>;\n lstat(path: string): Promise<FsStat>;\n readdir(path: string): Promise<string[]>;\n readdirStat(path: string): Promise<Array<[string, FsStat | null]>>;\n mkdir(path: string, options?: { recursive?: boolean; owner?: NewOwner }): Promise<void>;\n rm(\n path: string,\n options?: { recursive?: boolean; force?: boolean; file?: boolean }\n ): Promise<void>;\n rename(from: string, to: string): Promise<void>;\n link(from: string, to: string, follow?: boolean): Promise<void>;\n links?(path: string): boolean;\n symlink(target: string, path: string, owner?: NewOwner): Promise<void>;\n readlink(path: string): Promise<string>;\n realpath?(path: string, follow: boolean): Promise<string>;\n createExclusive?(path: string, owner?: NewOwner): Promise<void>;\n copyFile?(from: string, to: string, options: CopyOptions): Promise<void>;\n readonly rootName?: string;\n chown?(path: string, change: OwnerChange): Promise<void>;\n trackOwners?(): void;\n onPeer?(settle: () => Promise<void>): void;\n heldBy?(held: (dir: string) => readonly string[]): void;\n fileId?(path: string): Promise<string>;\n bindId?(path: string): Promise<string | undefined>;\n remoteChanges?(\n listener: (change: {\n paths: readonly string[];\n trees?: readonly string[];\n moved?: { from: string; to: string };\n content?: true;\n outside?: true;\n }) => void\n ): () => void;\n refresh?(trees: readonly string[]): void;\n peers?(): Peers | undefined;\n crossings?(mounted: (path: string) => boolean): void;\n ancestry?(path: string): Promise<Array<Perm | undefined>>;\n hasOwner?(path: string): Promise<boolean>;\n chmod(path: string, mode: number): Promise<void>;\n append?(path: string, bytes: Uint8Array, options?: PwriteOptions): Promise<number>;\n utimes(path: string, atime: Date, mtime: Date): Promise<void>;\n lutimes?(path: string, atime: Date, mtime: Date): Promise<void>;\n pread?(path: string, offset: number, length: number, version?: string): Promise<Uint8Array>;\n pwrite?(\n path: string,\n offset: number,\n bytes: Uint8Array,\n transfer?: boolean,\n options?: PwriteOptions\n ): Promise<void>;\n truncate?(path: string, size: number): Promise<void>;\n protect?(prefixes: readonly string[], versions?: boolean): void;\n unguarded?(): KernelFs;\n noLinks?(): KernelFs;\n mayChange?(path: string, follow: boolean, whole?: boolean): Promise<void>;\n commandTarget?(path: string): Promise<string | undefined>;\n readVersioned?(path: string, cached?: boolean): Promise<{ bytes: Uint8Array; version: string }>;\n version?(path: string, cached?: boolean): Promise<string>;\n opfsCalls?(): number;\n renameWaits?(): {\n acks: number;\n busy: number;\n waits: number;\n queries: number;\n ackMs: readonly number[];\n };\n opfsOps?(): number;\n metaTransactions?(): { reads: number; writes: number } | undefined;\n fileIdWrites?(): number;\n announceHolds?(\n add: ReadonlyArray<[string, string | undefined]>,\n drop: readonly string[],\n wait: boolean\n ): Promise<void>;\n holdCounts?(): { mine: number; peers: number; sent: number; held: string[] } | undefined;\n peerHolds?(listener: (paths: string[]) => void): void;\n rehold?(from: string, to: string): void;\n peerKeeps?(): PeerKeeps | undefined;\n servePeerKeeps?(\n instance: string,\n handlers: {\n keeping(tab: string, paths: readonly string[]): Promise<void>;\n kept(\n tab: string,\n items: ReadonlyArray<{\n path: string;\n hidden: string | undefined;\n fid: string | undefined;\n }>,\n paths: readonly string[]\n ): Promise<void>;\n left(tab: string): void;\n }\n ): void;\n}\n\nexport interface PeerKeeps {\n shared(): boolean;\n heldWithin(\n path: string\n ): Array<{ tab: string; path: string; fid: string | undefined; instance: string | undefined }>;\n ask(\n to: string,\n body: {\n keeping?: { paths: string[] };\n kept?: {\n items: Array<{\n path: string;\n hidden: string | undefined;\n fid: string | undefined;\n }>;\n paths?: string[];\n };\n },\n cap: number\n ): Promise<boolean>;\n}\n\nexport interface PwriteOptions {\n create?: false;\n alone?: true;\n}\n\nexport type RangedOps = Required<Pick<KernelFs, 'pread' | 'pwrite' | 'truncate'>>;\n\nexport function rangedOps(fs: Partial<RangedOps>): RangedOps | undefined {\n const { pread, pwrite, truncate } = fs;\n if (!pread || !pwrite || !truncate) return undefined;\n return { pread: pread.bind(fs), pwrite: pwrite.bind(fs), truncate: truncate.bind(fs) };\n}\n\nexport async function lutimesOf(\n fs: KernelFs,\n path: string,\n atime: Date,\n mtime: Date\n): Promise<void> {\n if (fs.lutimes) return fs.lutimes(path, atime, mtime);\n if ((await fs.lstat(path)).isSymbolicLink) throw fsError('EOPNOTSUPP', path);\n return fs.utimes(path, atime, mtime);\n}\n\nexport function mayLink(fs: { links?(path: string): boolean }, path: string): boolean {\n return fs.links?.(path) ?? true;\n}\n\nexport function modeOption(mode: unknown): number {\n if (!Number.isInteger(mode) || (mode as number) < 0 || (mode as number) > 0o7777) {\n throw fsError('EINVAL', `not a mode: ${String(mode)}`);\n }\n return mode as number;\n}\n\nexport function fsError(code: string, path: string): Error & { code: string } {\n return Object.assign(new Error(`${code}: ${path}`), { code });\n}\n\nexport function normalizePath(path: string): string {\n const parts: string[] = [];\n for (const part of path.split('/')) {\n if (part === '' || part === '.') continue;\n if (part === '..') parts.pop();\n else parts.push(part);\n }\n return `/${parts.join('/')}`;\n}\n\nexport function resolvePath(base: string, path: string): string {\n return normalizePath(path.startsWith('/') ? path : `${base}/${path}`);\n}\n\nexport function inodeOf(path: string): number {\n let h1 = 0xdeadbeef;\n let h2 = 0x41c6ce57;\n for (let i = 0; i < path.length; i++) {\n const c = path.charCodeAt(i);\n h1 = Math.imul(h1 ^ c, 2654435761);\n h2 = Math.imul(h2 ^ c, 1597334677);\n }\n h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909);\n h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);\n return 4294967296 * (2097151 & h2) + (h1 >>> 0) || 1;\n}\n\nexport interface Redirect {\n of: string;\n to: string;\n}\n\nexport function redirected(of: string, to: string): Error {\n return Object.assign(fsError('EXDEV', `${of} leads to the mount at ${to}`), {\n redirect: { of: normalizePath(of), to: normalizePath(to) },\n });\n}\n\nexport function redirectOf(err: unknown): Redirect | undefined {\n return (err as { redirect?: Redirect } | null)?.redirect;\n}\n", "export const REPLACING_LOCK = 'slicc-kernel-replacing:v1:';\nexport const REPLACED_WAIT_MS = 150;\n\nexport interface ReplacingLocks {\n request(\n name: string,\n options: { mode: 'shared' | 'exclusive'; ifAvailable?: boolean },\n callback: (lock: unknown) => unknown\n ): Promise<unknown>;\n}\n\nexport function replacingLocks(scope: object = globalThis): ReplacingLocks | undefined {\n const locks = (scope as { navigator?: { locks?: ReplacingLocks } }).navigator?.locks;\n return typeof locks?.request === 'function' ? locks : undefined;\n}\n\nexport function replacingLock(metadata: string, root: string): string {\n return `${REPLACING_LOCK}${metadata}:${root}`;\n}\n\nexport function exclusively<T>(\n locks: ReplacingLocks,\n names: readonly string[],\n run: () => Promise<T>\n): Promise<T> {\n const [name, ...rest] = names;\n if (name === undefined) return run();\n return locks.request(name, { mode: 'exclusive' }, () =>\n exclusively(locks, rest, run)\n ) as Promise<T>;\n}\n\nfunction shared<T>(\n locks: ReplacingLocks,\n names: readonly string[],\n run: (waited: boolean) => Promise<T>,\n waited: boolean\n): Promise<T> {\n const [name, ...rest] = names;\n if (name === undefined) return run(waited);\n const next = (after: boolean) => shared(locks, rest, run, after);\n return locks.request(name, { mode: 'shared', ifAvailable: true }, (lock) =>\n lock ? next(waited) : locks.request(name, { mode: 'shared' }, () => next(true))\n ) as Promise<T>;\n}\n\nexport class PackageLocks {\n private readonly locks: ReplacingLocks | undefined;\n private readonly metadata: string;\n\n constructor(metadata: string, locks = replacingLocks()) {\n this.metadata = metadata;\n this.locks = locks;\n }\n\n loading<T>(roots: readonly string[], run: (waited: boolean) => Promise<T>): Promise<T> {\n if (!this.locks || roots.length === 0) return run(false);\n const names = roots.map((root) => replacingLock(this.metadata, root));\n return shared(this.locks, names, run, false);\n }\n\n holding(root: string): Promise<() => void> {\n const { locks } = this;\n if (!locks) return Promise.resolve(() => {});\n const { promise: held, resolve: release } = Promise.withResolvers<void>();\n return new Promise((granted, failed) => {\n locks\n .request(replacingLock(this.metadata, root), { mode: 'exclusive' }, () => {\n granted(() => release());\n return held;\n })\n .catch(failed);\n });\n }\n}\n", "import { META_DB, type MetaEntry, openMeta } from './fs/meta.ts';\nimport { dirsChannel, type FsNotice } from './fs/notice.ts';\nimport { fsError, normalizePath } from './fs/types.ts';\nimport {\n exclusively,\n REPLACED_WAIT_MS,\n replacingLock,\n replacingLocks,\n} from './kernel/replacing.ts';\n\nexport type { FsNotice };\n\nexport interface MetadataEntry {\n path: string;\n mode?: number;\n uid?: number;\n gid?: number;\n}\n\nexport interface WriteMetadataOptions {\n metadata?: string;\n indexedDB?: IDBFactory;\n}\n\nconst MAX_ID = 0xfffffffe;\n\nfunction id(value: unknown, what: string): number {\n if (!Number.isInteger(value) || (value as number) < 0 || (value as number) > MAX_ID) {\n throw fsError('EINVAL', `not a ${what}: ${String(value)}`);\n }\n return value as number;\n}\n\nfunction checked(entry: MetadataEntry): MetadataEntry {\n if (!String(entry.path).startsWith('/'))\n throw fsError('EINVAL', `not an absolute path: ${entry.path}`);\n const path = normalizePath(String(entry.path));\n if ((entry.uid === undefined) !== (entry.gid === undefined)) {\n throw fsError('EINVAL', `${path}: give both uid and gid, or neither`);\n }\n const mode = entry.mode;\n if (mode !== undefined && (!Number.isInteger(mode) || mode < 0 || mode > 0o7777)) {\n throw fsError('EINVAL', `${path}: not a mode: ${String(mode)}`);\n }\n return {\n path,\n ...(mode !== undefined ? { mode } : {}),\n ...(entry.uid !== undefined ? { uid: id(entry.uid, 'uid'), gid: id(entry.gid, 'gid') } : {}),\n };\n}\n\nasync function present(root: FileSystemDirectoryHandle, path: string): Promise<boolean> {\n const parts = path.split('/').filter(Boolean);\n let dir = root;\n for (const [i, name] of parts.entries()) {\n const last = i === parts.length - 1;\n try {\n dir = await dir.getDirectoryHandle(name);\n } catch {\n if (!last) return false;\n return dir.getFileHandle(name).then(\n () => true,\n () => false\n );\n }\n }\n return true;\n}\n\nexport async function writeMetadata(\n root: FileSystemDirectoryHandle,\n entries: readonly MetadataEntry[],\n options: WriteMetadataOptions = {}\n): Promise<void> {\n const merged = new Map<string, MetadataEntry>();\n for (const want of entries.map(checked))\n merged.set(want.path, { ...merged.get(want.path), ...want });\n const wanted = [...merged.values()];\n for (const { path } of wanted) {\n if (!(await present(root, path))) throw fsError('ENOENT', path);\n }\n const factory = options.indexedDB ?? globalThis.indexedDB;\n const store = await openMeta(options.metadata ?? META_DB, factory);\n const ctimeMs = Date.now();\n try {\n await store.updateMany(\n wanted.map((want) => ({\n path: want.path,\n change: (entry: MetaEntry | undefined): MetaEntry => ({\n ...entry,\n path: want.path,\n ...(want.mode !== undefined ? { mode: want.mode } : {}),\n ...(want.uid !== undefined ? { uid: want.uid, gid: want.gid as number } : {}),\n ctimeMs,\n }),\n }))\n );\n } finally {\n store.close();\n }\n}\n\nexport interface PostFsNoticeOptions {\n metadata?: string;\n}\n\nfunction absolute(list: readonly string[] | undefined): string[] {\n return (list ?? []).map((path) => {\n if (!String(path).startsWith('/')) throw fsError('EINVAL', `not an absolute path: ${path}`);\n return normalizePath(String(path));\n });\n}\n\nexport function postFsNotice(notice: FsNotice, options: PostFsNoticeOptions = {}): void {\n const paths = absolute(notice.paths);\n const trees = absolute(notice.trees);\n if (paths.length === 0 && trees.length === 0) return;\n const channel = new BroadcastChannel(dirsChannel(options.metadata ?? META_DB));\n try {\n channel.postMessage({ paths, trees });\n } finally {\n channel.close();\n }\n}\n\nexport interface ReplacingOptions {\n metadata?: string;\n}\n\nconst HELD = Symbol.for('slicc-kernel.replacing.v1');\n\nfunction realmHeld(): Set<string> {\n const scope = globalThis as { [HELD]?: Set<string> };\n scope[HELD] ??= new Set();\n return scope[HELD];\n}\n\nexport async function replacing<T>(\n paths: readonly string[],\n task: () => T | Promise<T>,\n options: ReplacingOptions = {}\n): Promise<T> {\n if (!Array.isArray(paths)) throw new TypeError('replacing: paths must be an array of paths');\n if (typeof task !== 'function') throw new TypeError('replacing: task must be a function');\n const roots = [...new Set(absolute(paths))].sort();\n const metadata = options.metadata ?? META_DB;\n const held = realmHeld();\n const busy = roots.find((root) => held.has(root));\n if (busy !== undefined) {\n throw fsError('EDEADLK', `${busy} is already being replaced in this realm`);\n }\n const locks = replacingLocks();\n const run = async () => {\n try {\n return await task();\n } finally {\n postFsNotice({ trees: roots }, { metadata });\n if (locks && roots.length > 0) {\n await new Promise((resolve) => setTimeout(resolve, REPLACED_WAIT_MS));\n }\n }\n };\n for (const root of roots) held.add(root);\n try {\n if (!locks) return await run();\n const names = roots.map((root) => replacingLock(metadata, root));\n return await exclusively(locks, names, run);\n } finally {\n for (const root of roots) held.delete(root);\n }\n}\n", "import { postFsNotice } from '@ai-ecoverse/slicc-kernel/meta';\nimport { Emitter } from '../model/emitter.ts';\nimport type { Change, FileEntry, FileEvents, FilePort } from '../model/types.ts';\n\nexport interface KernelFilesOptions {\n skip?: readonly string[];\n hide?: readonly string[];\n interval?: number;\n metadata?: string;\n}\n\ninterface Observer {\n observe(handle: FileSystemHandle, options: { recursive: boolean }): Promise<void>;\n disconnect(): void;\n}\n\ninterface ChangeRecord {\n type: string;\n relativePathComponents: readonly string[];\n relativePathMovedFrom?: readonly string[] | null;\n}\n\ntype ObserverConstructor = new (callback: (records: readonly ChangeRecord[]) => void) => Observer;\n\nfunction split(path: string): string[] {\n return path.split('/').filter(Boolean);\n}\n\nfunction child(dir: string, name: string): string {\n return dir === '/' ? `/${name}` : `${dir}/${name}`;\n}\n\nfunction parentOf(path: string): string {\n return path.slice(0, path.lastIndexOf('/')) || '/';\n}\n\nasync function opened<T>(\n changed: string[],\n path: string,\n parent: string,\n open: (create: boolean) => Promise<T>\n): Promise<T> {\n try {\n return await open(false);\n } catch {\n const made = await open(true);\n changed.push(path, parent);\n return made;\n }\n}\n\nfunction same(a: FileEntry | undefined, b: FileEntry): boolean {\n return a?.kind === b.kind && a.size === b.size && a.modified === b.modified;\n}\n\nexport class KernelFiles extends Emitter<FileEvents> implements FilePort {\n #root: FileSystemDirectoryHandle;\n #skip: Set<string>;\n #hide: Set<string>;\n #interval: number;\n #metadata: string | undefined;\n #entries = new Map<string, FileEntry>();\n #scanned = false;\n #running: Promise<void> | null = null;\n #again = false;\n #timer: ReturnType<typeof setInterval> | null = null;\n #observer: Observer | null = null;\n #pending: ReturnType<typeof setTimeout> | null = null;\n #last = Number.NEGATIVE_INFINITY;\n\n constructor(\n root: FileSystemDirectoryHandle,\n { skip = [], hide = [], interval = 2000, metadata }: KernelFilesOptions = {}\n ) {\n super();\n this.#root = root;\n this.#metadata = metadata;\n this.#skip = new Set(skip);\n this.#hide = new Set(hide);\n this.#interval = interval;\n }\n\n start(): void {\n if (this.#timer || this.#observer) return;\n const Observer = (globalThis as { FileSystemObserver?: ObserverConstructor })\n .FileSystemObserver;\n if (!Observer) {\n this.#poll();\n return;\n }\n const observer = new Observer((records) => this.#observed(records));\n this.#observer = observer;\n observer.observe(this.#root, { recursive: true }).catch(() => {\n if (this.#observer !== observer) return;\n this.#observer = null;\n this.#poll();\n });\n }\n\n #poll(): void {\n this.#timer = setInterval(() => void this.refresh(), this.#interval);\n }\n\n #observed(records: readonly ChangeRecord[]): void {\n if (records.length > 0 && records.every((record) => this.#unseen(record))) return;\n if (this.#pending) return;\n const wait = Math.max(0, this.#last + this.#interval - performance.now());\n this.#pending = setTimeout(() => {\n this.#pending = null;\n this.#last = performance.now();\n void this.refresh();\n }, wait);\n }\n\n #unseen({ type, relativePathComponents, relativePathMovedFrom }: ChangeRecord): boolean {\n if (type === 'unknown' || type === 'errored') return false;\n return [relativePathComponents, relativePathMovedFrom ?? relativePathComponents].every(\n (parts) => this.#beneath(parts)\n );\n }\n\n #beneath(parts: readonly string[]): boolean {\n let path = '';\n for (const [i, part] of parts.entries()) {\n path += `/${part}`;\n if (this.#hide.has(path)) return true;\n if (this.#skip.has(path) && i < parts.length - 1) return true;\n }\n return false;\n }\n\n stop(): void {\n if (this.#timer) clearInterval(this.#timer);\n this.#timer = null;\n if (this.#pending) clearTimeout(this.#pending);\n this.#pending = null;\n this.#observer?.disconnect();\n this.#observer = null;\n }\n\n async #walk(\n dir: FileSystemDirectoryHandle,\n prefix: string,\n out: Map<string, FileEntry>\n ): Promise<void> {\n const values = (\n dir as FileSystemDirectoryHandle & {\n values(): AsyncIterable<FileSystemDirectoryHandle | FileSystemFileHandle>;\n }\n ).values();\n for await (const handle of values) {\n const path = `${prefix}/${handle.name}`;\n if (this.#hide.has(path)) continue;\n if (handle.kind === 'directory') {\n out.set(path, { path, kind: 'directory', size: 0, modified: 0 });\n if (!this.#skip.has(path)) await this.#walk(handle, path, out).catch(() => {});\n } else {\n const file = await handle.getFile().catch(() => null);\n if (file)\n out.set(path, { path, kind: 'file', size: file.size, modified: file.lastModified });\n }\n }\n }\n\n #list(): FileEntry[] {\n return [...this.#entries.values()].sort((a, b) => a.path.localeCompare(b.path));\n }\n\n async #scan(): Promise<void> {\n const next = new Map<string, FileEntry>();\n await this.#walk(this.#root, '', next);\n const touched = [...next.values()].filter(\n (entry) => !same(this.#entries.get(entry.path), entry)\n );\n const gone = [...this.#entries.values()].filter((entry) => !next.has(entry.path));\n const first = !this.#scanned;\n const previous = this.#entries;\n this.#entries = next;\n this.#scanned = true;\n if (first || touched.length + gone.length === 0) return;\n this.emit('files', this.#list());\n for (const entry of [...touched, ...gone]) {\n if (entry.kind === 'file' || previous.get(entry.path)?.kind === 'file')\n this.emit('file', entry.path);\n }\n }\n\n refresh(): Promise<void> {\n if (this.#running !== null) {\n this.#again = true;\n return this.#running;\n }\n this.#running = (async () => {\n do {\n this.#again = false;\n await this.#scan();\n } while (this.#again);\n })().finally(() => {\n this.#running = null;\n });\n return this.#running;\n }\n\n async list(): Promise<readonly FileEntry[]> {\n if (!this.#scanned) await this.refresh();\n return this.#list();\n }\n\n async #parent(path: string): Promise<[FileSystemDirectoryHandle, string]> {\n const parts = split(path);\n const name = parts.pop();\n if (!name) throw new Error(`EISDIR: ${path}`);\n let dir = this.#root;\n for (const part of parts) dir = await dir.getDirectoryHandle(part);\n return [dir, name];\n }\n\n #notice(paths: readonly string[], trees: readonly string[] = []): void {\n if (paths.length + trees.length === 0) return;\n postFsNotice({ paths, trees }, { metadata: this.#metadata });\n }\n\n async read(path: string): Promise<string> {\n try {\n const [dir, name] = await this.#parent(path);\n return await (await (await dir.getFileHandle(name)).getFile()).text();\n } catch {\n throw new Error(`ENOENT: ${path}`);\n }\n }\n\n async write(path: string, text: string): Promise<void> {\n const parts = split(path);\n const name = parts.pop();\n if (!name) throw new Error(`EISDIR: ${path}`);\n const changed: string[] = [];\n try {\n let dir = this.#root;\n let at = '/';\n for (const part of parts) {\n const parent = dir;\n const next = child(at, part);\n dir = await opened(changed, next, at, (create) =>\n parent.getDirectoryHandle(part, { create })\n );\n at = next;\n }\n const file = child(at, name);\n const handle = await opened(changed, file, at, (create) =>\n dir.getFileHandle(name, { create })\n );\n const writable = await handle.createWritable();\n await writable.write(text);\n await writable.close();\n changed.push(file);\n } finally {\n this.#notice([...new Set(changed)]);\n }\n await this.refresh();\n }\n\n async remove(path: string): Promise<void> {\n let target: string;\n let tree: boolean;\n try {\n const [dir, name] = await this.#parent(path);\n target = `/${split(path).join('/')}`;\n tree = await dir.getFileHandle(name).then(\n () => false,\n () => true\n );\n await dir.removeEntry(name, { recursive: true });\n } catch {\n throw new Error(`ENOENT: ${path}`);\n }\n if (tree) this.#notice([parentOf(target)], [target]);\n else this.#notice([target, parentOf(target)]);\n await this.refresh();\n }\n\n changes(): readonly Change[] {\n return [];\n }\n\n accept(): void {}\n\n async revert(): Promise<void> {}\n}\n", "import { Emitter } from '../model/emitter.ts';\nimport type {\n Agent,\n AgentEvents,\n AgentPort,\n BrowserEvents,\n BrowserPort,\n BrowserTab,\n FrozenCone,\n Memory,\n MemoryEvents,\n MemoryPort,\n Message,\n MonitorEvents,\n MonitorPort,\n MonitorSnapshot,\n SlashCommand,\n SprinkleEvents,\n SprinklePort,\n TrayEvents,\n TrayPort,\n TrayStatus,\n UserMessage,\n} from '../model/types.ts';\n\nfunction missing(what: string): Error {\n return new Error(`No ${what} in this SLICC yet`);\n}\n\nexport class IdleAgent extends Emitter<AgentEvents> implements AgentPort {\n list(): readonly Agent[] {\n return [];\n }\n active(): string {\n return '';\n }\n select(): void {}\n messages(): readonly Message[] {\n return [];\n }\n async send(): Promise<void> {\n throw missing('agent');\n }\n stop(): void {}\n busy(): boolean {\n return false;\n }\n queue(): readonly UserMessage[] {\n return [];\n }\n unqueue(): void {}\n suggestion(): string | null {\n return null;\n }\n answer(): void {}\n resolveLick(): void {}\n compact(): void {}\n clear(): void {}\n setModel(): void {}\n setThinking(): void {}\n async older(): Promise<readonly Message[]> {\n return [];\n }\n commands(): readonly SlashCommand[] {\n return [];\n }\n async ready(): Promise<void> {}\n createScoop(): Agent {\n throw missing('agent');\n }\n frozen(): readonly FrozenCone[] {\n return [];\n }\n freeze(): void {}\n thaw(): Agent | null {\n return null;\n }\n discard(): void {}\n}\n\nexport class IdleBrowser extends Emitter<BrowserEvents> implements BrowserPort {\n list(): readonly BrowserTab[] {\n return [];\n }\n active(): string | null {\n return null;\n }\n activate(): void {}\n open(): BrowserTab {\n throw missing('browser');\n }\n navigate(): void {}\n close(): void {}\n async screenshot(): Promise<string> {\n throw missing('browser');\n }\n}\n\nexport class IdleMemory extends Emitter<MemoryEvents> implements MemoryPort {\n list(): readonly Memory[] {\n return [];\n }\n save(): Memory {\n throw missing('memory');\n }\n remove(): void {}\n}\n\nexport class IdleMonitor extends Emitter<MonitorEvents> implements MonitorPort {\n snapshot(): MonitorSnapshot {\n return { vitals: [], alerts: [], sections: [], updatedAt: 0 };\n }\n resync(): void {}\n}\n\nexport class IdleSprinkles extends Emitter<SprinkleEvents> implements SprinklePort {\n list(): readonly [] {\n return [];\n }\n send(): void {}\n}\n\nexport class IdleTray extends Emitter<TrayEvents> implements TrayPort {\n status(): TrayStatus {\n return {\n name: 'slicc',\n kind: 'hosted',\n connection: 'offline',\n role: 'none',\n followers: [],\n spent: 0,\n rate: 0,\n budget: { percent: 0, window: 'daily', resets: '' },\n joinUrl: '',\n };\n }\n reconnect(): void {}\n disconnect(): void {}\n}\n", "import type { TerminalBackend } from '../backend.ts';\nimport { cwdPromptCommand, kernelBackend, type TerminalKernel } from '../kernel-backend.ts';\nimport { Emitter } from '../model/emitter.ts';\nimport type { TerminalEvents, TerminalInfo, TerminalPort } from '../model/types.ts';\n\nexport interface KernelTerminalsOptions {\n argv?: string[];\n cwd?: string;\n user?: string | number;\n env?: Record<string, string>;\n open?: number;\n}\n\nexport class KernelTerminals extends Emitter<TerminalEvents> implements TerminalPort {\n #kernel: TerminalKernel;\n #argv: string[];\n #cwd: string;\n #user: string | number | undefined;\n #env: Record<string, string> | undefined;\n #terminals: TerminalInfo[] = [];\n #backends = new Map<string, TerminalBackend>();\n #next = 1;\n\n constructor(\n kernel: TerminalKernel,\n { argv = ['bash', '-i'], cwd = '/home', user, env, open = 1 }: KernelTerminalsOptions = {}\n ) {\n super();\n this.#kernel = kernel;\n this.#argv = argv;\n this.#cwd = cwd;\n this.#user = user;\n this.#env = env;\n for (let i = 0; i < open; i++) this.open();\n }\n\n list(): readonly TerminalInfo[] {\n return this.#terminals.map((terminal) => ({ ...terminal }));\n }\n\n open({\n cwd = this.#cwd,\n agentId = null,\n }: {\n cwd?: string;\n agentId?: string | null;\n } = {}): TerminalInfo {\n const terminal: TerminalInfo = {\n id: `term-${this.#next}`,\n title: `${this.#argv[0]} ${this.#next}`,\n cwd,\n agentId,\n };\n this.#next += 1;\n this.#terminals.push(terminal);\n this.emit('terminals', this.list());\n return { ...terminal };\n }\n\n close(id: string): void {\n this.#terminals = this.#terminals.filter((terminal) => terminal.id !== id);\n this.#backends.delete(id);\n this.emit('terminals', this.list());\n }\n\n #move(id: string, cwd: string): void {\n const terminal = this.#terminals.find((candidate) => candidate.id === id);\n if (!terminal || terminal.cwd === cwd) return;\n terminal.cwd = cwd;\n this.emit('terminals', this.list());\n }\n\n backend(id: string): TerminalBackend {\n const terminal = this.#terminals.find((candidate) => candidate.id === id);\n if (!terminal) throw new Error(`No terminal ${id}`);\n let backend = this.#backends.get(id);\n if (!backend) {\n backend = kernelBackend(this.#kernel, {\n argv: this.#argv,\n cwd: terminal.cwd,\n user: this.#user,\n env: { PROMPT_COMMAND: cwdPromptCommand, ...this.#env },\n onCwd: (cwd) => this.#move(id, cwd),\n });\n this.#backends.set(id, backend);\n }\n return backend;\n }\n}\n", "import { clock } from '../dummy/clock.ts';\nimport { DummySettings, dummySettingsKey } from '../dummy/settings.ts';\nimport type { TerminalKernel } from '../kernel-backend.ts';\nimport type { Settings, SliccModel } from '../model/types.ts';\nimport { KernelFiles, type KernelFilesOptions } from './files.ts';\nimport {\n IdleAgent,\n IdleBrowser,\n IdleMemory,\n IdleMonitor,\n IdleSprinkles,\n IdleTray,\n} from './idle.ts';\nimport { KernelTerminals, type KernelTerminalsOptions } from './terminals.ts';\n\nexport interface KernelModelOptions {\n kernel: TerminalKernel;\n root: FileSystemDirectoryHandle;\n files?: KernelFilesOptions;\n terminals?: KernelTerminalsOptions;\n storage?: Storage | null;\n}\n\nexport interface KernelModel extends SliccModel {\n files: KernelFiles;\n terminals: KernelTerminals;\n}\n\nexport const settingsKey = 'slicc-agent.settings';\n\nconst defaults: Settings = {\n color: 'system',\n model: '',\n thinking: 'off',\n sendOnEnter: true,\n pushToTalk: false,\n showThinking: false,\n diffStyle: 'unified',\n};\n\nexport function idleModel(storage: Storage | null = null): Omit<SliccModel, 'files' | 'terminals'> {\n return {\n agent: new IdleAgent(),\n browser: new IdleBrowser(),\n settings: new DummySettings(defaults, [], [], storage, clock(0), [\n settingsKey,\n dummySettingsKey,\n ]),\n memory: new IdleMemory(),\n monitor: new IdleMonitor(),\n sprinkles: new IdleSprinkles(),\n tray: new IdleTray(),\n };\n}\n\nexport function createKernelModel({\n kernel,\n root,\n files,\n terminals,\n storage = null,\n}: KernelModelOptions): KernelModel {\n const model: KernelModel = {\n ...idleModel(storage),\n files: new KernelFiles(root, files),\n terminals: new KernelTerminals(kernel, terminals),\n };\n model.files.start();\n return model;\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/dist/slicc-ui.js CHANGED
@@ -50509,7 +50509,9 @@ function revealed(name, entries, now = Date.now(), more = false) {
50509
50509
  const today = reveals.filter((entry) => entry.at >= start2).length;
50510
50510
  const partial = more && (entries[entries.length - 1]?.at ?? 0) >= start2;
50511
50511
  const when2 = today ? `${partial ? "at least " : ""}${today} ${today === 1 ? "time" : "times"} today` : since(last.at, now);
50512
- return b`Revealed to programs ${when2}, last by ${shown4(who(last))} with <code>${shown4(last.argv)}</code>`;
50512
+ const argv = Array.from(last.argv);
50513
+ const clipped = argv.length > 120 ? `${argv.slice(0, 119).join("")}\u2026` : last.argv;
50514
+ return b`Revealed to programs ${when2}, last by ${shown4(who(last))} with <code>${shown4(clipped)}</code>`;
50513
50515
  }
50514
50516
  function changes(entry, devices) {
50515
50517
  if (entry.changes)
@@ -50844,8 +50846,15 @@ var SliccSecrets = class extends ThemedElement {
50844
50846
  .secret .form {
50845
50847
  grid-column: 1 / -1;
50846
50848
  }
50847
- .secret .buttons {
50849
+ .aside {
50850
+ display: flex;
50851
+ flex-wrap: wrap;
50852
+ align-items: center;
50853
+ justify-content: flex-end;
50854
+ gap: var(--swc-spacing-200);
50848
50855
  align-self: start;
50856
+ }
50857
+ .secret .buttons {
50849
50858
  justify-content: flex-end;
50850
50859
  }
50851
50860
  .name {
@@ -50961,10 +50970,13 @@ var SliccSecrets = class extends ThemedElement {
50961
50970
  .secret {
50962
50971
  grid-template-columns: minmax(0, 1fr);
50963
50972
  }
50964
- .secret .buttons {
50973
+ .aside {
50965
50974
  order: 1;
50966
50975
  justify-content: flex-start;
50967
50976
  }
50977
+ .secret .buttons {
50978
+ justify-content: flex-start;
50979
+ }
50968
50980
  .secret .form {
50969
50981
  order: 2;
50970
50982
  }
@@ -51563,9 +51575,11 @@ var SliccSecrets = class extends ThemedElement {
51563
51575
  <div class="name">
51564
51576
  <code>${secret.name}</code>
51565
51577
  ${kind ? b`<swc-badge size="s" variant="neutral" subtle>${kind}</swc-badge>` : A}
51578
+ </div>
51579
+ <div class="aside">
51566
51580
  ${this.#state(secret)}
51581
+ ${this.#controls(vault, secret)}
51567
51582
  </div>
51568
- ${this.#controls(vault, secret)}
51569
51583
  <dl>
51570
51584
  ${this.#reach(secret, gone3)}
51571
51585
  ${secret.exits ? b`<dt>${gone3 ? "Left through" : "Leaves through"}</dt><dd data-exits>${secret.exits.length ? list4(secret.exits.map((exit3) => exitLabel(exit3, devices))) : "No exit"}</dd>` : A}
@@ -51725,9 +51739,9 @@ var SliccSecrets = class extends ThemedElement {
51725
51739
  ${this.#feedback()}
51726
51740
  ${this.failure ? this.#error(this.failure, "vault") : A}
51727
51741
  ${waiting3.length ? b`<h3>Waiting for you</h3>${waiting3.map((request2) => this.#request(vault, request2))}` : A}
51728
- ${this.#backup(vault, status2)}
51729
51742
  ${this.#tools(vault, locked)}
51730
51743
  ${!current ? this.#offline ? A : b`<p class="muted" data-loading>Loading secrets…</p>` : current.secrets.length ? this.#secrets(vault, current, entries, devices, locked) : b`<p data-empty>No secrets yet. Add one here, or enter the value when an agent or program asks for one.</p>`}
51744
+ ${this.#backup(vault, status2)}
51731
51745
  ${this.#refused(current?.refused ?? [])}
51732
51746
  <p class="muted" data-policy>Rules live in <code>/etc/fnox/config.toml</code> and <code>/etc/slicc/secrets-policy.toml</code>. Change them with sudo; SLICC asks you before every change.</p>
51733
51747
  ${current ? this.#audit(vault, current, entries, devices) : A}