@ai-ecoverse/slicc-agent 6.18.2 → 6.18.4

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
@@ -154,7 +154,9 @@ The first provider is AWS Bedrock with a Bedrock API key as the bearer token; th
154
154
 
155
155
  **Blocked providers.** `needs: 'cors-free-transport'` is the static fact; `blocked: 'cors-free-transport'`, on both `ProviderOption` and the account, says the current transport can't reach that provider right now: the worker's kernel transport is unavailable, or it is the page's own fetch without a linked exit (`transportReach(client)` in `src/net.ts`, from the transport's traits). The worker follows `client.onTraits`, so a provider unblocks as soon as an extension or a linked slicc gives a CORS-free route, and blocks again when it goes, with no reload. A blocked provider is still offered and can be added; a connected one keeps its key and its row, but its models are left out of `models` and `labels`, so the picker offers none until the route is back.
156
156
 
157
- **Subscription sign-ins.** xAI, Kimi For Coding and ChatGPT (`openai-codex`, shown as ChatGPT) sign in with pi-ai's own device-code flows (`DEVICE_SIGN_INS` in `src/sign-ins.ts`); xAI and Kimi keep API-key entry too, so their accounts are `auth: 'oauth'` with `secret: 'api-key'` (spectrum's “Use an API key instead”) and a `keyUrl`. Spectrum's Sign in calls `connect(id, undefined, undefined, { method: 'sign-in' })` in the click, and a pasted key `put`s first and calls `connect(id, undefined, fields, { method: 'key' })` (spectrum's `ConnectOptions`); without the fourth argument an `oauth` account signs in. The adapter opens an `about:blank` popup (`slicc-sign-in`) before anything else in the click's task, then asks the worker's `signIn(id)`, which adds the account, runs the provider's `auth.oauth.login` and answers `{ url, code }` once the provider gives its device code (an `https:` page only). While it waits the account is `signing-in` with `signIn: { url, code? }`, kept in the page and never in the replicated settings state; the popup goes to `url`, and spectrum shows `code` with Copy and a link to `url` when the browser blocked the popup. The adapter's `connect` call then waits in the worker for the token (a sign-in no `connect` claims within a minute is dropped) and hands the vault `grant { provider, name, response, settings? }`, `name` being the account's `vaultName` and `response` the token response built from pi-ai's credential, as bytes zeroed after the call; the token is never stored, logged, licked or put in the state, and pi-ai's own error texts, which can quote a token response, never leave the worker. A refusal reads “The sign-in was refused.”, a timeout, network failure or malformed answer “The sign-in didn't finish. Try again.”, the vault's `EEXIST` “This name holds another provider's key.”, and Cancel aborts at once with an `AbortError`. Signing in again is a rotation that keeps the placeholder, and over a pasted key the vault keeps that key restorable. The vault refreshes the token through `egress`; after `invalid_grant` its record is `expired` and the account offers Sign in again. ChatGPT's API reads the account id from the access token's JWT, which a placeholder isn't: the grant carries `settings: { CHATGPT_ACCOUNT_ID }` (every sign-in), the credential's env gets it from the record, and the worker hands pi-ai a carrier key `slicc.<claims>.<placeholder>` that the worker's fetch turns back into `Bearer <placeholder>` before the vault fills it, so pi-ai sends `chatgpt-account-id` itself; its turns use SSE, not WebSockets. Kimi's auth host answers browsers but its API needs the CORS-free transport; xAI's API answers browsers but its device endpoints don't, so an xAI sign-in needs the CORS-free transport while its key doesn't; ChatGPT's auth host answers browsers and its API doesn't. The flows load lazily from pi-ai: xAI's and Kimi's modules import nothing from Node, and openai-codex's imports `node:http` for its browser callback, which the device flow never runs, so slicc-bios's `node:*` stubs let it load.
157
+ **Held sign-in windows** (`src/spectrum/held.ts`, slicc-bios#772). A sign-in popup opens in the click on `about:blank#slicc-sign-in-<nonce>` (a random UUID), and the page asks seven's owner to hold it with `{ type: 'hold', marker: <nonce> }` on the `slicc-held` BroadcastChannel. The owner holds the target with that exact URL, so no CDP client can list or drive it, and answers `{ type, marker }`: `held` once it's held on every connected browser host, `none` when no host is connected (nothing can reach the popup), and `unheld` when a host is connected but the target wasn't found. The popup goes to the IdP or device page only on `held` or `none`. On `unheld` or after 6 s without an answer, the popup closes without navigating and the sign-in fails with “SLICC couldn't protect the sign-in window. Try again.”; on Cancel it never navigates either.
158
+
159
+ **Subscription sign-ins.** xAI, Kimi For Coding and ChatGPT (`openai-codex`, shown as ChatGPT) sign in with pi-ai's own device-code flows (`DEVICE_SIGN_INS` in `src/sign-ins.ts`); xAI and Kimi keep API-key entry too, so their accounts are `auth: 'oauth'` with `secret: 'api-key'` (spectrum's “Use an API key instead”) and a `keyUrl`. Spectrum's Sign in calls `connect(id, undefined, undefined, { method: 'sign-in' })` in the click, and a pasted key `put`s first and calls `connect(id, undefined, fields, { method: 'key' })` (spectrum's `ConnectOptions`); without the fourth argument an `oauth` account signs in. The adapter opens a held popup (`slicc-sign-in`, see **Held sign-in windows**) before anything else in the click's task, then asks the worker's `signIn(id)`, which adds the account, runs the provider's `auth.oauth.login` and answers `{ url, code }` once the provider gives its device code (an `https:` page only). While it waits the account is `signing-in` with `signIn: { url, code? }`, kept in the page and never in the replicated settings state; the prompt appears and the popup goes to `url` only once the popup is held, and spectrum shows `code` with Copy and a link to `url` when the browser blocked the popup. The adapter's `connect` call then waits in the worker for the token (a sign-in no `connect` claims within a minute is dropped) and hands the vault `grant { provider, name, response, settings? }`, `name` being the account's `vaultName` and `response` the token response built from pi-ai's credential, as bytes zeroed after the call; the token is never stored, logged, licked or put in the state, and pi-ai's own error texts, which can quote a token response, never leave the worker. A refusal reads “The sign-in was refused.”, a timeout, network failure or malformed answer “The sign-in didn't finish. Try again.”, the vault's `EEXIST` “This name holds another provider's key.”, and Cancel aborts at once with an `AbortError`. Signing in again is a rotation that keeps the placeholder, and over a pasted key the vault keeps that key restorable. The vault refreshes the token through `egress`; after `invalid_grant` its record is `expired` and the account offers Sign in again. ChatGPT's API reads the account id from the access token's JWT, which a placeholder isn't: the grant carries `settings: { CHATGPT_ACCOUNT_ID }` (every sign-in), the credential's env gets it from the record, and the worker hands pi-ai a carrier key `slicc.<claims>.<placeholder>` that the worker's fetch turns back into `Bearer <placeholder>` before the vault fills it, so pi-ai sends `chatgpt-account-id` itself; its turns use SSE, not WebSockets. Kimi's auth host answers browsers but its API needs the CORS-free transport; xAI's API answers browsers but its device endpoints don't, so an xAI sign-in needs the CORS-free transport while its key doesn't; ChatGPT's auth host answers browsers and its API doesn't. The flows load lazily from pi-ai: xAI's and Kimi's modules import nothing from Node, and openai-codex's imports `node:http` for its browser callback, which the device flow never runs, so slicc-bios's `node:*` stubs let it load.
158
160
 
