@ai-ecoverse/slicc-agent 6.11.1 → 6.11.3

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
@@ -757,13 +757,21 @@ The agent never holds a secret's value. Values live in the vault (`vault.sliccy.
757
757
  - **Credentials by placeholder.** `records()` lists the `model` and `mcp` records (names, placeholders, states, never values). It is read at start, whenever `version` changes, and after `connect`, `disconnect` and `remove`. The read-only `CredentialStore` behind pi-ai returns a model record's placeholder as the API key (found by `kind: 'model'` and `provider`), with the account's non-secret fields as its env (Bedrock's `region` as `AWS_REGION`); writing to it throws.
758
758
  - **The transport.** The worker's `fetch` is `vaultFetch`: a request to a configured provider's base URL (Bedrock's standard `bedrock-runtime.*.amazonaws.com` and `-fips` endpoints included, so the AWS SDK's bearer requests are covered), or one carrying a model placeholder in its URL or headers, goes out as `model { provider?, request }`; MCP requests go out as `mcp { server, request }`, discovery and client registration included. The vault returns the request scrubbed over every value and with the credential filled in its headers or URL, and the existing transport sends it (`loopbackFetch` for a server on `http://<port>.kernel.localhost`); the response comes back directly. `EAGAIN` prepares the request once more; a refusal becomes a response (403 for `EACCES`, with the vault's sentence); a 401 calls `refresh { provider | server }` once and sends again. Anything else passes through, and without a vault every request does. Transport errors name only the method and origin, never a prepared URL or header.
759
759
  - **Endpoints.** `endpoints { model: [{ provider, url }], mcp: [{ server, url, names?, tokenUrl? }] }` is sent when the port opens and whenever the providers, the accounts or `mcp.json` change: every https base URL of every provider the vault doesn't already know (pi-ai's catalog, Bedrock's regions included, is built into the vault, so only Adobe's proxy and providers outside it are sent), and every server URL with the `${NAME}`s its headers use. Registrations only add.
760
- - **`slicc-secrets`**, a built-in extension modelled on [pi-fnox](https://github.com/Bucurenciu-Cristian/pi-fnox) (MIT, © 2026 Kicky), with its hooks mapped to pi-durable: `secret list --json` in the home env at start and whenever `version` changes (pi-fnox's `fnox export`; nothing goes into the worker's environment); an `afterTool` hook scrubbing every result's text, diagnostics and string `details` (pi-fnox's `tool_result`); a `secrets` system section with the names, placeholders, where each works and how to use them (`before_agent_start`); and `/secrets`, answered locally as a `slicc.secrets` entry (`/fnox-list`; reloading is automatic). When at least one secret is `shell = true`, every conversation command runs as `fnox exec --shell-grants -- <argv>` (pi-fnox's `spawnHook`). slicc-agent doesn't depend on `@kickythrust/pi-fnox`. Every conversation keeps the extension: new ones are pinned at creation, and conversations stored with an extension list that lacks it get it back at start.
760
+ - **`slicc-secrets`**, a built-in extension modelled on [pi-fnox](https://github.com/Bucurenciu-Cristian/pi-fnox) (MIT, © 2026 Kicky), with its hooks mapped to pi-durable. fnox ([`@ai-ecoverse/wasi-fnox`](https://www.npmjs.com/package/@ai-ecoverse/wasi-fnox) 1.39, built by homescoop and used unpatched here) is seven's only secrets CLI; the agent adapts to its own commands and needs `fnox` on `PATH` in the home env.
761
+ - **The listing**, read in the home env at start and whenever `version` changes (pi-fnox's `fnox export`; nothing goes into the worker's environment), joins three commands by name, none of which prints a value:
762
+ - `fnox env --json --describe` gives the fnox config's keys (`kind: "secret"`), which are the `$NAME`s bash sees. It runs only with `--describe`: without it fnox prints values, so every command goes through one guard that refuses `fnox env` without `--describe`. `--for exec|shell` isn't passed, since fnox 1.39 ignores it when describing (both print the same keys).
763
+ - the vault's own `list` op on `/dev/sealed` (`exec 3<>/dev/sealed`, write `{"v":1,"op":"list"}`, read the reply) gives each sealed secret's placeholder, its `[exec]` mode from `/etc/slicc/secrets-policy.toml` and the rules the vault accepted. fnox's describe output has no placeholders, and the agent port's `records` has no `secret` records.
764
+ - `fnox proxy rules --json` gives the config's `[[proxy.rules]]`, shown as proposed for a secret the vault has no accepted rule for. A config without `[proxy]` (exit 1, "No [proxy] configuration found") has no rules.
765
+ - **Shown as:** `- NAME = <placeholder>: <rules>; in every bash` for a sealed secret; a fnox key the vault doesn't hold reads `- NAME = $NAME: not sealed` (a plain fnox secret), and a sealed secret the config lacks says `not in the fnox config`.
766
+ - **Failures are loud.** When fnox is missing (exit 127), a command exits non-zero, `/dev/sealed` fails or a reply doesn't parse, the `secrets` section is "Secrets are unavailable: <reason>.", `/secrets` says the same, and the worker logs one `slicc-secrets: secrets are unavailable: <reason>` line until the reason changes. The reason is a fixed sentence: fnox's error `kind` (`config` reads "the fnox config has an error; check /etc/fnox/config.toml") or the vault's error `code`, never their message, since fnox quotes the offending config line and a plain secret's value can sit on it, which the vault's scrub doesn't know. The message goes only to that log line, redacted by the vault and cut to 200 characters. stderr is never read. Only a config with no keys and a vault with no sealed secrets shows "No secrets are configured".
767
+ - **Shell grants stay off while fnox would hand out unsealed values.** `fnox exec` also sets every config secret it may inject (`injectable.exec`, which is `env = true` and `env = "exec"`), and a plain or age secret's value isn't one the vault scrubs. So while any such key isn't in the vault's list, the wrapper isn't used, the section and `/secrets` say "Shell grants are off: fnox exec would also give every command the unsealed fnox secrets <names>; seal them or set env = false for them.", and one line is logged.
768
+ - **The hooks:** an `afterTool` hook scrubbing every result's text, diagnostics and string `details` (pi-fnox's `tool_result`); the `secrets` system section with the names, placeholders, where each works and how to use them (`before_agent_start`); and `/secrets`, answered locally as a `slicc.secrets` entry (`/fnox-list`; reloading is automatic). When the listing succeeded, no unsealed key would be injected and the vault grants at least one listed secret `{ shell = true }`, every conversation command runs as `fnox exec --shell-grants -- <argv>` (pi-fnox's `spawnHook`). slicc-agent doesn't depend on `@kickythrust/pi-fnox`. Every conversation keeps the extension: new ones are pinned at creation, and conversations stored with an extension list that lacks it get it back at start.
761
769
  - **Where text is scrubbed before anything keeps or cuts it:** command output, per stream, before the bash tool's tail window, the spill file and the live slot; `read`'s bytes (images aren't, and files over 8 MiB stream in 1 MiB chunks); MCP results before their middle cut and progress text; codemode output before its cut; user and remote sends after skill expansion; every lick (sprinkle sends and scoop results included) before triage and the outbox; and memory writes, before the pattern redaction. Remote-control summaries are built from stored entries only.
762
770
  - **The hold.** A vault that doesn't answer within 2 s reads as `waiting`. Nothing unscrubbed is appended: tool results, sends and licks wait in memory, model requests wait before they start, and command output held past 32 MiB kills the command with "output held: waiting for the vault". The state is the transcript's `vault` field (`{ port, needed, version, locked, held }`), and remote `watch` streams get a `notice` "waiting for the vault" and one when it answers again.
763
- - **In spectrum.** The agent port's `hold()` is `{ reason: 'locked', held }` while the vault needs a passkey, `{ reason: 'waiting', held }` while scrubbing is needed and the port isn't `ready`, and `null` otherwise, with a `hold` event on each change (and `null` when it clears); spectrum (≥ 1.62.0) shows it above the composer, and older spectrum ignores it. While a hold is up, the worker's active cone is busy (`working` when it isn't running), so the composer steers and queues; a send made during the hold waits in the tab, shown in that cone's queue, and goes to the worker once the hold clears; removing it before then means it's never sent. `/secrets` entries (`slicc.secrets`) show as a "Secrets listed" notice.
771
+ - **In spectrum.** The agent port's `hold()` is `{ reason: 'locked', held }` while the vault needs a passkey, `{ reason: 'waiting', held }` while scrubbing is needed and the port isn't `ready`, and `null` otherwise, with a `hold` event on each change (and `null` when it clears); spectrum (≥ 1.62.0) shows it above the composer, and older spectrum ignores it. While a hold is up, the worker's active cone is busy (`working` when it isn't running), so the composer steers and queues; a send made during the hold waits in the tab, shown in that cone's queue, and goes to the worker once the hold clears; removing it before then means it's never sent. `/secrets` entries (`slicc.secrets`) show as a "Secrets listed" notice, or "Secrets unavailable" with the reason.
764
772
  - **The 3.x store is wiped.** 3.x kept model and MCP keys in the IndexedDB database `slicc-agent-credentials`; 4.x doesn't migrate them. At start, before any model call, the worker reads only that database's record keys (never decrypting), deletes it, and keeps the accounts it found (`adobe` as Adobe, `amazon-bedrock` as Bedrock, `mcp:<server>|…` as the server, anything else by its providerId) in the session document `slicc.keys-moved`. A missing database, or no `indexedDB`, does nothing. The worker waits up to 3 s for the delete and claims the keys moved only once it succeeds. A delete blocked by an open 3.x tab shows no notice yet: if it succeeds later in the session (the tab closes) its accounts are added then, and otherwise the next start tries again. A delete that fails reports nothing. Once the vault's records are read, the accounts still without a record are the settings state's `moved`, and the spectrum adapter's `notices` port shows one notice, with model accounts first and MCP servers grouped after them: "Your keys moved to seven's vault. Add them again in Settings › Accounts: Adobe, Bedrock; MCP servers: github, jira." (or "…, and the MCP server github" for one). Dismissing it calls `AgentSettings.dismissMoved`, which marks the document, so it stays dismissed across reloads. An account without a record reads not connected. A model call that finds no credential ("Provider is not configured", "No API key") leads with "Adobe isn't connected." or "Bedrock isn't connected.", with "Connect it in Settings › Accounts." underneath instead of the raw error, and opens settings, and a send whose run finds no model because its account isn't connected (Adobe lists models only once signed in) shows the same error. "Your Adobe session has expired." is kept for a 401 from a token that exists. The settings state's `labels` names every model of a provider without a record from the agent's catalogue ("Claude Haiku 4.5 (US) on Bedrock"), and the adapter puts that name in the header of a reply whose model isn't in the picker, instead of the raw id.
765
773
  - **For bios:** the agent's `notices` (keys moved) would replace bios's own port in `{ ...bios, ...createAgentModel(connection) }`, so pass that port in instead, as `createAgentModel(connection, { notices: biosPort })`. The returned port lists bios's notices and then the agent's, sends `act` and `dismiss` to whichever port listed the id (never dismissing one with `dismissible: false`), and passes on both ports' `notices` events. Without the option, it lists only the agent's.
766
- - **Tests.** The worker never compiles values: `src/` takes only `import type { Hit }` from [`@ai-ecoverse/slicc-scrub`](https://www.npmjs.com/package/@ai-ecoverse/slicc-scrub), the vault's scrubber, and the package is a devDependency. The fake vault the tests use scrubs with that package, and its `vectors.json` (every form, line-wrapped base64 and hex included) runs through each scrub point and each cut. A suite-wide check asserts that no captured model request, entry, slot or memory file holds any form of a seeded value.
774
+ - **Tests.** The worker never compiles values: `src/` takes only `import type { Hit }` from [`@ai-ecoverse/slicc-scrub`](https://www.npmjs.com/package/@ai-ecoverse/slicc-scrub), the vault's scrubber, and the package is a devDependency. The fake vault the tests use scrubs with that package, and its `vectors.json` (every form, line-wrapped base64 and hex included) runs through each scrub point and each cut. A suite-wide check asserts that no captured model request, entry, slot or memory file holds any form of a seeded value. `test/integration/secrets-fnox.test.mjs` runs the listing on the page kernel with `@ai-ecoverse/wasi-fnox` (a devDependency) and the fake vault's `/dev/sealed` side (`fakeSealed`: `list`, `placeholder`, `materialize`, `materializeConsume`): a sealed secret in the section and `/secrets`, a shell grant reaching a command through `fnox exec --shell-grants`, shell grants kept off by an unsealed `env = true` secret, fnox missing, fnox exiting non-zero on a config error that quotes a plain value (which never reaches the prompt, `/secrets` or the transcript), and an empty config.
767
775
 
768
776
  ## The kernel
769
777
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/control.ts"],
4
- "sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport {\n AssistantEntry,\n type Conversation,\n ConversationBusy,\n type ConversationView,\n type EntryId,\n type EntryRecord,\n type Harness,\n type SubmissionId,\n type UserInput,\n} from '@earendil-works/pi-durable';\nimport { answerText } from './agent.ts';\nimport type { Agents, ScoopRecord } from './agents.ts';\nimport { fromBase64, type ShownAttachment, splitUser, userParts } from './attachments.ts';\nimport type { ChangesRuntime } from './changes/index.ts';\nimport type { Cones } from './cone.ts';\nimport type { Deliveries } from './deliveries.ts';\nimport type { FreezerRuntime } from './freezer/index.ts';\nimport type { Guests } from './guests/index.ts';\nimport type { LickSources, Licks } from './licks/index.ts';\nimport { LICK_STATE_KIND } from './licks/state.ts';\nimport type { MemoryRuntime } from './memory/index.ts';\nimport type { Monitor } from './monitor/index.ts';\nimport type { Scoops, ScoopsRuntime } from './scoops/index.ts';\nimport { SECRETS_COMMAND, secretsEntry } from './secrets/extension.ts';\nimport type { SecretName } from './secrets/names.ts';\nimport { scrubItems, scrubText } from './secrets/scrub.ts';\nimport { copyAgent, storedAgent } from './selection.ts';\nimport type {\n AgentControl,\n Created,\n ImageRef,\n LickResolved,\n OperationError,\n Rewound,\n SendRequest,\n SendResponse,\n StoredImage,\n} from './services.ts';\nimport type { SkillsRuntime } from './skills/index.ts';\nimport type { SprinklesRuntime } from './sprinkles/index.ts';\nimport type { LabelsRuntime } from './titles/labels.ts';\nimport { saverFor, type Uploads } from './uploads.ts';\n\nexport interface HostLicks {\n licks: Licks;\n sources: LickSources;\n}\n\nexport interface HostScoops {\n scoops: Scoops;\n runtime: ScoopsRuntime;\n}\n\ntype WithAgents = { agents?: Agents };\n\nexport function readable(out: string): string {\n const text = out.replace(/^(?:sub)?agent: /, '').trim();\n const sentence = text.charAt(0).toUpperCase() + text.slice(1);\n return /[.!?]$/.test(sentence) ? sentence : `${sentence}.`;\n}\n\nfunction created(run: () => Promise<string>): Promise<Created> {\n return run().then(\n (id) => ({ id, error: null }),\n (error: Error) => ({ id: null, error: error.message })\n );\n}\n\nfunction monitorControl(\n monitor: Pick<Monitor, 'open' | 'close'> | undefined\n): Pick<AgentControl, 'monitorOpen' | 'monitorClose'> {\n return {\n monitorOpen: async (lease, resync, context) =>\n monitor ? monitor.open(lease, resync, context) : { lease: '', ttlMs: 0 },\n monitorClose: async (lease, context) => monitor?.close(lease, context),\n };\n}\n\nconst noApprovals: AgentControl['decide'] = async () => ({\n done: false,\n state: null,\n error: 'This agent has no approvals.',\n});\n\ntype FreezerControl = Pick<\n FreezerRuntime,\n 'freeze' | 'newChat' | 'thaw' | 'discard' | 'importProfile' | 'cancelImport'\n>;\n\nfunction freezerControl(\n freezer: FreezerControl | undefined\n): Pick<\n AgentControl,\n 'freeze' | 'newChat' | 'thaw' | 'discard' | 'importProfile' | 'importCancel'\n> {\n const needed = () => {\n if (!freezer) throw new Error('This agent has no freezer.');\n return freezer;\n };\n return {\n freeze: (agentId, context) => created(async () => needed().freeze(agentId, context)),\n newChat: (agentId, context) =>\n created(async () => (await needed().newChat(agentId, context)) ?? ''),\n thaw: (id, context) => created(async () => needed().thaw(id, context)),\n discard: (id, context) =>\n created(async () => {\n await needed().discard(id, context);\n return id;\n }),\n importProfile: (request, context) =>\n freezer\n ? freezer.importProfile(\n request.data === null\n ? { upload: request.upload ?? '' }\n : { bytes: fromBase64(request.data) },\n { again: request.again, preview: request.preview },\n context\n )\n : Promise.resolve({\n done: false,\n preview: null,\n cones: [],\n report: null,\n text: '',\n line: '',\n error: 'This agent has no freezer.',\n }),\n importCancel: async () => freezer?.cancelImport(),\n };\n}\n\nfunction guestControl(\n guests: Pick<Guests, 'open' | 'revoke' | 'forgetAlways'> | undefined\n): Pick<AgentControl, 'guestOpen' | 'guestRevoke' | 'guestAlwaysForget'> {\n return {\n guestOpen: async (draft, context) =>\n guests ? guests.open(draft, context) : { seat: null, error: NO_GUESTS },\n guestRevoke: async (id, context) => (guests ? guests.revoke(id, context) : { revoked: false }),\n guestAlwaysForget: async (seat, approval, context) =>\n guests ? guests.forgetAlways(seat, approval, context) : { removed: false },\n };\n}\n\nconst NO_GUESTS = 'This agent has no guest seats.';\n\nfunction changesControl(\n changes: Pick<ChangesRuntime, 'open' | 'accept' | 'revert'> | undefined\n): Pick<AgentControl, 'changesOpen' | 'changeAccept' | 'changeRevert'> {\n const needed = () => {\n if (!changes) throw new Error('This agent tracks no changes.');\n return changes;\n };\n const done = (path: string, run: (runtime: NonNullable<typeof changes>) => Promise<void>) =>\n created(async () => {\n await run(needed());\n return path;\n });\n return {\n changesOpen: async (context) => changes?.open(context),\n changeAccept: (path, context) => done(path, (runtime) => runtime.accept(path, context)),\n changeRevert: (path, context) => done(path, (runtime) => runtime.revert(path, context)),\n };\n}\n\nfunction memoryControl(\n memory: Pick<MemoryRuntime, 'save' | 'remove'> | undefined\n): Pick<AgentControl, 'memorySave' | 'memoryRemove'> {\n return {\n async memorySave(draft, context) {\n if (!memory) throw new Error('This agent has no memory.');\n const { id, ...rest } = draft;\n return memory.save({ ...rest, ...(id ? { id } : {}) }, context);\n },\n async memoryRemove(id, context) {\n return { removed: memory ? await memory.remove(id, context) : false };\n },\n };\n}\n\nfunction sprinkleControl(\n sprinkles: Pick<SprinklesRuntime, 'send' | 'call'> | undefined\n): Pick<AgentControl, 'sprinkleSend' | 'sprinkleCall'> {\n return {\n sprinkleSend: async (id, payload, context) =>\n sprinkles ? sprinkles.send(id, payload, context) : { delivered: false },\n async sprinkleCall(id, method, args, context) {\n if (!sprinkles) throw new Error('This agent has no sprinkles.');\n return sprinkles.call(id, method, args, context);\n },\n };\n}\n\nexport function submissionId(value: string): SubmissionId | undefined {\n const id = Number(value);\n return /^[1-9][0-9]*$/.test(value) && Number.isSafeInteger(id) ? (id as SubmissionId) : undefined;\n}\n\nfunction operationError(error: unknown): OperationError {\n if (error instanceof ConversationBusy) return { code: 'busy', message: error.message };\n return { code: 'failed', message: error instanceof Error ? error.message : String(error) };\n}\n\nasync function accepted(operation: () => Promise<{ id: number } | number>): Promise<SendResponse> {\n try {\n const result = await operation();\n const id = typeof result === 'number' ? result : result.id;\n return { accepted: true, submissionId: String(id), error: null };\n } catch (error) {\n return { accepted: false, submissionId: null, error: operationError(error) };\n }\n}\n\nfunction cones(target: Conversation | Cones): Cones {\n if ('cone' in target) return target;\n return {\n cone: () => target,\n switchCone: async () => {\n throw new Error('this conversation has no cone to rewind');\n },\n };\n}\n\ninterface Turn {\n text: string;\n attachments: ShownAttachment[];\n at: EntryId | null;\n files: boolean;\n}\n\ntype Block = { type?: string; name?: string };\n\nconst WRITERS = ['write', 'edit', 'bash', 'codemode'];\n\nfunction writesIn(entries: readonly EntryRecord[]): boolean {\n return entries.some((entry) =>\n (entry.model ?? []).some(\n (message) =>\n Array.isArray(message.content) &&\n (message.content as Block[]).some(\n (block) => block.type === 'toolCall' && WRITERS.includes(String(block.name))\n )\n )\n );\n}\n\nfunction lastTurn(view: ConversationView, messageId: string | null): Turn | null {\n const { entries } = view;\n const target = entries.findIndex((entry) => `e${entry.id}` === messageId);\n if (target < 0) return null;\n const turn = entries.slice(0, target).findLastIndex((entry) => entry.kind === 'pi.user');\n if (turn < 0) return null;\n const user = entries[turn] as EntryRecord;\n return {\n ...splitUser(user.model?.[0]?.content),\n at: entries[turn - 1]?.id ?? null,\n files: writesIn(entries.slice(turn)),\n };\n}\n\nfunction busy(view: ConversationView): boolean {\n const live = view.docs['pi.live'] as { run?: unknown } | undefined;\n const inbox = view.docs['pi.inbox'] as { items?: unknown[] } | undefined;\n return Boolean(live?.run) || Boolean(inbox?.items?.length);\n}\n\ntype RewindDeps = {\n harness: Harness;\n agents?: Agents;\n cone: Cones;\n scoops?: HostScoops;\n};\n\nasync function turnIn(\n harness: Harness,\n conversation: Conversation,\n messageId: string | null,\n context: Context\n): Promise<{ turn: Turn; view: ConversationView } | Rewound> {\n const attached = await conversation.viewState(context);\n const view = attached.value;\n attached.dispose();\n const pending = (await harness.inspect(context)).submissions.some(\n (submission) => submission.conversationId === conversation.id\n );\n if (pending || busy(view)) return { done: false, text: null, reason: 'busy' };\n const turn = lastTurn(view, messageId);\n return turn ? { turn, view } : { done: false, text: null, reason: 'no-turn' };\n}\n\nasync function forkAt(\n harness: Harness,\n conversation: Conversation,\n found: { turn: Turn },\n context: Context\n): Promise<Conversation> {\n const ownership = { kind: 'ownerless' } as const;\n if (found.turn.at !== null) return conversation.fork(found.turn.at, { ownership }, context);\n const init = copyAgent(await storedAgent(harness, conversation.id, context));\n return harness.createConversation({ ownership, init }, context);\n}\n\nasync function rewindAgent(\n deps: RewindDeps,\n agentId: string | null,\n messageId: string | null,\n context: Context\n): Promise<Rewound> {\n const { harness, agents, cone, scoops } = deps;\n const scoop = agentId && agents ? agents.state().scoops[agentId] : undefined;\n if (agentId && agents && !scoop && agentId !== agents.activeCone())\n await agents.selectCone(agentId, context);\n const conversation =\n scoop && agents\n ? ((await agents.conversation(agentId as string, context)) as Conversation)\n : cone.cone();\n const found = await turnIn(harness, conversation, messageId, context);\n if ('reason' in found) return found;\n const fork = await forkAt(harness, conversation, found, context);\n const changed =\n scoops && agents && !scoop\n ? await scoops.scoops.rewound(agents.activeCone(), fork, context)\n : { stopped: [], restored: [] };\n const files = found.turn.files;\n await fork.submit(\n { type: 'write', entry: { kind: 'slicc.rewound', data: { turns: 1, ...changed, files } } },\n context\n );\n if (scoop && agents)\n await agents.update((_tx, state) => {\n const record = state.scoops[agentId as string] as ScoopRecord;\n record.conversation = fork.id;\n delete record.title;\n delete record.titled;\n }, context);\n else await cone.switchCone(fork, context);\n const { attachments } = found.turn;\n return {\n done: true,\n text: found.turn.text,\n reason: null,\n ...(attachments.length ? { attachments } : {}),\n };\n}\n\ntype Picture = { type?: string; data?: string; mimeType?: string };\n\nasync function storedImage(\n harness: Harness,\n ref: ImageRef,\n context: Context\n): Promise<StoredImage> {\n const entry = await harness.commit((tx) => tx.entry(ref.entry as EntryId), context);\n const content = entry?.model?.[ref.message]?.content;\n const part = Array.isArray(content) ? (content[ref.part] as Picture | undefined) : undefined;\n return part?.type === 'image' && part.data && part.mimeType\n ? { data: part.data, mimeType: part.mimeType }\n : { data: null, mimeType: null };\n}\n\nasync function contentOf(\n text: string,\n request: SendRequest,\n conversation: Conversation,\n uploads: Uploads | undefined,\n context: Context\n): Promise<UserInput> {\n if (!request.attachments?.length) return scrubText(text);\n const saver = await saverFor(conversation, uploads, context);\n return scrubItems(await userParts(text, request.attachments, saver));\n}\n\nexport function createAgentControl(\n harness: Harness,\n target: Conversation | Cones,\n deliveries?: Deliveries,\n licks?: HostLicks,\n scoops?: HostScoops,\n skills?: Pick<SkillsRuntime, 'expand' | 'commandsFor'>,\n sprinkles?: Pick<SprinklesRuntime, 'send' | 'call'>,\n memory?: Pick<MemoryRuntime, 'save' | 'remove'>,\n freezer?: FreezerControl,\n changes?: Pick<ChangesRuntime, 'open' | 'accept' | 'revert'>,\n monitor?: Pick<Monitor, 'open' | 'close'>,\n secrets?: () => readonly SecretName[],\n uploads?: Uploads,\n labels?: Pick<LabelsRuntime, 'label'>,\n guests?: Pick<Guests, 'open' | 'revoke' | 'forgetAlways'>\n): Omit<AgentControl, 'approvalPolicySet'> {\n const cone = cones(target);\n const current = () => cone.cone();\n const agents = (target as WithAgents).agents;\n const conversationFor = async (agentId: string | null | undefined, context: Context) => {\n if (!agentId || !agents) return current();\n const found = await agents.conversation(agentId, context);\n if (!found) throw new Error(`There is no agent ${agentId}.`);\n return found;\n };\n const withdraw = async (conversation: Conversation, id: string, context: Context) => {\n const parsed = submissionId(id);\n if (parsed === undefined) return { outcome: 'not_found' as const };\n const result = await harness.abortSubmission(parsed, context, conversation.id);\n if (result === 'aborted') return { outcome: 'withdrawn' as const };\n return {\n outcome: result === 'not_found' ? ('not_found' as const) : ('already_placed' as const),\n };\n };\n let lock: Promise<unknown> = Promise.resolve();\n const serial = <T>(operation: () => Promise<T>): Promise<T> => {\n const run = lock.then(operation, operation);\n lock = run.catch(() => undefined);\n return run;\n };\n const rewindIn = (agentId: string | null, messageId: string | null, context: Context) =>\n serial(() => rewindAgent({ harness, agents, cone, scoops }, agentId, messageId, context));\n const decide = (\n agentId: string | null,\n lickId: string,\n state: 'confirmed' | 'dismissed',\n context: Context\n ): Promise<LickResolved> =>\n serial(async () => {\n if (!licks) return { done: false, text: null, error: 'this agent has no licks' };\n const action = state === 'confirmed' ? 'confirm' : 'dismiss';\n try {\n const conversation = await conversationFor(agentId, context);\n const text = await licks.licks.decide(conversation, lickId, action, undefined, context);\n await conversation.submit(\n {\n type: 'write',\n entry: {\n kind: LICK_STATE_KIND,\n data: { lick: lickId, state, by: 'user' },\n model: [\n {\n role: 'user',\n content: `The user ${state} lick ${lickId}: ${text}`,\n timestamp: Date.now(),\n },\n ],\n },\n },\n context\n );\n return { done: true, text, error: null };\n } catch (error) {\n return { done: false, text: null, error: (error as Error).message };\n }\n });\n return {\n send(request, context) {\n return serial(async () => {\n if (secrets && request.text.trim() === SECRETS_COMMAND)\n return accepted(async () =>\n (await conversationFor(request.agentId, context)).submit(\n { type: 'write', entry: secretsEntry(secrets()) },\n context\n )\n );\n const response = await accepted(async () => {\n const conversation = await conversationFor(request.agentId, context);\n const expanded = skills\n ? await skills.expand(request.text, context, conversation.id)\n : request.text;\n return conversation.submit(\n {\n type: 'input',\n content: await contentOf(expanded, request, conversation, uploads, context),\n whenBusy: request.whenBusy,\n ...(request.requestId ? { requestId: request.requestId } : {}),\n },\n context\n );\n });\n const id = response.submissionId === null ? undefined : submissionId(response.submissionId);\n if (id !== undefined) deliveries?.expect(id, request.whenBusy);\n return response;\n });\n },\n rewind: (messageId, context) => rewindIn(null, messageId, context),\n rewindAgent: (agentId, messageId, context) => rewindIn(agentId, messageId, context),\n\n async wait(id, context) {\n const parsed = submissionId(id);\n const submission =\n parsed === undefined ? undefined : await harness.submission(parsed, context);\n if (!submission) throw new Error(`unknown submission ${id}`);\n const settled = await submission.wait(context);\n const { answer, reason } = settled as { answer?: EntryId; reason?: string };\n if (answer === undefined) return { status: 'unanswered', text: null, reason: String(reason) };\n const entry = await harness.commit((tx) => tx.entry(AssistantEntry, answer), context);\n return { status: 'done', text: answerText(entry?.model?.[0]?.content), reason: null };\n },\n withdraw: (id, context) => withdraw(current(), id, context),\n ...sprinkleControl(sprinkles),\n ...memoryControl(memory),\n ...freezerControl(freezer),\n ...changesControl(changes),\n ...guestControl(guests),\n ...monitorControl(monitor),\n image: (ref, context) => storedImage(harness, ref, context),\n async unqueue(agentId, id, context) {\n return withdraw(await conversationFor(agentId, context), id, context);\n },\n abort: (context) => current().abort(context),\n compact: (instructions, context) =>\n accepted(() => current().compact(instructions ?? undefined, context)),\n compactAgent: (agentId, instructions, context) =>\n accepted(async () =>\n (await conversationFor(agentId, context)).compact(instructions ?? undefined, context)\n ),\n reset: (handoff, context) => current().reset(handoff ?? undefined, context),\n resolveLick: (lickId, state, context) => decide(null, lickId, state, context),\n resolveAgentLick: (agentId, lickId, state, context) => decide(agentId, lickId, state, context),\n async webhook(name, delivery, context) {\n const delivered = licks\n ? await licks.sources.webhook(\n name,\n {\n ...(delivery.id ? { id: delivery.id } : {}),\n headers: delivery.headers,\n body: delivery.body,\n },\n context\n )\n : false;\n return { delivered };\n },\n async stopAgent(agentId, context) {\n await (await conversationFor(agentId, context)).abort(context);\n },\n async selectCone(agentId, context) {\n await agents?.selectCone(agentId, context);\n },\n createCone(name, context) {\n return created(async () => {\n if (!agents) throw new Error('This agent has a single cone.');\n return agents.createCone(name, context);\n });\n },\n createScoop(parentId, name, context) {\n return created(async () => {\n if (!scoops) throw new Error('This agent has no scoops.');\n const roles = await scoops.runtime.roles(context);\n const answer = await scoops.scoops.spawn(\n { cone: parentId, name, prompts: [], fromAgent: false, limits: roles.limits },\n context\n );\n if (answer.code !== 0) throw new Error(readable(answer.out));\n return `scoop:${answer.out.trim()}`;\n });\n },\n drop(agentId, context) {\n return created(async () => {\n if (!scoops) throw new Error('This agent has no scoops.');\n const answer = await scoops.scoops.stop(agentId, false, context);\n if (answer.code !== 0) throw new Error(readable(answer.out));\n return agentId;\n });\n },\n configure: async (change, context) =>\n (await conversationFor(change.agentId, context)).configure(\n {\n ...(change.model ? { model: change.model } : {}),\n ...(change.thinkingLevel ? { thinkingLevel: change.thinkingLevel } : {}),\n },\n context\n ),\n agentCommands: async (agentId) => skills?.commandsFor(agentId) ?? [],\n toolLabel: async (agentId, callIds, context) =>\n labels ? labels.label(agentId, callIds, context) : null,\n decide: noApprovals,\n };\n}\n"],
4
+ "sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport {\n AssistantEntry,\n type Conversation,\n ConversationBusy,\n type ConversationView,\n type EntryId,\n type EntryRecord,\n type Harness,\n type SubmissionId,\n type UserInput,\n} from '@earendil-works/pi-durable';\nimport { answerText } from './agent.ts';\nimport type { Agents, ScoopRecord } from './agents.ts';\nimport { fromBase64, type ShownAttachment, splitUser, userParts } from './attachments.ts';\nimport type { ChangesRuntime } from './changes/index.ts';\nimport type { Cones } from './cone.ts';\nimport type { Deliveries } from './deliveries.ts';\nimport type { FreezerRuntime } from './freezer/index.ts';\nimport type { Guests } from './guests/index.ts';\nimport type { LickSources, Licks } from './licks/index.ts';\nimport { LICK_STATE_KIND } from './licks/state.ts';\nimport type { MemoryRuntime } from './memory/index.ts';\nimport type { Monitor } from './monitor/index.ts';\nimport type { Scoops, ScoopsRuntime } from './scoops/index.ts';\nimport { SECRETS_COMMAND, secretsEntry } from './secrets/extension.ts';\nimport type { SecretListing } from './secrets/names.ts';\nimport { scrubItems, scrubText } from './secrets/scrub.ts';\nimport { copyAgent, storedAgent } from './selection.ts';\nimport type {\n AgentControl,\n Created,\n ImageRef,\n LickResolved,\n OperationError,\n Rewound,\n SendRequest,\n SendResponse,\n StoredImage,\n} from './services.ts';\nimport type { SkillsRuntime } from './skills/index.ts';\nimport type { SprinklesRuntime } from './sprinkles/index.ts';\nimport type { LabelsRuntime } from './titles/labels.ts';\nimport { saverFor, type Uploads } from './uploads.ts';\n\nexport interface HostLicks {\n licks: Licks;\n sources: LickSources;\n}\n\nexport interface HostScoops {\n scoops: Scoops;\n runtime: ScoopsRuntime;\n}\n\ntype WithAgents = { agents?: Agents };\n\nexport function readable(out: string): string {\n const text = out.replace(/^(?:sub)?agent: /, '').trim();\n const sentence = text.charAt(0).toUpperCase() + text.slice(1);\n return /[.!?]$/.test(sentence) ? sentence : `${sentence}.`;\n}\n\nfunction created(run: () => Promise<string>): Promise<Created> {\n return run().then(\n (id) => ({ id, error: null }),\n (error: Error) => ({ id: null, error: error.message })\n );\n}\n\nfunction monitorControl(\n monitor: Pick<Monitor, 'open' | 'close'> | undefined\n): Pick<AgentControl, 'monitorOpen' | 'monitorClose'> {\n return {\n monitorOpen: async (lease, resync, context) =>\n monitor ? monitor.open(lease, resync, context) : { lease: '', ttlMs: 0 },\n monitorClose: async (lease, context) => monitor?.close(lease, context),\n };\n}\n\nconst noApprovals: AgentControl['decide'] = async () => ({\n done: false,\n state: null,\n error: 'This agent has no approvals.',\n});\n\ntype FreezerControl = Pick<\n FreezerRuntime,\n 'freeze' | 'newChat' | 'thaw' | 'discard' | 'importProfile' | 'cancelImport'\n>;\n\nfunction freezerControl(\n freezer: FreezerControl | undefined\n): Pick<\n AgentControl,\n 'freeze' | 'newChat' | 'thaw' | 'discard' | 'importProfile' | 'importCancel'\n> {\n const needed = () => {\n if (!freezer) throw new Error('This agent has no freezer.');\n return freezer;\n };\n return {\n freeze: (agentId, context) => created(async () => needed().freeze(agentId, context)),\n newChat: (agentId, context) =>\n created(async () => (await needed().newChat(agentId, context)) ?? ''),\n thaw: (id, context) => created(async () => needed().thaw(id, context)),\n discard: (id, context) =>\n created(async () => {\n await needed().discard(id, context);\n return id;\n }),\n importProfile: (request, context) =>\n freezer\n ? freezer.importProfile(\n request.data === null\n ? { upload: request.upload ?? '' }\n : { bytes: fromBase64(request.data) },\n { again: request.again, preview: request.preview },\n context\n )\n : Promise.resolve({\n done: false,\n preview: null,\n cones: [],\n report: null,\n text: '',\n line: '',\n error: 'This agent has no freezer.',\n }),\n importCancel: async () => freezer?.cancelImport(),\n };\n}\n\nfunction guestControl(\n guests: Pick<Guests, 'open' | 'revoke' | 'forgetAlways'> | undefined\n): Pick<AgentControl, 'guestOpen' | 'guestRevoke' | 'guestAlwaysForget'> {\n return {\n guestOpen: async (draft, context) =>\n guests ? guests.open(draft, context) : { seat: null, error: NO_GUESTS },\n guestRevoke: async (id, context) => (guests ? guests.revoke(id, context) : { revoked: false }),\n guestAlwaysForget: async (seat, approval, context) =>\n guests ? guests.forgetAlways(seat, approval, context) : { removed: false },\n };\n}\n\nconst NO_GUESTS = 'This agent has no guest seats.';\n\nfunction changesControl(\n changes: Pick<ChangesRuntime, 'open' | 'accept' | 'revert'> | undefined\n): Pick<AgentControl, 'changesOpen' | 'changeAccept' | 'changeRevert'> {\n const needed = () => {\n if (!changes) throw new Error('This agent tracks no changes.');\n return changes;\n };\n const done = (path: string, run: (runtime: NonNullable<typeof changes>) => Promise<void>) =>\n created(async () => {\n await run(needed());\n return path;\n });\n return {\n changesOpen: async (context) => changes?.open(context),\n changeAccept: (path, context) => done(path, (runtime) => runtime.accept(path, context)),\n changeRevert: (path, context) => done(path, (runtime) => runtime.revert(path, context)),\n };\n}\n\nfunction memoryControl(\n memory: Pick<MemoryRuntime, 'save' | 'remove'> | undefined\n): Pick<AgentControl, 'memorySave' | 'memoryRemove'> {\n return {\n async memorySave(draft, context) {\n if (!memory) throw new Error('This agent has no memory.');\n const { id, ...rest } = draft;\n return memory.save({ ...rest, ...(id ? { id } : {}) }, context);\n },\n async memoryRemove(id, context) {\n return { removed: memory ? await memory.remove(id, context) : false };\n },\n };\n}\n\nfunction sprinkleControl(\n sprinkles: Pick<SprinklesRuntime, 'send' | 'call'> | undefined\n): Pick<AgentControl, 'sprinkleSend' | 'sprinkleCall'> {\n return {\n sprinkleSend: async (id, payload, context) =>\n sprinkles ? sprinkles.send(id, payload, context) : { delivered: false },\n async sprinkleCall(id, method, args, context) {\n if (!sprinkles) throw new Error('This agent has no sprinkles.');\n return sprinkles.call(id, method, args, context);\n },\n };\n}\n\nexport function submissionId(value: string): SubmissionId | undefined {\n const id = Number(value);\n return /^[1-9][0-9]*$/.test(value) && Number.isSafeInteger(id) ? (id as SubmissionId) : undefined;\n}\n\nfunction operationError(error: unknown): OperationError {\n if (error instanceof ConversationBusy) return { code: 'busy', message: error.message };\n return { code: 'failed', message: error instanceof Error ? error.message : String(error) };\n}\n\nasync function accepted(operation: () => Promise<{ id: number } | number>): Promise<SendResponse> {\n try {\n const result = await operation();\n const id = typeof result === 'number' ? result : result.id;\n return { accepted: true, submissionId: String(id), error: null };\n } catch (error) {\n return { accepted: false, submissionId: null, error: operationError(error) };\n }\n}\n\nfunction cones(target: Conversation | Cones): Cones {\n if ('cone' in target) return target;\n return {\n cone: () => target,\n switchCone: async () => {\n throw new Error('this conversation has no cone to rewind');\n },\n };\n}\n\ninterface Turn {\n text: string;\n attachments: ShownAttachment[];\n at: EntryId | null;\n files: boolean;\n}\n\ntype Block = { type?: string; name?: string };\n\nconst WRITERS = ['write', 'edit', 'bash', 'codemode'];\n\nfunction writesIn(entries: readonly EntryRecord[]): boolean {\n return entries.some((entry) =>\n (entry.model ?? []).some(\n (message) =>\n Array.isArray(message.content) &&\n (message.content as Block[]).some(\n (block) => block.type === 'toolCall' && WRITERS.includes(String(block.name))\n )\n )\n );\n}\n\nfunction lastTurn(view: ConversationView, messageId: string | null): Turn | null {\n const { entries } = view;\n const target = entries.findIndex((entry) => `e${entry.id}` === messageId);\n if (target < 0) return null;\n const turn = entries.slice(0, target).findLastIndex((entry) => entry.kind === 'pi.user');\n if (turn < 0) return null;\n const user = entries[turn] as EntryRecord;\n return {\n ...splitUser(user.model?.[0]?.content),\n at: entries[turn - 1]?.id ?? null,\n files: writesIn(entries.slice(turn)),\n };\n}\n\nfunction busy(view: ConversationView): boolean {\n const live = view.docs['pi.live'] as { run?: unknown } | undefined;\n const inbox = view.docs['pi.inbox'] as { items?: unknown[] } | undefined;\n return Boolean(live?.run) || Boolean(inbox?.items?.length);\n}\n\ntype RewindDeps = {\n harness: Harness;\n agents?: Agents;\n cone: Cones;\n scoops?: HostScoops;\n};\n\nasync function turnIn(\n harness: Harness,\n conversation: Conversation,\n messageId: string | null,\n context: Context\n): Promise<{ turn: Turn; view: ConversationView } | Rewound> {\n const attached = await conversation.viewState(context);\n const view = attached.value;\n attached.dispose();\n const pending = (await harness.inspect(context)).submissions.some(\n (submission) => submission.conversationId === conversation.id\n );\n if (pending || busy(view)) return { done: false, text: null, reason: 'busy' };\n const turn = lastTurn(view, messageId);\n return turn ? { turn, view } : { done: false, text: null, reason: 'no-turn' };\n}\n\nasync function forkAt(\n harness: Harness,\n conversation: Conversation,\n found: { turn: Turn },\n context: Context\n): Promise<Conversation> {\n const ownership = { kind: 'ownerless' } as const;\n if (found.turn.at !== null) return conversation.fork(found.turn.at, { ownership }, context);\n const init = copyAgent(await storedAgent(harness, conversation.id, context));\n return harness.createConversation({ ownership, init }, context);\n}\n\nasync function rewindAgent(\n deps: RewindDeps,\n agentId: string | null,\n messageId: string | null,\n context: Context\n): Promise<Rewound> {\n const { harness, agents, cone, scoops } = deps;\n const scoop = agentId && agents ? agents.state().scoops[agentId] : undefined;\n if (agentId && agents && !scoop && agentId !== agents.activeCone())\n await agents.selectCone(agentId, context);\n const conversation =\n scoop && agents\n ? ((await agents.conversation(agentId as string, context)) as Conversation)\n : cone.cone();\n const found = await turnIn(harness, conversation, messageId, context);\n if ('reason' in found) return found;\n const fork = await forkAt(harness, conversation, found, context);\n const changed =\n scoops && agents && !scoop\n ? await scoops.scoops.rewound(agents.activeCone(), fork, context)\n : { stopped: [], restored: [] };\n const files = found.turn.files;\n await fork.submit(\n { type: 'write', entry: { kind: 'slicc.rewound', data: { turns: 1, ...changed, files } } },\n context\n );\n if (scoop && agents)\n await agents.update((_tx, state) => {\n const record = state.scoops[agentId as string] as ScoopRecord;\n record.conversation = fork.id;\n delete record.title;\n delete record.titled;\n }, context);\n else await cone.switchCone(fork, context);\n const { attachments } = found.turn;\n return {\n done: true,\n text: found.turn.text,\n reason: null,\n ...(attachments.length ? { attachments } : {}),\n };\n}\n\ntype Picture = { type?: string; data?: string; mimeType?: string };\n\nasync function storedImage(\n harness: Harness,\n ref: ImageRef,\n context: Context\n): Promise<StoredImage> {\n const entry = await harness.commit((tx) => tx.entry(ref.entry as EntryId), context);\n const content = entry?.model?.[ref.message]?.content;\n const part = Array.isArray(content) ? (content[ref.part] as Picture | undefined) : undefined;\n return part?.type === 'image' && part.data && part.mimeType\n ? { data: part.data, mimeType: part.mimeType }\n : { data: null, mimeType: null };\n}\n\nasync function contentOf(\n text: string,\n request: SendRequest,\n conversation: Conversation,\n uploads: Uploads | undefined,\n context: Context\n): Promise<UserInput> {\n if (!request.attachments?.length) return scrubText(text);\n const saver = await saverFor(conversation, uploads, context);\n return scrubItems(await userParts(text, request.attachments, saver));\n}\n\nexport function createAgentControl(\n harness: Harness,\n target: Conversation | Cones,\n deliveries?: Deliveries,\n licks?: HostLicks,\n scoops?: HostScoops,\n skills?: Pick<SkillsRuntime, 'expand' | 'commandsFor'>,\n sprinkles?: Pick<SprinklesRuntime, 'send' | 'call'>,\n memory?: Pick<MemoryRuntime, 'save' | 'remove'>,\n freezer?: FreezerControl,\n changes?: Pick<ChangesRuntime, 'open' | 'accept' | 'revert'>,\n monitor?: Pick<Monitor, 'open' | 'close'>,\n secrets?: () => SecretListing,\n uploads?: Uploads,\n labels?: Pick<LabelsRuntime, 'label'>,\n guests?: Pick<Guests, 'open' | 'revoke' | 'forgetAlways'>\n): Omit<AgentControl, 'approvalPolicySet'> {\n const cone = cones(target);\n const current = () => cone.cone();\n const agents = (target as WithAgents).agents;\n const conversationFor = async (agentId: string | null | undefined, context: Context) => {\n if (!agentId || !agents) return current();\n const found = await agents.conversation(agentId, context);\n if (!found) throw new Error(`There is no agent ${agentId}.`);\n return found;\n };\n const withdraw = async (conversation: Conversation, id: string, context: Context) => {\n const parsed = submissionId(id);\n if (parsed === undefined) return { outcome: 'not_found' as const };\n const result = await harness.abortSubmission(parsed, context, conversation.id);\n if (result === 'aborted') return { outcome: 'withdrawn' as const };\n return {\n outcome: result === 'not_found' ? ('not_found' as const) : ('already_placed' as const),\n };\n };\n let lock: Promise<unknown> = Promise.resolve();\n const serial = <T>(operation: () => Promise<T>): Promise<T> => {\n const run = lock.then(operation, operation);\n lock = run.catch(() => undefined);\n return run;\n };\n const rewindIn = (agentId: string | null, messageId: string | null, context: Context) =>\n serial(() => rewindAgent({ harness, agents, cone, scoops }, agentId, messageId, context));\n const decide = (\n agentId: string | null,\n lickId: string,\n state: 'confirmed' | 'dismissed',\n context: Context\n ): Promise<LickResolved> =>\n serial(async () => {\n if (!licks) return { done: false, text: null, error: 'this agent has no licks' };\n const action = state === 'confirmed' ? 'confirm' : 'dismiss';\n try {\n const conversation = await conversationFor(agentId, context);\n const text = await licks.licks.decide(conversation, lickId, action, undefined, context);\n await conversation.submit(\n {\n type: 'write',\n entry: {\n kind: LICK_STATE_KIND,\n data: { lick: lickId, state, by: 'user' },\n model: [\n {\n role: 'user',\n content: `The user ${state} lick ${lickId}: ${text}`,\n timestamp: Date.now(),\n },\n ],\n },\n },\n context\n );\n return { done: true, text, error: null };\n } catch (error) {\n return { done: false, text: null, error: (error as Error).message };\n }\n });\n return {\n send(request, context) {\n return serial(async () => {\n if (secrets && request.text.trim() === SECRETS_COMMAND)\n return accepted(async () =>\n (await conversationFor(request.agentId, context)).submit(\n { type: 'write', entry: secretsEntry(secrets()) },\n context\n )\n );\n const response = await accepted(async () => {\n const conversation = await conversationFor(request.agentId, context);\n const expanded = skills\n ? await skills.expand(request.text, context, conversation.id)\n : request.text;\n return conversation.submit(\n {\n type: 'input',\n content: await contentOf(expanded, request, conversation, uploads, context),\n whenBusy: request.whenBusy,\n ...(request.requestId ? { requestId: request.requestId } : {}),\n },\n context\n );\n });\n const id = response.submissionId === null ? undefined : submissionId(response.submissionId);\n if (id !== undefined) deliveries?.expect(id, request.whenBusy);\n return response;\n });\n },\n rewind: (messageId, context) => rewindIn(null, messageId, context),\n rewindAgent: (agentId, messageId, context) => rewindIn(agentId, messageId, context),\n\n async wait(id, context) {\n const parsed = submissionId(id);\n const submission =\n parsed === undefined ? undefined : await harness.submission(parsed, context);\n if (!submission) throw new Error(`unknown submission ${id}`);\n const settled = await submission.wait(context);\n const { answer, reason } = settled as { answer?: EntryId; reason?: string };\n if (answer === undefined) return { status: 'unanswered', text: null, reason: String(reason) };\n const entry = await harness.commit((tx) => tx.entry(AssistantEntry, answer), context);\n return { status: 'done', text: answerText(entry?.model?.[0]?.content), reason: null };\n },\n withdraw: (id, context) => withdraw(current(), id, context),\n ...sprinkleControl(sprinkles),\n ...memoryControl(memory),\n ...freezerControl(freezer),\n ...changesControl(changes),\n ...guestControl(guests),\n ...monitorControl(monitor),\n image: (ref, context) => storedImage(harness, ref, context),\n async unqueue(agentId, id, context) {\n return withdraw(await conversationFor(agentId, context), id, context);\n },\n abort: (context) => current().abort(context),\n compact: (instructions, context) =>\n accepted(() => current().compact(instructions ?? undefined, context)),\n compactAgent: (agentId, instructions, context) =>\n accepted(async () =>\n (await conversationFor(agentId, context)).compact(instructions ?? undefined, context)\n ),\n reset: (handoff, context) => current().reset(handoff ?? undefined, context),\n resolveLick: (lickId, state, context) => decide(null, lickId, state, context),\n resolveAgentLick: (agentId, lickId, state, context) => decide(agentId, lickId, state, context),\n async webhook(name, delivery, context) {\n const delivered = licks\n ? await licks.sources.webhook(\n name,\n {\n ...(delivery.id ? { id: delivery.id } : {}),\n headers: delivery.headers,\n body: delivery.body,\n },\n context\n )\n : false;\n return { delivered };\n },\n async stopAgent(agentId, context) {\n await (await conversationFor(agentId, context)).abort(context);\n },\n async selectCone(agentId, context) {\n await agents?.selectCone(agentId, context);\n },\n createCone(name, context) {\n return created(async () => {\n if (!agents) throw new Error('This agent has a single cone.');\n return agents.createCone(name, context);\n });\n },\n createScoop(parentId, name, context) {\n return created(async () => {\n if (!scoops) throw new Error('This agent has no scoops.');\n const roles = await scoops.runtime.roles(context);\n const answer = await scoops.scoops.spawn(\n { cone: parentId, name, prompts: [], fromAgent: false, limits: roles.limits },\n context\n );\n if (answer.code !== 0) throw new Error(readable(answer.out));\n return `scoop:${answer.out.trim()}`;\n });\n },\n drop(agentId, context) {\n return created(async () => {\n if (!scoops) throw new Error('This agent has no scoops.');\n const answer = await scoops.scoops.stop(agentId, false, context);\n if (answer.code !== 0) throw new Error(readable(answer.out));\n return agentId;\n });\n },\n configure: async (change, context) =>\n (await conversationFor(change.agentId, context)).configure(\n {\n ...(change.model ? { model: change.model } : {}),\n ...(change.thinkingLevel ? { thinkingLevel: change.thinkingLevel } : {}),\n },\n context\n ),\n agentCommands: async (agentId) => skills?.commandsFor(agentId) ?? [],\n toolLabel: async (agentId, callIds, context) =>\n labels ? labels.label(agentId, callIds, context) : null,\n decide: noApprovals,\n };\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
package/dist/host.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/host.ts"],
4
- "sourcesContent": ["import {\n type Context,\n createHost,\n forbidden,\n type Host,\n type Readable,\n type ReplicatedState,\n replicatedState,\n type Self,\n} from '@earendil-works/chord';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport type { ConversationView } from '@earendil-works/pi-durable';\nimport type { Agent } from './agent.ts';\nimport {\n type ApprovalJudge,\n type Approvals,\n attachApprovals,\n type Decider,\n} from './approvals/index.ts';\nimport { type ApprovalPolicies, alwaysFor, attachApprovalPolicy } from './approvals/policy.ts';\nimport { type AgentSudo, createSudo, ownedSudo } from './approvals/sudo.ts';\nimport type { ChangesView } from './changes/git.ts';\nimport type { ChangesRuntime } from './changes/index.ts';\nimport { createAgentControl, type HostLicks, type HostScoops } from './control.ts';\nimport { trackDeliveries } from './deliveries.ts';\nimport type { FreezerRuntime, FrozenCone } from './freezer/index.ts';\nimport { IDLE, type ImportProgress } from './freezer/v6.ts';\nimport { createGuests, type Guests } from './guests/index.ts';\nimport type { ScoopReviewHost } from './guests/scoop.ts';\nimport { admitGuest, guestOpener } from './guests/serve.ts';\nimport type { UserClients } from './kernel/users.ts';\nimport type { McpRuntime } from './mcp/index.ts';\nimport type { MemoryEntry, MemoryScope } from './memory/format.ts';\nimport type { MemoryRuntime } from './memory/index.ts';\nimport { createMonitor, type MonitorOptions } from './monitor/index.ts';\nimport { viewRemote } from './readonly.ts';\nimport { type AgentRemote, createRemote } from './remote/host.ts';\nimport { createPages, ownedPages, type Pages } from './remote/pages.ts';\nimport type { SecretName } from './secrets/names.ts';\nimport type { VaultState } from './secrets/vault.ts';\nimport {\n AgentControl,\n type AgentGuest,\n AgentGuestService,\n AgentPagesService,\n AgentRemoteService,\n AgentSettings,\n AgentSudoService,\n AgentTranscript,\n CONTROL_METHODS,\n CONTROL_READS,\n type Command,\n GUEST_METHODS,\n OWNER_DEVICE_METHODS,\n PAGE_METHODS,\n type PrivateRevision,\n REMOTE_METHODS,\n REMOTE_READS,\n SETTINGS_METHODS,\n SUDO_METHODS,\n} from './services.ts';\nimport {\n AGENT_HOST,\n type AgentPrincipal,\n type GuestPrincipal,\n principalKey,\n readable,\n readablesOf,\n SELF,\n type Seat,\n served,\n} from './serving.ts';\nimport type { SkillsRuntime } from './skills/index.ts';\nimport type { SprinklesRuntime } from './sprinkles/index.ts';\nimport type { Sprinkle } from './sprinkles/kind.ts';\nimport { slimmer, type Thumbnail, thumbnail } from './thumbs.ts';\nimport { type LabelsRuntime, visibleLabels, watchLabel } from './titles/labels.ts';\nimport type { Uploads } from './uploads.ts';\nimport { agentViews } from './views.ts';\nimport { type PortEndpoint, PortLink } from './wire.ts';\n\nexport interface ConnectOptions {\n readOnly?: boolean;\n pages?: boolean;\n guest?: { id: string; label: string };\n device?: string;\n}\n\nexport interface AgentHost {\n readonly serverId: string;\n readonly agent: Agent;\n readonly users?: UserClients;\n readonly pages: Pages;\n readonly guests: Guests;\n readonly approvals: Approvals;\n connect(endpoint: PortEndpoint, options?: ConnectOptions): void;\n close(): Promise<void>;\n}\n\nconst noSettings: AgentSettings = {\n state: replicatedState({ models: [], accounts: [] }),\n connect: async () => {\n throw new Error('this agent has no accounts');\n },\n disconnect: async () => {},\n remove: async () => {},\n serverUrl: async () => null,\n signIn: async () => null,\n dismissMoved: async () => {},\n};\n\nasync function follow(\n agent: Agent,\n context: Context,\n slim: (view: ConversationView) => ConversationView\n) {\n let attached = await agent.cone().viewState(context);\n const state = replicatedState<ConversationView>(slim(attached.value));\n const mirror = (value: ConversationView, from: Context) => state.replace(from, slim(value));\n let off = attached.subscribe(mirror);\n const stop = agent.onCone(async (conversation) => {\n const next = await conversation.viewState(context);\n off();\n attached.dispose();\n attached = next;\n state.replace(context, slim(next.value));\n off = next.subscribe(mirror);\n });\n return {\n state,\n refresh: () => state.replace(context, slim(attached.value)),\n dispose() {\n stop();\n off();\n attached.dispose();\n },\n };\n}\n\nexport interface HostSecrets {\n vault: ReplicatedState<VaultState>;\n list(): readonly SecretName[];\n close?(): void;\n}\n\nconst idleVault = replicatedState<VaultState>({\n port: 'none',\n needed: false,\n version: 0,\n locked: 'no',\n held: 0,\n});\n\nfunction gatedControls(\n control: AgentControl,\n shown: (entry: number) => boolean,\n views: () => readonly ConversationView[],\n labels: ReplicatedState<Record<string, string>>\n) {\n const visible = (entry: number) =>\n views().some((view) => view.entries.some((item) => item.id === entry));\n const gated =\n (allowed: (entry: number) => boolean): AgentControl['image'] =>\n async (ref, using) =>\n allowed(ref.entry) ? control.image(ref, using) : { data: null, mimeType: null };\n const trusted: AgentControl = { ...control, image: gated(shown) };\n const viewed: AgentControl = { ...control, image: gated(visible), toolLabel: watchLabel(labels) };\n return { trusted, viewed };\n}\n\nasync function approving(\n agent: Agent,\n policy: ApprovalPolicies,\n options: { judge?: ApprovalJudge; intent?: ScoopReviewHost['intent']; approvalMs?: number },\n context: Context\n): Promise<Approvals> {\n const approvals = await attachApprovals(\n {\n harness: agent.harness,\n guestAlways: alwaysFor(policy),\n sudoApprover: () => policy.current().sudo.approver,\n scoops: agent.scoopReviews,\n ...(options.judge ? { judge: options.judge } : {}),\n ...(options.approvalMs ? { ttlMs: options.approvalMs } : {}),\n },\n context\n );\n agent.scoopReviews.bind({\n approvals,\n agents: agent.agents,\n harness: agent.harness,\n ...(options.intent ? { intent: options.intent } : {}),\n });\n return approvals;\n}\n\nasync function guestsOf(\n agent: Agent,\n approvals: Approvals,\n policy: ApprovalPolicies,\n context: Context\n): Promise<Guests> {\n const guests = await createGuests({\n harness: agent.harness,\n agents: agent.agents,\n approvals,\n policy,\n reviews: agent.reviews,\n context,\n });\n agent.gate.bind({ approvals, policy: () => guests.policy() });\n return guests;\n}\n\nconst noRevision = () => replicatedState<PrivateRevision>({ revision: 0 });\n\nexport async function hostAgent(\n agent: Agent,\n options: {\n serverId?: string;\n context?: Context;\n settings?: AgentSettings;\n licks?: HostLicks;\n scoops?: HostScoops;\n skills?: SkillsRuntime;\n sprinkles?: SprinklesRuntime;\n memory?: MemoryRuntime;\n freezer?: FreezerRuntime;\n changes?: ChangesRuntime;\n mcp?: Pick<McpRuntime, 'close'>;\n users?: UserClients;\n monitor?: Pick<MonitorOptions, 'kernel' | 'groups' | 'users'>;\n pages?: Pages;\n secrets?: HostSecrets;\n uploads?: Uploads;\n thumbnail?: Thumbnail;\n labels?: LabelsRuntime;\n approvals?: Approvals;\n judge?: ApprovalJudge;\n intent?: ScoopReviewHost['intent'];\n approvalMs?: number;\n } = {}\n): Promise<AgentHost> {\n const context = options.context ?? BACKGROUND_CONTEXT;\n const serverId = options.serverId ?? crypto.randomUUID();\n let refresh = () => {};\n const slims = slimmer(options.thumbnail ?? thumbnail, () => refresh());\n const transcript = await follow(agent, context, slims.slim);\n const { state } = transcript;\n const mounted = await agentViews(agent, context, slims.slim);\n refresh = () => {\n transcript.refresh();\n mounted.refresh();\n };\n const touch = () => options.freezer?.touch();\n const untouch = [state.subscribe(touch), mounted.views.subscribe(touch)];\n const pages = options.pages ?? createPages();\n const deliveries = trackDeliveries(agent.harness, state, context);\n const policy = await attachApprovalPolicy(agent.harness, context);\n const approvals = options.approvals ?? (await approving(agent, policy, options, context));\n const monitor = createMonitor({\n ...options.monitor,\n harness: agent.harness,\n agents: agent.agents,\n views: mounted.views,\n context,\n });\n const guests = await guestsOf(agent, approvals, policy, context);\n const control: AgentControl = {\n ...createAgentControl(\n agent.harness,\n agent,\n deliveries,\n options.licks,\n options.scoops,\n options.skills,\n options.sprinkles,\n options.memory,\n options.freezer,\n options.changes,\n monitor,\n options.secrets?.list,\n options.uploads,\n options.labels,\n guests\n ),\n approvalPolicySet: (change, using) => policy.set(change, using),\n };\n const remote = createRemote({\n harness: agent.harness,\n send: (request, using) => control.send(request, using),\n vault: options.secrets?.vault ?? idleVault,\n async conversation(agentId, using) {\n if (!agentId) return agent.cone();\n const found = await agent.agents.conversation(agentId, using);\n if (!found) throw new Error(`There is no agent ${agentId}.`);\n return found;\n },\n });\n const shownViews = () => [state.value, ...Object.values(mounted.views.value)];\n const kept = options.labels?.labels ?? replicatedState<Record<string, string>>({});\n const labels = visibleLabels(kept, [state, mounted.views], shownViews);\n const transcriptService = {\n state,\n deliveries: deliveries.state,\n agents: mounted.agents,\n views: mounted.views,\n commands: options.skills?.commands ?? replicatedState<Command[]>([]),\n privateRevision: options.skills?.privateRevision ?? noRevision(),\n sprinkles: options.sprinkles?.sprinkles ?? replicatedState<Sprinkle[]>([]),\n memories: options.memory?.memories ?? replicatedState<MemoryEntry[]>([]),\n memoryScopes: options.memory?.scopes ?? replicatedState<MemoryScope[]>([]),\n frozen: options.freezer?.frozen ?? replicatedState<FrozenCone[]>([]),\n importing: options.freezer?.importing ?? replicatedState<ImportProgress>(IDLE),\n changes:\n options.changes?.view ?? replicatedState<ChangesView>({ unavailable: null, changes: [] }),\n monitor: monitor.state,\n vault: options.secrets?.vault ?? idleVault,\n labels: labels.state,\n guests: guests.seats,\n approvals: approvals.state,\n approvalPolicy: policy.state,\n };\n const sudo = createSudo({\n approvals,\n agents: () => agent.agents.state(),\n ...(options.licks ? { licks: options.licks.licks } : {}),\n });\n const settings = options.settings ?? noSettings;\n const gates = gatedControls(control, slims.shown, shownViews, transcriptService.labels);\n const { trusted, viewed } = gates;\n const host = serveAgent({\n trusted,\n viewed,\n transcript: transcriptService,\n viewMonitor: monitor.viewState,\n sudo,\n settings,\n remote,\n pages,\n guest: guestOpener(guests, agent.agents, mounted.views, context),\n decide: (by) => (id, decision, reason, using) =>\n approvals.decide(id, decision, reason, by, using),\n });\n const offGuests = guests.onEnd((id) => {\n void host.invalidatePrincipal(\n (principal) => principal.seat === 'guest' && principal.guest.id === id\n );\n });\n let links = 0;\n return {\n serverId,\n agent,\n pages,\n guests,\n approvals,\n ...(options.users ? { users: options.users } : {}),\n connect(endpoint, connectOptions) {\n if (connectOptions?.guest) {\n links += 1;\n admitGuest(guests, endpoint, connectOptions.guest, serverId, links, host);\n return;\n }\n const seat: Exclude<Seat, 'guest'> = connectOptions?.readOnly\n ? 'view'\n : connectOptions?.pages === false\n ? 'control'\n : 'owner';\n links += 1;\n const link = new PortLink(endpoint);\n link.greet(serverId);\n const device = connectOptions?.device ? { device: connectOptions.device } : {};\n host.accept(link, {\n principal: { seat, link: links, ...device } satisfies AgentPrincipal,\n });\n },\n async close() {\n offGuests();\n await host.close();\n guests.dispose();\n await pages.dispose();\n await monitor.dispose();\n await remote.dispose();\n deliveries.dispose();\n sudo.close();\n approvals.close();\n transcript.dispose();\n mounted.dispose();\n labels.dispose();\n slims.dispose();\n for (const off of untouch) off();\n options.freezer?.close();\n options.secrets?.close?.();\n await options.changes?.close(context);\n await options.memory?.close(context);\n await options.sprinkles?.close(context);\n await options.skills?.close(context);\n await options.mcp?.close(context);\n await options.scoops?.runtime.close(context);\n await options.licks?.sources.close(context);\n await agent.close();\n await options.users?.close();\n },\n };\n}\n\ninterface Served {\n trusted: AgentControl;\n viewed: AgentControl;\n transcript: Record<keyof AgentTranscript, ReplicatedState<unknown>>;\n viewMonitor: ReplicatedState<unknown>;\n sudo?: AgentSudo;\n settings: AgentSettings;\n remote: AgentRemote;\n pages: Pages;\n guest?: (principal: GuestPrincipal) => { value: AgentGuest; dispose(): void } | undefined;\n decide: (by: Decider) => AgentControl['decide'];\n}\n\nexport const CLOSED_TO_GUESTS = 'a guest seat has only its transcript and propose';\n\nconst opened = new WeakSet<object>();\n\nexport const openToGuests = <F extends object>(factory: F): F => {\n opened.add(factory);\n return factory;\n};\n\nexport function closedToGuests(host: Host<AgentPrincipal>): Host<AgentPrincipal> {\n const serve = ((type: never, factory: (...args: unknown[]) => unknown, options?: never) =>\n host.serve(\n type,\n (opened.has(factory)\n ? factory\n : (input: unknown, principal: AgentPrincipal, self: unknown) => {\n if (principal.seat === 'guest') throw forbidden(CLOSED_TO_GUESTS);\n return factory(input, principal, self);\n }) as never,\n options\n )) as Host<AgentPrincipal>['serve'];\n return new Proxy(host, {\n get(target, key) {\n if (key === 'serve') return serve;\n const value = Reflect.get(target, key, target) as unknown;\n return typeof value === 'function' ? value.bind(target) : value;\n },\n });\n}\n\nexport function lazily<T>(state: () => ReplicatedState<T>): Readable<T> {\n return {\n get value() {\n return readable(state()).value;\n },\n subscribe: (listener) => readable(state()).subscribe(listener),\n events: (listener) => readable(state()).events(listener),\n };\n}\n\nconst seated = (serving: Served, principal: AgentPrincipal): AgentControl => ({\n ...(principal.seat === 'view' ? serving.viewed : serving.trusted),\n decide: serving.decide({\n by: principal.seat === 'owner' ? 'owner' : 'follower',\n ...('device' in principal && principal.device ? { device: principal.device } : {}),\n }),\n});\n\nconst VIEW_METHODS = [...CONTROL_READS, ...OWNER_DEVICE_METHODS];\n\nexport function serveAgent(serving: Served): Host<AgentPrincipal> {\n const host = closedToGuests(\n createHost<AgentPrincipal>({ name: AGENT_HOST, self: SELF, principalKey })\n );\n const viewing = { ...serving.transcript, monitor: serving.viewMonitor };\n host.serve(AgentControl, (_input, principal) => {\n const control = seated(serving, principal);\n return principal.seat === 'view'\n ? { variant: 'view', ...served(() => control, VIEW_METHODS) }\n : { variant: 'control', ...served(() => control, CONTROL_METHODS) };\n });\n host.serve(AgentTranscript, (_input, principal) => ({\n variant: 'transcript',\n ...readablesOf(principal.seat === 'view' ? viewing : serving.transcript),\n }));\n host.serve(AgentSettings, (_input, principal) =>\n principal.seat === 'view'\n ? { variant: 'view', state: readable(serving.settings.state) }\n : {\n variant: 'settings',\n state: readable(serving.settings.state),\n ...served(() => serving.settings, SETTINGS_METHODS),\n }\n );\n host.serve(AgentRemoteService, (_input, principal) => {\n if (principal.seat !== 'view')\n return { variant: 'remote', ...served(() => serving.remote, REMOTE_METHODS) };\n const scoped = viewRemote(serving.remote);\n return { variant: 'watch', ...served(() => scoped, REMOTE_READS) };\n });\n host.serve(AgentPagesService, (_input, principal, self) => {\n if (principal.seat !== 'owner') return { variant: 'none' };\n const mine = self.resource('mine', {\n version: 1,\n open: () => ownedPages(serving.pages),\n close: (owned) => owned.release(),\n });\n return { variant: 'pages', ...served(() => mine.value, PAGE_METHODS) };\n });\n host.serve(\n AgentGuestService,\n openToGuests((_input: unknown, principal: AgentPrincipal, self: Self<AgentPrincipal>) => {\n if (principal.seat !== 'guest') return { variant: 'none' };\n const opening = serving.guest?.(principal.guest);\n if (!opening) throw forbidden(CLOSED_TO_GUESTS);\n const seat = self.resource('seat', {\n version: 1,\n open: () => opening,\n close: (made) => made.dispose(),\n });\n const later = (name: 'transcript' | 'seat' | 'proposals') =>\n lazily(() => seat.value.value[name] as ReplicatedState<unknown>);\n return {\n variant: 'guest',\n transcript: later('transcript'),\n seat: later('seat'),\n proposals: later('proposals'),\n ...served(() => seat.value.value, GUEST_METHODS),\n };\n })\n );\n host.serve(AgentSudoService, (_input, principal, self) => {\n const sudo = serving.sudo;\n if (principal.seat !== 'owner' || !sudo) return { variant: 'none' };\n const mine = self.resource('mine', {\n version: 1,\n open: () => ownedSudo(sudo),\n close: (owned) => owned.release(),\n });\n return {\n variant: 'sudo',\n approvals: readable(sudo.approvals),\n ...served(() => mine.value, SUDO_METHODS),\n };\n });\n return host;\n}\n"],
4
+ "sourcesContent": ["import {\n type Context,\n createHost,\n forbidden,\n type Host,\n type Readable,\n type ReplicatedState,\n replicatedState,\n type Self,\n} from '@earendil-works/chord';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport type { ConversationView } from '@earendil-works/pi-durable';\nimport type { Agent } from './agent.ts';\nimport {\n type ApprovalJudge,\n type Approvals,\n attachApprovals,\n type Decider,\n} from './approvals/index.ts';\nimport { type ApprovalPolicies, alwaysFor, attachApprovalPolicy } from './approvals/policy.ts';\nimport { type AgentSudo, createSudo, ownedSudo } from './approvals/sudo.ts';\nimport type { ChangesView } from './changes/git.ts';\nimport type { ChangesRuntime } from './changes/index.ts';\nimport { createAgentControl, type HostLicks, type HostScoops } from './control.ts';\nimport { trackDeliveries } from './deliveries.ts';\nimport type { FreezerRuntime, FrozenCone } from './freezer/index.ts';\nimport { IDLE, type ImportProgress } from './freezer/v6.ts';\nimport { createGuests, type Guests } from './guests/index.ts';\nimport type { ScoopReviewHost } from './guests/scoop.ts';\nimport { admitGuest, guestOpener } from './guests/serve.ts';\nimport type { UserClients } from './kernel/users.ts';\nimport type { McpRuntime } from './mcp/index.ts';\nimport type { MemoryEntry, MemoryScope } from './memory/format.ts';\nimport type { MemoryRuntime } from './memory/index.ts';\nimport { createMonitor, type MonitorOptions } from './monitor/index.ts';\nimport { viewRemote } from './readonly.ts';\nimport { type AgentRemote, createRemote } from './remote/host.ts';\nimport { createPages, ownedPages, type Pages } from './remote/pages.ts';\nimport type { SecretListing } from './secrets/names.ts';\nimport type { VaultState } from './secrets/vault.ts';\nimport {\n AgentControl,\n type AgentGuest,\n AgentGuestService,\n AgentPagesService,\n AgentRemoteService,\n AgentSettings,\n AgentSudoService,\n AgentTranscript,\n CONTROL_METHODS,\n CONTROL_READS,\n type Command,\n GUEST_METHODS,\n OWNER_DEVICE_METHODS,\n PAGE_METHODS,\n type PrivateRevision,\n REMOTE_METHODS,\n REMOTE_READS,\n SETTINGS_METHODS,\n SUDO_METHODS,\n} from './services.ts';\nimport {\n AGENT_HOST,\n type AgentPrincipal,\n type GuestPrincipal,\n principalKey,\n readable,\n readablesOf,\n SELF,\n type Seat,\n served,\n} from './serving.ts';\nimport type { SkillsRuntime } from './skills/index.ts';\nimport type { SprinklesRuntime } from './sprinkles/index.ts';\nimport type { Sprinkle } from './sprinkles/kind.ts';\nimport { slimmer, type Thumbnail, thumbnail } from './thumbs.ts';\nimport { type LabelsRuntime, visibleLabels, watchLabel } from './titles/labels.ts';\nimport type { Uploads } from './uploads.ts';\nimport { agentViews } from './views.ts';\nimport { type PortEndpoint, PortLink } from './wire.ts';\n\nexport interface ConnectOptions {\n readOnly?: boolean;\n pages?: boolean;\n guest?: { id: string; label: string };\n device?: string;\n}\n\nexport interface AgentHost {\n readonly serverId: string;\n readonly agent: Agent;\n readonly users?: UserClients;\n readonly pages: Pages;\n readonly guests: Guests;\n readonly approvals: Approvals;\n connect(endpoint: PortEndpoint, options?: ConnectOptions): void;\n close(): Promise<void>;\n}\n\nconst noSettings: AgentSettings = {\n state: replicatedState({ models: [], accounts: [] }),\n connect: async () => {\n throw new Error('this agent has no accounts');\n },\n disconnect: async () => {},\n remove: async () => {},\n serverUrl: async () => null,\n signIn: async () => null,\n dismissMoved: async () => {},\n};\n\nasync function follow(\n agent: Agent,\n context: Context,\n slim: (view: ConversationView) => ConversationView\n) {\n let attached = await agent.cone().viewState(context);\n const state = replicatedState<ConversationView>(slim(attached.value));\n const mirror = (value: ConversationView, from: Context) => state.replace(from, slim(value));\n let off = attached.subscribe(mirror);\n const stop = agent.onCone(async (conversation) => {\n const next = await conversation.viewState(context);\n off();\n attached.dispose();\n attached = next;\n state.replace(context, slim(next.value));\n off = next.subscribe(mirror);\n });\n return {\n state,\n refresh: () => state.replace(context, slim(attached.value)),\n dispose() {\n stop();\n off();\n attached.dispose();\n },\n };\n}\n\nexport interface HostSecrets {\n vault: ReplicatedState<VaultState>;\n list(): SecretListing;\n close?(): void;\n}\n\nconst idleVault = replicatedState<VaultState>({\n port: 'none',\n needed: false,\n version: 0,\n locked: 'no',\n held: 0,\n});\n\nfunction gatedControls(\n control: AgentControl,\n shown: (entry: number) => boolean,\n views: () => readonly ConversationView[],\n labels: ReplicatedState<Record<string, string>>\n) {\n const visible = (entry: number) =>\n views().some((view) => view.entries.some((item) => item.id === entry));\n const gated =\n (allowed: (entry: number) => boolean): AgentControl['image'] =>\n async (ref, using) =>\n allowed(ref.entry) ? control.image(ref, using) : { data: null, mimeType: null };\n const trusted: AgentControl = { ...control, image: gated(shown) };\n const viewed: AgentControl = { ...control, image: gated(visible), toolLabel: watchLabel(labels) };\n return { trusted, viewed };\n}\n\nasync function approving(\n agent: Agent,\n policy: ApprovalPolicies,\n options: { judge?: ApprovalJudge; intent?: ScoopReviewHost['intent']; approvalMs?: number },\n context: Context\n): Promise<Approvals> {\n const approvals = await attachApprovals(\n {\n harness: agent.harness,\n guestAlways: alwaysFor(policy),\n sudoApprover: () => policy.current().sudo.approver,\n scoops: agent.scoopReviews,\n ...(options.judge ? { judge: options.judge } : {}),\n ...(options.approvalMs ? { ttlMs: options.approvalMs } : {}),\n },\n context\n );\n agent.scoopReviews.bind({\n approvals,\n agents: agent.agents,\n harness: agent.harness,\n ...(options.intent ? { intent: options.intent } : {}),\n });\n return approvals;\n}\n\nasync function guestsOf(\n agent: Agent,\n approvals: Approvals,\n policy: ApprovalPolicies,\n context: Context\n): Promise<Guests> {\n const guests = await createGuests({\n harness: agent.harness,\n agents: agent.agents,\n approvals,\n policy,\n reviews: agent.reviews,\n context,\n });\n agent.gate.bind({ approvals, policy: () => guests.policy() });\n return guests;\n}\n\nconst noRevision = () => replicatedState<PrivateRevision>({ revision: 0 });\n\nexport async function hostAgent(\n agent: Agent,\n options: {\n serverId?: string;\n context?: Context;\n settings?: AgentSettings;\n licks?: HostLicks;\n scoops?: HostScoops;\n skills?: SkillsRuntime;\n sprinkles?: SprinklesRuntime;\n memory?: MemoryRuntime;\n freezer?: FreezerRuntime;\n changes?: ChangesRuntime;\n mcp?: Pick<McpRuntime, 'close'>;\n users?: UserClients;\n monitor?: Pick<MonitorOptions, 'kernel' | 'groups' | 'users'>;\n pages?: Pages;\n secrets?: HostSecrets;\n uploads?: Uploads;\n thumbnail?: Thumbnail;\n labels?: LabelsRuntime;\n approvals?: Approvals;\n judge?: ApprovalJudge;\n intent?: ScoopReviewHost['intent'];\n approvalMs?: number;\n } = {}\n): Promise<AgentHost> {\n const context = options.context ?? BACKGROUND_CONTEXT;\n const serverId = options.serverId ?? crypto.randomUUID();\n let refresh = () => {};\n const slims = slimmer(options.thumbnail ?? thumbnail, () => refresh());\n const transcript = await follow(agent, context, slims.slim);\n const { state } = transcript;\n const mounted = await agentViews(agent, context, slims.slim);\n refresh = () => {\n transcript.refresh();\n mounted.refresh();\n };\n const touch = () => options.freezer?.touch();\n const untouch = [state.subscribe(touch), mounted.views.subscribe(touch)];\n const pages = options.pages ?? createPages();\n const deliveries = trackDeliveries(agent.harness, state, context);\n const policy = await attachApprovalPolicy(agent.harness, context);\n const approvals = options.approvals ?? (await approving(agent, policy, options, context));\n const monitor = createMonitor({\n ...options.monitor,\n harness: agent.harness,\n agents: agent.agents,\n views: mounted.views,\n context,\n });\n const guests = await guestsOf(agent, approvals, policy, context);\n const control: AgentControl = {\n ...createAgentControl(\n agent.harness,\n agent,\n deliveries,\n options.licks,\n options.scoops,\n options.skills,\n options.sprinkles,\n options.memory,\n options.freezer,\n options.changes,\n monitor,\n options.secrets?.list,\n options.uploads,\n options.labels,\n guests\n ),\n approvalPolicySet: (change, using) => policy.set(change, using),\n };\n const remote = createRemote({\n harness: agent.harness,\n send: (request, using) => control.send(request, using),\n vault: options.secrets?.vault ?? idleVault,\n async conversation(agentId, using) {\n if (!agentId) return agent.cone();\n const found = await agent.agents.conversation(agentId, using);\n if (!found) throw new Error(`There is no agent ${agentId}.`);\n return found;\n },\n });\n const shownViews = () => [state.value, ...Object.values(mounted.views.value)];\n const kept = options.labels?.labels ?? replicatedState<Record<string, string>>({});\n const labels = visibleLabels(kept, [state, mounted.views], shownViews);\n const transcriptService = {\n state,\n deliveries: deliveries.state,\n agents: mounted.agents,\n views: mounted.views,\n commands: options.skills?.commands ?? replicatedState<Command[]>([]),\n privateRevision: options.skills?.privateRevision ?? noRevision(),\n sprinkles: options.sprinkles?.sprinkles ?? replicatedState<Sprinkle[]>([]),\n memories: options.memory?.memories ?? replicatedState<MemoryEntry[]>([]),\n memoryScopes: options.memory?.scopes ?? replicatedState<MemoryScope[]>([]),\n frozen: options.freezer?.frozen ?? replicatedState<FrozenCone[]>([]),\n importing: options.freezer?.importing ?? replicatedState<ImportProgress>(IDLE),\n changes:\n options.changes?.view ?? replicatedState<ChangesView>({ unavailable: null, changes: [] }),\n monitor: monitor.state,\n vault: options.secrets?.vault ?? idleVault,\n labels: labels.state,\n guests: guests.seats,\n approvals: approvals.state,\n approvalPolicy: policy.state,\n };\n const sudo = createSudo({\n approvals,\n agents: () => agent.agents.state(),\n ...(options.licks ? { licks: options.licks.licks } : {}),\n });\n const settings = options.settings ?? noSettings;\n const gates = gatedControls(control, slims.shown, shownViews, transcriptService.labels);\n const { trusted, viewed } = gates;\n const host = serveAgent({\n trusted,\n viewed,\n transcript: transcriptService,\n viewMonitor: monitor.viewState,\n sudo,\n settings,\n remote,\n pages,\n guest: guestOpener(guests, agent.agents, mounted.views, context),\n decide: (by) => (id, decision, reason, using) =>\n approvals.decide(id, decision, reason, by, using),\n });\n const offGuests = guests.onEnd((id) => {\n void host.invalidatePrincipal(\n (principal) => principal.seat === 'guest' && principal.guest.id === id\n );\n });\n let links = 0;\n return {\n serverId,\n agent,\n pages,\n guests,\n approvals,\n ...(options.users ? { users: options.users } : {}),\n connect(endpoint, connectOptions) {\n if (connectOptions?.guest) {\n links += 1;\n admitGuest(guests, endpoint, connectOptions.guest, serverId, links, host);\n return;\n }\n const seat: Exclude<Seat, 'guest'> = connectOptions?.readOnly\n ? 'view'\n : connectOptions?.pages === false\n ? 'control'\n : 'owner';\n links += 1;\n const link = new PortLink(endpoint);\n link.greet(serverId);\n const device = connectOptions?.device ? { device: connectOptions.device } : {};\n host.accept(link, {\n principal: { seat, link: links, ...device } satisfies AgentPrincipal,\n });\n },\n async close() {\n offGuests();\n await host.close();\n guests.dispose();\n await pages.dispose();\n await monitor.dispose();\n await remote.dispose();\n deliveries.dispose();\n sudo.close();\n approvals.close();\n transcript.dispose();\n mounted.dispose();\n labels.dispose();\n slims.dispose();\n for (const off of untouch) off();\n options.freezer?.close();\n options.secrets?.close?.();\n await options.changes?.close(context);\n await options.memory?.close(context);\n await options.sprinkles?.close(context);\n await options.skills?.close(context);\n await options.mcp?.close(context);\n await options.scoops?.runtime.close(context);\n await options.licks?.sources.close(context);\n await agent.close();\n await options.users?.close();\n },\n };\n}\n\ninterface Served {\n trusted: AgentControl;\n viewed: AgentControl;\n transcript: Record<keyof AgentTranscript, ReplicatedState<unknown>>;\n viewMonitor: ReplicatedState<unknown>;\n sudo?: AgentSudo;\n settings: AgentSettings;\n remote: AgentRemote;\n pages: Pages;\n guest?: (principal: GuestPrincipal) => { value: AgentGuest; dispose(): void } | undefined;\n decide: (by: Decider) => AgentControl['decide'];\n}\n\nexport const CLOSED_TO_GUESTS = 'a guest seat has only its transcript and propose';\n\nconst opened = new WeakSet<object>();\n\nexport const openToGuests = <F extends object>(factory: F): F => {\n opened.add(factory);\n return factory;\n};\n\nexport function closedToGuests(host: Host<AgentPrincipal>): Host<AgentPrincipal> {\n const serve = ((type: never, factory: (...args: unknown[]) => unknown, options?: never) =>\n host.serve(\n type,\n (opened.has(factory)\n ? factory\n : (input: unknown, principal: AgentPrincipal, self: unknown) => {\n if (principal.seat === 'guest') throw forbidden(CLOSED_TO_GUESTS);\n return factory(input, principal, self);\n }) as never,\n options\n )) as Host<AgentPrincipal>['serve'];\n return new Proxy(host, {\n get(target, key) {\n if (key === 'serve') return serve;\n const value = Reflect.get(target, key, target) as unknown;\n return typeof value === 'function' ? value.bind(target) : value;\n },\n });\n}\n\nexport function lazily<T>(state: () => ReplicatedState<T>): Readable<T> {\n return {\n get value() {\n return readable(state()).value;\n },\n subscribe: (listener) => readable(state()).subscribe(listener),\n events: (listener) => readable(state()).events(listener),\n };\n}\n\nconst seated = (serving: Served, principal: AgentPrincipal): AgentControl => ({\n ...(principal.seat === 'view' ? serving.viewed : serving.trusted),\n decide: serving.decide({\n by: principal.seat === 'owner' ? 'owner' : 'follower',\n ...('device' in principal && principal.device ? { device: principal.device } : {}),\n }),\n});\n\nconst VIEW_METHODS = [...CONTROL_READS, ...OWNER_DEVICE_METHODS];\n\nexport function serveAgent(serving: Served): Host<AgentPrincipal> {\n const host = closedToGuests(\n createHost<AgentPrincipal>({ name: AGENT_HOST, self: SELF, principalKey })\n );\n const viewing = { ...serving.transcript, monitor: serving.viewMonitor };\n host.serve(AgentControl, (_input, principal) => {\n const control = seated(serving, principal);\n return principal.seat === 'view'\n ? { variant: 'view', ...served(() => control, VIEW_METHODS) }\n : { variant: 'control', ...served(() => control, CONTROL_METHODS) };\n });\n host.serve(AgentTranscript, (_input, principal) => ({\n variant: 'transcript',\n ...readablesOf(principal.seat === 'view' ? viewing : serving.transcript),\n }));\n host.serve(AgentSettings, (_input, principal) =>\n principal.seat === 'view'\n ? { variant: 'view', state: readable(serving.settings.state) }\n : {\n variant: 'settings',\n state: readable(serving.settings.state),\n ...served(() => serving.settings, SETTINGS_METHODS),\n }\n );\n host.serve(AgentRemoteService, (_input, principal) => {\n if (principal.seat !== 'view')\n return { variant: 'remote', ...served(() => serving.remote, REMOTE_METHODS) };\n const scoped = viewRemote(serving.remote);\n return { variant: 'watch', ...served(() => scoped, REMOTE_READS) };\n });\n host.serve(AgentPagesService, (_input, principal, self) => {\n if (principal.seat !== 'owner') return { variant: 'none' };\n const mine = self.resource('mine', {\n version: 1,\n open: () => ownedPages(serving.pages),\n close: (owned) => owned.release(),\n });\n return { variant: 'pages', ...served(() => mine.value, PAGE_METHODS) };\n });\n host.serve(\n AgentGuestService,\n openToGuests((_input: unknown, principal: AgentPrincipal, self: Self<AgentPrincipal>) => {\n if (principal.seat !== 'guest') return { variant: 'none' };\n const opening = serving.guest?.(principal.guest);\n if (!opening) throw forbidden(CLOSED_TO_GUESTS);\n const seat = self.resource('seat', {\n version: 1,\n open: () => opening,\n close: (made) => made.dispose(),\n });\n const later = (name: 'transcript' | 'seat' | 'proposals') =>\n lazily(() => seat.value.value[name] as ReplicatedState<unknown>);\n return {\n variant: 'guest',\n transcript: later('transcript'),\n seat: later('seat'),\n proposals: later('proposals'),\n ...served(() => seat.value.value, GUEST_METHODS),\n };\n })\n );\n host.serve(AgentSudoService, (_input, principal, self) => {\n const sudo = serving.sudo;\n if (principal.seat !== 'owner' || !sudo) return { variant: 'none' };\n const mine = self.resource('mine', {\n version: 1,\n open: () => ownedSudo(sudo),\n close: (owned) => owned.release(),\n });\n return {\n variant: 'sudo',\n approvals: readable(sudo.approvals),\n ...served(() => mine.value, SUDO_METHODS),\n };\n });\n return host;\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -42,6 +42,7 @@ class SliccKernelEnv {
42
42
  #poll;
43
43
  #activity;
44
44
  #groups;
45
+ #jobs;
45
46
  #owner;
46
47
  #exports;
47
48
  #hooks;
@@ -53,6 +54,7 @@ class SliccKernelEnv {
53
54
  this.#poll = options.watch ?? {};
54
55
  this.#activity = options.activity;
55
56
  this.#groups = options.groups;
57
+ this.#jobs = options.jobs;
56
58
  this.#owner = options.owner;
57
59
  this.#exports = options.exports;
58
60
  this.#scrub = options.scrub;
@@ -255,27 +257,27 @@ class SliccKernelEnv {
255
257
  exec(command, options, context) {
256
258
  const ended = this.#activity?.began();
257
259
  const line = typeof command === "string" ? prefixed(command, this.#exports) : command;
260
+ const jobs = this.#jobs;
261
+ const client = this.#client;
262
+ let recorded;
263
+ const tracked = jobs && typeof command === "string" ? (process) => {
264
+ recorded = jobs.started(client, command, process);
265
+ } : void 0;
258
266
  const groups = this.#groups;
259
267
  const owner = this.#owner;
260
- if (!groups || owner === void 0)
261
- return execute(
262
- this.#client,
263
- this.cwd,
264
- line,
265
- options,
266
- context,
267
- void 0,
268
- void 0,
269
- this.#hooks
270
- ).finally(ended);
271
- return this.#grouped(groups, owner, line, options, context).finally(ended);
268
+ const run = !groups || owner === void 0 ? execute(client, this.cwd, line, options, context, tracked, void 0, this.#hooks) : this.#grouped(groups, owner, line, options, context, tracked);
269
+ return run.then(async (result) => {
270
+ await recorded;
271
+ return result;
272
+ }).finally(ended);
272
273
  }
273
- async #grouped(groups, owner, line, options, context) {
274
+ async #grouped(groups, owner, line, options, context, tracked) {
274
275
  for (let wait = groups.pending(owner); wait; wait = groups.pending(owner)) await wait;
275
276
  const pgid = groups.shared(owner);
276
277
  const release = pgid === void 0 && groups.joins() ? groups.reserve(owner) : () => void 0;
277
278
  const spawned = (process) => {
278
279
  groups.track(owner, process);
280
+ tracked?.(process);
279
281
  release();
280
282
  };
281
283
  const join = groups.joins() ? {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/kernel/env.ts"],
4
- "sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport {\n type BinaryReader,\n type DirReader,\n type ExecutionEnv,\n type ExecutionError,\n err,\n FileError,\n type FileInfo,\n type FileWatcher,\n ok,\n type Result,\n type ShellExecOptions,\n type ShellExecResult,\n type TextLineReader,\n type WatchChange,\n type WatchTarget,\n} from '@earendil-works/pi-durable/env';\nimport { mimeOf } from '../images/index.ts';\nimport type { Scrub } from '../secrets/scrub.ts';\nimport type { Activity } from './activity.ts';\nimport type { KernelClient, KernelProcess } from './client.ts';\nimport { type ExecHooks, execute } from './exec.ts';\nimport { attempt, info, KernelDirReader, SnapshotLines, SnapshotReader } from './files.ts';\nimport type { ProcessGroups } from './groups.ts';\nimport { dirname, normalize, resolve } from './paths.ts';\nimport { nativeWatch, type PollOptions, pollWatch, RESCAN_MS } from './watch.ts';\n\nexport interface KernelEnvOptions {\n cwd: string;\n id?: string;\n watch?: PollOptions;\n activity?: Activity;\n groups?: ProcessGroups;\n owner?: number;\n exports?: Readonly<Record<string, string>>;\n scrub?: Scrub;\n spawn?: (argv: readonly string[]) => readonly string[];\n}\n\nexport const STREAMED_READ = 8 * 1024 * 1024;\nexport const READ_CHUNK = 1024 * 1024;\n\nexport async function scrubbedFile(scrub: Scrub, bytes: Uint8Array): Promise<Uint8Array> {\n if (mimeOf(bytes)) return bytes;\n if (bytes.length <= STREAMED_READ) return (await scrub.texts([bytes]))[0] as Uint8Array;\n const stream = scrub.stream();\n const parts: Uint8Array[] = [];\n for (let at = 0; at < bytes.length; at += READ_CHUNK)\n parts.push(await stream.push(bytes.subarray(at, at + READ_CHUNK)));\n parts.push(await stream.end());\n const all = new Uint8Array(parts.reduce((sum, part) => sum + part.length, 0));\n let offset = 0;\n for (const part of parts) {\n all.set(part, offset);\n offset += part.length;\n }\n return all;\n}\n\nexport function quote(value: string): string {\n return `'${value.replace(/'/g, \"'\\\\''\")}'`;\n}\n\nexport function prefixed(\n command: string,\n exports: Readonly<Record<string, string>> | undefined\n): string {\n const names = Object.keys(exports ?? {}).filter((name) => /^[A-Z_][A-Z0-9_]*$/.test(name));\n if (!names.length) return command;\n return `export ${names.map((name) => `${name}=${quote((exports as Record<string, string>)[name] as string)}`).join(' ')}; ${command}`;\n}\n\ntype FileResult<T> = Promise<Result<T, FileError>>;\n\nexport class SliccKernelEnv implements ExecutionEnv {\n readonly id: string;\n cwd: string;\n readonly #client: KernelClient;\n readonly #watchers = new Set<FileWatcher>();\n readonly #poll: PollOptions;\n readonly #activity: Activity | undefined;\n readonly #groups: ProcessGroups | undefined;\n readonly #owner: number | undefined;\n readonly #exports: Readonly<Record<string, string>> | undefined;\n readonly #hooks: ExecHooks;\n readonly #scrub: Scrub | undefined;\n\n constructor(client: KernelClient, options: KernelEnvOptions) {\n this.#client = client;\n this.cwd = options.cwd;\n this.id = options.id ?? 'slicc-kernel';\n this.#poll = options.watch ?? {};\n this.#activity = options.activity;\n this.#groups = options.groups;\n this.#owner = options.owner;\n this.#exports = options.exports;\n this.#scrub = options.scrub;\n const scrub = options.scrub;\n this.#hooks = {\n ...(scrub ? { scrub: () => scrub.stream() } : {}),\n ...(options.spawn ? { spawn: options.spawn } : {}),\n };\n }\n\n #path(path: string): string {\n return resolve(this.cwd, path);\n }\n\n #written(path: string): string {\n const resolved = this.#path(path);\n this.#activity?.wrote(resolved);\n return resolved;\n }\n\n get #fs() {\n return this.#client.fs;\n }\n\n async absolutePath(path: string): FileResult<string> {\n return ok(this.#path(path));\n }\n\n async joinPath(parts: string[]): FileResult<string> {\n return ok(normalize(parts.join('/')));\n }\n\n readTextFile(path: string, context: Context): FileResult<string> {\n const resolved = this.#path(path);\n return attempt(context, resolved, () => this.#fs.readText(resolved));\n }\n\n readBinaryFile(path: string, context: Context): FileResult<Uint8Array> {\n const resolved = this.#path(path);\n return attempt(context, resolved, () => this.#fs.readFile(resolved));\n }\n\n async openTextLineReader(path: string, context: Context): FileResult<TextLineReader> {\n const text = await this.readTextFile(path, context);\n return text.ok ? ok(new SnapshotLines(text.value)) : text;\n }\n\n async readTextLines(\n path: string,\n options: { maxLines?: number } | undefined,\n context: Context\n ): FileResult<string[]> {\n if (options?.maxLines !== undefined && options.maxLines <= 0) return ok([]);\n const text = await this.readTextFile(path, context);\n if (!text.ok) return text;\n const reader = new SnapshotLines(text.value);\n const lines: string[] = [];\n for (\n let line = await reader.readLine();\n line.ok && line.value;\n line = await reader.readLine()\n ) {\n if (options?.maxLines !== undefined && lines.length >= options.maxLines) break;\n lines.push(line.value.text);\n }\n return ok(lines);\n }\n\n openBinaryReader(\n path: string,\n options: { noFollow?: boolean } | undefined,\n context: Context\n ): FileResult<BinaryReader> {\n const resolved = this.#path(path);\n return attempt(context, resolved, async () => {\n if (options?.noFollow && (await this.#fs.lstat(resolved)).isSymbolicLink) {\n throw new FileError('invalid', 'Refusing to follow a symbolic link', resolved);\n }\n const stat = await this.#fs.stat(resolved);\n if (stat.isDirectory) throw new FileError('is_directory', 'Is a directory', resolved);\n if (!stat.isFile) throw new FileError('invalid', 'Not a regular file', resolved);\n const bytes = await this.#fs.readFile(resolved);\n if (!this.#scrub) return new SnapshotReader(bytes, info(resolved, stat));\n const shown = await scrubbedFile(this.#scrub, bytes);\n return new SnapshotReader(shown, { ...info(resolved, stat), size: shown.length });\n });\n }\n\n writeFile(path: string, content: string | Uint8Array, context: Context): FileResult<void> {\n const resolved = this.#written(path);\n return attempt(context, resolved, async () => {\n await this.#fs.mkdir(dirname(resolved));\n await this.#fs.writeFile(resolved, content);\n });\n }\n\n appendFile(path: string, content: string | Uint8Array, context: Context): FileResult<void> {\n const resolved = this.#written(path);\n return attempt(context, resolved, async () => {\n const before = (await this.#fs.exists(resolved))\n ? await this.#fs.readFile(resolved)\n : new Uint8Array();\n const added = typeof content === 'string' ? new TextEncoder().encode(content) : content;\n const all = new Uint8Array(before.length + added.length);\n all.set(before);\n all.set(added, before.length);\n await this.#fs.mkdir(dirname(resolved));\n await this.#fs.writeFile(resolved, all);\n });\n }\n\n truncateFile(path: string, size: number, context: Context): FileResult<void> {\n const resolved = this.#written(path);\n if (!Number.isSafeInteger(size) || size < 0) {\n return Promise.resolve(\n err(new FileError('invalid', 'File size must be a non-negative integer', resolved))\n );\n }\n return attempt(context, resolved, async () => {\n const before = await this.#fs.readFile(resolved);\n const after = new Uint8Array(size);\n after.set(before.subarray(0, size));\n await this.#fs.writeFile(resolved, after);\n });\n }\n\n flushFile(path: string, context: Context): FileResult<void> {\n const resolved = this.#path(path);\n return attempt(context, resolved, async () => {\n if ((await this.#fs.stat(resolved)).isDirectory)\n throw Object.assign(new Error('Is a directory'), { code: 'EISDIR' });\n });\n }\n\n renameFile(sourcePath: string, destinationPath: string, context: Context): FileResult<void> {\n const source = this.#written(sourcePath);\n return attempt(context, source, () => this.#fs.rename(source, this.#written(destinationPath)));\n }\n\n fileInfo(path: string, context: Context): FileResult<FileInfo> {\n const resolved = this.#path(path);\n return attempt(context, resolved, async () => info(resolved, await this.#fs.lstat(resolved)));\n }\n\n async listDir(path: string, context: Context): FileResult<FileInfo[]> {\n const reader = await this.openDirReader(path, context);\n if (!reader.ok) return reader;\n return ok(await (reader.value as KernelDirReader).rest());\n }\n\n openDirReader(path: string, context: Context): FileResult<DirReader> {\n const resolved = this.#path(path);\n return attempt(context, resolved, async () => {\n if (!(await this.#fs.stat(resolved)).isDirectory) {\n throw Object.assign(new Error('Not a directory'), { code: 'ENOTDIR' });\n }\n return new KernelDirReader(this.#fs, resolved, await this.#fs.readdir(resolved));\n });\n }\n\n watch(\n targets: readonly WatchTarget[],\n onChange: (change: WatchChange) => void,\n context: Context\n ): FileResult<FileWatcher> {\n return attempt(context, this.cwd, async () => {\n let absorb: (paths: readonly string[]) => void = () => undefined;\n const reported = (change: WatchChange) => {\n if ('paths' in change) absorb(change.paths);\n onChange(change);\n };\n const native =\n this.#poll.mode === 'polling'\n ? undefined\n : await nativeWatch(this.#fs, this.cwd, targets, reported);\n const intervalMs = native ? (this.#poll.rescanMs ?? RESCAN_MS) : this.#poll.intervalMs;\n const polled = await pollWatch(this.#fs, this.cwd, targets, onChange, {\n ...(intervalMs === undefined ? {} : { intervalMs }),\n });\n absorb = (paths) => polled.absorb(paths);\n const watcher: FileWatcher = native\n ? {\n mode: 'native',\n async close(closing: Context) {\n await native.close(closing);\n await polled.close(closing);\n },\n }\n : polled;\n this.#watchers.add(watcher);\n return {\n mode: watcher.mode,\n close: async (closing: Context) => {\n this.#watchers.delete(watcher);\n await watcher.close(closing);\n },\n };\n });\n }\n\n canonicalPath(path: string, context: Context): FileResult<string> {\n const resolved = this.#path(path);\n return attempt(context, resolved, () => this.#fs.realpath(resolved));\n }\n\n async exists(path: string, context: Context): FileResult<boolean> {\n const found = await this.fileInfo(path, context);\n if (found.ok) return ok(true);\n return found.error.code === 'not_found' ? ok(false) : found;\n }\n\n createDir(\n path: string,\n _options: { recursive?: boolean } | undefined,\n context: Context\n ): FileResult<void> {\n const resolved = this.#written(path);\n return attempt(context, resolved, () => this.#fs.mkdir(resolved));\n }\n\n remove(\n path: string,\n options: { recursive?: boolean; force?: boolean } | undefined,\n context: Context\n ): FileResult<void> {\n const resolved = this.#written(path);\n return attempt(context, resolved, async () => {\n if (!options?.recursive) {\n const stat = await this.#fs.lstat(resolved).catch((error: unknown) => {\n if (options?.force) return undefined;\n throw error;\n });\n if (stat?.isDirectory) throw Object.assign(new Error('Is a directory'), { code: 'EISDIR' });\n }\n await this.#fs.rm(resolved, { force: options?.force ?? false });\n });\n }\n\n async createTempDir(prefix: string | undefined, context: Context): FileResult<string> {\n const path = `/tmp/${prefix ?? 'tmp-'}${crypto.randomUUID()}`;\n return attempt(context, path, async () => {\n await this.#fs.mkdir(path);\n return path;\n });\n }\n\n async createTempFile(\n options: { prefix?: string; suffix?: string } | undefined,\n context: Context\n ): FileResult<string> {\n const dir = await this.createTempDir('tmp-', context);\n if (!dir.ok) return dir;\n const path = `${dir.value}/${options?.prefix ?? ''}${crypto.randomUUID()}${options?.suffix ?? ''}`;\n return attempt(context, path, async () => {\n await this.#fs.writeFile(path, '');\n return path;\n });\n }\n\n exec(\n command: string | readonly string[],\n options: ShellExecOptions | undefined,\n context: Context\n ): Promise<Result<ShellExecResult, ExecutionError>> {\n const ended = this.#activity?.began();\n const line = typeof command === 'string' ? prefixed(command, this.#exports) : command;\n const groups = this.#groups;\n const owner = this.#owner;\n if (!groups || owner === undefined)\n return execute(\n this.#client,\n this.cwd,\n line,\n options,\n context,\n undefined,\n undefined,\n this.#hooks\n ).finally(ended);\n return this.#grouped(groups, owner, line, options, context).finally(ended);\n }\n\n async #grouped(\n groups: ProcessGroups,\n owner: number,\n line: string | readonly string[],\n options: ShellExecOptions | undefined,\n context: Context\n ): Promise<Result<ShellExecResult, ExecutionError>> {\n for (let wait = groups.pending(owner); wait; wait = groups.pending(owner)) await wait;\n const pgid = groups.shared(owner);\n const release = pgid === undefined && groups.joins() ? groups.reserve(owner) : () => undefined;\n const spawned = (process: KernelProcess) => {\n groups.track(owner, process);\n release();\n };\n const join = groups.joins()\n ? {\n ...(pgid === undefined ? {} : { pgid }),\n refused: (code: string) => groups.refused(owner, code),\n }\n : undefined;\n return execute(\n this.#client,\n this.cwd,\n line,\n options,\n context,\n spawned,\n join,\n this.#hooks\n ).finally(release);\n }\n\n async cleanup(context: Context): Promise<void> {\n for (const watcher of [...this.#watchers]) await watcher.close(context);\n this.#watchers.clear();\n }\n}\n\nexport const HOME = '/home';\n\nexport type EnvTargetLike = { cwd?: string; conversationId?: number; read?: unknown };\n\nexport type EnvRoute = { client: KernelClient; home: string; cwd?: string };\n\nexport function kernelEnvironment(\n client: KernelClient,\n options: Omit<KernelEnvOptions, 'cwd'> & {\n identify?: (target: EnvTargetLike, context: Context) => Promise<Record<string, string>>;\n route?: (target: EnvTargetLike) => Promise<EnvRoute | undefined>;\n } = {}\n): (target: EnvTargetLike, context?: Context) => SliccKernelEnv | Promise<SliccKernelEnv> {\n const { identify, route, ...rest } = options;\n const make = (target: EnvTargetLike, exports?: Record<string, string>, routed?: EnvRoute) =>\n new SliccKernelEnv(routed?.client ?? client, {\n ...rest,\n cwd: routed?.cwd ?? target.cwd ?? routed?.home ?? HOME,\n ...(target.conversationId === undefined ? {} : { owner: target.conversationId }),\n ...(exports ? { exports } : {}),\n });\n if (route)\n return async (target, context) => {\n const routed = await route(target);\n const exports = identify && context ? await identify(target, context) : undefined;\n return make(target, exports, routed);\n };\n return (target, context) =>\n identify && context\n ? identify(target, context).then((exports) => make(target, exports))\n : make(target);\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport {\n type BinaryReader,\n type DirReader,\n type ExecutionEnv,\n type ExecutionError,\n err,\n FileError,\n type FileInfo,\n type FileWatcher,\n ok,\n type Result,\n type ShellExecOptions,\n type ShellExecResult,\n type TextLineReader,\n type WatchChange,\n type WatchTarget,\n} from '@earendil-works/pi-durable/env';\nimport { mimeOf } from '../images/index.ts';\nimport type { Scrub } from '../secrets/scrub.ts';\nimport type { Activity } from './activity.ts';\nimport type { KernelClient, KernelProcess } from './client.ts';\nimport { type ExecHooks, execute } from './exec.ts';\nimport { attempt, info, KernelDirReader, SnapshotLines, SnapshotReader } from './files.ts';\nimport type { ProcessGroups } from './groups.ts';\nimport type { Jobs } from './jobs.ts';\nimport { dirname, normalize, resolve } from './paths.ts';\nimport { nativeWatch, type PollOptions, pollWatch, RESCAN_MS } from './watch.ts';\n\nexport interface KernelEnvOptions {\n cwd: string;\n id?: string;\n watch?: PollOptions;\n activity?: Activity;\n groups?: ProcessGroups;\n jobs?: Jobs;\n owner?: number;\n exports?: Readonly<Record<string, string>>;\n scrub?: Scrub;\n spawn?: (argv: readonly string[]) => readonly string[];\n}\n\nexport const STREAMED_READ = 8 * 1024 * 1024;\nexport const READ_CHUNK = 1024 * 1024;\n\nexport async function scrubbedFile(scrub: Scrub, bytes: Uint8Array): Promise<Uint8Array> {\n if (mimeOf(bytes)) return bytes;\n if (bytes.length <= STREAMED_READ) return (await scrub.texts([bytes]))[0] as Uint8Array;\n const stream = scrub.stream();\n const parts: Uint8Array[] = [];\n for (let at = 0; at < bytes.length; at += READ_CHUNK)\n parts.push(await stream.push(bytes.subarray(at, at + READ_CHUNK)));\n parts.push(await stream.end());\n const all = new Uint8Array(parts.reduce((sum, part) => sum + part.length, 0));\n let offset = 0;\n for (const part of parts) {\n all.set(part, offset);\n offset += part.length;\n }\n return all;\n}\n\nexport function quote(value: string): string {\n return `'${value.replace(/'/g, \"'\\\\''\")}'`;\n}\n\nexport function prefixed(\n command: string,\n exports: Readonly<Record<string, string>> | undefined\n): string {\n const names = Object.keys(exports ?? {}).filter((name) => /^[A-Z_][A-Z0-9_]*$/.test(name));\n if (!names.length) return command;\n return `export ${names.map((name) => `${name}=${quote((exports as Record<string, string>)[name] as string)}`).join(' ')}; ${command}`;\n}\n\ntype FileResult<T> = Promise<Result<T, FileError>>;\n\nexport class SliccKernelEnv implements ExecutionEnv {\n readonly id: string;\n cwd: string;\n readonly #client: KernelClient;\n readonly #watchers = new Set<FileWatcher>();\n readonly #poll: PollOptions;\n readonly #activity: Activity | undefined;\n readonly #groups: ProcessGroups | undefined;\n readonly #jobs: Jobs | undefined;\n readonly #owner: number | undefined;\n readonly #exports: Readonly<Record<string, string>> | undefined;\n readonly #hooks: ExecHooks;\n readonly #scrub: Scrub | undefined;\n\n constructor(client: KernelClient, options: KernelEnvOptions) {\n this.#client = client;\n this.cwd = options.cwd;\n this.id = options.id ?? 'slicc-kernel';\n this.#poll = options.watch ?? {};\n this.#activity = options.activity;\n this.#groups = options.groups;\n this.#jobs = options.jobs;\n this.#owner = options.owner;\n this.#exports = options.exports;\n this.#scrub = options.scrub;\n const scrub = options.scrub;\n this.#hooks = {\n ...(scrub ? { scrub: () => scrub.stream() } : {}),\n ...(options.spawn ? { spawn: options.spawn } : {}),\n };\n }\n\n #path(path: string): string {\n return resolve(this.cwd, path);\n }\n\n #written(path: string): string {\n const resolved = this.#path(path);\n this.#activity?.wrote(resolved);\n return resolved;\n }\n\n get #fs() {\n return this.#client.fs;\n }\n\n async absolutePath(path: string): FileResult<string> {\n return ok(this.#path(path));\n }\n\n async joinPath(parts: string[]): FileResult<string> {\n return ok(normalize(parts.join('/')));\n }\n\n readTextFile(path: string, context: Context): FileResult<string> {\n const resolved = this.#path(path);\n return attempt(context, resolved, () => this.#fs.readText(resolved));\n }\n\n readBinaryFile(path: string, context: Context): FileResult<Uint8Array> {\n const resolved = this.#path(path);\n return attempt(context, resolved, () => this.#fs.readFile(resolved));\n }\n\n async openTextLineReader(path: string, context: Context): FileResult<TextLineReader> {\n const text = await this.readTextFile(path, context);\n return text.ok ? ok(new SnapshotLines(text.value)) : text;\n }\n\n async readTextLines(\n path: string,\n options: { maxLines?: number } | undefined,\n context: Context\n ): FileResult<string[]> {\n if (options?.maxLines !== undefined && options.maxLines <= 0) return ok([]);\n const text = await this.readTextFile(path, context);\n if (!text.ok) return text;\n const reader = new SnapshotLines(text.value);\n const lines: string[] = [];\n for (\n let line = await reader.readLine();\n line.ok && line.value;\n line = await reader.readLine()\n ) {\n if (options?.maxLines !== undefined && lines.length >= options.maxLines) break;\n lines.push(line.value.text);\n }\n return ok(lines);\n }\n\n openBinaryReader(\n path: string,\n options: { noFollow?: boolean } | undefined,\n context: Context\n ): FileResult<BinaryReader> {\n const resolved = this.#path(path);\n return attempt(context, resolved, async () => {\n if (options?.noFollow && (await this.#fs.lstat(resolved)).isSymbolicLink) {\n throw new FileError('invalid', 'Refusing to follow a symbolic link', resolved);\n }\n const stat = await this.#fs.stat(resolved);\n if (stat.isDirectory) throw new FileError('is_directory', 'Is a directory', resolved);\n if (!stat.isFile) throw new FileError('invalid', 'Not a regular file', resolved);\n const bytes = await this.#fs.readFile(resolved);\n if (!this.#scrub) return new SnapshotReader(bytes, info(resolved, stat));\n const shown = await scrubbedFile(this.#scrub, bytes);\n return new SnapshotReader(shown, { ...info(resolved, stat), size: shown.length });\n });\n }\n\n writeFile(path: string, content: string | Uint8Array, context: Context): FileResult<void> {\n const resolved = this.#written(path);\n return attempt(context, resolved, async () => {\n await this.#fs.mkdir(dirname(resolved));\n await this.#fs.writeFile(resolved, content);\n });\n }\n\n appendFile(path: string, content: string | Uint8Array, context: Context): FileResult<void> {\n const resolved = this.#written(path);\n return attempt(context, resolved, async () => {\n const before = (await this.#fs.exists(resolved))\n ? await this.#fs.readFile(resolved)\n : new Uint8Array();\n const added = typeof content === 'string' ? new TextEncoder().encode(content) : content;\n const all = new Uint8Array(before.length + added.length);\n all.set(before);\n all.set(added, before.length);\n await this.#fs.mkdir(dirname(resolved));\n await this.#fs.writeFile(resolved, all);\n });\n }\n\n truncateFile(path: string, size: number, context: Context): FileResult<void> {\n const resolved = this.#written(path);\n if (!Number.isSafeInteger(size) || size < 0) {\n return Promise.resolve(\n err(new FileError('invalid', 'File size must be a non-negative integer', resolved))\n );\n }\n return attempt(context, resolved, async () => {\n const before = await this.#fs.readFile(resolved);\n const after = new Uint8Array(size);\n after.set(before.subarray(0, size));\n await this.#fs.writeFile(resolved, after);\n });\n }\n\n flushFile(path: string, context: Context): FileResult<void> {\n const resolved = this.#path(path);\n return attempt(context, resolved, async () => {\n if ((await this.#fs.stat(resolved)).isDirectory)\n throw Object.assign(new Error('Is a directory'), { code: 'EISDIR' });\n });\n }\n\n renameFile(sourcePath: string, destinationPath: string, context: Context): FileResult<void> {\n const source = this.#written(sourcePath);\n return attempt(context, source, () => this.#fs.rename(source, this.#written(destinationPath)));\n }\n\n fileInfo(path: string, context: Context): FileResult<FileInfo> {\n const resolved = this.#path(path);\n return attempt(context, resolved, async () => info(resolved, await this.#fs.lstat(resolved)));\n }\n\n async listDir(path: string, context: Context): FileResult<FileInfo[]> {\n const reader = await this.openDirReader(path, context);\n if (!reader.ok) return reader;\n return ok(await (reader.value as KernelDirReader).rest());\n }\n\n openDirReader(path: string, context: Context): FileResult<DirReader> {\n const resolved = this.#path(path);\n return attempt(context, resolved, async () => {\n if (!(await this.#fs.stat(resolved)).isDirectory) {\n throw Object.assign(new Error('Not a directory'), { code: 'ENOTDIR' });\n }\n return new KernelDirReader(this.#fs, resolved, await this.#fs.readdir(resolved));\n });\n }\n\n watch(\n targets: readonly WatchTarget[],\n onChange: (change: WatchChange) => void,\n context: Context\n ): FileResult<FileWatcher> {\n return attempt(context, this.cwd, async () => {\n let absorb: (paths: readonly string[]) => void = () => undefined;\n const reported = (change: WatchChange) => {\n if ('paths' in change) absorb(change.paths);\n onChange(change);\n };\n const native =\n this.#poll.mode === 'polling'\n ? undefined\n : await nativeWatch(this.#fs, this.cwd, targets, reported);\n const intervalMs = native ? (this.#poll.rescanMs ?? RESCAN_MS) : this.#poll.intervalMs;\n const polled = await pollWatch(this.#fs, this.cwd, targets, onChange, {\n ...(intervalMs === undefined ? {} : { intervalMs }),\n });\n absorb = (paths) => polled.absorb(paths);\n const watcher: FileWatcher = native\n ? {\n mode: 'native',\n async close(closing: Context) {\n await native.close(closing);\n await polled.close(closing);\n },\n }\n : polled;\n this.#watchers.add(watcher);\n return {\n mode: watcher.mode,\n close: async (closing: Context) => {\n this.#watchers.delete(watcher);\n await watcher.close(closing);\n },\n };\n });\n }\n\n canonicalPath(path: string, context: Context): FileResult<string> {\n const resolved = this.#path(path);\n return attempt(context, resolved, () => this.#fs.realpath(resolved));\n }\n\n async exists(path: string, context: Context): FileResult<boolean> {\n const found = await this.fileInfo(path, context);\n if (found.ok) return ok(true);\n return found.error.code === 'not_found' ? ok(false) : found;\n }\n\n createDir(\n path: string,\n _options: { recursive?: boolean } | undefined,\n context: Context\n ): FileResult<void> {\n const resolved = this.#written(path);\n return attempt(context, resolved, () => this.#fs.mkdir(resolved));\n }\n\n remove(\n path: string,\n options: { recursive?: boolean; force?: boolean } | undefined,\n context: Context\n ): FileResult<void> {\n const resolved = this.#written(path);\n return attempt(context, resolved, async () => {\n if (!options?.recursive) {\n const stat = await this.#fs.lstat(resolved).catch((error: unknown) => {\n if (options?.force) return undefined;\n throw error;\n });\n if (stat?.isDirectory) throw Object.assign(new Error('Is a directory'), { code: 'EISDIR' });\n }\n await this.#fs.rm(resolved, { force: options?.force ?? false });\n });\n }\n\n async createTempDir(prefix: string | undefined, context: Context): FileResult<string> {\n const path = `/tmp/${prefix ?? 'tmp-'}${crypto.randomUUID()}`;\n return attempt(context, path, async () => {\n await this.#fs.mkdir(path);\n return path;\n });\n }\n\n async createTempFile(\n options: { prefix?: string; suffix?: string } | undefined,\n context: Context\n ): FileResult<string> {\n const dir = await this.createTempDir('tmp-', context);\n if (!dir.ok) return dir;\n const path = `${dir.value}/${options?.prefix ?? ''}${crypto.randomUUID()}${options?.suffix ?? ''}`;\n return attempt(context, path, async () => {\n await this.#fs.writeFile(path, '');\n return path;\n });\n }\n\n exec(\n command: string | readonly string[],\n options: ShellExecOptions | undefined,\n context: Context\n ): Promise<Result<ShellExecResult, ExecutionError>> {\n const ended = this.#activity?.began();\n const line = typeof command === 'string' ? prefixed(command, this.#exports) : command;\n const jobs = this.#jobs;\n const client = this.#client;\n let recorded: Promise<void> | undefined;\n const tracked =\n jobs && typeof command === 'string'\n ? (process: KernelProcess) => {\n recorded = jobs.started(client, command, process);\n }\n : undefined;\n const groups = this.#groups;\n const owner = this.#owner;\n const run =\n !groups || owner === undefined\n ? execute(client, this.cwd, line, options, context, tracked, undefined, this.#hooks)\n : this.#grouped(groups, owner, line, options, context, tracked);\n return run\n .then(async (result) => {\n await recorded;\n return result;\n })\n .finally(ended);\n }\n\n async #grouped(\n groups: ProcessGroups,\n owner: number,\n line: string | readonly string[],\n options: ShellExecOptions | undefined,\n context: Context,\n tracked: ((process: KernelProcess) => void) | undefined\n ): Promise<Result<ShellExecResult, ExecutionError>> {\n for (let wait = groups.pending(owner); wait; wait = groups.pending(owner)) await wait;\n const pgid = groups.shared(owner);\n const release = pgid === undefined && groups.joins() ? groups.reserve(owner) : () => undefined;\n const spawned = (process: KernelProcess) => {\n groups.track(owner, process);\n tracked?.(process);\n release();\n };\n const join = groups.joins()\n ? {\n ...(pgid === undefined ? {} : { pgid }),\n refused: (code: string) => groups.refused(owner, code),\n }\n : undefined;\n return execute(\n this.#client,\n this.cwd,\n line,\n options,\n context,\n spawned,\n join,\n this.#hooks\n ).finally(release);\n }\n\n async cleanup(context: Context): Promise<void> {\n for (const watcher of [...this.#watchers]) await watcher.close(context);\n this.#watchers.clear();\n }\n}\n\nexport const HOME = '/home';\n\nexport type EnvTargetLike = { cwd?: string; conversationId?: number; read?: unknown };\n\nexport type EnvRoute = { client: KernelClient; home: string; cwd?: string };\n\nexport function kernelEnvironment(\n client: KernelClient,\n options: Omit<KernelEnvOptions, 'cwd'> & {\n identify?: (target: EnvTargetLike, context: Context) => Promise<Record<string, string>>;\n route?: (target: EnvTargetLike) => Promise<EnvRoute | undefined>;\n } = {}\n): (target: EnvTargetLike, context?: Context) => SliccKernelEnv | Promise<SliccKernelEnv> {\n const { identify, route, ...rest } = options;\n const make = (target: EnvTargetLike, exports?: Record<string, string>, routed?: EnvRoute) =>\n new SliccKernelEnv(routed?.client ?? client, {\n ...rest,\n cwd: routed?.cwd ?? target.cwd ?? routed?.home ?? HOME,\n ...(target.conversationId === undefined ? {} : { owner: target.conversationId }),\n ...(exports ? { exports } : {}),\n });\n if (route)\n return async (target, context) => {\n const routed = await route(target);\n const exports = identify && context ? await identify(target, context) : undefined;\n return make(target, exports, routed);\n };\n return (target, context) =>\n identify && context\n ? identify(target, context).then((exports) => make(target, exports))\n : make(target);\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,87 @@
1
+ import { redacted } from "../monitor/redact.js";
2
+ const JOBS_MS = 2e3;
3
+ function shortCommand(command) {
4
+ const lines = command.trim().split("\n");
5
+ const first = redacted(lines[0]);
6
+ return lines.length > 1 && !first.endsWith("\u2026") ? `${first} \u2026` : first;
7
+ }
8
+ function backgroundJobs(client, ms = JOBS_MS) {
9
+ const jobs = /* @__PURE__ */ new Map();
10
+ const running = /* @__PURE__ */ new Set();
11
+ const listeners = /* @__PURE__ */ new Set();
12
+ let timer;
13
+ const list = () => [...jobs.values()].map(({ pid, pgid, command }) => ({ pid, pgid, command }));
14
+ const orphans = (table, pgid, after) => table.filter(
15
+ (row) => row.ppid === 1 && row.pgid === pgid && row.pid > after && row.state !== "Z" && !running.has(row.pid) && !jobs.has(row.pid)
16
+ );
17
+ const keep = (rows, pgid, command, owner) => {
18
+ for (const row of rows) jobs.set(row.pid, { pid: row.pid, pgid, command, client: owner });
19
+ };
20
+ const schedule = () => {
21
+ const wanted = jobs.size > 0 && listeners.size > 0;
22
+ if (wanted && !timer) timer = setInterval(() => void prune(), ms);
23
+ if (!wanted && timer) {
24
+ clearInterval(timer);
25
+ timer = void 0;
26
+ }
27
+ };
28
+ const changed = async () => {
29
+ schedule();
30
+ await Promise.all([...listeners].map((listener) => listener()));
31
+ };
32
+ const sweep = (owner, table) => {
33
+ const alive = new Set(table.filter((row) => row.state !== "Z").map((row) => row.pid));
34
+ let gone = false;
35
+ for (const [pid, job] of [...jobs])
36
+ if (job.client === owner && !alive.has(pid)) {
37
+ gone = jobs.delete(pid);
38
+ keep(orphans(table, job.pgid, pid), job.pgid, job.command, owner);
39
+ }
40
+ return gone;
41
+ };
42
+ async function prune() {
43
+ let gone = false;
44
+ for (const each of new Set([...jobs.values()].map((job) => job.client))) {
45
+ const table = await each.ps?.().catch(() => void 0);
46
+ if (table && sweep(each, table)) gone = true;
47
+ }
48
+ if (gone) await changed();
49
+ }
50
+ async function survivors(owner, command, process) {
51
+ const table = await owner.ps?.().catch(() => void 0);
52
+ if (!table) return;
53
+ const found = orphans(table, process.pgid, process.pid);
54
+ keep(found, process.pgid, shortCommand(command), owner);
55
+ if (sweep(owner, table) || found.length) await changed();
56
+ }
57
+ return {
58
+ async started(owner, command, process) {
59
+ if (!owner.ps) return;
60
+ running.add(process.pid);
61
+ const ended = () => {
62
+ running.delete(process.pid);
63
+ return survivors(owner, command, process);
64
+ };
65
+ await process.exited.then(ended, ended).catch(() => void 0);
66
+ },
67
+ list,
68
+ adopt(adopted) {
69
+ for (const job of adopted) if (!jobs.has(job.pid)) jobs.set(job.pid, { ...job, client });
70
+ schedule();
71
+ },
72
+ watch(listener) {
73
+ listeners.add(listener);
74
+ schedule();
75
+ return () => {
76
+ listeners.delete(listener);
77
+ schedule();
78
+ };
79
+ }
80
+ };
81
+ }
82
+ export {
83
+ JOBS_MS,
84
+ backgroundJobs,
85
+ shortCommand
86
+ };
87
+ //# sourceMappingURL=jobs.js.map