159
161
  **Adding providers.** The settings state's `providers` lists every provider seven can add (`ProviderOption`: `id`, `name`, `description`, `auth`, `keyUrl`, `needs`, `fields` and `vaultName`, sorted by name), and the spectrum adapter serves it as `providers()` with a `providers` event when it changes. `accounts` then lists only the added ones: Bedrock and Adobe start as added, `connect` adds a provider, and one with a vault record or stored fields counts as added too. The added list is `providers` in `/.slicc/accounts.json`. `vaultName` is the record name spectrum `put`s the key under: pi-ai's env name, or `<ID>_API_KEY` for providers that share one (Moonshot AI, OpenCode and the Qwen token plans), since a `put` under another provider's name would rebind that record; Bedrock's is `AWS_BEARER_TOKEN_BEDROCK` and Adobe's `ADOBE_IMS_TOKEN`. The agent finds a provider's record by its `provider`, never by name. Each provider has at most one secret, its key; `fields` are the non-secret inputs (`name`, `label`, `required`, `placeholder`, `help`, `customBase`, `vaultSetting`, `options` and `value`). `vaultSetting` names the vault record setting a field fills (slicc-vault's tenant-bound wildcards, V3), so the vault can bind a provider's wildcard endpoints to the user's own tenant: Cloudflare's `account` is `CLOUDFLARE_ACCOUNT_ID` and `gateway` `CLOUDFLARE_GATEWAY_ID`, and Azure's `endpoint` is `AZURE_OPENAI_RESOURCE_NAME` (spectrum applies it only to a bare resource name; an `https:` value stays a `customBase`). On an added account each field's `value` is its stored value, trimmed, so Settings can show it and knows a custom base URL to unregister after a reload; a field with a default carries that instead (Bedrock's `us-west-2`). Bedrock's `region` has `options`, the 21 regions where AWS supports Bedrock API keys (the bearer token seven uses; AWS's “Supported Regions … for Amazon Bedrock API keys”), labelled with AWS's names (`Europe (Frankfurt), eu-central-1`); a stored region outside the list comes first, under its own name, so a select never shows a region the account doesn't use. `disconnect(id)` keeps the row and its fields, so connecting again needs only the key, and `remove(id)` drops the row and its fields; for both, spectrum removes the vault record first. On an MCP row, `remove` disconnects it. `connect` with a secret is refused for every model account ("API keys are kept in seven's vault: update seven"); the only secret it still takes is an MCP sign-in's callback answer.
160
162
 
@@ -365,7 +367,7 @@ MCP servers reachable over Streamable HTTP become native agent tools. The client
365
367
 
366
368
  **Per agent.** Today every cone reads the one file in `/home` and gets every enabled server. A scoop doesn't read a file of its own: it inherits its cone's servers, and a role's `tools:` or `agent --tools` list narrows them. A list with no `mcp__` entry gets no MCP servers. A list with `mcp__docs__search` or `mcp__docs__*` keeps only those, and `codemode` is added so codemode tools stay reachable. The resource tools must be listed by name, and they reach only the servers the list names. A nested scoop without a list inherits its calling scoop's list. A `beforeTool` hook blocks the model's calls outside the list, and codemode scripts only get the tools on it. When kernel users give each cone its own home, each cone reads `~/.pi/agent/mcp.json` in that home, and scoops keep inheriting from their cone.
367
369
 
368
- **Secrets.** `${NAME}` placeholders resolve to the vault's placeholders for that server (`records()`: an `mcp` record with `server` equal to the `mcp.json` key and that name, else the server's only record when the headers use one name, else an unbound record of that name, which covers mcpcat's `${MCP_<NAME>_TOKEN}` bridge tokens). The headers go out holding placeholders, and the vault fills them in its `mcp` op, only for that server's registered URL. A server whose record is missing comes up as needing one, with a lick, and reconnects when the record appears. The worker never holds a value; server log lines are scrubbed through the vault before they reach `~/.pi/agent/mcp.log`. **OAuth.** A server without an `Authorization` header that answers 401 needs a sign-in, as in pi. Its row in Settings › MCP servers offers Connect, which opens a pop-up on the authorization server, found through the server's protected-resource metadata (RFC 9728) or `oauth.authServerMetadataUrl`. SLICC registers itself dynamically as a public client named `SLICC` (`oauth.clientName` changes it), or uses `oauth.clientId`, with PKCE S256 and the redirect URI `https://<origin>/oauth/mcp-callback`. slicc-bios serves that path from `dist/oauth/mcp-callback.html` in this package. The callback page hands the code back over the `slicc-mcp-oauth` BroadcastChannel and clears it from its address. Before the pop-up opens, the worker registers the server's `tokenUrl` (the discovered `token_endpoint`, else `<authServer>/token`) with the vault. It exchanges the code, checking `state` and `iss`, as a prepared `mcp` request to that `tokenUrl`, and hands the token response straight to the vault's `grant`; the raw response is dropped, and pi-mcp keeps only the reply, which holds placeholders, with the client and discovery state in `/.slicc/accounts.json` under the server's name and URL. When the server answers 401, the worker asks the vault to `refresh` once and sends again; pi-mcp's own refresh is off, because the refresh token is a placeholder. When that fails, the row shows the sign-in as expired. Disconnect deletes the local state; spectrum removes the vault records. Without a vault, the exchange is refused ("MCP sign-in needs seven's vault: update seven") before any token is issued. `oauth.scope` sets the scopes, else the server's advertised ones are used. `clientSecret`, `callbackPort`, `callbackUrl` and `clientRegistration` are refused, because SLICC signs in as a public client through its own page.
370
+ **Secrets.** `${NAME}` placeholders resolve to the vault's placeholders for that server (`records()`: an `mcp` record with `server` equal to the `mcp.json` key and that name, else the server's only record when the headers use one name, else an unbound record of that name, which covers mcpcat's `${MCP_<NAME>_TOKEN}` bridge tokens). The headers go out holding placeholders, and the vault fills them in its `mcp` op, only for that server's registered URL. A server whose record is missing comes up as needing one, with a lick, and reconnects when the record appears. The worker never holds a value; server log lines are scrubbed through the vault before they reach `~/.pi/agent/mcp.log`. **OAuth.** A server without an `Authorization` header that answers 401 needs a sign-in, as in pi. Its row in Settings › MCP servers offers Connect, which opens a held pop-up (`slicc-mcp-oauth`, see **Held sign-in windows**) on the authorization server, found through the server's protected-resource metadata (RFC 9728) or `oauth.authServerMetadataUrl`. SLICC registers itself dynamically as a public client named `SLICC` (`oauth.clientName` changes it), or uses `oauth.clientId`, with PKCE S256 and the redirect URI `https://<origin>/oauth/mcp-callback`. slicc-bios serves that path from `dist/oauth/mcp-callback.html` in this package. The callback page hands the code back over the `slicc-mcp-oauth` BroadcastChannel and clears it from its address. Before the pop-up opens, the worker registers the server's `tokenUrl` (the discovered `token_endpoint`, else `<authServer>/token`) with the vault. It exchanges the code, checking `state` and `iss`, as a prepared `mcp` request to that `tokenUrl`, and hands the token response straight to the vault's `grant`; the raw response is dropped, and pi-mcp keeps only the reply, which holds placeholders, with the client and discovery state in `/.slicc/accounts.json` under the server's name and URL. When the server answers 401, the worker asks the vault to `refresh` once and sends again; pi-mcp's own refresh is off, because the refresh token is a placeholder. When that fails, the row shows the sign-in as expired. Disconnect deletes the local state; spectrum removes the vault records. Without a vault, the exchange is refused ("MCP sign-in needs seven's vault: update seven") before any token is issued. `oauth.scope` sets the scopes, else the server's advertised ones are used. `clientSecret`, `callbackPort`, `callbackUrl` and `clientRegistration` are refused, because SLICC signs in as a public client through its own page.
369
371
 
370
372
  **Settings › MCP servers** lists each server whose headers use a placeholder, under its own heading below Accounts, with the URL host. The page can ask for a server's URL, the `mcp.json` URL without user name, password, query or fragment, with `serverUrl('mcp:<server>')`, so it can register it with the vault (its `endpoints` op) before it connects a token it put there; from slicc-vault 1.13.0 a page-sealed token is filled only on a URL the page registered. The URL is never in the replicated settings state, since a path can carry a token and view-only principals read that state; `serverUrl` is a settings method, which view-only principals don't get, and it answers `null` for model accounts. The spectrum adapter fills each MCP account's `url` from it. Connect takes the token, which fills every placeholder of a server that uses one name; a server whose headers use several different names can't be filled from Settings yet. The server then reconnects. A server that answers 401 shows as **Rejected**, with Reconnect, and Disconnect deletes the token. Tool cards and remote summaries label MCP calls `server · tool`, unless the name is ambiguous (a server name that starts or ends with `_`, or a name with more than two `__` separators); then they show the raw name.
371
373
 
@@ -314,7 +314,7 @@ function nextSteps(run, mounts) {
314
314
  return [
315
315
  "Re-enter your keys and logins in Settings.",
316
316
  ...mounts.length ? [`Mount these folders again: ${mounts.join(", ")}.`] : [],
317
- ...manifest.mcp?.length ? [
317
+ ...manifest.mcp.length ? [
318
318
  `Add these MCP servers again: ${manifest.mcp.map((item) => `${item.name} (${item.url})`).join(", ")}.`
319
319
  ] : [],
320
320
  ...notes.crons.length ? [
@@ -356,7 +356,7 @@ async function reportOf(run, made) {
356
356
  ...split.again.map((id) => `${split.nameOf(id)}: re-enter in Settings`),
357
357
  ...split.missing.map((id) => `${split.nameOf(id)}: SLICC 7 has no such provider`)
358
358
  ];
359
- const never = (manifest.secrets ?? []).map((name) => NEVER[name] ?? name);
359
+ const never = manifest.secrets.map((name) => NEVER[name] ?? name);
360
360
  const part = manifest.part ? `, part ${manifest.part.index} of ${manifest.part.of}` : "";
361
361
  const text = [
362
362
  `# SLICC 6 import, ${date}`,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/freezer/v6-place.ts"],
4
- "sourcesContent": ["import { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport type { ExecutionEnv } from '@earendil-works/pi-durable/env';\nimport { coneSpec } from '../kernel/accounts.ts';\nimport { ROOT_HOME } from '../layout.ts';\nimport type { LickTarget } from '../licks/lick.ts';\nimport { memoryRoot } from '../memory/store.ts';\nimport { GLOBAL_DIR } from '../skills/index.ts';\nimport { validateName } from '../skills/skills.ts';\nimport type { ImportDeps } from './v6.ts';\nimport {\n accountsSplit,\n type Opened,\n type Parsed,\n textOf,\n type Unit,\n type Where,\n whereOf,\n} from './v6.ts';\nimport { lickName, skillName } from './v6-schema.ts';\nimport {\n type CronLine,\n installCrons,\n ownTree,\n remember,\n same,\n userSection,\n type Writer,\n} from './v6-write.ts';\nimport { ImportError } from './zip.ts';\n\nexport const V6_ROOT = `${ROOT_HOME}/v6`;\n\nexport type Notes = {\n lines: string[];\n skipped: string[];\n shadowed: string[];\n tasks: string[];\n sprinkles: Map<string, Set<string>>;\n crons: string[];\n webhooks: string[];\n placed: Map<string, string>;\n};\n\nexport type Run = {\n deps: ImportDeps;\n opened: Opened;\n parsed: Parsed;\n writer: Writer;\n stage: string;\n date: string;\n notes: Notes;\n show: (phase: 'writing', done: number, total: number) => void;\n fresh: Map<string, boolean>;\n ready: Set<Unit>;\n at: Map<string, string>;\n};\n\ntype Source = Uint8Array | { zip: string };\n\nasync function staged(run: Run, zip: string): Promise<Uint8Array> {\n const read = await run.writer.env.readBinaryFile(\n run.at.get(zip) ?? `${run.stage}/${zip}`,\n run.writer.context\n );\n if (!read.ok) throw new ImportError(`the staged copy of ${zip} is gone`);\n return read.value;\n}\n\nasync function prepare(run: Run, unit: Unit): Promise<void> {\n if (run.ready.has(unit)) return;\n run.ready.add(unit);\n const { users, layout, env } = run.deps;\n if (unit.fresh)\n run.writer.later(async () => {\n await users?.remove(unit.cone).catch(() => false);\n await env.remove(unit.home, { recursive: true, force: true }, BACKGROUND_CONTEXT);\n });\n if (layout.users && users) await users.add(coneSpec(unit.cone));\n}\n\nasync function envOf(run: Run, unit: Unit | null): Promise<ExecutionEnv | undefined> {\n if (!unit || unit.fresh || !run.deps.layout.users) return undefined;\n const env = await run.deps.envOf?.(unit.cone);\n if (!env) throw new ImportError(`there is no kernel user ${unit.cone} to write its files as`);\n return env;\n}\n\nasync function putIn(run: Run, unit: Unit | null, root: string, rest: string, source: Source) {\n if (unit) await prepare(run, unit);\n const env = await envOf(run, unit);\n let fresh = run.fresh.get(root);\n if (fresh === undefined) {\n fresh = unit?.fresh || (await run.writer.tree(root, env));\n run.fresh.set(root, fresh);\n }\n if (unit?.fresh) unit.roots.add(root);\n const path = `${root}/${rest}`;\n const from = 'zip' in source && !env ? run.at.get(source.zip) : undefined;\n if (from && 'zip' in source) {\n await run.writer.move(from, path, fresh);\n run.at.set(source.zip, path);\n } else\n await run.writer.put(\n path,\n 'zip' in source ? await staged(run, source.zip) : source,\n fresh,\n env\n );\n if ('zip' in source && !run.notes.placed.has(source.zip)) run.notes.placed.set(source.zip, path);\n}\n\nconst clean = (name: string) => name.replace(/[^A-Za-z0-9._-]+/g, '_').slice(-80) || 'file';\n\ntype Maps = { byFolder: Map<string, Unit>; owners: Map<string, Unit>; primary?: Unit };\n\nfunction targets(run: Run, maps: Maps, where: Where, zip: string): [Unit, string, string][] {\n if (where.kind === 'cone' || where.kind === 'scoop') {\n const unit = (where.kind === 'cone' ? maps.byFolder : maps.owners).get(where.folder) as Unit;\n const root =\n where.kind === 'cone'\n ? `${unit.home}/workspace${unit.suffix}`\n : `${unit.home}/scoops${unit.suffix}/${where.folder}`;\n return [[unit, root, where.rest]];\n }\n if (where.kind === 'sprinkle') {\n if (!maps.primary)\n run.notes.skipped.push(`the sprinkle ${where.folder}: there is no cone to put it in`);\n return maps.primary\n ? [\n [\n maps.primary,\n `${maps.primary.home}/sprinkles/${lickName(where.folder, 'sprinkle')}`,\n where.rest,\n ],\n ]\n : [];\n }\n if (where.kind !== 'attachment') return [];\n return run.parsed.units.flatMap((unit) =>\n (unit.doc?.attachments ?? [])\n .filter((item) => item.present && item.path === zip)\n .map((item): [Unit, string, string] => {\n const rest = `${item.sha256.slice(0, 12)}-${clean(item.originalName)}`;\n unit.files.set(item.id, `${unit.home}/v6-attachments/${rest}`);\n return [unit, `${unit.home}/v6-attachments`, rest];\n })\n );\n}\n\nfunction sprinkleCalls(run: Run, name: string, bytes: Uint8Array) {\n const found = [...new TextDecoder().decode(bytes).matchAll(/\\bslicc\\.([A-Za-z_$][\\w$]*)/g)]\n .map((match) => `slicc.${match[1]}`)\n .filter((call) => call !== 'slicc.lick');\n if (!found.length) return;\n const known = run.notes.sprinkles.get(name) ?? new Set<string>();\n for (const call of found) known.add(call);\n run.notes.sprinkles.set(name, known);\n}\n\nasync function alike(\n run: Run,\n dir: string,\n six: string,\n rests: readonly string[]\n): Promise<boolean> {\n for (const rest of rests) {\n const old = await run.writer.env.readBinaryFile(`${dir}/${rest}`, run.writer.context);\n if (!old.ok || !same(old.value, await staged(run, `skills/${six}/${rest}`))) return false;\n }\n return true;\n}\n\nasync function placeSkills(run: Run, skills: Map<string, string[]>, primary: Unit | undefined) {\n const seen = new Set<string>();\n for (const [six, rests] of skills) {\n const name = skillName(six);\n if (!name || validateName(name).length || seen.has(name)) {\n run.notes.skipped.push(\n `the skill ${six}: its name doesn't fit seven's rule (a-z, 0-9 and -)`\n );\n continue;\n }\n seen.add(name);\n const global = `${GLOBAL_DIR}/${name}`;\n const exists = await run.writer.exists(global);\n if (exists && (await alike(run, global, six, rests))) {\n run.notes.lines.push(`the skill ${name}: already in ${GLOBAL_DIR}`);\n continue;\n }\n const own = primary && exists ? `${primary.home}/.pi/agent/skills/${name}` : undefined;\n if (exists && !own) {\n run.notes.skipped.push(`the skill ${name}: ${GLOBAL_DIR} has another skill of that name`);\n continue;\n }\n for (const rest of rests)\n await putIn(run, own ? (primary as Unit) : null, own ?? global, rest, {\n zip: `skills/${six}/${rest}`,\n });\n if (own)\n run.notes.shadowed.push(\n `the skill ${name}: ${GLOBAL_DIR} has another skill of that name, so SLICC 6's copy is in ${own}, for ${(primary as Unit).cone} only`\n );\n else run.notes.lines.push(`the skill ${name}, for every agent, in ${global}`);\n }\n}\n\nasync function placeMemory(run: Run) {\n const root = memoryRoot(run.deps.memoryHome);\n const { data, units } = run.parsed;\n const places = [\n { name: 'memory/global.md', file: `${root}/MEMORY.md`, rest: `${V6_ROOT}/memory/global.md` },\n ...units\n .filter((unit) => unit.memory)\n .map((unit) => ({\n name: unit.memory as string,\n file: `${root}/${unit.cone}/MEMORY.md`,\n rest: `${V6_ROOT}/memory/${unit.cone}.md`,\n })),\n ];\n for (const place of places) {\n const bytes = data.get(place.name);\n const done = bytes\n ? await remember(run.writer, place.file, textOf(bytes, place.name), {\n file: place.rest,\n date: run.date,\n })\n : 'none';\n if (done === 'merged')\n run.notes.lines.push(`memory, under \"Imported from SLICC 6 (${run.date})\", in ${place.file}`);\n if (done === 'aside')\n run.notes.lines.push(`memory, in ${place.rest}: ${place.file} has no room left for it`);\n }\n}\n\nfunction earlier(run: Run, unit: Unit, name: string): LickTarget | undefined {\n const found = Object.entries(run.deps.agents.state().scoops).filter(\n ([, record]) => record.cone === unit.cone && record.name === name && !record.removed\n );\n return found.at(-1)?.[0] as LickTarget | undefined;\n}\n\nfunction router(run: Run, maps: Maps) {\n return (folder: string | null | undefined): [Unit, LickTarget] | undefined => {\n const cone = folder ? maps.byFolder.get(folder) : undefined;\n if (cone) return [cone, 'cone'];\n const owner = folder ? maps.owners.get(folder) : undefined;\n const scoop = owner?.scoops.find((item) => item.six === folder);\n const target =\n owner && scoop\n ? owner.record\n ? (`scoop:${scoop.folder}` as LickTarget)\n : earlier(run, owner, scoop.name)\n : undefined;\n if (owner && target) return [owner, target];\n const unit = owner ?? maps.primary;\n return unit ? [unit, 'cone'] : undefined;\n };\n}\n\nconst FIELD = '[0-9A-Za-z*,/-]+';\nconst SCHEDULE = new RegExp(\n `^(@(yearly|annually|monthly|weekly|daily|midnight|hourly|reboot)|${FIELD}( ${FIELD}){4})$`\n);\n\nconst flat = (text: string) => text.replace(/\\s+/g, ' ').trim();\n\nasync function placeLicks(run: Run, maps: Maps) {\n const { licks } = run.parsed;\n const route = router(run, maps);\n const crons = new Map<Unit, CronLine[]>();\n const add = (found: [Unit, LickTarget] | undefined, line: Omit<CronLine, 'target'>) => {\n const why = !SCHEDULE.test(line.schedule)\n ? `\"${line.schedule}\" is not a cron schedule`\n : !found && 'there is no cone to send it to';\n if (why) run.notes.skipped.push(`the cron ${line.name}: ${why}`);\n else if (found)\n crons.set(found[0], [...(crons.get(found[0]) ?? []), { ...line, target: found[1] }]);\n };\n for (const [at, task] of licks.crontasks.entries())\n add(route(task.targetFolder), {\n name: lickName(task.name, `v6-cron-${at + 1}`),\n schedule: flat(task.cron),\n message: '',\n ...(task.filter ? { filter: task.filter } : {}),\n });\n for (const [at, task] of licks.tasks.entries()) {\n if (task.scheduleType === 'cron')\n add(route(task.groupFolder), {\n name: `v6-task-${at + 1}`,\n schedule: flat(task.scheduleValue),\n message: flat(task.prompt),\n });\n else\n run.notes.tasks.push(\n `${task.scheduleType} ${task.scheduleValue}: ${flat(task.prompt).slice(0, 80)}`\n );\n }\n for (const [unit, list] of crons) {\n await prepare(run, unit);\n const user = run.deps.layout.users ? unit.cone : 'root';\n const count = await installCrons(run.writer, user, list, run.date);\n if (count) run.notes.crons.push(`${count} in crontab -u ${user}`);\n }\n for (const [at, hook] of licks.webhooks.entries()) {\n const found = route(hook.targetFolder);\n const name = lickName(hook.name, `v6-webhook-${at + 1}`);\n if (!found) run.notes.skipped.push(`the webhook ${name}: there is no cone to send it to`);\n if (!found) continue;\n const root = `${found[0].home}/.slicc/webhooks`;\n await putIn(\n run,\n found[0],\n root,\n `${name}.json.v6`,\n new TextEncoder().encode(`${JSON.stringify({ target: found[1] }, null, 2)}\\n`)\n );\n run.notes.webhooks.push(`${root}/${name}.json.v6`);\n }\n}\n\nasync function placeGit(run: Run) {\n const name = run.opened.manifest.gitconfig;\n const bytes = name ? run.parsed.data.get(name) : undefined;\n const section = bytes ? userSection(textOf(bytes, name as string)) : '';\n if (!section) return;\n const file = `${ROOT_HOME}/.gitconfig`;\n const read = await run.writer.env.readTextFile(file, run.writer.context);\n const before = read.ok ? read.value.trimEnd() : '';\n if (/^\\s*\\[user\\]/im.test(before)) {\n run.notes.skipped.push(`SLICC 6's git [user]: ${file} already has one`);\n return;\n }\n await run.writer.put(file, `${before ? `${before}\\n` : ''}${section}`, false);\n run.notes.lines.push(`your git name and email, in ${file}`);\n}\n\nexport async function place(run: Run): Promise<void> {\n const { manifest } = run.opened;\n const units = run.parsed.units;\n const byFolder = new Map(\n units.filter((unit) => unit.six).map((unit) => [unit.six as string, unit])\n );\n const owners = new Map(\n units.flatMap((unit) =>\n unit.scoops.filter((scoop) => scoop.six).map((scoop) => [scoop.six as string, unit])\n )\n );\n const maps: Maps = {\n byFolder,\n owners,\n primary: byFolder.get('cone') ?? units.find((unit) => unit.six !== null),\n };\n const skills = new Map<string, string[]>();\n const later: [string, string][] = [];\n for (const [at, file] of manifest.files.entries()) {\n const where = whereOf(manifest, file.path);\n if (where.kind === 'skill')\n skills.set(where.folder, [...(skills.get(where.folder) ?? []), where.rest]);\n if (where.kind === 'home-user') later.push([file.path, where.rest]);\n if (where.kind === 'sprinkle') sprinkleCalls(run, where.folder, await staged(run, file.path));\n for (const [unit, root, rest] of targets(run, maps, where, file.path))\n await putIn(run, unit, root, rest, { zip: file.path });\n run.show('writing', at + 1, manifest.files.length);\n }\n await placeSkills(run, skills, maps.primary);\n await placeMemory(run);\n await placeLicks(run, maps);\n await placeGit(run);\n for (const [zip, rest] of later) await putIn(run, null, `${V6_ROOT}/home-user`, rest, { zip });\n if (run.deps.layout.users)\n for (const unit of units) await ownTree(run.writer, unit.cone, unit.home, unit.roots);\n}\n\nconst WHY: Record<string, string> = {\n env: 'environment file, often holds keys',\n pem: 'PEM file, may be a private key',\n 'git-credentials': 'git credentials in plain text',\n npmrc: 'npm config, may hold an auth token',\n 'slicc-oauth': 'SLICC 6 OAuth state',\n};\n\nconst NEVER: Record<string, string> = {\n 'secrets-env': '~/.slicc/secrets.env, which stays on the host',\n 'github-token': 'the GitHub token',\n 'mcp-tokens': 'MCP tokens',\n 'chrome-storage': \"the extension's chrome.storage secrets\",\n};\n\nconst bullets = (items: readonly string[], none: string) =>\n (items.length ? items : [none]).map((item) => `- ${item}`);\n\nfunction nextSteps(run: Run, mounts: readonly string[]): string[] {\n const { manifest } = run.opened;\n const { notes } = run;\n const sprinkles = [...notes.sprinkles].map(\n ([name, calls]) => `${name} (${[...calls].join(', ')})`\n );\n return [\n 'Re-enter your keys and logins in Settings.',\n ...(mounts.length ? [`Mount these folders again: ${mounts.join(', ')}.`] : []),\n ...(manifest.mcp?.length\n ? [\n `Add these MCP servers again: ${manifest.mcp.map((item) => `${item.name} (${item.url})`).join(', ')}.`,\n ]\n : []),\n ...(notes.crons.length\n ? [\n `Turn crons on with sudo crontab -u <user> -e: remove the leading \"# \" from a job's two lines.`,\n ]\n : []),\n ...(notes.webhooks.length\n ? [\n \"Turn a webhook on by renaming <name>.json.v6 to <name>.json, then point its sender at seven's webhook URL.\",\n ]\n : []),\n ...(sprinkles.length\n ? [\n `These sprinkles call SLICC APIs other than slicc.lick and may need edits: ${sprinkles.join(', ')}.`,\n ]\n : []),\n ];\n}\n\nexport async function reportOf(run: Run, made: readonly string[]): Promise<string> {\n const { manifest } = run.opened;\n const { notes, date, parsed } = run;\n const recorded = parsed.units.filter((unit) => unit.record);\n const imported = [\n ...recorded.map(\n (unit, at) =>\n `${unit.title}: frozen cone ${made[at]}, home ${unit.home}${unit.suffix ? `, new files in ${unit.home}/workspace${unit.suffix}` : ''}${unit.scoops.length ? `, with ${unit.scoops.map((scoop) => scoop.name).join(', ')}` : ''}`\n ),\n ...notes.lines,\n ...notes.crons.map((line) => `crons, imported off: ${line}`),\n ...notes.webhooks.map((path) => `a webhook, imported off: ${path}`),\n ];\n const mounts = manifest.skipped\n .filter((item) => item.reason === 'mount')\n .map((item) => item.path);\n const skipped = [\n ...manifest.skipped.map((item) => `${item.path}: ${item.reason}`),\n ...parsed.units\n .filter((unit) => !unit.record)\n .map((unit) => `${unit.title}: unchanged since the last import`),\n ...notes.skipped,\n ...notes.shadowed,\n ...notes.tasks.map((task) => `a SLICC 6 task that cron can't express: ${task}`),\n ];\n const flagged = manifest.flagged.map(\n (item) => `| ${notes.placed.get(item.path) ?? item.path} | ${item.size} B | ${WHY[item.rule]} |`\n );\n const split = accountsSplit(manifest.providers, run.deps.providers);\n const providers = [\n ...split.again.map((id) => `${split.nameOf(id)}: re-enter in Settings`),\n ...split.missing.map((id) => `${split.nameOf(id)}: SLICC 7 has no such provider`),\n ];\n const never = (manifest.secrets ?? []).map((name) => NEVER[name] ?? name);\n const part = manifest.part ? `, part ${manifest.part.index} of ${manifest.part.of}` : '';\n const text = [\n `# SLICC 6 import, ${date}`,\n '',\n `From ${manifest.producer.origin} (SLICC ${manifest.producer.slicc}), exported ${manifest.createdAt}, export ${manifest.exportId}${part}.`,\n '',\n '## Imported',\n '',\n ...bullets(imported, 'nothing new'),\n '',\n '## Skipped',\n '',\n ...bullets(skipped, 'nothing'),\n '',\n ...(flagged.length\n ? [\n '## Review these files',\n '',\n '| Path | Size | Why |',\n '| --- | --- | --- |',\n ...flagged,\n '',\n \"Delete or move these if you don't need them here.\",\n '',\n ]\n : []),\n '## Re-enter in Settings',\n '',\n ...bullets(providers, 'no providers'),\n ...(never.length ? ['', `These never move: ${never.join(', ')}.`] : []),\n '',\n '## Next steps',\n '',\n ...nextSteps(run, mounts).map((step) => `- ${step}`),\n '',\n ].join('\\n');\n let file = `${ROOT_HOME}/v6-import-${date}.md`;\n for (let n = 2; await run.writer.exists(file); n++)\n file = `${ROOT_HOME}/v6-import-${date}-${n}.md`;\n const written = await run.writer.env.writeFile(file, text, BACKGROUND_CONTEXT);\n if (!written.ok)\n throw new ImportError(`couldn't write the report ${file}: ${written.error.message}`);\n return file;\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport type { ExecutionEnv } from '@earendil-works/pi-durable/env';\nimport { coneSpec } from '../kernel/accounts.ts';\nimport { ROOT_HOME } from '../layout.ts';\nimport type { LickTarget } from '../licks/lick.ts';\nimport { memoryRoot } from '../memory/store.ts';\nimport { GLOBAL_DIR } from '../skills/index.ts';\nimport { validateName } from '../skills/skills.ts';\nimport type { ImportDeps } from './v6.ts';\nimport {\n accountsSplit,\n type Opened,\n type Parsed,\n textOf,\n type Unit,\n type Where,\n whereOf,\n} from './v6.ts';\nimport { lickName, skillName } from './v6-schema.ts';\nimport {\n type CronLine,\n installCrons,\n ownTree,\n remember,\n same,\n userSection,\n type Writer,\n} from './v6-write.ts';\nimport { ImportError } from './zip.ts';\n\nexport const V6_ROOT = `${ROOT_HOME}/v6`;\n\nexport type Notes = {\n lines: string[];\n skipped: string[];\n shadowed: string[];\n tasks: string[];\n sprinkles: Map<string, Set<string>>;\n crons: string[];\n webhooks: string[];\n placed: Map<string, string>;\n};\n\nexport type Run = {\n deps: ImportDeps;\n opened: Opened;\n parsed: Parsed;\n writer: Writer;\n stage: string;\n date: string;\n notes: Notes;\n show: (phase: 'writing', done: number, total: number) => void;\n fresh: Map<string, boolean>;\n ready: Set<Unit>;\n at: Map<string, string>;\n};\n\ntype Source = Uint8Array | { zip: string };\n\nasync function staged(run: Run, zip: string): Promise<Uint8Array> {\n const read = await run.writer.env.readBinaryFile(\n run.at.get(zip) ?? `${run.stage}/${zip}`,\n run.writer.context\n );\n if (!read.ok) throw new ImportError(`the staged copy of ${zip} is gone`);\n return read.value;\n}\n\nasync function prepare(run: Run, unit: Unit): Promise<void> {\n if (run.ready.has(unit)) return;\n run.ready.add(unit);\n const { users, layout, env } = run.deps;\n if (unit.fresh)\n run.writer.later(async () => {\n await users?.remove(unit.cone).catch(() => false);\n await env.remove(unit.home, { recursive: true, force: true }, BACKGROUND_CONTEXT);\n });\n if (layout.users && users) await users.add(coneSpec(unit.cone));\n}\n\nasync function envOf(run: Run, unit: Unit | null): Promise<ExecutionEnv | undefined> {\n if (!unit || unit.fresh || !run.deps.layout.users) return undefined;\n const env = await run.deps.envOf?.(unit.cone);\n if (!env) throw new ImportError(`there is no kernel user ${unit.cone} to write its files as`);\n return env;\n}\n\nasync function putIn(run: Run, unit: Unit | null, root: string, rest: string, source: Source) {\n if (unit) await prepare(run, unit);\n const env = await envOf(run, unit);\n let fresh = run.fresh.get(root);\n if (fresh === undefined) {\n fresh = unit?.fresh || (await run.writer.tree(root, env));\n run.fresh.set(root, fresh);\n }\n if (unit?.fresh) unit.roots.add(root);\n const path = `${root}/${rest}`;\n const from = 'zip' in source && !env ? run.at.get(source.zip) : undefined;\n if (from && 'zip' in source) {\n await run.writer.move(from, path, fresh);\n run.at.set(source.zip, path);\n } else\n await run.writer.put(\n path,\n 'zip' in source ? await staged(run, source.zip) : source,\n fresh,\n env\n );\n if ('zip' in source && !run.notes.placed.has(source.zip)) run.notes.placed.set(source.zip, path);\n}\n\nconst clean = (name: string) => name.replace(/[^A-Za-z0-9._-]+/g, '_').slice(-80) || 'file';\n\ntype Maps = { byFolder: Map<string, Unit>; owners: Map<string, Unit>; primary?: Unit };\n\nfunction targets(run: Run, maps: Maps, where: Where, zip: string): [Unit, string, string][] {\n if (where.kind === 'cone' || where.kind === 'scoop') {\n const unit = (where.kind === 'cone' ? maps.byFolder : maps.owners).get(where.folder) as Unit;\n const root =\n where.kind === 'cone'\n ? `${unit.home}/workspace${unit.suffix}`\n : `${unit.home}/scoops${unit.suffix}/${where.folder}`;\n return [[unit, root, where.rest]];\n }\n if (where.kind === 'sprinkle') {\n if (!maps.primary)\n run.notes.skipped.push(`the sprinkle ${where.folder}: there is no cone to put it in`);\n return maps.primary\n ? [\n [\n maps.primary,\n `${maps.primary.home}/sprinkles/${lickName(where.folder, 'sprinkle')}`,\n where.rest,\n ],\n ]\n : [];\n }\n if (where.kind !== 'attachment') return [];\n return run.parsed.units.flatMap((unit) =>\n (unit.doc?.attachments ?? [])\n .filter((item) => item.present && item.path === zip)\n .map((item): [Unit, string, string] => {\n const rest = `${item.sha256.slice(0, 12)}-${clean(item.originalName)}`;\n unit.files.set(item.id, `${unit.home}/v6-attachments/${rest}`);\n return [unit, `${unit.home}/v6-attachments`, rest];\n })\n );\n}\n\nfunction sprinkleCalls(run: Run, name: string, bytes: Uint8Array) {\n const found = [...new TextDecoder().decode(bytes).matchAll(/\\bslicc\\.([A-Za-z_$][\\w$]*)/g)]\n .map((match) => `slicc.${match[1]}`)\n .filter((call) => call !== 'slicc.lick');\n if (!found.length) return;\n const known = run.notes.sprinkles.get(name) ?? new Set<string>();\n for (const call of found) known.add(call);\n run.notes.sprinkles.set(name, known);\n}\n\nasync function alike(\n run: Run,\n dir: string,\n six: string,\n rests: readonly string[]\n): Promise<boolean> {\n for (const rest of rests) {\n const old = await run.writer.env.readBinaryFile(`${dir}/${rest}`, run.writer.context);\n if (!old.ok || !same(old.value, await staged(run, `skills/${six}/${rest}`))) return false;\n }\n return true;\n}\n\nasync function placeSkills(run: Run, skills: Map<string, string[]>, primary: Unit | undefined) {\n const seen = new Set<string>();\n for (const [six, rests] of skills) {\n const name = skillName(six);\n if (!name || validateName(name).length || seen.has(name)) {\n run.notes.skipped.push(\n `the skill ${six}: its name doesn't fit seven's rule (a-z, 0-9 and -)`\n );\n continue;\n }\n seen.add(name);\n const global = `${GLOBAL_DIR}/${name}`;\n const exists = await run.writer.exists(global);\n if (exists && (await alike(run, global, six, rests))) {\n run.notes.lines.push(`the skill ${name}: already in ${GLOBAL_DIR}`);\n continue;\n }\n const own = primary && exists ? `${primary.home}/.pi/agent/skills/${name}` : undefined;\n if (exists && !own) {\n run.notes.skipped.push(`the skill ${name}: ${GLOBAL_DIR} has another skill of that name`);\n continue;\n }\n for (const rest of rests)\n await putIn(run, own ? (primary as Unit) : null, own ?? global, rest, {\n zip: `skills/${six}/${rest}`,\n });\n if (own)\n run.notes.shadowed.push(\n `the skill ${name}: ${GLOBAL_DIR} has another skill of that name, so SLICC 6's copy is in ${own}, for ${(primary as Unit).cone} only`\n );\n else run.notes.lines.push(`the skill ${name}, for every agent, in ${global}`);\n }\n}\n\nasync function placeMemory(run: Run) {\n const root = memoryRoot(run.deps.memoryHome);\n const { data, units } = run.parsed;\n const places = [\n { name: 'memory/global.md', file: `${root}/MEMORY.md`, rest: `${V6_ROOT}/memory/global.md` },\n ...units\n .filter((unit) => unit.memory)\n .map((unit) => ({\n name: unit.memory as string,\n file: `${root}/${unit.cone}/MEMORY.md`,\n rest: `${V6_ROOT}/memory/${unit.cone}.md`,\n })),\n ];\n for (const place of places) {\n const bytes = data.get(place.name);\n const done = bytes\n ? await remember(run.writer, place.file, textOf(bytes, place.name), {\n file: place.rest,\n date: run.date,\n })\n : 'none';\n if (done === 'merged')\n run.notes.lines.push(`memory, under \"Imported from SLICC 6 (${run.date})\", in ${place.file}`);\n if (done === 'aside')\n run.notes.lines.push(`memory, in ${place.rest}: ${place.file} has no room left for it`);\n }\n}\n\nfunction earlier(run: Run, unit: Unit, name: string): LickTarget | undefined {\n const found = Object.entries(run.deps.agents.state().scoops).filter(\n ([, record]) => record.cone === unit.cone && record.name === name && !record.removed\n );\n return found.at(-1)?.[0] as LickTarget | undefined;\n}\n\nfunction router(run: Run, maps: Maps) {\n return (folder: string | null | undefined): [Unit, LickTarget] | undefined => {\n const cone = folder ? maps.byFolder.get(folder) : undefined;\n if (cone) return [cone, 'cone'];\n const owner = folder ? maps.owners.get(folder) : undefined;\n const scoop = owner?.scoops.find((item) => item.six === folder);\n const target =\n owner && scoop\n ? owner.record\n ? (`scoop:${scoop.folder}` as LickTarget)\n : earlier(run, owner, scoop.name)\n : undefined;\n if (owner && target) return [owner, target];\n const unit = owner ?? maps.primary;\n return unit ? [unit, 'cone'] : undefined;\n };\n}\n\nconst FIELD = '[0-9A-Za-z*,/-]+';\nconst SCHEDULE = new RegExp(\n `^(@(yearly|annually|monthly|weekly|daily|midnight|hourly|reboot)|${FIELD}( ${FIELD}){4})$`\n);\n\nconst flat = (text: string) => text.replace(/\\s+/g, ' ').trim();\n\nasync function placeLicks(run: Run, maps: Maps) {\n const { licks } = run.parsed;\n const route = router(run, maps);\n const crons = new Map<Unit, CronLine[]>();\n const add = (found: [Unit, LickTarget] | undefined, line: Omit<CronLine, 'target'>) => {\n const why = !SCHEDULE.test(line.schedule)\n ? `\"${line.schedule}\" is not a cron schedule`\n : !found && 'there is no cone to send it to';\n if (why) run.notes.skipped.push(`the cron ${line.name}: ${why}`);\n else if (found)\n crons.set(found[0], [...(crons.get(found[0]) ?? []), { ...line, target: found[1] }]);\n };\n for (const [at, task] of licks.crontasks.entries())\n add(route(task.targetFolder), {\n name: lickName(task.name, `v6-cron-${at + 1}`),\n schedule: flat(task.cron),\n message: '',\n ...(task.filter ? { filter: task.filter } : {}),\n });\n for (const [at, task] of licks.tasks.entries()) {\n if (task.scheduleType === 'cron')\n add(route(task.groupFolder), {\n name: `v6-task-${at + 1}`,\n schedule: flat(task.scheduleValue),\n message: flat(task.prompt),\n });\n else\n run.notes.tasks.push(\n `${task.scheduleType} ${task.scheduleValue}: ${flat(task.prompt).slice(0, 80)}`\n );\n }\n for (const [unit, list] of crons) {\n await prepare(run, unit);\n const user = run.deps.layout.users ? unit.cone : 'root';\n const count = await installCrons(run.writer, user, list, run.date);\n if (count) run.notes.crons.push(`${count} in crontab -u ${user}`);\n }\n for (const [at, hook] of licks.webhooks.entries()) {\n const found = route(hook.targetFolder);\n const name = lickName(hook.name, `v6-webhook-${at + 1}`);\n if (!found) run.notes.skipped.push(`the webhook ${name}: there is no cone to send it to`);\n if (!found) continue;\n const root = `${found[0].home}/.slicc/webhooks`;\n await putIn(\n run,\n found[0],\n root,\n `${name}.json.v6`,\n new TextEncoder().encode(`${JSON.stringify({ target: found[1] }, null, 2)}\\n`)\n );\n run.notes.webhooks.push(`${root}/${name}.json.v6`);\n }\n}\n\nasync function placeGit(run: Run) {\n const name = run.opened.manifest.gitconfig;\n const bytes = name ? run.parsed.data.get(name) : undefined;\n const section = bytes ? userSection(textOf(bytes, name as string)) : '';\n if (!section) return;\n const file = `${ROOT_HOME}/.gitconfig`;\n const read = await run.writer.env.readTextFile(file, run.writer.context);\n const before = read.ok ? read.value.trimEnd() : '';\n if (/^\\s*\\[user\\]/im.test(before)) {\n run.notes.skipped.push(`SLICC 6's git [user]: ${file} already has one`);\n return;\n }\n await run.writer.put(file, `${before ? `${before}\\n` : ''}${section}`, false);\n run.notes.lines.push(`your git name and email, in ${file}`);\n}\n\nexport async function place(run: Run): Promise<void> {\n const { manifest } = run.opened;\n const units = run.parsed.units;\n const byFolder = new Map(\n units.filter((unit) => unit.six).map((unit) => [unit.six as string, unit])\n );\n const owners = new Map(\n units.flatMap((unit) =>\n unit.scoops.filter((scoop) => scoop.six).map((scoop) => [scoop.six as string, unit])\n )\n );\n const maps: Maps = {\n byFolder,\n owners,\n primary: byFolder.get('cone') ?? units.find((unit) => unit.six !== null),\n };\n const skills = new Map<string, string[]>();\n const later: [string, string][] = [];\n for (const [at, file] of manifest.files.entries()) {\n const where = whereOf(manifest, file.path);\n if (where.kind === 'skill')\n skills.set(where.folder, [...(skills.get(where.folder) ?? []), where.rest]);\n if (where.kind === 'home-user') later.push([file.path, where.rest]);\n if (where.kind === 'sprinkle') sprinkleCalls(run, where.folder, await staged(run, file.path));\n for (const [unit, root, rest] of targets(run, maps, where, file.path))\n await putIn(run, unit, root, rest, { zip: file.path });\n run.show('writing', at + 1, manifest.files.length);\n }\n await placeSkills(run, skills, maps.primary);\n await placeMemory(run);\n await placeLicks(run, maps);\n await placeGit(run);\n for (const [zip, rest] of later) await putIn(run, null, `${V6_ROOT}/home-user`, rest, { zip });\n if (run.deps.layout.users)\n for (const unit of units) await ownTree(run.writer, unit.cone, unit.home, unit.roots);\n}\n\nconst WHY: Record<string, string> = {\n env: 'environment file, often holds keys',\n pem: 'PEM file, may be a private key',\n 'git-credentials': 'git credentials in plain text',\n npmrc: 'npm config, may hold an auth token',\n 'slicc-oauth': 'SLICC 6 OAuth state',\n};\n\nconst NEVER: Record<string, string> = {\n 'secrets-env': '~/.slicc/secrets.env, which stays on the host',\n 'github-token': 'the GitHub token',\n 'mcp-tokens': 'MCP tokens',\n 'chrome-storage': \"the extension's chrome.storage secrets\",\n};\n\nconst bullets = (items: readonly string[], none: string) =>\n (items.length ? items : [none]).map((item) => `- ${item}`);\n\nfunction nextSteps(run: Run, mounts: readonly string[]): string[] {\n const { manifest } = run.opened;\n const { notes } = run;\n const sprinkles = [...notes.sprinkles].map(\n ([name, calls]) => `${name} (${[...calls].join(', ')})`\n );\n return [\n 'Re-enter your keys and logins in Settings.',\n ...(mounts.length ? [`Mount these folders again: ${mounts.join(', ')}.`] : []),\n ...(manifest.mcp.length\n ? [\n `Add these MCP servers again: ${manifest.mcp.map((item) => `${item.name} (${item.url})`).join(', ')}.`,\n ]\n : []),\n ...(notes.crons.length\n ? [\n `Turn crons on with sudo crontab -u <user> -e: remove the leading \"# \" from a job's two lines.`,\n ]\n : []),\n ...(notes.webhooks.length\n ? [\n \"Turn a webhook on by renaming <name>.json.v6 to <name>.json, then point its sender at seven's webhook URL.\",\n ]\n : []),\n ...(sprinkles.length\n ? [\n `These sprinkles call SLICC APIs other than slicc.lick and may need edits: ${sprinkles.join(', ')}.`,\n ]\n : []),\n ];\n}\n\nexport async function reportOf(run: Run, made: readonly string[]): Promise<string> {\n const { manifest } = run.opened;\n const { notes, date, parsed } = run;\n const recorded = parsed.units.filter((unit) => unit.record);\n const imported = [\n ...recorded.map(\n (unit, at) =>\n `${unit.title}: frozen cone ${made[at]}, home ${unit.home}${unit.suffix ? `, new files in ${unit.home}/workspace${unit.suffix}` : ''}${unit.scoops.length ? `, with ${unit.scoops.map((scoop) => scoop.name).join(', ')}` : ''}`\n ),\n ...notes.lines,\n ...notes.crons.map((line) => `crons, imported off: ${line}`),\n ...notes.webhooks.map((path) => `a webhook, imported off: ${path}`),\n ];\n const mounts = manifest.skipped\n .filter((item) => item.reason === 'mount')\n .map((item) => item.path);\n const skipped = [\n ...manifest.skipped.map((item) => `${item.path}: ${item.reason}`),\n ...parsed.units\n .filter((unit) => !unit.record)\n .map((unit) => `${unit.title}: unchanged since the last import`),\n ...notes.skipped,\n ...notes.shadowed,\n ...notes.tasks.map((task) => `a SLICC 6 task that cron can't express: ${task}`),\n ];\n const flagged = manifest.flagged.map(\n (item) => `| ${notes.placed.get(item.path) ?? item.path} | ${item.size} B | ${WHY[item.rule]} |`\n );\n const split = accountsSplit(manifest.providers, run.deps.providers);\n const providers = [\n ...split.again.map((id) => `${split.nameOf(id)}: re-enter in Settings`),\n ...split.missing.map((id) => `${split.nameOf(id)}: SLICC 7 has no such provider`),\n ];\n const never = manifest.secrets.map((name) => NEVER[name] ?? name);\n const part = manifest.part ? `, part ${manifest.part.index} of ${manifest.part.of}` : '';\n const text = [\n `# SLICC 6 import, ${date}`,\n '',\n `From ${manifest.producer.origin} (SLICC ${manifest.producer.slicc}), exported ${manifest.createdAt}, export ${manifest.exportId}${part}.`,\n '',\n '## Imported',\n '',\n ...bullets(imported, 'nothing new'),\n '',\n '## Skipped',\n '',\n ...bullets(skipped, 'nothing'),\n '',\n ...(flagged.length\n ? [\n '## Review these files',\n '',\n '| Path | Size | Why |',\n '| --- | --- | --- |',\n ...flagged,\n '',\n \"Delete or move these if you don't need them here.\",\n '',\n ]\n : []),\n '## Re-enter in Settings',\n '',\n ...bullets(providers, 'no providers'),\n ...(never.length ? ['', `These never move: ${never.join(', ')}.`] : []),\n '',\n '## Next steps',\n '',\n ...nextSteps(run, mounts).map((step) => `- ${step}`),\n '',\n ].join('\\n');\n let file = `${ROOT_HOME}/v6-import-${date}.md`;\n for (let n = 2; await run.writer.exists(file); n++)\n file = `${ROOT_HOME}/v6-import-${date}-${n}.md`;\n const written = await run.writer.env.writeFile(file, text, BACKGROUND_CONTEXT);\n if (!written.ok)\n throw new ImportError(`couldn't write the report ${file}: ${written.error.message}`);\n return file;\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["place"]
7
7
  }
@@ -295,6 +295,25 @@ function manifestLists(value) {
295
295
  `${key} must be strings`
296
296
  );
297
297
  }
298
+ const orNull = (value) => value ?? null;
299
+ const scoopOf = (scoop) => ({ ...scoop, conversation: orNull(scoop.conversation) });
300
+ function normalised(value) {
301
+ return {
302
+ ...value,
303
+ cones: value.cones.map((cone) => ({
304
+ ...scoopOf(cone),
305
+ transcript: orNull(cone.transcript),
306
+ memory: orNull(cone.memory),
307
+ scoops: cone.scoops.map(scoopOf)
308
+ })),
309
+ frozen: value.frozen.map((item) => ({ ...item, frozenAt: orNull(item.frozenAt) })),
310
+ licks: orNull(value.licks),
311
+ gitconfig: orNull(value.gitconfig),
312
+ providers: value.providers ?? [],
313
+ mcp: value.mcp ?? [],
314
+ secrets: value.secrets ?? []
315
+ };
316
+ }
298
317
  function checkManifest(value, version = 1) {
299
318
  need(obj(value) && value.format === "slicc-profile", "this is not a SLICC 6 profile export");
300
319
  need(
@@ -331,7 +350,7 @@ function checkManifest(value, version = 1) {
331
350
  ...cone.scoops.map((scoop) => `scoop:${scoop.folder}`)
332
351
  ]);
333
352
  need(new Set(folders).size === folders.length, "two cones or two scoops share a folder");
334
- return value;
353
+ return normalised(value);
335
354
  }
336
355
  const optional = (value) => value === void 0 || str(value);
337
356
  const target = (value) => maybe(value, (folder) => FOLDER.test(String(folder)));
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/freezer/v6-schema.ts"],
4
- "sourcesContent": ["import { ImportError } from './zip.ts';\n\ntype Json = Record<string, unknown>;\n\nexport type Block =\n | { type: 'text'; text: string }\n | { type: 'tool-call'; id: string; name: string; input: unknown }\n | { type: 'attachment-ref'; attachmentId: string };\n\nexport type V6Message = {\n id: string;\n sequence: number;\n role: 'user' | 'assistant' | 'tool-result';\n timestamp: string;\n content: Block[];\n toolCallId?: string;\n isError?: boolean;\n model?: { provider: string; id: string; api?: string };\n usage?: Json;\n stopReason?: string;\n};\n\nexport type V6Conversation = {\n id: string;\n kind: 'cone' | 'scoop';\n name: string;\n folder?: string;\n messages: V6Message[];\n};\n\nexport type V6Attachment = {\n id: string;\n path: string;\n originalName: string;\n sha256: string;\n byteLength: number;\n present: boolean;\n};\n\nexport type Transcript = {\n session: { id: string; title: string; updatedAt?: string; frozenAt?: string; createdAt?: string };\n conversations: V6Conversation[];\n attachments: V6Attachment[];\n};\n\nexport type ManifestScoop = {\n key: string;\n folder: string;\n name: string;\n conversation: string | null;\n};\n\nexport type ManifestCone = ManifestScoop & {\n transcript: string | null;\n memory: string | null;\n scoops: ManifestScoop[];\n};\n\nexport type Manifest = {\n exportId: string;\n producer: { slicc: string; origin: string };\n createdAt: string;\n part?: { index: number; of: number };\n cones: ManifestCone[];\n frozen: { id: string; title: string; frozenAt: string | null; transcript: string }[];\n skills: { name: string; path: string }[];\n sprinkles: { name: string; path: string }[];\n licks: string | null;\n gitconfig?: string | null;\n files: { path: string; size: number; sha256: string; source?: string }[];\n skipped: { path: string; reason: string }[];\n flagged: { path: string; size: number; rule: string }[];\n providers: string[];\n mcp?: { name: string; url: string }[];\n secrets?: string[];\n};\n\nexport type Licks = {\n crontasks: { name: string; cron: string; targetFolder?: string | null; filter?: string }[];\n tasks: { groupFolder: string; prompt: string; scheduleType: string; scheduleValue: string }[];\n webhooks: { name: string; targetFolder?: string | null; filter?: string }[];\n};\n\nexport const FOLDER = /^[a-z0-9][a-z0-9._-]{0,63}$/;\nconst SHA = /^[0-9a-fA-F]{64}$/;\nconst REASONS = [\n 'canonical-agent-history-unavailable',\n 'tool-data-may-be-truncated',\n 'model-metadata-unavailable',\n 'scoop-history-unavailable',\n 'attachment-file-missing',\n 'attachment-association-unavailable',\n 'complete-snapshot-unavailable',\n];\nconst RULES = ['env', 'pem', 'git-credentials', 'npmrc', 'slicc-oauth'];\n\nconst obj = (value: unknown): value is Json =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\nconst str = (value: unknown): value is string => typeof value === 'string';\nconst full = (value: unknown): value is string => str(value) && value !== '';\nconst count = (value: unknown) => Number.isInteger(value) && (value as number) >= 0;\nconst amount = (value: unknown) =>\n typeof value === 'number' && Number.isFinite(value) && value >= 0;\nconst iso = (value: unknown) =>\n str(value) &&\n /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}/.test(value) &&\n !Number.isNaN(Date.parse(value));\nconst maybe = (value: unknown, check: (value: unknown) => boolean) =>\n value === undefined || value === null || check(value);\n\nfunction need(ok: boolean, error: string): asserts ok {\n if (!ok) throw new ImportError(error);\n}\n\nfunction oneOf(value: unknown, allowed: readonly string[], label: string) {\n need(str(value) && allowed.includes(value), `${label} must be one of ${allowed.join(', ')}`);\n}\n\nfunction list(value: unknown, label: string, check: (item: Json, path: string) => void) {\n need(Array.isArray(value), `${label} must be an array`);\n value.forEach((item, at) => {\n need(obj(item), `${label}[${at}] must be an object`);\n check(item, `${label}[${at}]`);\n });\n}\n\nfunction block(item: Json, path: string) {\n oneOf(item.type, ['text', 'tool-call', 'attachment-ref'], `${path}.type`);\n if (item.type === 'text') need(str(item.text), `${path}.text must be a string`);\n else if (item.type === 'tool-call')\n need(\n full(item.id) && full(item.name) && 'input' in item,\n `${path} needs an id, a name and an input`\n );\n else need(full(item.attachmentId), `${path}.attachmentId must be a non-empty string`);\n}\n\nconst TOKENS = ['input', 'output', 'cacheRead', 'cacheWrite', 'totalTokens'];\nconst COSTS = ['input', 'output', 'cacheRead', 'cacheWrite', 'total'];\n\nfunction message(item: Json, path: string) {\n oneOf(item.role, ['user', 'assistant', 'tool-result'], `${path}.role`);\n if (item.role === 'tool-result') need(full(item.toolCallId), `${path}.toolCallId must be set`);\n need(str(item.id), `${path}.id must be a string`);\n need(\n Number.isInteger(item.sequence) && (item.sequence as number) >= 1,\n `${path}.sequence must be a positive integer`\n );\n need(iso(item.timestamp), `${path}.timestamp must be an ISO 8601 date-time`);\n list(item.content, `${path}.content`, block);\n const usage = item.usage;\n if (usage === undefined) return;\n need(\n obj(usage) &&\n TOKENS.every((key) => count(usage[key])) &&\n obj(usage.cost) &&\n COSTS.every((key) => amount((usage.cost as Json)[key])),\n `${path}.usage is malformed`\n );\n}\n\nfunction attachment(item: Json, path: string) {\n const fields = [\n 'id',\n 'path',\n 'originalName',\n 'mimeType',\n 'sha256',\n 'sourceConversationId',\n 'sourceMessageId',\n ];\n need(\n fields.every((key) => str(item[key])),\n `${path} needs ${fields.join(', ')}`\n );\n need(\n typeof item.byteLength === 'number' && typeof item.present === 'boolean',\n `${path} needs byteLength and present`\n );\n oneOf(item.handling, ['text-redacted', 'binary-unchanged'], `${path}.handling`);\n if (item.present)\n need(\n item.path !== '' &&\n SHA.test(item.sha256 as string) &&\n count(item.byteLength) &&\n item.missingReason === undefined,\n `${path} is present but malformed`\n );\n else {\n need(\n item.path === '' && item.sha256 === '' && item.byteLength === 0,\n `${path} is missing but has a file`\n );\n oneOf(item.missingReason, REASONS.slice(4, 6), `${path}.missingReason`);\n }\n}\n\nfunction redaction(item: Json, path: string) {\n need(str(item.id) && str(item.category) && obj(item.target), `${path} is malformed`);\n oneOf(\n item.detector,\n ['known-secret', 'credential-pattern', 'pre-obfuscated'],\n `${path}.detector`\n );\n const target = item.target as Json;\n oneOf(target.kind, ['json', 'attachment'], `${path}.target.kind`);\n if (target.kind === 'json')\n need(\n str(target.pointer) && (target.pointer === '' || target.pointer.startsWith('/')),\n `${path}.target.pointer is not a JSON pointer`\n );\n else need(full(target.attachmentId), `${path}.target.attachmentId must be set`);\n}\n\nfunction sections(doc: Json) {\n need(doc.schemaVersion === 1, 'schemaVersion must equal 1');\n const exported = doc.export;\n need(\n obj(exported) &&\n str(exported.id) &&\n str(exported.generatedAt) &&\n exported.format === 'slicc-transcript' &&\n obj(exported.producer) &&\n exported.producer.application === 'slicc' &&\n str(exported.producer.version),\n 'export is malformed'\n );\n const session = doc.session;\n need(\n obj(session) && str(session.id) && str(session.title) && obj(session.completeness),\n 'session is malformed'\n );\n oneOf(session.state, ['active', 'frozen'], 'session.state');\n const completeness = session.completeness as Json;\n oneOf(completeness.status, ['complete', 'partial'], 'session.completeness.status');\n need(\n Array.isArray(completeness.missing) &&\n completeness.missing.every((reason) => REASONS.includes(reason)),\n 'session.completeness.missing is malformed'\n );\n const privacy = doc.privacy;\n need(\n obj(privacy) &&\n privacy.reasoningExcluded === true &&\n count(privacy.excludedReasoningBlocks) &&\n privacy.binaryAttachments === 'included-unchanged' &&\n obj(privacy.redactionCounts),\n 'privacy is malformed'\n );\n list(privacy.redactions, 'privacy.redactions', redaction);\n list(doc.conversations, 'conversations', (item, path) => {\n need(str(item.id) && str(item.name), `${path} needs an id and a name`);\n oneOf(item.kind, ['cone', 'scoop'], `${path}.kind`);\n list(item.messages, `${path}.messages`, message);\n });\n list(doc.delegations, 'delegations', (item, path) =>\n need(str(item.sourceConversationId) && str(item.targetConversationId), `${path} is malformed`)\n );\n list(doc.attachments, 'attachments', attachment);\n}\n\nfunction relations(doc: Json) {\n const conversations = new Map<string, { messages: Set<string>; calls: Set<string> }>();\n const messages = new Set<string>();\n const calls = new Set<string>();\n const results: string[] = [];\n const refs: string[] = [];\n for (const conversation of doc.conversations as Json[]) {\n const id = conversation.id as string;\n need(!conversations.has(id), `duplicate conversation id \"${id}\"`);\n const own = { messages: new Set<string>(), calls: new Set<string>() };\n conversations.set(id, own);\n let previous = 0;\n for (const item of conversation.messages as V6Message[]) {\n need(!messages.has(item.id), `duplicate message id \"${item.id}\"`);\n need(\n item.sequence > previous,\n `sequences in conversation \"${id}\" must be strictly increasing`\n );\n previous = item.sequence;\n messages.add(item.id);\n own.messages.add(item.id);\n if (item.role === 'tool-result') results.push(item.toolCallId as string);\n for (const part of item.content)\n if (part.type === 'tool-call') {\n calls.add(part.id);\n own.calls.add(part.id);\n } else if (part.type === 'attachment-ref') refs.push(part.attachmentId);\n }\n }\n for (const id of results)\n need(calls.has(id), `tool-result message references unknown toolCallId \"${id}\"`);\n const attached = new Set((doc.attachments as Json[]).map((item) => item.id as string));\n for (const id of refs)\n need(attached.has(id), `attachment-ref references unknown attachmentId \"${id}\"`);\n for (const item of doc.attachments as Json[])\n need(\n conversations\n .get(item.sourceConversationId as string)\n ?.messages.has(item.sourceMessageId as string) === true,\n `attachment ${item.id as string} comes from an unknown message`\n );\n for (const item of doc.delegations as Json[]) {\n const source = conversations.get(item.sourceConversationId as string);\n need(\n source !== undefined && conversations.has(item.targetConversationId as string),\n 'a delegation names an unknown conversation'\n );\n need(\n !full(item.toolCallId) || source.calls.has(item.toolCallId),\n 'a delegation names an unknown tool call'\n );\n }\n for (const item of (doc.privacy as Json).redactions as Json[]) {\n const target = item.target as Json;\n need(\n target.kind !== 'attachment' || attached.has(target.attachmentId as string),\n 'a redaction names an unknown attachment'\n );\n }\n}\n\nexport function checkTranscript(value: unknown): Transcript {\n need(obj(value), 'document must be a non-null object');\n sections(value);\n relations(value);\n return value as unknown as Transcript;\n}\n\nfunction named(item: Json, path: string) {\n need(\n str(item.key) && str(item.name) && FOLDER.test(String(item.folder)),\n `${path} needs a key, a name and a folder`\n );\n need(maybe(item.conversation, str), `${path}.conversation must be a string`);\n}\n\nexport const DIRECTORY_ROOT = '/slicc-v6-upgrade';\nexport const SOURCE_ROOT = '/slicc-fs/';\nconst SIDECAR = '/slicc-fs/.metadata.json';\nconst MOVABLE = ['files/', 'skills/', 'sprinkles/'];\n\nexport function safeSource(path: string): boolean {\n if (!path.startsWith(SOURCE_ROOT) || path === SIDECAR) return false;\n const rest = path.slice(SOURCE_ROOT.length);\n return (\n !/\\\\|\\0/.test(rest) &&\n rest.split('/').every((part) => part !== '' && part !== '.' && part !== '..')\n );\n}\n\nfunction sources(value: Json, version: 1 | 2) {\n const seen = new Set<string>();\n for (const [at, file] of (value.files as Json[]).entries()) {\n if (file.source === undefined) continue;\n need(version === 2, `files[${at}].source needs version 2, the directory form`);\n need(\n str(file.source) && safeSource(file.source),\n `files[${at}].source must be a path under ${SOURCE_ROOT}`\n );\n need(\n MOVABLE.some((top) => String(file.path).startsWith(top)),\n `files[${at}] can't stay in place: only ${MOVABLE.join(', ')} entries can`\n );\n need(!seen.has(file.source), `files moves ${file.source} twice`);\n seen.add(file.source);\n }\n if (version === 2)\n need(\n (value.skipped as Json[]).every((item) => item.reason !== 'archive-limit'),\n 'a directory form has no archive-limit skips'\n );\n}\n\nfunction manifestLists(value: Json) {\n list(value.cones, 'cones', (cone, path) => {\n named(cone, path);\n need(maybe(cone.transcript, str) && maybe(cone.memory, str), `${path} is malformed`);\n list(cone.scoops, `${path}.scoops`, named);\n });\n list(value.frozen, 'frozen', (item, path) =>\n need(\n str(item.id) && str(item.title) && str(item.transcript) && maybe(item.frozenAt, iso),\n `${path} is malformed`\n )\n );\n for (const key of ['skills', 'sprinkles'])\n list(value[key], key, (item, path) =>\n need(str(item.name) && str(item.path), `${path} is malformed`)\n );\n list(value.files, 'files', (item, path) =>\n need(\n str(item.path) && count(item.size) && SHA.test(String(item.sha256)),\n `${path} is malformed`\n )\n );\n list(value.skipped, 'skipped', (item, path) =>\n need(str(item.path) && str(item.reason), `${path} is malformed`)\n );\n list(value.flagged, 'flagged', (item, path) => {\n need(str(item.path) && count(item.size), `${path} is malformed`);\n oneOf(item.rule, RULES, `${path}.rule`);\n });\n list(value.mcp ?? [], 'mcp', (item, path) =>\n need(str(item.name) && str(item.url), `${path} is malformed`)\n );\n for (const key of ['providers', 'secrets'])\n need(\n Array.isArray(value[key] ?? []) && ((value[key] as unknown[] | undefined) ?? []).every(str),\n `${key} must be strings`\n );\n}\n\nexport function checkManifest(value: unknown, version: 1 | 2 = 1): Manifest {\n need(obj(value) && value.format === 'slicc-profile', 'this is not a SLICC 6 profile export');\n need(\n !(typeof value.version === 'number' && value.version > 2),\n 'This export is from a newer SLICC 6; update seven.'\n );\n need(\n value.version !== 2 || version === 2,\n \"this is SLICC 6's directory form; import it with freezer import --in-place\"\n );\n need(value.version === version, `the export version must be ${version}`);\n need(\n str(value.exportId) && /^[A-Za-z0-9-]{1,64}$/.test(value.exportId),\n 'exportId must be a UUID'\n );\n need(\n obj(value.producer) && str(value.producer.slicc) && str(value.producer.origin),\n 'producer is malformed'\n );\n need(\n iso(value.createdAt) && maybe(value.licks, str) && maybe(value.gitconfig, str),\n 'createdAt, licks or gitconfig is malformed'\n );\n const part = value.part as Json | undefined;\n need(version === 1 || part === undefined, 'a directory form is never split into parts');\n need(\n part === undefined ||\n (obj(part) &&\n count(part.index) &&\n (part.index as number) >= 1 &&\n (part.index as number) <= (part.of as number)),\n 'part is malformed'\n );\n manifestLists(value);\n sources(value, version);\n const folders = (value.cones as Manifest['cones']).flatMap((cone) => [\n `cone:${cone.folder}`,\n ...cone.scoops.map((scoop) => `scoop:${scoop.folder}`),\n ]);\n need(new Set(folders).size === folders.length, 'two cones or two scoops share a folder');\n return value as unknown as Manifest;\n}\n\nconst optional = (value: unknown) => value === undefined || str(value);\nconst target = (value: unknown) => maybe(value, (folder) => FOLDER.test(String(folder)));\n\nexport function checkLicks(value: unknown): Licks {\n need(obj(value), 'licks.json must be an object');\n list(value.crontasks, 'crontasks', (item, path) =>\n need(\n str(item.name) && str(item.cron) && target(item.targetFolder) && optional(item.filter),\n `${path} is malformed`\n )\n );\n list(value.tasks, 'tasks', (item, path) => {\n need(\n str(item.groupFolder) && str(item.prompt) && str(item.scheduleValue),\n `${path} is malformed`\n );\n oneOf(item.scheduleType, ['cron', 'interval', 'once'], `${path}.scheduleType`);\n });\n list(value.webhooks, 'webhooks', (item, path) =>\n need(\n str(item.name) && target(item.targetFolder) && optional(item.filter),\n `${path} is malformed`\n )\n );\n return value as unknown as Licks;\n}\n\nexport function lickName(name: string, fallback: string): string {\n const clean = name\n .toLowerCase()\n .replace(/[^a-z0-9._-]+/g, '-')\n .replace(/^[^a-z0-9]+/, '')\n .slice(0, 64);\n return clean || fallback;\n}\n\nexport function skillName(name: string): string {\n return name\n .toLowerCase()\n .replace(/[^a-z0-9-]+/g, '-')\n .replace(/-{2,}/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, 64)\n .replace(/-+$/, '');\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { ImportError } from './zip.ts';\n\ntype Json = Record<string, unknown>;\n\nexport type Block =\n | { type: 'text'; text: string }\n | { type: 'tool-call'; id: string; name: string; input: unknown }\n | { type: 'attachment-ref'; attachmentId: string };\n\nexport type V6Message = {\n id: string;\n sequence: number;\n role: 'user' | 'assistant' | 'tool-result';\n timestamp: string;\n content: Block[];\n toolCallId?: string;\n isError?: boolean;\n model?: { provider: string; id: string; api?: string };\n usage?: Json;\n stopReason?: string;\n};\n\nexport type V6Conversation = {\n id: string;\n kind: 'cone' | 'scoop';\n name: string;\n folder?: string;\n messages: V6Message[];\n};\n\nexport type V6Attachment = {\n id: string;\n path: string;\n originalName: string;\n sha256: string;\n byteLength: number;\n present: boolean;\n};\n\nexport type Transcript = {\n session: { id: string; title: string; updatedAt?: string; frozenAt?: string; createdAt?: string };\n conversations: V6Conversation[];\n attachments: V6Attachment[];\n};\n\nexport type ManifestScoop = {\n key: string;\n folder: string;\n name: string;\n conversation: string | null;\n};\n\nexport type ManifestCone = ManifestScoop & {\n transcript: string | null;\n memory: string | null;\n scoops: ManifestScoop[];\n};\n\nexport type Manifest = {\n exportId: string;\n producer: { slicc: string; origin: string };\n createdAt: string;\n part?: { index: number; of: number };\n cones: ManifestCone[];\n frozen: { id: string; title: string; frozenAt: string | null; transcript: string }[];\n skills: { name: string; path: string }[];\n sprinkles: { name: string; path: string }[];\n licks: string | null;\n gitconfig: string | null;\n files: { path: string; size: number; sha256: string; source?: string }[];\n skipped: { path: string; reason: string }[];\n flagged: { path: string; size: number; rule: string }[];\n providers: string[];\n mcp: { name: string; url: string }[];\n secrets: string[];\n};\n\nexport type Licks = {\n crontasks: { name: string; cron: string; targetFolder?: string | null; filter?: string }[];\n tasks: { groupFolder: string; prompt: string; scheduleType: string; scheduleValue: string }[];\n webhooks: { name: string; targetFolder?: string | null; filter?: string }[];\n};\n\nexport const FOLDER = /^[a-z0-9][a-z0-9._-]{0,63}$/;\nconst SHA = /^[0-9a-fA-F]{64}$/;\nconst REASONS = [\n 'canonical-agent-history-unavailable',\n 'tool-data-may-be-truncated',\n 'model-metadata-unavailable',\n 'scoop-history-unavailable',\n 'attachment-file-missing',\n 'attachment-association-unavailable',\n 'complete-snapshot-unavailable',\n];\nconst RULES = ['env', 'pem', 'git-credentials', 'npmrc', 'slicc-oauth'];\n\nconst obj = (value: unknown): value is Json =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\nconst str = (value: unknown): value is string => typeof value === 'string';\nconst full = (value: unknown): value is string => str(value) && value !== '';\nconst count = (value: unknown) => Number.isInteger(value) && (value as number) >= 0;\nconst amount = (value: unknown) =>\n typeof value === 'number' && Number.isFinite(value) && value >= 0;\nconst iso = (value: unknown) =>\n str(value) &&\n /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}/.test(value) &&\n !Number.isNaN(Date.parse(value));\nconst maybe = (value: unknown, check: (value: unknown) => boolean) =>\n value === undefined || value === null || check(value);\n\nfunction need(ok: boolean, error: string): asserts ok {\n if (!ok) throw new ImportError(error);\n}\n\nfunction oneOf(value: unknown, allowed: readonly string[], label: string) {\n need(str(value) && allowed.includes(value), `${label} must be one of ${allowed.join(', ')}`);\n}\n\nfunction list(value: unknown, label: string, check: (item: Json, path: string) => void) {\n need(Array.isArray(value), `${label} must be an array`);\n value.forEach((item, at) => {\n need(obj(item), `${label}[${at}] must be an object`);\n check(item, `${label}[${at}]`);\n });\n}\n\nfunction block(item: Json, path: string) {\n oneOf(item.type, ['text', 'tool-call', 'attachment-ref'], `${path}.type`);\n if (item.type === 'text') need(str(item.text), `${path}.text must be a string`);\n else if (item.type === 'tool-call')\n need(\n full(item.id) && full(item.name) && 'input' in item,\n `${path} needs an id, a name and an input`\n );\n else need(full(item.attachmentId), `${path}.attachmentId must be a non-empty string`);\n}\n\nconst TOKENS = ['input', 'output', 'cacheRead', 'cacheWrite', 'totalTokens'];\nconst COSTS = ['input', 'output', 'cacheRead', 'cacheWrite', 'total'];\n\nfunction message(item: Json, path: string) {\n oneOf(item.role, ['user', 'assistant', 'tool-result'], `${path}.role`);\n if (item.role === 'tool-result') need(full(item.toolCallId), `${path}.toolCallId must be set`);\n need(str(item.id), `${path}.id must be a string`);\n need(\n Number.isInteger(item.sequence) && (item.sequence as number) >= 1,\n `${path}.sequence must be a positive integer`\n );\n need(iso(item.timestamp), `${path}.timestamp must be an ISO 8601 date-time`);\n list(item.content, `${path}.content`, block);\n const usage = item.usage;\n if (usage === undefined) return;\n need(\n obj(usage) &&\n TOKENS.every((key) => count(usage[key])) &&\n obj(usage.cost) &&\n COSTS.every((key) => amount((usage.cost as Json)[key])),\n `${path}.usage is malformed`\n );\n}\n\nfunction attachment(item: Json, path: string) {\n const fields = [\n 'id',\n 'path',\n 'originalName',\n 'mimeType',\n 'sha256',\n 'sourceConversationId',\n 'sourceMessageId',\n ];\n need(\n fields.every((key) => str(item[key])),\n `${path} needs ${fields.join(', ')}`\n );\n need(\n typeof item.byteLength === 'number' && typeof item.present === 'boolean',\n `${path} needs byteLength and present`\n );\n oneOf(item.handling, ['text-redacted', 'binary-unchanged'], `${path}.handling`);\n if (item.present)\n need(\n item.path !== '' &&\n SHA.test(item.sha256 as string) &&\n count(item.byteLength) &&\n item.missingReason === undefined,\n `${path} is present but malformed`\n );\n else {\n need(\n item.path === '' && item.sha256 === '' && item.byteLength === 0,\n `${path} is missing but has a file`\n );\n oneOf(item.missingReason, REASONS.slice(4, 6), `${path}.missingReason`);\n }\n}\n\nfunction redaction(item: Json, path: string) {\n need(str(item.id) && str(item.category) && obj(item.target), `${path} is malformed`);\n oneOf(\n item.detector,\n ['known-secret', 'credential-pattern', 'pre-obfuscated'],\n `${path}.detector`\n );\n const target = item.target as Json;\n oneOf(target.kind, ['json', 'attachment'], `${path}.target.kind`);\n if (target.kind === 'json')\n need(\n str(target.pointer) && (target.pointer === '' || target.pointer.startsWith('/')),\n `${path}.target.pointer is not a JSON pointer`\n );\n else need(full(target.attachmentId), `${path}.target.attachmentId must be set`);\n}\n\nfunction sections(doc: Json) {\n need(doc.schemaVersion === 1, 'schemaVersion must equal 1');\n const exported = doc.export;\n need(\n obj(exported) &&\n str(exported.id) &&\n str(exported.generatedAt) &&\n exported.format === 'slicc-transcript' &&\n obj(exported.producer) &&\n exported.producer.application === 'slicc' &&\n str(exported.producer.version),\n 'export is malformed'\n );\n const session = doc.session;\n need(\n obj(session) && str(session.id) && str(session.title) && obj(session.completeness),\n 'session is malformed'\n );\n oneOf(session.state, ['active', 'frozen'], 'session.state');\n const completeness = session.completeness as Json;\n oneOf(completeness.status, ['complete', 'partial'], 'session.completeness.status');\n need(\n Array.isArray(completeness.missing) &&\n completeness.missing.every((reason) => REASONS.includes(reason)),\n 'session.completeness.missing is malformed'\n );\n const privacy = doc.privacy;\n need(\n obj(privacy) &&\n privacy.reasoningExcluded === true &&\n count(privacy.excludedReasoningBlocks) &&\n privacy.binaryAttachments === 'included-unchanged' &&\n obj(privacy.redactionCounts),\n 'privacy is malformed'\n );\n list(privacy.redactions, 'privacy.redactions', redaction);\n list(doc.conversations, 'conversations', (item, path) => {\n need(str(item.id) && str(item.name), `${path} needs an id and a name`);\n oneOf(item.kind, ['cone', 'scoop'], `${path}.kind`);\n list(item.messages, `${path}.messages`, message);\n });\n list(doc.delegations, 'delegations', (item, path) =>\n need(str(item.sourceConversationId) && str(item.targetConversationId), `${path} is malformed`)\n );\n list(doc.attachments, 'attachments', attachment);\n}\n\nfunction relations(doc: Json) {\n const conversations = new Map<string, { messages: Set<string>; calls: Set<string> }>();\n const messages = new Set<string>();\n const calls = new Set<string>();\n const results: string[] = [];\n const refs: string[] = [];\n for (const conversation of doc.conversations as Json[]) {\n const id = conversation.id as string;\n need(!conversations.has(id), `duplicate conversation id \"${id}\"`);\n const own = { messages: new Set<string>(), calls: new Set<string>() };\n conversations.set(id, own);\n let previous = 0;\n for (const item of conversation.messages as V6Message[]) {\n need(!messages.has(item.id), `duplicate message id \"${item.id}\"`);\n need(\n item.sequence > previous,\n `sequences in conversation \"${id}\" must be strictly increasing`\n );\n previous = item.sequence;\n messages.add(item.id);\n own.messages.add(item.id);\n if (item.role === 'tool-result') results.push(item.toolCallId as string);\n for (const part of item.content)\n if (part.type === 'tool-call') {\n calls.add(part.id);\n own.calls.add(part.id);\n } else if (part.type === 'attachment-ref') refs.push(part.attachmentId);\n }\n }\n for (const id of results)\n need(calls.has(id), `tool-result message references unknown toolCallId \"${id}\"`);\n const attached = new Set((doc.attachments as Json[]).map((item) => item.id as string));\n for (const id of refs)\n need(attached.has(id), `attachment-ref references unknown attachmentId \"${id}\"`);\n for (const item of doc.attachments as Json[])\n need(\n conversations\n .get(item.sourceConversationId as string)\n ?.messages.has(item.sourceMessageId as string) === true,\n `attachment ${item.id as string} comes from an unknown message`\n );\n for (const item of doc.delegations as Json[]) {\n const source = conversations.get(item.sourceConversationId as string);\n need(\n source !== undefined && conversations.has(item.targetConversationId as string),\n 'a delegation names an unknown conversation'\n );\n need(\n !full(item.toolCallId) || source.calls.has(item.toolCallId),\n 'a delegation names an unknown tool call'\n );\n }\n for (const item of (doc.privacy as Json).redactions as Json[]) {\n const target = item.target as Json;\n need(\n target.kind !== 'attachment' || attached.has(target.attachmentId as string),\n 'a redaction names an unknown attachment'\n );\n }\n}\n\nexport function checkTranscript(value: unknown): Transcript {\n need(obj(value), 'document must be a non-null object');\n sections(value);\n relations(value);\n return value as unknown as Transcript;\n}\n\nfunction named(item: Json, path: string) {\n need(\n str(item.key) && str(item.name) && FOLDER.test(String(item.folder)),\n `${path} needs a key, a name and a folder`\n );\n need(maybe(item.conversation, str), `${path}.conversation must be a string`);\n}\n\nexport const DIRECTORY_ROOT = '/slicc-v6-upgrade';\nexport const SOURCE_ROOT = '/slicc-fs/';\nconst SIDECAR = '/slicc-fs/.metadata.json';\nconst MOVABLE = ['files/', 'skills/', 'sprinkles/'];\n\nexport function safeSource(path: string): boolean {\n if (!path.startsWith(SOURCE_ROOT) || path === SIDECAR) return false;\n const rest = path.slice(SOURCE_ROOT.length);\n return (\n !/\\\\|\\0/.test(rest) &&\n rest.split('/').every((part) => part !== '' && part !== '.' && part !== '..')\n );\n}\n\nfunction sources(value: Json, version: 1 | 2) {\n const seen = new Set<string>();\n for (const [at, file] of (value.files as Json[]).entries()) {\n if (file.source === undefined) continue;\n need(version === 2, `files[${at}].source needs version 2, the directory form`);\n need(\n str(file.source) && safeSource(file.source),\n `files[${at}].source must be a path under ${SOURCE_ROOT}`\n );\n need(\n MOVABLE.some((top) => String(file.path).startsWith(top)),\n `files[${at}] can't stay in place: only ${MOVABLE.join(', ')} entries can`\n );\n need(!seen.has(file.source), `files moves ${file.source} twice`);\n seen.add(file.source);\n }\n if (version === 2)\n need(\n (value.skipped as Json[]).every((item) => item.reason !== 'archive-limit'),\n 'a directory form has no archive-limit skips'\n );\n}\n\nfunction manifestLists(value: Json) {\n list(value.cones, 'cones', (cone, path) => {\n named(cone, path);\n need(maybe(cone.transcript, str) && maybe(cone.memory, str), `${path} is malformed`);\n list(cone.scoops, `${path}.scoops`, named);\n });\n list(value.frozen, 'frozen', (item, path) =>\n need(\n str(item.id) && str(item.title) && str(item.transcript) && maybe(item.frozenAt, iso),\n `${path} is malformed`\n )\n );\n for (const key of ['skills', 'sprinkles'])\n list(value[key], key, (item, path) =>\n need(str(item.name) && str(item.path), `${path} is malformed`)\n );\n list(value.files, 'files', (item, path) =>\n need(\n str(item.path) && count(item.size) && SHA.test(String(item.sha256)),\n `${path} is malformed`\n )\n );\n list(value.skipped, 'skipped', (item, path) =>\n need(str(item.path) && str(item.reason), `${path} is malformed`)\n );\n list(value.flagged, 'flagged', (item, path) => {\n need(str(item.path) && count(item.size), `${path} is malformed`);\n oneOf(item.rule, RULES, `${path}.rule`);\n });\n list(value.mcp ?? [], 'mcp', (item, path) =>\n need(str(item.name) && str(item.url), `${path} is malformed`)\n );\n for (const key of ['providers', 'secrets'])\n need(\n Array.isArray(value[key] ?? []) && ((value[key] as unknown[] | undefined) ?? []).every(str),\n `${key} must be strings`\n );\n}\n\nconst orNull = (value: unknown) => value ?? null;\n\nconst scoopOf = (scoop: Json) => ({ ...scoop, conversation: orNull(scoop.conversation) });\n\nfunction normalised(value: Json): Manifest {\n return {\n ...value,\n cones: (value.cones as Json[]).map((cone) => ({\n ...scoopOf(cone),\n transcript: orNull(cone.transcript),\n memory: orNull(cone.memory),\n scoops: (cone.scoops as Json[]).map(scoopOf),\n })),\n frozen: (value.frozen as Json[]).map((item) => ({ ...item, frozenAt: orNull(item.frozenAt) })),\n licks: orNull(value.licks),\n gitconfig: orNull(value.gitconfig),\n providers: value.providers ?? [],\n mcp: value.mcp ?? [],\n secrets: value.secrets ?? [],\n } as unknown as Manifest;\n}\n\nexport function checkManifest(value: unknown, version: 1 | 2 = 1): Manifest {\n need(obj(value) && value.format === 'slicc-profile', 'this is not a SLICC 6 profile export');\n need(\n !(typeof value.version === 'number' && value.version > 2),\n 'This export is from a newer SLICC 6; update seven.'\n );\n need(\n value.version !== 2 || version === 2,\n \"this is SLICC 6's directory form; import it with freezer import --in-place\"\n );\n need(value.version === version, `the export version must be ${version}`);\n need(\n str(value.exportId) && /^[A-Za-z0-9-]{1,64}$/.test(value.exportId),\n 'exportId must be a UUID'\n );\n need(\n obj(value.producer) && str(value.producer.slicc) && str(value.producer.origin),\n 'producer is malformed'\n );\n need(\n iso(value.createdAt) && maybe(value.licks, str) && maybe(value.gitconfig, str),\n 'createdAt, licks or gitconfig is malformed'\n );\n const part = value.part as Json | undefined;\n need(version === 1 || part === undefined, 'a directory form is never split into parts');\n need(\n part === undefined ||\n (obj(part) &&\n count(part.index) &&\n (part.index as number) >= 1 &&\n (part.index as number) <= (part.of as number)),\n 'part is malformed'\n );\n manifestLists(value);\n sources(value, version);\n const folders = (value.cones as Manifest['cones']).flatMap((cone) => [\n `cone:${cone.folder}`,\n ...cone.scoops.map((scoop) => `scoop:${scoop.folder}`),\n ]);\n need(new Set(folders).size === folders.length, 'two cones or two scoops share a folder');\n return normalised(value);\n}\n\nconst optional = (value: unknown) => value === undefined || str(value);\nconst target = (value: unknown) => maybe(value, (folder) => FOLDER.test(String(folder)));\n\nexport function checkLicks(value: unknown): Licks {\n need(obj(value), 'licks.json must be an object');\n list(value.crontasks, 'crontasks', (item, path) =>\n need(\n str(item.name) && str(item.cron) && target(item.targetFolder) && optional(item.filter),\n `${path} is malformed`\n )\n );\n list(value.tasks, 'tasks', (item, path) => {\n need(\n str(item.groupFolder) && str(item.prompt) && str(item.scheduleValue),\n `${path} is malformed`\n );\n oneOf(item.scheduleType, ['cron', 'interval', 'once'], `${path}.scheduleType`);\n });\n list(value.webhooks, 'webhooks', (item, path) =>\n need(\n str(item.name) && target(item.targetFolder) && optional(item.filter),\n `${path} is malformed`\n )\n );\n return value as unknown as Licks;\n}\n\nexport function lickName(name: string, fallback: string): string {\n const clean = name\n .toLowerCase()\n .replace(/[^a-z0-9._-]+/g, '-')\n .replace(/^[^a-z0-9]+/, '')\n .slice(0, 64);\n return clean || fallback;\n}\n\nexport function skillName(name: string): string {\n return name\n .toLowerCase()\n .replace(/[^a-z0-9-]+/g, '-')\n .replace(/-{2,}/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, 64)\n .replace(/-+$/, '');\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["target"]
7
7
  }
@@ -0,0 +1,54 @@
1
+ const HELD_CHANNEL = "slicc-held";
2
+ const HELD_MARK = "slicc-sign-in-";
3
+ const HOLD_WAIT_MS = 6e3;
4
+ const UNHELD = "SLICC couldn't protect the sign-in window. Try again.";
5
+ const listen = (name) => new BroadcastChannel(name);
6
+ function markerUrl(marker) {
7
+ return `about:blank#${HELD_MARK}${marker}`;
8
+ }
9
+ function askHold(marker, signal, options = {}) {
10
+ return new Promise((resolve) => {
11
+ const channel = (options.listen ?? listen)(HELD_CHANNEL);
12
+ const finish = (held) => {
13
+ clearTimeout(timer);
14
+ signal.removeEventListener("abort", stop);
15
+ channel.close();
16
+ resolve(held);
17
+ };
18
+ const stop = () => finish(false);
19
+ const timer = setTimeout(stop, options.wait ?? HOLD_WAIT_MS);
20
+ signal.addEventListener("abort", stop, { once: true });
21
+ channel.onmessage = ({ data }) => {
22
+ const answer = data;
23
+ if (answer?.marker !== marker) return;
24
+ if (answer.type === "held" || answer.type === "none") finish(true);
25
+ else if (answer.type === "unheld") finish(false);
26
+ };
27
+ if (signal.aborted) stop();
28
+ else channel.postMessage({ type: "hold", marker });
29
+ });
30
+ }
31
+ function refusal(held, refuse = () => {
32
+ }) {
33
+ const refused = held.then((ok) => {
34
+ if (ok) return;
35
+ refuse();
36
+ throw new Error(UNHELD);
37
+ });
38
+ refused.catch(() => void 0);
39
+ return refused;
40
+ }
41
+ function unlessRefused(work, refused) {
42
+ return Promise.race([work, refused.then(() => work)]);
43
+ }
44
+ export {
45
+ HELD_CHANNEL,
46
+ HELD_MARK,
47
+ HOLD_WAIT_MS,
48
+ UNHELD,
49
+ askHold,
50
+ markerUrl,
51
+ refusal,
52
+ unlessRefused
53
+ };
54
+ //# sourceMappingURL=held.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/spectrum/held.ts"],
4
+ "sourcesContent": ["export const HELD_CHANNEL = 'slicc-held';\nexport const HELD_MARK = 'slicc-sign-in-';\nexport const HOLD_WAIT_MS = 6000;\nexport const UNHELD = \"SLICC couldn't protect the sign-in window. Try again.\";\n\nexport interface HeldChannel {\n onmessage: ((event: { data: unknown }) => void) | null;\n postMessage(data: unknown): void;\n close(): void;\n}\n\nexport type Listen = (name: string) => HeldChannel;\n\nconst listen: Listen = (name) => new BroadcastChannel(name) as unknown as HeldChannel;\n\nexport function markerUrl(marker: string): string {\n return `about:blank#${HELD_MARK}${marker}`;\n}\n\nexport function askHold(\n marker: string,\n signal: AbortSignal,\n options: { listen?: Listen; wait?: number } = {}\n): Promise<boolean> {\n return new Promise((resolve) => {\n const channel = (options.listen ?? listen)(HELD_CHANNEL);\n const finish = (held: boolean) => {\n clearTimeout(timer);\n signal.removeEventListener('abort', stop);\n channel.close();\n resolve(held);\n };\n const stop = () => finish(false);\n const timer = setTimeout(stop, options.wait ?? HOLD_WAIT_MS);\n signal.addEventListener('abort', stop, { once: true });\n channel.onmessage = ({ data }) => {\n const answer = data as { type?: unknown; marker?: unknown } | null;\n if (answer?.marker !== marker) return;\n if (answer.type === 'held' || answer.type === 'none') finish(true);\n else if (answer.type === 'unheld') finish(false);\n };\n if (signal.aborted) stop();\n else channel.postMessage({ type: 'hold', marker });\n });\n}\n\nexport function refusal(held: Promise<boolean>, refuse: () => void = () => {}): Promise<void> {\n const refused = held.then((ok) => {\n if (ok) return;\n refuse();\n throw new Error(UNHELD);\n });\n refused.catch(() => undefined);\n return refused;\n}\n\nexport function unlessRefused<T>(work: Promise<T>, refused: Promise<void>): Promise<T> {\n return Promise.race([work, refused.then(() => work)]);\n}\n"],
5
+ "mappings": "AAAO,MAAM,eAAe;AACrB,MAAM,YAAY;AAClB,MAAM,eAAe;AACrB,MAAM,SAAS;AAUtB,MAAM,SAAiB,CAAC,SAAS,IAAI,iBAAiB,IAAI;AAEnD,SAAS,UAAU,QAAwB;AAChD,SAAO,eAAe,SAAS,GAAG,MAAM;AAC1C;AAEO,SAAS,QACd,QACA,QACA,UAA8C,CAAC,GAC7B;AAClB,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,UAAM,WAAW,QAAQ,UAAU,QAAQ,YAAY;AACvD,UAAM,SAAS,CAAC,SAAkB;AAChC,mBAAa,KAAK;AAClB,aAAO,oBAAoB,SAAS,IAAI;AACxC,cAAQ,MAAM;AACd,cAAQ,IAAI;AAAA,IACd;AACA,UAAM,OAAO,MAAM,OAAO,KAAK;AAC/B,UAAM,QAAQ,WAAW,MAAM,QAAQ,QAAQ,YAAY;AAC3D,WAAO,iBAAiB,SAAS,MAAM,EAAE,MAAM,KAAK,CAAC;AACrD,YAAQ,YAAY,CAAC,EAAE,KAAK,MAAM;AAChC,YAAM,SAAS;AACf,UAAI,QAAQ,WAAW,OAAQ;AAC/B,UAAI,OAAO,SAAS,UAAU,OAAO,SAAS,OAAQ,QAAO,IAAI;AAAA,eACxD,OAAO,SAAS,SAAU,QAAO,KAAK;AAAA,IACjD;AACA,QAAI,OAAO,QAAS,MAAK;AAAA,QACpB,SAAQ,YAAY,EAAE,MAAM,QAAQ,OAAO,CAAC;AAAA,EACnD,CAAC;AACH;AAEO,SAAS,QAAQ,MAAwB,SAAqB,MAAM;AAAC,GAAkB;AAC5F,QAAM,UAAU,KAAK,KAAK,CAAC,OAAO;AAChC,QAAI,GAAI;AACR,WAAO;AACP,UAAM,IAAI,MAAM,MAAM;AAAA,EACxB,CAAC;AACD,UAAQ,MAAM,MAAM,MAAS;AAC7B,SAAO;AACT;AAEO,SAAS,cAAiB,MAAkB,SAAoC;AACrF,SAAO,QAAQ,KAAK,CAAC,MAAM,QAAQ,KAAK,MAAM,IAAI,CAAC,CAAC;AACtD;",
6
+ "names": []
7
+ }
@@ -1,8 +1,10 @@
1
+ import { askHold, markerUrl, refusal, unlessRefused } from "./held.js";
1
2
  const MCP_CHANNEL = "slicc-mcp-oauth";
2
3
  const MCP_NAME_KEY = "slicc-mcp-oauth:";
3
4
  const SIGN_IN_TIMEOUT_MS = 5 * 60 * 1e3;
4
5
  const browser = {
5
- open: () => globalThis.open?.("about:blank", "slicc-mcp-oauth", "popup,width=520,height=720") ?? null,
6
+ open: (url) => globalThis.open?.(url, "slicc-mcp-oauth", "popup,width=520,height=720") ?? null,
7
+ hold: (marker, signal) => askHold(marker, signal),
6
8
  channel: () => new BroadcastChannel(MCP_CHANNEL),
7
9
  remember: (state, name) => {
8
10
  const key = `${MCP_NAME_KEY}${state}`;
@@ -49,16 +51,22 @@ function untilAborted(promise, signal) {
49
51
  }
50
52
  function mcpLogin(host = browser) {
51
53
  return async (providerId, signIn, options) => {
52
- const popup = host.open();
54
+ const marker = crypto.randomUUID();
55
+ const popup = host.open(markerUrl(marker));
56
+ const refused = refusal(popup ? host.hold(marker, options.signal) : Promise.resolve(true));
53
57
  const channel = host.channel();
54
58
  let state;
55
59
  try {
56
- const started = await untilAborted(signIn(), options.signal);
60
+ const started = await untilAborted(
61
+ unlessRefused(signIn(), refused),
62
+ options.signal
63
+ );
57
64
  if (!started?.url) throw new Error("This MCP server has no sign-in.");
58
65
  if (!safeAuthorizationUrl(started.url))
59
66
  throw new Error("The authorization server gave a sign-in address that isn't https.");
60
67
  if (!popup)
61
68
  throw new Error("The browser blocked the sign-in window. Allow pop-ups for SLICC.");
69
+ await untilAborted(refused, options.signal);
62
70
  state = started.state;
63
71
  host.remember?.(state, providerId.replace(/^mcp:/, ""));
64
72
  popup.location.href = started.url;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/spectrum/mcp-login.ts"],
4
- "sourcesContent": ["import type { McpSignIn, SignIn } from '../services.ts';\n\nexport const MCP_CHANNEL = 'slicc-mcp-oauth';\nexport const MCP_NAME_KEY = 'slicc-mcp-oauth:';\nexport const SIGN_IN_TIMEOUT_MS = 5 * 60 * 1000;\n\nexport interface Popup {\n location: { href: string };\n close(): void;\n}\n\nexport interface Channel {\n onmessage: ((event: { data: unknown }) => void) | null;\n close(): void;\n}\n\nexport interface McpLoginHost {\n open(): Popup | null;\n channel(): Channel;\n remember?(state: string, name: string | null): void;\n timeoutMs?: number;\n}\n\nconst browser: McpLoginHost = {\n open: () =>\n globalThis.open?.('about:blank', 'slicc-mcp-oauth', 'popup,width=520,height=720') ?? null,\n channel: () => new BroadcastChannel(MCP_CHANNEL) as unknown as Channel,\n remember: (state, name) => {\n const key = `${MCP_NAME_KEY}${state}`;\n if (name === null) globalThis.localStorage?.removeItem(key);\n else globalThis.localStorage?.setItem(key, name);\n },\n};\n\nconst LOOPBACK = new Set(['localhost', '127.0.0.1', '[::1]']);\n\nexport function safeAuthorizationUrl(value: string): boolean {\n if (!URL.canParse(value)) return false;\n const url = new URL(value);\n return url.protocol === 'https:' || (url.protocol === 'http:' && LOOPBACK.has(url.hostname));\n}\n\nfunction answered(channel: Channel, state: string, signal: AbortSignal, timeoutMs: number) {\n return new Promise<string>((resolve, reject) => {\n const done = () => {\n clearTimeout(timer);\n signal.removeEventListener('abort', stop);\n };\n const stop = () => {\n done();\n reject(signal.reason);\n };\n const timer = setTimeout(() => {\n done();\n reject(new Error('The sign-in window gave no answer. Try again.'));\n }, timeoutMs);\n signal.addEventListener('abort', stop, { once: true });\n channel.onmessage = ({ data }) => {\n const answer = data as { state?: unknown } | null;\n if (answer?.state !== state) return;\n done();\n resolve(JSON.stringify(answer));\n };\n });\n}\n\nexport function untilAborted<T>(promise: Promise<T>, signal: AbortSignal): Promise<T> {\n signal.throwIfAborted();\n return new Promise<T>((resolve, reject) => {\n const stop = () => reject(signal.reason);\n signal.addEventListener('abort', stop, { once: true });\n promise.then(resolve, reject).finally(() => signal.removeEventListener('abort', stop));\n });\n}\n\nexport function mcpLogin(host: McpLoginHost = browser) {\n return async (\n providerId: string,\n signIn: () => Promise<SignIn | McpSignIn | null>,\n options: { signal: AbortSignal }\n ): Promise<string> => {\n const popup = host.open();\n const channel = host.channel();\n let state: string | undefined;\n try {\n const started = (await untilAborted(signIn(), options.signal)) as McpSignIn | null;\n if (!started?.url) throw new Error('This MCP server has no sign-in.');\n if (!safeAuthorizationUrl(started.url))\n throw new Error(\"The authorization server gave a sign-in address that isn't https.\");\n if (!popup)\n throw new Error('The browser blocked the sign-in window. Allow pop-ups for SLICC.');\n state = started.state;\n host.remember?.(state, providerId.replace(/^mcp:/, ''));\n popup.location.href = started.url;\n return await answered(channel, state, options.signal, host.timeoutMs ?? SIGN_IN_TIMEOUT_MS);\n } finally {\n if (state) host.remember?.(state, null);\n channel.close();\n popup?.close();\n }\n };\n}\n"],
5
- "mappings": "AAEO,MAAM,cAAc;AACpB,MAAM,eAAe;AACrB,MAAM,qBAAqB,IAAI,KAAK;AAmB3C,MAAM,UAAwB;AAAA,EAC5B,MAAM,MACJ,WAAW,OAAO,eAAe,mBAAmB,4BAA4B,KAAK;AAAA,EACvF,SAAS,MAAM,IAAI,iBAAiB,WAAW;AAAA,EAC/C,UAAU,CAAC,OAAO,SAAS;AACzB,UAAM,MAAM,GAAG,YAAY,GAAG,KAAK;AACnC,QAAI,SAAS,KAAM,YAAW,cAAc,WAAW,GAAG;AAAA,QACrD,YAAW,cAAc,QAAQ,KAAK,IAAI;AAAA,EACjD;AACF;AAEA,MAAM,WAAW,oBAAI,IAAI,CAAC,aAAa,aAAa,OAAO,CAAC;AAErD,SAAS,qBAAqB,OAAwB;AAC3D,MAAI,CAAC,IAAI,SAAS,KAAK,EAAG,QAAO;AACjC,QAAM,MAAM,IAAI,IAAI,KAAK;AACzB,SAAO,IAAI,aAAa,YAAa,IAAI,aAAa,WAAW,SAAS,IAAI,IAAI,QAAQ;AAC5F;AAEA,SAAS,SAAS,SAAkB,OAAe,QAAqB,WAAmB;AACzF,SAAO,IAAI,QAAgB,CAAC,SAAS,WAAW;AAC9C,UAAM,OAAO,MAAM;AACjB,mBAAa,KAAK;AAClB,aAAO,oBAAoB,SAAS,IAAI;AAAA,IAC1C;AACA,UAAM,OAAO,MAAM;AACjB,WAAK;AACL,aAAO,OAAO,MAAM;AAAA,IACtB;AACA,UAAM,QAAQ,WAAW,MAAM;AAC7B,WAAK;AACL,aAAO,IAAI,MAAM,+CAA+C,CAAC;AAAA,IACnE,GAAG,SAAS;AACZ,WAAO,iBAAiB,SAAS,MAAM,EAAE,MAAM,KAAK,CAAC;AACrD,YAAQ,YAAY,CAAC,EAAE,KAAK,MAAM;AAChC,YAAM,SAAS;AACf,UAAI,QAAQ,UAAU,MAAO;AAC7B,WAAK;AACL,cAAQ,KAAK,UAAU,MAAM,CAAC;AAAA,IAChC;AAAA,EACF,CAAC;AACH;AAEO,SAAS,aAAgB,SAAqB,QAAiC;AACpF,SAAO,eAAe;AACtB,SAAO,IAAI,QAAW,CAAC,SAAS,WAAW;AACzC,UAAM,OAAO,MAAM,OAAO,OAAO,MAAM;AACvC,WAAO,iBAAiB,SAAS,MAAM,EAAE,MAAM,KAAK,CAAC;AACrD,YAAQ,KAAK,SAAS,MAAM,EAAE,QAAQ,MAAM,OAAO,oBAAoB,SAAS,IAAI,CAAC;AAAA,EACvF,CAAC;AACH;AAEO,SAAS,SAAS,OAAqB,SAAS;AACrD,SAAO,OACL,YACA,QACA,YACoB;AACpB,UAAM,QAAQ,KAAK,KAAK;AACxB,UAAM,UAAU,KAAK,QAAQ;AAC7B,QAAI;AACJ,QAAI;AACF,YAAM,UAAW,MAAM,aAAa,OAAO,GAAG,QAAQ,MAAM;AAC5D,UAAI,CAAC,SAAS,IAAK,OAAM,IAAI,MAAM,iCAAiC;AACpE,UAAI,CAAC,qBAAqB,QAAQ,GAAG;AACnC,cAAM,IAAI,MAAM,mEAAmE;AACrF,UAAI,CAAC;AACH,cAAM,IAAI,MAAM,kEAAkE;AACpF,cAAQ,QAAQ;AAChB,WAAK,WAAW,OAAO,WAAW,QAAQ,SAAS,EAAE,CAAC;AACtD,YAAM,SAAS,OAAO,QAAQ;AAC9B,aAAO,MAAM,SAAS,SAAS,OAAO,QAAQ,QAAQ,KAAK,aAAa,kBAAkB;AAAA,IAC5F,UAAE;AACA,UAAI,MAAO,MAAK,WAAW,OAAO,IAAI;AACtC,cAAQ,MAAM;AACd,aAAO,MAAM;AAAA,IACf;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["import type { McpSignIn, SignIn } from '../services.ts';\nimport { askHold, markerUrl, refusal, unlessRefused } from './held.ts';\n\nexport const MCP_CHANNEL = 'slicc-mcp-oauth';\nexport const MCP_NAME_KEY = 'slicc-mcp-oauth:';\nexport const SIGN_IN_TIMEOUT_MS = 5 * 60 * 1000;\n\nexport interface Popup {\n location: { href: string };\n close(): void;\n}\n\nexport interface Channel {\n onmessage: ((event: { data: unknown }) => void) | null;\n close(): void;\n}\n\nexport interface SignInWindow {\n open(url: string): Popup | null;\n hold(marker: string, signal: AbortSignal): Promise<boolean>;\n}\n\nexport interface McpLoginHost extends SignInWindow {\n channel(): Channel;\n remember?(state: string, name: string | null): void;\n timeoutMs?: number;\n}\n\nconst browser: McpLoginHost = {\n open: (url) => globalThis.open?.(url, 'slicc-mcp-oauth', 'popup,width=520,height=720') ?? null,\n hold: (marker, signal) => askHold(marker, signal),\n channel: () => new BroadcastChannel(MCP_CHANNEL) as unknown as Channel,\n remember: (state, name) => {\n const key = `${MCP_NAME_KEY}${state}`;\n if (name === null) globalThis.localStorage?.removeItem(key);\n else globalThis.localStorage?.setItem(key, name);\n },\n};\n\nconst LOOPBACK = new Set(['localhost', '127.0.0.1', '[::1]']);\n\nexport function safeAuthorizationUrl(value: string): boolean {\n if (!URL.canParse(value)) return false;\n const url = new URL(value);\n return url.protocol === 'https:' || (url.protocol === 'http:' && LOOPBACK.has(url.hostname));\n}\n\nfunction answered(channel: Channel, state: string, signal: AbortSignal, timeoutMs: number) {\n return new Promise<string>((resolve, reject) => {\n const done = () => {\n clearTimeout(timer);\n signal.removeEventListener('abort', stop);\n };\n const stop = () => {\n done();\n reject(signal.reason);\n };\n const timer = setTimeout(() => {\n done();\n reject(new Error('The sign-in window gave no answer. Try again.'));\n }, timeoutMs);\n signal.addEventListener('abort', stop, { once: true });\n channel.onmessage = ({ data }) => {\n const answer = data as { state?: unknown } | null;\n if (answer?.state !== state) return;\n done();\n resolve(JSON.stringify(answer));\n };\n });\n}\n\nexport function untilAborted<T>(promise: Promise<T>, signal: AbortSignal): Promise<T> {\n signal.throwIfAborted();\n return new Promise<T>((resolve, reject) => {\n const stop = () => reject(signal.reason);\n signal.addEventListener('abort', stop, { once: true });\n promise.then(resolve, reject).finally(() => signal.removeEventListener('abort', stop));\n });\n}\n\nexport function mcpLogin(host: McpLoginHost = browser) {\n return async (\n providerId: string,\n signIn: () => Promise<SignIn | McpSignIn | null>,\n options: { signal: AbortSignal }\n ): Promise<string> => {\n const marker = crypto.randomUUID();\n const popup = host.open(markerUrl(marker));\n const refused = refusal(popup ? host.hold(marker, options.signal) : Promise.resolve(true));\n const channel = host.channel();\n let state: string | undefined;\n try {\n const started = (await untilAborted(\n unlessRefused(signIn(), refused),\n options.signal\n )) as McpSignIn | null;\n if (!started?.url) throw new Error('This MCP server has no sign-in.');\n if (!safeAuthorizationUrl(started.url))\n throw new Error(\"The authorization server gave a sign-in address that isn't https.\");\n if (!popup)\n throw new Error('The browser blocked the sign-in window. Allow pop-ups for SLICC.');\n await untilAborted(refused, options.signal);\n state = started.state;\n host.remember?.(state, providerId.replace(/^mcp:/, ''));\n popup.location.href = started.url;\n return await answered(channel, state, options.signal, host.timeoutMs ?? SIGN_IN_TIMEOUT_MS);\n } finally {\n if (state) host.remember?.(state, null);\n channel.close();\n popup?.close();\n }\n };\n}\n"],
5
+ "mappings": "AACA,SAAS,SAAS,WAAW,SAAS,qBAAqB;AAEpD,MAAM,cAAc;AACpB,MAAM,eAAe;AACrB,MAAM,qBAAqB,IAAI,KAAK;AAuB3C,MAAM,UAAwB;AAAA,EAC5B,MAAM,CAAC,QAAQ,WAAW,OAAO,KAAK,mBAAmB,4BAA4B,KAAK;AAAA,EAC1F,MAAM,CAAC,QAAQ,WAAW,QAAQ,QAAQ,MAAM;AAAA,EAChD,SAAS,MAAM,IAAI,iBAAiB,WAAW;AAAA,EAC/C,UAAU,CAAC,OAAO,SAAS;AACzB,UAAM,MAAM,GAAG,YAAY,GAAG,KAAK;AACnC,QAAI,SAAS,KAAM,YAAW,cAAc,WAAW,GAAG;AAAA,QACrD,YAAW,cAAc,QAAQ,KAAK,IAAI;AAAA,EACjD;AACF;AAEA,MAAM,WAAW,oBAAI,IAAI,CAAC,aAAa,aAAa,OAAO,CAAC;AAErD,SAAS,qBAAqB,OAAwB;AAC3D,MAAI,CAAC,IAAI,SAAS,KAAK,EAAG,QAAO;AACjC,QAAM,MAAM,IAAI,IAAI,KAAK;AACzB,SAAO,IAAI,aAAa,YAAa,IAAI,aAAa,WAAW,SAAS,IAAI,IAAI,QAAQ;AAC5F;AAEA,SAAS,SAAS,SAAkB,OAAe,QAAqB,WAAmB;AACzF,SAAO,IAAI,QAAgB,CAAC,SAAS,WAAW;AAC9C,UAAM,OAAO,MAAM;AACjB,mBAAa,KAAK;AAClB,aAAO,oBAAoB,SAAS,IAAI;AAAA,IAC1C;AACA,UAAM,OAAO,MAAM;AACjB,WAAK;AACL,aAAO,OAAO,MAAM;AAAA,IACtB;AACA,UAAM,QAAQ,WAAW,MAAM;AAC7B,WAAK;AACL,aAAO,IAAI,MAAM,+CAA+C,CAAC;AAAA,IACnE,GAAG,SAAS;AACZ,WAAO,iBAAiB,SAAS,MAAM,EAAE,MAAM,KAAK,CAAC;AACrD,YAAQ,YAAY,CAAC,EAAE,KAAK,MAAM;AAChC,YAAM,SAAS;AACf,UAAI,QAAQ,UAAU,MAAO;AAC7B,WAAK;AACL,cAAQ,KAAK,UAAU,MAAM,CAAC;AAAA,IAChC;AAAA,EACF,CAAC;AACH;AAEO,SAAS,aAAgB,SAAqB,QAAiC;AACpF,SAAO,eAAe;AACtB,SAAO,IAAI,QAAW,CAAC,SAAS,WAAW;AACzC,UAAM,OAAO,MAAM,OAAO,OAAO,MAAM;AACvC,WAAO,iBAAiB,SAAS,MAAM,EAAE,MAAM,KAAK,CAAC;AACrD,YAAQ,KAAK,SAAS,MAAM,EAAE,QAAQ,MAAM,OAAO,oBAAoB,SAAS,IAAI,CAAC;AAAA,EACvF,CAAC;AACH;AAEO,SAAS,SAAS,OAAqB,SAAS;AACrD,SAAO,OACL,YACA,QACA,YACoB;AACpB,UAAM,SAAS,OAAO,WAAW;AACjC,UAAM,QAAQ,KAAK,KAAK,UAAU,MAAM,CAAC;AACzC,UAAM,UAAU,QAAQ,QAAQ,KAAK,KAAK,QAAQ,QAAQ,MAAM,IAAI,QAAQ,QAAQ,IAAI,CAAC;AACzF,UAAM,UAAU,KAAK,QAAQ;AAC7B,QAAI;AACJ,QAAI;AACF,YAAM,UAAW,MAAM;AAAA,QACrB,cAAc,OAAO,GAAG,OAAO;AAAA,QAC/B,QAAQ;AAAA,MACV;AACA,UAAI,CAAC,SAAS,IAAK,OAAM,IAAI,MAAM,iCAAiC;AACpE,UAAI,CAAC,qBAAqB,QAAQ,GAAG;AACnC,cAAM,IAAI,MAAM,mEAAmE;AACrF,UAAI,CAAC;AACH,cAAM,IAAI,MAAM,kEAAkE;AACpF,YAAM,aAAa,SAAS,QAAQ,MAAM;AAC1C,cAAQ,QAAQ;AAChB,WAAK,WAAW,OAAO,WAAW,QAAQ,SAAS,EAAE,CAAC;AACtD,YAAM,SAAS,OAAO,QAAQ;AAC9B,aAAO,MAAM,SAAS,SAAS,OAAO,QAAQ,QAAQ,KAAK,aAAa,kBAAkB;AAAA,IAC5F,UAAE;AACA,UAAI,MAAO,MAAK,WAAW,OAAO,IAAI;AACtC,cAAQ,MAAM;AACd,aAAO,MAAM;AAAA,IACf;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -7,6 +7,7 @@ import {
7
7
  } from "../sign-ins.js";
8
8
  import { attempt } from "./attempt.js";
9
9
  import { Emitter } from "./emitter.js";
10
+ import { askHold, markerUrl, refusal, unlessRefused } from "./held.js";
10
11
  import { mcpLogin, untilAborted } from "./mcp-login.js";
11
12
  const SETTINGS_KEY = "slicc-agent.settings";
12
13
  const defaults = {
@@ -19,7 +20,10 @@ const defaults = {
19
20
  };
20
21
  const SIGN_IN_NEEDS_VAULT = "Adobe sign-in needs seven's vault: update seven";
21
22
  const UNFINISHED = SIGN_IN_UNFINISHED;
22
- const openPopup = () => globalThis.open?.("about:blank", "slicc-sign-in", "popup,width=520,height=720") ?? null;
23
+ const signInWindow = {
24
+ open: (url) => globalThis.open?.(url, "slicc-sign-in", "popup,width=520,height=720") ?? null,
25
+ hold: (marker, signal) => askHold(marker, signal)
26
+ };
23
27
  function readable(error) {
24
28
  if (error instanceof DOMException && error.name === "AbortError") return error;
25
29
  const text = (error instanceof Error ? error.message : String(error ?? "")).trim();
@@ -35,14 +39,14 @@ class SettingsAdapter extends Emitter {
35
39
  #mcpLogin;
36
40
  #signing = /* @__PURE__ */ new Map();
37
41
  #prompts = /* @__PURE__ */ new Map();
38
- #open;
42
+ #window;
39
43
  #local;
40
44
  #offered = "";
41
45
  #urls = /* @__PURE__ */ new Map();
42
- constructor(settings, agent, storage, login = null, mcp = mcpLogin(), open = openPopup) {
46
+ constructor(settings, agent, storage, login = null, mcp = mcpLogin(), popups = signInWindow) {
43
47
  super();
44
48
  this.#mcpLogin = mcp;
45
- this.#open = open;
49
+ this.#window = popups;
46
50
  this.#settings = settings;
47
51
  this.#agent = agent;
48
52
  this.#storage = storage;
@@ -188,16 +192,24 @@ class SettingsAdapter extends Emitter {
188
192
  this.emit("accounts", this.accounts());
189
193
  }
190
194
  async #device(id, given) {
191
- const popup = this.#open();
195
+ const marker = crypto.randomUUID();
196
+ const popup = this.#window.open(markerUrl(marker));
192
197
  const controller = this.#begin(id);
193
198
  const { signal } = controller;
199
+ const unheld = new AbortController();
200
+ const refused = refusal(
201
+ popup ? this.#window.hold(marker, signal) : Promise.resolve(true),
202
+ () => unheld.abort(new DOMException("The sign-in window was not held.", "AbortError"))
203
+ );
194
204
  const context = withAbortSignal(signal, BACKGROUND_CONTEXT);
205
+ const discovery = withAbortSignal(AbortSignal.any([signal, unheld.signal]), BACKGROUND_CONTEXT);
195
206
  try {
196
207
  const started = await untilAborted(
197
- this.#settings.signIn(id, context),
208
+ unlessRefused(this.#settings.signIn(id, discovery), refused),
198
209
  signal
199
210
  );
200
211
  if (!started || !httpsUrl(started.url)) throw new Error(UNFINISHED);
212
+ await untilAborted(refused, signal);
201
213
  const code = typeof started.code === "string" && started.code ? started.code : void 0;
202
214
  this.#prompts.set(id, { url: started.url, ...code ? { code } : {} });
203
215
  this.emit("accounts", this.accounts());
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/spectrum/settings-port.ts"],
4
- "sourcesContent": ["import type {\n Account,\n ModelOption,\n Settings,\n SettingsEvents,\n SettingsPort,\n} from '@ai-ecoverse/slicc-spectrum/ui';\nimport { BACKGROUND_CONTEXT, withAbortSignal } from '@earendil-works/chord/context';\nimport { preferredModel } from '../preferred.ts';\nimport type { AgentSettings, ProviderOption, SettingsState, SignIn } from '../services.ts';\nimport {\n type ConnectOptions,\n DEVICE_SIGN_INS,\n type DeviceSignIn,\n httpsUrl,\n SIGN_IN_UNFINISHED,\n} from '../sign-ins.ts';\nimport type { AgentAdapter } from './agent-port.ts';\nimport { attempt } from './attempt.ts';\nimport { Emitter } from './emitter.ts';\nimport { mcpLogin, type Popup, untilAborted } from './mcp-login.ts';\n\nexport const SETTINGS_KEY = 'slicc-agent.settings';\n\nconst defaults: Settings = {\n color: 'system',\n model: '',\n thinking: 'off',\n sendOnEnter: true,\n showThinking: false,\n diffStyle: 'unified',\n};\n\ntype Local = Omit<Settings, 'model' | 'thinking'>;\n\nexport type Sealed = { sealed: true };\n\nexport type Login = (\n providerId: string,\n signIn: () => Promise<SignIn | null>,\n options: { signal: AbortSignal }\n) => Promise<string | Sealed>;\n\nexport const SIGN_IN_NEEDS_VAULT = \"Adobe sign-in needs seven's vault: update seven\";\nexport const UNFINISHED = SIGN_IN_UNFINISHED;\n\nexport type OpenPopup = () => Popup | null;\n\nconst openPopup: OpenPopup = () =>\n globalThis.open?.('about:blank', 'slicc-sign-in', 'popup,width=520,height=720') ?? null;\n\nexport function readable(error: unknown): Error {\n if (error instanceof DOMException && error.name === 'AbortError') return error;\n const text = (error instanceof Error ? error.message : String(error ?? '')).trim();\n if (!text) return new Error(UNFINISHED);\n const lead = /^[a-z]+\\b(?![-.@/])/.test(text) ? text[0].toUpperCase() + text.slice(1) : text;\n return new Error(/[.!?]$/.test(lead) ? lead : `${lead}.`);\n}\n\ntype Events = SettingsEvents & { providers: readonly ProviderOption[] };\n\nexport class SettingsAdapter extends Emitter<Events> implements SettingsPort {\n readonly #settings: AgentSettings;\n readonly #agent: AgentAdapter;\n readonly #storage: Pick<Storage, 'getItem' | 'setItem'> | null;\n readonly #login: Login | null;\n readonly #mcpLogin: Login;\n readonly #signing = new Map<string, AbortController>();\n readonly #prompts = new Map<string, DeviceSignIn>();\n readonly #open: OpenPopup;\n #local: Local;\n #offered = '';\n readonly #urls = new Map<string, string>();\n\n constructor(\n settings: AgentSettings,\n agent: AgentAdapter,\n storage: Pick<Storage, 'getItem' | 'setItem'> | null,\n login: Login | null = null,\n mcp: Login = mcpLogin() as Login,\n open: OpenPopup = openPopup\n ) {\n super();\n this.#mcpLogin = mcp;\n this.#open = open;\n this.#settings = settings;\n this.#agent = agent;\n this.#storage = storage;\n this.#login = login;\n const saved = JSON.parse(storage?.getItem(SETTINGS_KEY) ?? '{}') as Partial<Local>;\n const { model: _model, thinking: _thinking, ...local } = { ...defaults, ...saved };\n this.#local = local;\n this.#offered = JSON.stringify(this.providers());\n this.#locate();\n settings.state.subscribe(() => {\n this.#locate();\n this.emit('accounts', this.accounts());\n this.emit('settings', this.get());\n const offered = JSON.stringify(this.providers());\n if (offered === this.#offered) return;\n this.#offered = offered;\n this.emit('providers', this.providers());\n });\n agent.on('agents', () => this.emit('settings', this.get()));\n }\n\n #locate(): void {\n const servers = this.#state()\n .accounts.filter(({ group }) => group === 'mcp')\n .map(({ id }) => id);\n for (const id of this.#urls.keys()) if (!servers.includes(id)) this.#urls.delete(id);\n for (const id of servers) {\n void this.#settings\n .serverUrl?.(id, BACKGROUND_CONTEXT)\n .then((url) => {\n if ((url ?? undefined) === this.#urls.get(id)) return;\n if (url) this.#urls.set(id, url);\n else this.#urls.delete(id);\n this.emit('accounts', this.accounts());\n })\n .catch(() => undefined);\n }\n }\n\n #state(): SettingsState {\n return this.#settings.state.value ?? { models: [], accounts: [] };\n }\n\n get(): Settings {\n const cone = this.#agent.list()[0];\n return { ...this.#local, model: cone?.model ?? '', thinking: cone?.thinking ?? 'off' };\n }\n\n update(patch: Partial<Settings>): void {\n const { model, thinking, ...local } = patch;\n const id = this.#agent.active();\n if (model !== undefined) this.#agent.setModel(id, model);\n if (thinking !== undefined) this.#agent.setThinking(id, thinking);\n this.#local = { ...this.#local, ...local };\n this.#storage?.setItem(SETTINGS_KEY, JSON.stringify(this.#local));\n this.emit('settings', this.get());\n }\n\n models(): readonly ModelOption[] {\n const choices = this.#state().models;\n const groups = Map.groupBy(choices, ({ id }) => id.slice(0, id.indexOf('/')));\n const chosen = new Set(\n [...groups].map(\n ([providerId, group]) =>\n preferredModel(\n providerId,\n group.map((choice) => ({ id: choice.id.slice(providerId.length + 1), choice }))\n )?.choice\n )\n );\n return choices.map((choice) => {\n const { id, label, provider, kind, reasoning, images } = choice;\n return {\n id,\n label,\n provider,\n kind,\n reasoning,\n ...(images === undefined ? {} : { images }),\n ...(chosen.has(choice) ? { default: true } : {}),\n };\n });\n }\n\n #offer(id: string) {\n const option = this.providers().find((candidate) => candidate.id === id);\n if (!option) return undefined;\n return {\n auth: option.auth === 'oauth' ? ('oauth' as const) : ('api-key' as const),\n store: 'vault' as const,\n group: 'providers' as const,\n };\n }\n\n providers(): readonly ProviderOption[] {\n return this.#state().providers ?? [];\n }\n\n async remove(id: string): Promise<void> {\n this.cancel(id);\n await this.#settings.remove(id, BACKGROUND_CONTEXT);\n }\n\n accounts(): readonly Account[] {\n return this.#state().accounts.map(({ needs, ...account }) => {\n const url = this.#urls.get(account.id);\n const located = url ? { ...account, url } : account;\n const prompt = this.#prompts.get(account.id);\n const shown = this.#signing.has(account.id)\n ? {\n ...located,\n status: 'signing-in' as const,\n ...(prompt ? { signIn: { ...prompt } } : {}),\n }\n : located;\n return needs ? { ...shown, needs } : shown;\n });\n }\n\n async connect(\n id: string,\n secret?: string,\n fields?: Record<string, string>,\n options?: ConnectOptions\n ): Promise<void> {\n const account =\n this.#state().accounts.find((candidate) => candidate.id === id) ?? this.#offer(id);\n const mcp = account?.group === 'mcp';\n const given = fields ? { ...fields } : null;\n const keyed = options?.method === 'key';\n if (account?.auth === 'oauth' && DEVICE_SIGN_INS.has(id) && secret === undefined && !keyed)\n return this.#device(id, given);\n const login = account?.auth !== 'oauth' || keyed ? null : mcp ? this.#mcpLogin : this.#login;\n if (keyed) this.cancel(id);\n if (secret !== undefined) {\n await this.#settings.connect(id, secret, given, BACKGROUND_CONTEXT);\n return;\n }\n if (account?.auth === 'oauth' && !login && !keyed) throw new Error(`${id} needs a sign-in`);\n if (!login) {\n if (account?.store !== 'vault') throw new Error(`${id} needs an API key`);\n await this.#settings.connect(id, null, given, BACKGROUND_CONTEXT);\n return;\n }\n const answer = await this.#signIn(id, login);\n if (typeof answer === 'string') {\n if (!mcp) throw new Error(SIGN_IN_NEEDS_VAULT);\n await this.#settings.connect(id, answer, given, BACKGROUND_CONTEXT);\n return;\n }\n if (answer?.sealed !== true) throw new Error(UNFINISHED);\n await this.#settings.connect(id, null, given, BACKGROUND_CONTEXT);\n }\n\n #begin(id: string): AbortController {\n const controller = new AbortController();\n this.#signing\n .get(id)\n ?.abort(new DOMException('A newer sign-in replaced this one.', 'AbortError'));\n this.#signing.set(id, controller);\n this.#prompts.delete(id);\n this.emit('accounts', this.accounts());\n return controller;\n }\n\n #end(id: string, controller: AbortController): void {\n if (this.#signing.get(id) === controller) {\n this.#signing.delete(id);\n this.#prompts.delete(id);\n }\n this.emit('accounts', this.accounts());\n }\n\n async #device(id: string, given: Record<string, string> | null): Promise<void> {\n const popup = this.#open();\n const controller = this.#begin(id);\n const { signal } = controller;\n const context = withAbortSignal(signal, BACKGROUND_CONTEXT);\n try {\n const started = (await untilAborted(\n this.#settings.signIn(id, context),\n signal\n )) as DeviceSignIn | null;\n if (!started || !httpsUrl(started.url)) throw new Error(UNFINISHED);\n const code = typeof started.code === 'string' && started.code ? started.code : undefined;\n this.#prompts.set(id, { url: started.url, ...(code ? { code } : {}) });\n this.emit('accounts', this.accounts());\n if (popup) popup.location.href = started.url;\n await untilAborted(this.#settings.connect(id, null, given, context), signal);\n } catch (error) {\n signal.throwIfAborted();\n throw readable(error);\n } finally {\n popup?.close();\n this.#end(id, controller);\n }\n }\n\n async #signIn(id: string, login: Login): Promise<string | Sealed> {\n const controller = this.#begin(id);\n try {\n const key = await login(\n id,\n () => this.#settings.signIn(id, BACKGROUND_CONTEXT) as Promise<SignIn | null>,\n {\n signal: controller.signal,\n }\n ).catch((error: unknown) => {\n controller.signal.throwIfAborted();\n throw readable(error);\n });\n controller.signal.throwIfAborted();\n return key;\n } finally {\n this.#end(id, controller);\n }\n }\n\n cancel(id: string): void {\n const controller = this.#signing.get(id);\n if (!controller) return;\n this.#signing.delete(id);\n this.#prompts.delete(id);\n controller.abort(new DOMException('The sign-in was cancelled.', 'AbortError'));\n this.emit('accounts', this.accounts());\n }\n\n disconnect(id: string): void {\n attempt(this.#settings.disconnect(id, BACKGROUND_CONTEXT), 'disconnect accounts', (text) =>\n this.#agent.notify(text)\n );\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type {\n Account,\n ModelOption,\n Settings,\n SettingsEvents,\n SettingsPort,\n} from '@ai-ecoverse/slicc-spectrum/ui';\nimport { BACKGROUND_CONTEXT, withAbortSignal } from '@earendil-works/chord/context';\nimport { preferredModel } from '../preferred.ts';\nimport type { AgentSettings, ProviderOption, SettingsState, SignIn } from '../services.ts';\nimport {\n type ConnectOptions,\n DEVICE_SIGN_INS,\n type DeviceSignIn,\n httpsUrl,\n SIGN_IN_UNFINISHED,\n} from '../sign-ins.ts';\nimport type { AgentAdapter } from './agent-port.ts';\nimport { attempt } from './attempt.ts';\nimport { Emitter } from './emitter.ts';\nimport { askHold, markerUrl, refusal, unlessRefused } from './held.ts';\nimport { mcpLogin, type SignInWindow, untilAborted } from './mcp-login.ts';\n\nexport const SETTINGS_KEY = 'slicc-agent.settings';\n\nconst defaults: Settings = {\n color: 'system',\n model: '',\n thinking: 'off',\n sendOnEnter: true,\n showThinking: false,\n diffStyle: 'unified',\n};\n\ntype Local = Omit<Settings, 'model' | 'thinking'>;\n\nexport type Sealed = { sealed: true };\n\nexport type Login = (\n providerId: string,\n signIn: () => Promise<SignIn | null>,\n options: { signal: AbortSignal }\n) => Promise<string | Sealed>;\n\nexport const SIGN_IN_NEEDS_VAULT = \"Adobe sign-in needs seven's vault: update seven\";\nexport const UNFINISHED = SIGN_IN_UNFINISHED;\n\nconst signInWindow: SignInWindow = {\n open: (url) => globalThis.open?.(url, 'slicc-sign-in', 'popup,width=520,height=720') ?? null,\n hold: (marker, signal) => askHold(marker, signal),\n};\n\nexport function readable(error: unknown): Error {\n if (error instanceof DOMException && error.name === 'AbortError') return error;\n const text = (error instanceof Error ? error.message : String(error ?? '')).trim();\n if (!text) return new Error(UNFINISHED);\n const lead = /^[a-z]+\\b(?![-.@/])/.test(text) ? text[0].toUpperCase() + text.slice(1) : text;\n return new Error(/[.!?]$/.test(lead) ? lead : `${lead}.`);\n}\n\ntype Events = SettingsEvents & { providers: readonly ProviderOption[] };\n\nexport class SettingsAdapter extends Emitter<Events> implements SettingsPort {\n readonly #settings: AgentSettings;\n readonly #agent: AgentAdapter;\n readonly #storage: Pick<Storage, 'getItem' | 'setItem'> | null;\n readonly #login: Login | null;\n readonly #mcpLogin: Login;\n readonly #signing = new Map<string, AbortController>();\n readonly #prompts = new Map<string, DeviceSignIn>();\n readonly #window: SignInWindow;\n #local: Local;\n #offered = '';\n readonly #urls = new Map<string, string>();\n\n constructor(\n settings: AgentSettings,\n agent: AgentAdapter,\n storage: Pick<Storage, 'getItem' | 'setItem'> | null,\n login: Login | null = null,\n mcp: Login = mcpLogin() as Login,\n popups: SignInWindow = signInWindow\n ) {\n super();\n this.#mcpLogin = mcp;\n this.#window = popups;\n this.#settings = settings;\n this.#agent = agent;\n this.#storage = storage;\n this.#login = login;\n const saved = JSON.parse(storage?.getItem(SETTINGS_KEY) ?? '{}') as Partial<Local>;\n const { model: _model, thinking: _thinking, ...local } = { ...defaults, ...saved };\n this.#local = local;\n this.#offered = JSON.stringify(this.providers());\n this.#locate();\n settings.state.subscribe(() => {\n this.#locate();\n this.emit('accounts', this.accounts());\n this.emit('settings', this.get());\n const offered = JSON.stringify(this.providers());\n if (offered === this.#offered) return;\n this.#offered = offered;\n this.emit('providers', this.providers());\n });\n agent.on('agents', () => this.emit('settings', this.get()));\n }\n\n #locate(): void {\n const servers = this.#state()\n .accounts.filter(({ group }) => group === 'mcp')\n .map(({ id }) => id);\n for (const id of this.#urls.keys()) if (!servers.includes(id)) this.#urls.delete(id);\n for (const id of servers) {\n void this.#settings\n .serverUrl?.(id, BACKGROUND_CONTEXT)\n .then((url) => {\n if ((url ?? undefined) === this.#urls.get(id)) return;\n if (url) this.#urls.set(id, url);\n else this.#urls.delete(id);\n this.emit('accounts', this.accounts());\n })\n .catch(() => undefined);\n }\n }\n\n #state(): SettingsState {\n return this.#settings.state.value ?? { models: [], accounts: [] };\n }\n\n get(): Settings {\n const cone = this.#agent.list()[0];\n return { ...this.#local, model: cone?.model ?? '', thinking: cone?.thinking ?? 'off' };\n }\n\n update(patch: Partial<Settings>): void {\n const { model, thinking, ...local } = patch;\n const id = this.#agent.active();\n if (model !== undefined) this.#agent.setModel(id, model);\n if (thinking !== undefined) this.#agent.setThinking(id, thinking);\n this.#local = { ...this.#local, ...local };\n this.#storage?.setItem(SETTINGS_KEY, JSON.stringify(this.#local));\n this.emit('settings', this.get());\n }\n\n models(): readonly ModelOption[] {\n const choices = this.#state().models;\n const groups = Map.groupBy(choices, ({ id }) => id.slice(0, id.indexOf('/')));\n const chosen = new Set(\n [...groups].map(\n ([providerId, group]) =>\n preferredModel(\n providerId,\n group.map((choice) => ({ id: choice.id.slice(providerId.length + 1), choice }))\n )?.choice\n )\n );\n return choices.map((choice) => {\n const { id, label, provider, kind, reasoning, images } = choice;\n return {\n id,\n label,\n provider,\n kind,\n reasoning,\n ...(images === undefined ? {} : { images }),\n ...(chosen.has(choice) ? { default: true } : {}),\n };\n });\n }\n\n #offer(id: string) {\n const option = this.providers().find((candidate) => candidate.id === id);\n if (!option) return undefined;\n return {\n auth: option.auth === 'oauth' ? ('oauth' as const) : ('api-key' as const),\n store: 'vault' as const,\n group: 'providers' as const,\n };\n }\n\n providers(): readonly ProviderOption[] {\n return this.#state().providers ?? [];\n }\n\n async remove(id: string): Promise<void> {\n this.cancel(id);\n await this.#settings.remove(id, BACKGROUND_CONTEXT);\n }\n\n accounts(): readonly Account[] {\n return this.#state().accounts.map(({ needs, ...account }) => {\n const url = this.#urls.get(account.id);\n const located = url ? { ...account, url } : account;\n const prompt = this.#prompts.get(account.id);\n const shown = this.#signing.has(account.id)\n ? {\n ...located,\n status: 'signing-in' as const,\n ...(prompt ? { signIn: { ...prompt } } : {}),\n }\n : located;\n return needs ? { ...shown, needs } : shown;\n });\n }\n\n async connect(\n id: string,\n secret?: string,\n fields?: Record<string, string>,\n options?: ConnectOptions\n ): Promise<void> {\n const account =\n this.#state().accounts.find((candidate) => candidate.id === id) ?? this.#offer(id);\n const mcp = account?.group === 'mcp';\n const given = fields ? { ...fields } : null;\n const keyed = options?.method === 'key';\n if (account?.auth === 'oauth' && DEVICE_SIGN_INS.has(id) && secret === undefined && !keyed)\n return this.#device(id, given);\n const login = account?.auth !== 'oauth' || keyed ? null : mcp ? this.#mcpLogin : this.#login;\n if (keyed) this.cancel(id);\n if (secret !== undefined) {\n await this.#settings.connect(id, secret, given, BACKGROUND_CONTEXT);\n return;\n }\n if (account?.auth === 'oauth' && !login && !keyed) throw new Error(`${id} needs a sign-in`);\n if (!login) {\n if (account?.store !== 'vault') throw new Error(`${id} needs an API key`);\n await this.#settings.connect(id, null, given, BACKGROUND_CONTEXT);\n return;\n }\n const answer = await this.#signIn(id, login);\n if (typeof answer === 'string') {\n if (!mcp) throw new Error(SIGN_IN_NEEDS_VAULT);\n await this.#settings.connect(id, answer, given, BACKGROUND_CONTEXT);\n return;\n }\n if (answer?.sealed !== true) throw new Error(UNFINISHED);\n await this.#settings.connect(id, null, given, BACKGROUND_CONTEXT);\n }\n\n #begin(id: string): AbortController {\n const controller = new AbortController();\n this.#signing\n .get(id)\n ?.abort(new DOMException('A newer sign-in replaced this one.', 'AbortError'));\n this.#signing.set(id, controller);\n this.#prompts.delete(id);\n this.emit('accounts', this.accounts());\n return controller;\n }\n\n #end(id: string, controller: AbortController): void {\n if (this.#signing.get(id) === controller) {\n this.#signing.delete(id);\n this.#prompts.delete(id);\n }\n this.emit('accounts', this.accounts());\n }\n\n async #device(id: string, given: Record<string, string> | null): Promise<void> {\n const marker = crypto.randomUUID();\n const popup = this.#window.open(markerUrl(marker));\n const controller = this.#begin(id);\n const { signal } = controller;\n const unheld = new AbortController();\n const refused = refusal(popup ? this.#window.hold(marker, signal) : Promise.resolve(true), () =>\n unheld.abort(new DOMException('The sign-in window was not held.', 'AbortError'))\n );\n const context = withAbortSignal(signal, BACKGROUND_CONTEXT);\n const discovery = withAbortSignal(AbortSignal.any([signal, unheld.signal]), BACKGROUND_CONTEXT);\n try {\n const started = (await untilAborted(\n unlessRefused(this.#settings.signIn(id, discovery), refused),\n signal\n )) as DeviceSignIn | null;\n if (!started || !httpsUrl(started.url)) throw new Error(UNFINISHED);\n await untilAborted(refused, signal);\n const code = typeof started.code === 'string' && started.code ? started.code : undefined;\n this.#prompts.set(id, { url: started.url, ...(code ? { code } : {}) });\n this.emit('accounts', this.accounts());\n if (popup) popup.location.href = started.url;\n await untilAborted(this.#settings.connect(id, null, given, context), signal);\n } catch (error) {\n signal.throwIfAborted();\n throw readable(error);\n } finally {\n popup?.close();\n this.#end(id, controller);\n }\n }\n\n async #signIn(id: string, login: Login): Promise<string | Sealed> {\n const controller = this.#begin(id);\n try {\n const key = await login(\n id,\n () => this.#settings.signIn(id, BACKGROUND_CONTEXT) as Promise<SignIn | null>,\n {\n signal: controller.signal,\n }\n ).catch((error: unknown) => {\n controller.signal.throwIfAborted();\n throw readable(error);\n });\n controller.signal.throwIfAborted();\n return key;\n } finally {\n this.#end(id, controller);\n }\n }\n\n cancel(id: string): void {\n const controller = this.#signing.get(id);\n if (!controller) return;\n this.#signing.delete(id);\n this.#prompts.delete(id);\n controller.abort(new DOMException('The sign-in was cancelled.', 'AbortError'));\n this.emit('accounts', this.accounts());\n }\n\n disconnect(id: string): void {\n attempt(this.#settings.disconnect(id, BACKGROUND_CONTEXT), 'disconnect accounts', (text) =>\n this.#agent.notify(text)\n );\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -91,7 +91,7 @@ export type Manifest = {
91
91
  path: string;
92
92
  }[];
93
93
  licks: string | null;
94
- gitconfig?: string | null;
94
+ gitconfig: string | null;
95
95
  files: {
96
96
  path: string;
97
97
  size: number;
@@ -108,11 +108,11 @@ export type Manifest = {
108
108
  rule: string;
109
109
  }[];
110
110
  providers: string[];
111
- mcp?: {
111
+ mcp: {
112
112
  name: string;
113
113
  url: string;
114
114
  }[];
115
- secrets?: string[];
115
+ secrets: string[];
116
116
  };
117
117
  export type Licks = {
118
118
  crontasks: {
@@ -0,0 +1,19 @@
1
+ export declare const HELD_CHANNEL = "slicc-held";
2
+ export declare const HELD_MARK = "slicc-sign-in-";
3
+ export declare const HOLD_WAIT_MS = 6000;
4
+ export declare const UNHELD = "SLICC couldn't protect the sign-in window. Try again.";
5
+ export interface HeldChannel {
6
+ onmessage: ((event: {
7
+ data: unknown;
8
+ }) => void) | null;
9
+ postMessage(data: unknown): void;
10
+ close(): void;
11
+ }
12
+ export type Listen = (name: string) => HeldChannel;
13
+ export declare function markerUrl(marker: string): string;
14
+ export declare function askHold(marker: string, signal: AbortSignal, options?: {
15
+ listen?: Listen;
16
+ wait?: number;
17
+ }): Promise<boolean>;
18
+ export declare function refusal(held: Promise<boolean>, refuse?: () => void): Promise<void>;
19
+ export declare function unlessRefused<T>(work: Promise<T>, refused: Promise<void>): Promise<T>;
@@ -14,8 +14,11 @@ export interface Channel {
14
14
  }) => void) | null;
15
15
  close(): void;
16
16
  }
17
- export interface McpLoginHost {
18
- open(): Popup | null;
17
+ export interface SignInWindow {
18
+ open(url: string): Popup | null;
19
+ hold(marker: string, signal: AbortSignal): Promise<boolean>;
20
+ }
21
+ export interface McpLoginHost extends SignInWindow {
19
22
  channel(): Channel;
20
23
  remember?(state: string, name: string | null): void;
21
24
  timeoutMs?: number;
@@ -3,7 +3,7 @@ import type { AgentSettings, ProviderOption, SignIn } from '../services.ts';
3
3
  import { type ConnectOptions } from '../sign-ins.ts';
4
4
  import type { AgentAdapter } from './agent-port.ts';
5
5
  import { Emitter } from './emitter.ts';
6
- import { type Popup } from './mcp-login.ts';
6
+ import { type SignInWindow } from './mcp-login.ts';
7
7
  export declare const SETTINGS_KEY = "slicc-agent.settings";
8
8
  export type Sealed = {
9
9
  sealed: true;
@@ -13,14 +13,13 @@ export type Login = (providerId: string, signIn: () => Promise<SignIn | null>, o
13
13
  }) => Promise<string | Sealed>;
14
14
  export declare const SIGN_IN_NEEDS_VAULT = "Adobe sign-in needs seven's vault: update seven";
15
15
  export declare const UNFINISHED = "The sign-in didn't finish. Try again.";
16
- export type OpenPopup = () => Popup | null;
17
16
  export declare function readable(error: unknown): Error;
18
17
  type Events = SettingsEvents & {
19
18
  providers: readonly ProviderOption[];
20
19
  };
21
20
  export declare class SettingsAdapter extends Emitter<Events> implements SettingsPort {
22
21
  #private;
23
- constructor(settings: AgentSettings, agent: AgentAdapter, storage: Pick<Storage, 'getItem' | 'setItem'> | null, login?: Login | null, mcp?: Login, open?: OpenPopup);
22
+ constructor(settings: AgentSettings, agent: AgentAdapter, storage: Pick<Storage, 'getItem' | 'setItem'> | null, login?: Login | null, mcp?: Login, popups?: SignInWindow);
24
23
  get(): Settings;
25
24
  update(patch: Partial<Settings>): void;
26
25
  models(): readonly ModelOption[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-ecoverse/slicc-agent",
3
- "version": "6.18.2",
3
+ "version": "6.18.4",
4
4
  "description": "SLICC's agent core on pi durable: an agent worker, session storage, remoting and pi extensions",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -65,11 +65,11 @@
65
65
  "release": "semantic-release"
66
66
  },
67
67
  "devDependencies": {
68
- "@ai-ecoverse/slicc-kernel": "1.136.5",
68
+ "@ai-ecoverse/slicc-kernel": "1.138.1",
69
69
  "@ai-ecoverse/slicc-scrub": "1.0.0",
70
70
  "@ai-ecoverse/slicc-shared-web": "1.29.1",
71
- "@ai-ecoverse/slicc-spectrum": "1.103.1",
72
- "@ai-ecoverse/wasi-fnox": "1.39.0-5",
71
+ "@ai-ecoverse/slicc-spectrum": "1.103.2",
72
+ "@ai-ecoverse/wasi-fnox": "1.39.0-6",
73
73
  "@ai-ecoverse/wasm-bash": "5.3.0-22",
74
74
  "@ai-ecoverse/wasm-coreutils": "9.12.0-12",
75
75
  "@ai-ecoverse/wasm-git": "2.55.0-17",